1 /*
2 * Copyright (C) 2001 - 2003 Gonzalo Paniagua Javier <gonzalo@ximian.com>
3 * Copyright (C) 2001 - 2004 Rodrigo Moya <rodrigo@ximian.com>
4 * Copyright (C) 2002 Zbigniew Chyla <cyba@gnome.pl>
5 * Copyright (C) 2003 Akira TAGOH <tagoh@gnome-db.org>
6 * Copyright (C) 2003 Laurent Sansonetti <laurent@datarescue.be>
7 * Copyright (C) 2004 Andrew Hill <andru@src.gnome.org>
8 * Copyright (C) 2004 - 2005 Bas Driessen <bas.driessen@xobas.com>
9 * Copyright (C) 2004 Szalai Ferenc <szferi@einstein.ki.iif.hu>
10 * Copyright (C) 2004 - 2013 Vivien Malerba <malerba@gnome-db.org>
11 * Copyright (C) 2005 Alex <alex@igalia.com>
12 * Copyright (C) 2005 Álvaro Peña <alvaropg@telefonica.net>
13 * Copyright (C) 2006 - 2011 Murray Cumming <murrayc@murrayc.com>
14 * Copyright (C) 2007 Armin Burgmeier <armin@openismus.com>
15 * Copyright (C) 2010 David King <davidk@openismus.com>
16 * Copyright (C) 2011 Daniel Espinosa <despinosa@src.gnome.org>
17 *
18 * This library is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU Lesser General Public
20 * License as published by the Free Software Foundation; either
21 * version 2 of the License, or (at your option) any later version.
22 *
23 * This library is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * Lesser General Public License for more details.
27 *
28 * You should have received a copy of the GNU Lesser General Public
29 * License along with this library; if not, write to the
30 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
31 * Boston, MA 02110-1301, USA.
32 */
33
34 #include <stdarg.h>
35 #include <string.h>
36 #include <glib/gi18n-lib.h>
37 #include <libgda/gda-util.h>
38 #include <libgda/gda-connection-private.h>
39 #include "gda-postgres.h"
40 #include "gda-postgres-recordset.h"
41 #include "gda-postgres-provider.h"
42 #include "gda-postgres-blob-op.h"
43 #include "gda-postgres-util.h"
44 #include <libgda/libgda-global-variables.h>
45 #ifdef HAVE_LOCALE_H
46 #include <locale.h>
47 #endif
48
49 #define _GDA_PSTMT(x) ((GdaPStmt*)(x))
50
51 static void gda_postgres_recordset_class_init (GdaPostgresRecordsetClass *klass);
52 static void gda_postgres_recordset_init (GdaPostgresRecordset *recset,
53 GdaPostgresRecordsetClass *klass);
54 static void gda_postgres_recordset_dispose (GObject *object);
55
56 static void gda_postgres_recordset_set_property (GObject *object,
57 guint param_id,
58 const GValue *value,
59 GParamSpec *pspec);
60 static void gda_postgres_recordset_get_property (GObject *object,
61 guint param_id,
62 GValue *value,
63 GParamSpec *pspec);
64
65 /* virtual methods */
66 static gint gda_postgres_recordset_fetch_nb_rows (GdaDataSelect *model);
67 static gboolean gda_postgres_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
68 static gboolean gda_postgres_recordset_store_all (GdaDataSelect *model, GError **error);
69 static gboolean gda_postgres_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
70 static gboolean gda_postgres_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
71 static gboolean gda_postgres_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error);
72
73 /* static helper functions */
74 static void make_point (GdaGeometricPoint *point, const gchar *value);
75 static void set_value (GdaConnection *cnc, GdaRow *row, GValue *value, GType type, const gchar *thevalue, gint length, GError **error);
76
77 static void set_prow_with_pg_res (GdaPostgresRecordset *imodel, GdaRow *prow, gint pg_res_rownum, GError **error);
78 static GdaRow *new_row_from_pg_res (GdaPostgresRecordset *imodel, gint pg_res_rownum, GError **error);
79 static gboolean row_is_in_current_pg_res (GdaPostgresRecordset *model, gint row);
80 static gboolean fetch_next_chunk (GdaPostgresRecordset *model, gboolean *fetch_error, GError **error);
81 static gboolean fetch_prev_chunk (GdaPostgresRecordset *model, gboolean *fetch_error, GError **error);
82 static gboolean fetch_row_number_chunk (GdaPostgresRecordset *model, int row_index, gboolean *fetch_error, GError **error);
83
84
85 struct _GdaPostgresRecordsetPrivate {
86 /* random access attributes */
87 PGresult *pg_res;
88
89 /* cursor access attributes */
90 GdaRow *tmp_row; /* used to store a reference to the last #GdaRow returned */
91 gchar *cursor_name;
92 PGconn *pconn;
93 gint chunk_size; /* Number of rows to fetch at a time when iterating forward or backwards. */
94 gint chunks_read; /* Effectively equal to the number of times that we have iterated forwards or backwards. */
95
96 /* Pg cursor's information */
97 gint pg_pos; /* from G_MININT to G_MAXINT */
98 gint pg_res_size; /* The number of rows in the current chunk - usually equal to chunk_size when iterating forward or backward. */
99 gint pg_res_inf; /* The row number of the first row in the current chunk. Don't use if (@pg_res_size <= 0). */
100 };
101 static GObjectClass *parent_class = NULL;
102
103 /* properties */
104 enum
105 {
106 PROP_0,
107 PROP_CHUNCK_SIZE,
108 PROP_CHUNCKS_READ
109 };
110
111 /*
112 * Object init and finalize
113 */
114 static void
gda_postgres_recordset_init(GdaPostgresRecordset * recset,G_GNUC_UNUSED GdaPostgresRecordsetClass * klass)115 gda_postgres_recordset_init (GdaPostgresRecordset *recset, G_GNUC_UNUSED GdaPostgresRecordsetClass *klass)
116 {
117 g_return_if_fail (GDA_IS_POSTGRES_RECORDSET (recset));
118 recset->priv = g_new0 (GdaPostgresRecordsetPrivate, 1);
119
120 recset->priv->pg_res = NULL;
121 recset->priv->pg_pos = G_MININT;
122 recset->priv->pg_res_size = 0;
123
124 recset->priv->chunk_size = 10;
125 recset->priv->chunks_read = 0;
126 }
127
128 static void
gda_postgres_recordset_class_init(GdaPostgresRecordsetClass * klass)129 gda_postgres_recordset_class_init (GdaPostgresRecordsetClass *klass)
130 {
131 GObjectClass *object_class = G_OBJECT_CLASS (klass);
132 GdaDataSelectClass *pmodel_class = GDA_DATA_SELECT_CLASS (klass);
133
134 parent_class = g_type_class_peek_parent (klass);
135
136 object_class->dispose = gda_postgres_recordset_dispose;
137 pmodel_class->fetch_nb_rows = gda_postgres_recordset_fetch_nb_rows;
138 pmodel_class->fetch_random = gda_postgres_recordset_fetch_random;
139 pmodel_class->store_all = gda_postgres_recordset_store_all;
140
141 pmodel_class->fetch_next = gda_postgres_recordset_fetch_next;
142 pmodel_class->fetch_prev = gda_postgres_recordset_fetch_prev;
143 pmodel_class->fetch_at = gda_postgres_recordset_fetch_at;
144
145 /* properties */
146 object_class->set_property = gda_postgres_recordset_set_property;
147 object_class->get_property = gda_postgres_recordset_get_property;
148 g_object_class_install_property (object_class, PROP_CHUNCK_SIZE,
149 g_param_spec_int ("chunk-size", _("Number of rows fetched at a time"), NULL,
150 1, G_MAXINT - 1, 10,
151 G_PARAM_CONSTRUCT | G_PARAM_READABLE | G_PARAM_WRITABLE));
152 g_object_class_install_property (object_class, PROP_CHUNCKS_READ,
153 g_param_spec_int ("chunks-read",
154 _("Number of rows chunks read since the object creation"), NULL,
155 0, G_MAXINT - 1, 0,
156 G_PARAM_READABLE));
157 }
158
159 static void
gda_postgres_recordset_dispose(GObject * object)160 gda_postgres_recordset_dispose (GObject *object)
161 {
162 GdaPostgresRecordset *recset = (GdaPostgresRecordset *) object;
163
164 g_return_if_fail (GDA_IS_POSTGRES_RECORDSET (recset));
165
166 if (recset->priv) {
167 if (recset->priv->tmp_row)
168 g_object_unref (recset->priv->tmp_row);
169
170 if (recset->priv->pg_res)
171 PQclear (recset->priv->pg_res);
172
173 if (recset->priv->cursor_name) {
174 gchar *str;
175 PGresult *pg_res;
176 str = g_strdup_printf ("CLOSE %s", recset->priv->cursor_name);
177 pg_res = PQexec (recset->priv->pconn, str);
178 g_free (str);
179 PQclear (pg_res);
180 g_free (recset->priv->cursor_name);
181 }
182
183 g_free (recset->priv);
184 recset->priv = NULL;
185 }
186
187 parent_class->dispose (object);
188 }
189
190 static void
gda_postgres_recordset_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)191 gda_postgres_recordset_set_property (GObject *object,
192 guint param_id,
193 const GValue *value,
194 GParamSpec *pspec)
195 {
196 GdaPostgresRecordset *model = (GdaPostgresRecordset *) object;
197 if (model->priv) {
198 switch (param_id) {
199 case PROP_CHUNCK_SIZE:
200 model->priv->chunk_size = g_value_get_int (value);
201 break;
202 default:
203 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
204 break;
205 }
206 }
207 }
208
209 static void
gda_postgres_recordset_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)210 gda_postgres_recordset_get_property (GObject *object,
211 guint param_id,
212 GValue *value,
213 GParamSpec *pspec)
214 {
215 GdaPostgresRecordset *model = (GdaPostgresRecordset *) object;
216 if (model->priv) {
217 switch (param_id) {
218 case PROP_CHUNCK_SIZE:
219 g_value_set_int (value, model->priv->chunk_size);
220 break;
221 case PROP_CHUNCKS_READ:
222 g_value_set_int (value, model->priv->chunks_read);
223 break;
224 default:
225 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
226 break;
227 }
228 }
229 }
230
231 /*
232 * Public functions
233 */
234
235 GType
gda_postgres_recordset_get_type(void)236 gda_postgres_recordset_get_type (void)
237 {
238 static GType type = 0;
239
240 if (G_UNLIKELY (type == 0)) {
241 static GMutex registering;
242 static const GTypeInfo info = {
243 sizeof (GdaPostgresRecordsetClass),
244 (GBaseInitFunc) NULL,
245 (GBaseFinalizeFunc) NULL,
246 (GClassInitFunc) gda_postgres_recordset_class_init,
247 NULL,
248 NULL,
249 sizeof (GdaPostgresRecordset),
250 0,
251 (GInstanceInitFunc) gda_postgres_recordset_init,
252 0
253 };
254 g_mutex_lock (®istering);
255 if (type == 0)
256 type = g_type_register_static (GDA_TYPE_DATA_SELECT, "GdaPostgresRecordset", &info, 0);
257 g_mutex_unlock (®istering);
258 }
259
260 return type;
261 }
262
263 static void
finish_prep_stmt_init(PostgresConnectionData * cdata,GdaPostgresPStmt * ps,PGresult * pg_res,GType * col_types)264 finish_prep_stmt_init (PostgresConnectionData *cdata, GdaPostgresPStmt *ps, PGresult *pg_res, GType *col_types)
265 {
266 /* make sure @ps reports the correct number of columns */
267 if (_GDA_PSTMT (ps)->ncols < 0) {
268 if (pg_res)
269 _GDA_PSTMT (ps)->ncols = PQnfields (pg_res);
270 else
271 _GDA_PSTMT (ps)->ncols = 0;
272 }
273
274 /* completing @ps if not yet done */
275 if (!_GDA_PSTMT (ps)->types && (_GDA_PSTMT (ps)->ncols > 0)) {
276 /* create prepared statement's columns */
277 GSList *list;
278 gint i;
279 for (i = 0; i < _GDA_PSTMT (ps)->ncols; i++)
280 _GDA_PSTMT (ps)->tmpl_columns = g_slist_prepend (_GDA_PSTMT (ps)->tmpl_columns,
281 gda_column_new ());
282 _GDA_PSTMT (ps)->tmpl_columns = g_slist_reverse (_GDA_PSTMT (ps)->tmpl_columns);
283
284 /* create prepared statement's types, all types are initialized to GDA_TYPE_NULL */
285 _GDA_PSTMT (ps)->types = g_new (GType, _GDA_PSTMT (ps)->ncols);
286 for (i = 0; i < _GDA_PSTMT (ps)->ncols; i++)
287 _GDA_PSTMT (ps)->types [i] = GDA_TYPE_NULL;
288
289 if (col_types) {
290 for (i = 0; ; i++) {
291 if (col_types [i] > 0) {
292 if (col_types [i] == G_TYPE_NONE)
293 break;
294 if (i >= _GDA_PSTMT (ps)->ncols) {
295 g_warning (_("Column %d out of range (0-%d), ignoring its specified type"), i,
296 _GDA_PSTMT (ps)->ncols - 1);
297 break;
298 }
299 else
300 _GDA_PSTMT (ps)->types [i] = col_types [i];
301 }
302 }
303 }
304
305 /* fill GdaColumn's data */
306 for (i=0, list = _GDA_PSTMT (ps)->tmpl_columns;
307 i < GDA_PSTMT (ps)->ncols;
308 i++, list = list->next) {
309 GdaColumn *column;
310 Oid postgres_type;
311 GType gtype;
312 column = GDA_COLUMN (list->data);
313 postgres_type = PQftype (pg_res, i);
314 gtype = _GDA_PSTMT (ps)->types [i];
315 if (gtype == GDA_TYPE_NULL) {
316 gtype = _gda_postgres_type_oid_to_gda (cdata->cnc, cdata->reuseable, postgres_type);
317 _GDA_PSTMT (ps)->types [i] = gtype;
318 }
319 _GDA_PSTMT (ps)->types [i] = gtype;
320 gda_column_set_g_type (column, gtype);
321 gda_column_set_name (column, PQfname (pg_res, i));
322 gda_column_set_description (column, PQfname (pg_res, i));
323 }
324 }
325 }
326
327 GdaDataModel *
gda_postgres_recordset_new_random(GdaConnection * cnc,GdaPostgresPStmt * ps,GdaSet * exec_params,PGresult * pg_res,GType * col_types)328 gda_postgres_recordset_new_random (GdaConnection *cnc, GdaPostgresPStmt *ps, GdaSet *exec_params,
329 PGresult *pg_res, GType *col_types)
330 {
331 GdaPostgresRecordset *model;
332 PostgresConnectionData *cdata;
333
334 g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
335 g_return_val_if_fail (ps, NULL);
336
337 cdata = (PostgresConnectionData*) gda_connection_internal_get_provider_data (cnc);
338 if (!cdata)
339 return NULL;
340
341 /* finish prepared statement's init */
342 finish_prep_stmt_init (cdata, ps, pg_res, col_types);
343
344 /* create data model */
345 model = g_object_new (GDA_TYPE_POSTGRES_RECORDSET, "connection", cnc,
346 "prepared-stmt", ps,
347 "model-usage", GDA_DATA_MODEL_ACCESS_RANDOM,
348 "exec-params", exec_params, NULL);
349 model->priv->pg_res = pg_res;
350 ((GdaDataSelect*) model)->advertized_nrows = PQntuples (model->priv->pg_res);
351
352 return GDA_DATA_MODEL (model);
353 }
354
355 /*
356 * Takes ownership of @cursor_name
357 */
358 GdaDataModel *
gda_postgres_recordset_new_cursor(GdaConnection * cnc,GdaPostgresPStmt * ps,GdaSet * exec_params,gchar * cursor_name,GType * col_types)359 gda_postgres_recordset_new_cursor (GdaConnection *cnc, GdaPostgresPStmt *ps, GdaSet *exec_params,
360 gchar *cursor_name, GType *col_types)
361 {
362 GdaPostgresRecordset *model;
363 PostgresConnectionData *cdata;
364
365 g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
366 g_return_val_if_fail (ps, NULL);
367
368 cdata = (PostgresConnectionData*) gda_connection_internal_get_provider_data (cnc);
369 if (!cdata)
370 return NULL;
371
372 /* Fetch the 1st row to finish initialization of @ps */
373 gchar *str;
374 int status;
375 PGresult *pg_res;
376
377 str = g_strdup_printf ("FETCH FORWARD 1 FROM %s;", cursor_name);
378 pg_res = PQexec (cdata->pconn, str);
379 g_free (str);
380 status = PQresultStatus (pg_res);
381 if (!pg_res || (status != PGRES_TUPLES_OK)) {
382 _gda_postgres_make_error (cdata->cnc, cdata->pconn, pg_res, NULL);
383 if (pg_res) {
384 PQclear (pg_res);
385 pg_res = NULL;
386 }
387 }
388 else {
389 PGresult *tmp_res;
390 str = g_strdup_printf ("MOVE BACKWARD 1 FROM %s;", cursor_name);
391 tmp_res = PQexec (cdata->pconn, str);
392 g_free (str);
393 if (tmp_res)
394 PQclear (tmp_res);
395 }
396
397 /* finish prepared statement's init */
398 finish_prep_stmt_init (cdata, ps, pg_res, col_types);
399 if (pg_res)
400 PQclear (pg_res);
401
402 /* create model */
403 model = g_object_new (GDA_TYPE_POSTGRES_RECORDSET, "connection", cnc,
404 "prepared-stmt", ps, "model-usage",
405 GDA_DATA_MODEL_ACCESS_CURSOR_FORWARD | GDA_DATA_MODEL_ACCESS_CURSOR_BACKWARD,
406 "exec-params", exec_params, NULL);
407 model->priv->pconn = cdata->pconn;
408 model->priv->cursor_name = cursor_name;
409 gboolean fetch_error;
410 fetch_next_chunk (model, &fetch_error, NULL);
411
412 return GDA_DATA_MODEL (model);
413 }
414
415 /*
416 * Get the number of rows in @model, if possible
417 */
418 static gint
gda_postgres_recordset_fetch_nb_rows(GdaDataSelect * model)419 gda_postgres_recordset_fetch_nb_rows (GdaDataSelect *model)
420 {
421 GdaPostgresRecordset *imodel;
422
423 imodel = GDA_POSTGRES_RECORDSET (model);
424 if (model->advertized_nrows >= 0)
425 return model->advertized_nrows;
426
427 /* use C API to determine number of rows,if possible */
428 if (!imodel->priv->cursor_name)
429 model->advertized_nrows = PQntuples (imodel->priv->pg_res);
430
431 return model->advertized_nrows;
432 }
433
434 /*
435 * Create a new filled #GdaRow object for the row at position @rownum.
436 *
437 * Each new #GdaRow created is "given" to the #GdaDataSelect implementation using gda_data_select_take_row ().
438 */
439 static gboolean
gda_postgres_recordset_fetch_random(GdaDataSelect * model,GdaRow ** prow,gint rownum,GError ** error)440 gda_postgres_recordset_fetch_random (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
441 {
442 GdaPostgresRecordset *imodel = (GdaPostgresRecordset *) model;
443
444 if (!imodel->priv->pg_res) {
445 g_set_error (error, GDA_SERVER_PROVIDER_ERROR, GDA_SERVER_PROVIDER_INTERNAL_ERROR,
446 "%s", _("Internal error"));
447 return TRUE;
448 }
449
450 *prow = new_row_from_pg_res (imodel, rownum, error);
451 gda_data_select_take_row (model, *prow, rownum);
452
453 if (model->nb_stored_rows == model->advertized_nrows) {
454 /* all the rows have been converted from PGresult to GdaRow objects => we can
455 * discard the PGresult */
456 PQclear (imodel->priv->pg_res);
457 imodel->priv->pg_res = NULL;
458 }
459
460 return TRUE;
461 }
462
463 /*
464 * Create and "give" filled #GdaRow object for all the rows in the model
465 */
466 static gboolean
gda_postgres_recordset_store_all(GdaDataSelect * model,GError ** error)467 gda_postgres_recordset_store_all (GdaDataSelect *model, GError **error)
468 {
469 GdaPostgresRecordset *imodel = (GdaPostgresRecordset*) model;
470 gint i;
471
472 if (!imodel->priv->pg_res) {
473 g_set_error (error, GDA_SERVER_PROVIDER_ERROR, GDA_SERVER_PROVIDER_INTERNAL_ERROR,
474 "%s", _("Internal error"));
475 return FALSE;
476 }
477
478 for (i = 0; i < model->advertized_nrows; i++) {
479 GdaRow *prow;
480 if (! gda_postgres_recordset_fetch_random (model, &prow, i, error))
481 return FALSE;
482 }
483 return TRUE;
484 }
485
486 /*
487 * Create a new filled #GdaRow object for the next cursor row, and put it into *prow.
488 *
489 * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
490 * never keeps a reference to it).
491 */
492 static gboolean
gda_postgres_recordset_fetch_next(GdaDataSelect * model,GdaRow ** prow,gint rownum,GError ** error)493 gda_postgres_recordset_fetch_next (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
494 {
495 GdaPostgresRecordset *imodel = (GdaPostgresRecordset*) model;
496
497 if (row_is_in_current_pg_res (imodel, rownum)) {
498 if (imodel->priv->tmp_row)
499 set_prow_with_pg_res (imodel, imodel->priv->tmp_row, rownum - imodel->priv->pg_res_inf, error);
500 else
501 imodel->priv->tmp_row = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
502 *prow = imodel->priv->tmp_row;
503 }
504 else {
505 gboolean fetch_error = FALSE;
506 if (fetch_next_chunk (imodel, &fetch_error, error)) {
507 if (imodel->priv->tmp_row)
508 set_prow_with_pg_res (imodel, imodel->priv->tmp_row, rownum - imodel->priv->pg_res_inf, error);
509 else
510 imodel->priv->tmp_row = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
511 *prow = imodel->priv->tmp_row;
512 }
513 }
514 return TRUE;
515 }
516
517 /*
518 * Create a new filled #GdaRow object for the previous cursor row, and put it into *prow.
519 *
520 * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
521 * never keeps a reference to it).
522 */
523 static gboolean
gda_postgres_recordset_fetch_prev(GdaDataSelect * model,GdaRow ** prow,gint rownum,GError ** error)524 gda_postgres_recordset_fetch_prev (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
525 {
526 GdaPostgresRecordset *imodel = (GdaPostgresRecordset*) model;
527
528 if (row_is_in_current_pg_res (imodel, rownum)) {
529 if (imodel->priv->tmp_row)
530 set_prow_with_pg_res (imodel, imodel->priv->tmp_row, rownum - imodel->priv->pg_res_inf, error);
531 else
532 imodel->priv->tmp_row = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
533 *prow = imodel->priv->tmp_row;
534 }
535 else {
536 gboolean fetch_error = FALSE;
537 if (fetch_prev_chunk (imodel, &fetch_error, error)) {
538 if (imodel->priv->tmp_row)
539 set_prow_with_pg_res (imodel, imodel->priv->tmp_row, rownum - imodel->priv->pg_res_inf, error);
540 else
541 imodel->priv->tmp_row = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
542 *prow = imodel->priv->tmp_row;
543 }
544 }
545 return TRUE;
546 }
547
548 /*
549 * Create a new filled #GdaRow object for the cursor row at position @rownum, and put it into *prow.
550 *
551 * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
552 * never keeps a reference to it).
553 */
554 static gboolean
gda_postgres_recordset_fetch_at(GdaDataSelect * model,GdaRow ** prow,gint rownum,GError ** error)555 gda_postgres_recordset_fetch_at (GdaDataSelect *model, GdaRow **prow, gint rownum, GError **error)
556 {
557 GdaPostgresRecordset *imodel = (GdaPostgresRecordset*) model;
558
559 if (imodel->priv->tmp_row) {
560 g_object_unref (imodel->priv->tmp_row);
561 imodel->priv->tmp_row = NULL;
562 }
563
564 if (row_is_in_current_pg_res (imodel, rownum)) {
565 *prow = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
566 imodel->priv->tmp_row = *prow;
567 }
568 else {
569 gboolean fetch_error = FALSE;
570 if (fetch_row_number_chunk (imodel, rownum, &fetch_error, error)) {
571 *prow = new_row_from_pg_res (imodel, rownum - imodel->priv->pg_res_inf, error);
572 imodel->priv->tmp_row = *prow;
573 }
574 }
575 return TRUE;
576 }
577
578
579
580
581
582
583 /*
584 * Static helper functions
585 */
586
587 /* Makes a point from a string like "(3.2,5.6)" */
588 static void
make_point(GdaGeometricPoint * point,const gchar * value)589 make_point (GdaGeometricPoint *point, const gchar *value)
590 {
591 value++;
592 point->x = g_ascii_strtod (value, NULL);
593 value = strchr (value, ',');
594 value++;
595 point->y = g_ascii_strtod (value, NULL);
596 }
597
598 static void
set_value(GdaConnection * cnc,GdaRow * row,GValue * value,GType type,const gchar * thevalue,G_GNUC_UNUSED gint length,GError ** error)599 set_value (GdaConnection *cnc, GdaRow *row, GValue *value, GType type, const gchar *thevalue,
600 G_GNUC_UNUSED gint length, GError **error)
601 {
602 gda_value_reset_with_type (value, type);
603
604 if (type == G_TYPE_BOOLEAN)
605 g_value_set_boolean (value, (*thevalue == 't') ? TRUE : FALSE);
606 else if (type == G_TYPE_STRING)
607 g_value_set_string (value, thevalue);
608 else if (type == G_TYPE_INT)
609 g_value_set_int (value, atol (thevalue));
610 else if (type == G_TYPE_UINT)
611 g_value_set_uint (value, (guint) g_ascii_strtoull (thevalue, NULL, 10));
612 else if (type == G_TYPE_DATE) {
613 PostgresConnectionData *cdata;
614 cdata = (PostgresConnectionData*) gda_connection_internal_get_provider_data_error (cnc, error);
615 if (cdata) {
616 GDate date;
617 if (gda_parse_formatted_date (&date, thevalue, cdata->date_first, cdata->date_second,
618 cdata->date_third, cdata->date_sep))
619 g_value_set_boxed (value, &date);
620 else {
621 gda_row_invalidate_value (row, value);
622 g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
623 GDA_SERVER_PROVIDER_DATA_ERROR,
624 _("Invalid date format '%s'"), thevalue);
625 }
626 }
627 else
628 g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
629 GDA_SERVER_PROVIDER_INTERNAL_ERROR,
630 "%s", _("Internal error"));
631 }
632 else if (type == GDA_TYPE_TIME) {
633 GdaTime timegda;
634 if (!gda_parse_iso8601_time (&timegda, thevalue)) {
635 gda_row_invalidate_value (row, value);
636 g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
637 GDA_SERVER_PROVIDER_DATA_ERROR,
638 _("Invalid time '%s' (time format should be HH:MM:SS[.ms])"), thevalue);
639 }
640 else {
641 if (timegda.timezone == GDA_TIMEZONE_INVALID)
642 timegda.timezone = 0; /* set to GMT */
643 gda_value_set_time (value, &timegda);
644 }
645 }
646 else if (type == G_TYPE_INT64)
647 g_value_set_int64 (value, atoll (thevalue));
648 else if (type == G_TYPE_ULONG)
649 g_value_set_ulong (value, atoll (thevalue));
650 else if (type == G_TYPE_LONG)
651 g_value_set_ulong (value, atoll (thevalue));
652 else if (type == GDA_TYPE_SHORT)
653 gda_value_set_short (value, atoi (thevalue));
654 else if (type == G_TYPE_FLOAT) {
655 g_value_set_float (value, g_ascii_strtod (thevalue, NULL));
656 }
657 else if (type == G_TYPE_DOUBLE) {
658 g_value_set_double (value, g_ascii_strtod (thevalue, NULL));
659 }
660 else if (type == GDA_TYPE_NUMERIC) {
661 GdaNumeric* numeric = gda_numeric_new ();
662 gda_numeric_set_from_string (numeric, thevalue);
663 gda_numeric_set_precision (numeric, 0); /* FIXME */
664 gda_numeric_set_width (numeric, 0); /* FIXME */
665 gda_value_set_numeric (value, numeric);
666 gda_numeric_free (numeric);
667 }
668 else if (type == GDA_TYPE_GEOMETRIC_POINT) {
669 GdaGeometricPoint point;
670 make_point (&point, thevalue);
671 gda_value_set_geometric_point (value, &point);
672 }
673 else if (type == GDA_TYPE_TIMESTAMP) {
674 PostgresConnectionData *cdata;
675 cdata = (PostgresConnectionData*) gda_connection_internal_get_provider_data_error (cnc, error);
676 if (cdata) {
677 GdaTimestamp timestamp;
678 if (gda_parse_formatted_timestamp (×tamp, thevalue, cdata->date_first, cdata->date_second,
679 cdata->date_third, cdata->date_sep)) {
680 if (timestamp.timezone == GDA_TIMEZONE_INVALID)
681 timestamp.timezone = 0; /* set to GMT */
682 gda_value_set_timestamp (value, ×tamp);
683 }
684 else {
685 gda_row_invalidate_value (row, value);
686 g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
687 GDA_SERVER_PROVIDER_DATA_ERROR,
688 _("Invalid timestamp value '%s'"), thevalue);
689 }
690 }
691 else
692 g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
693 GDA_SERVER_PROVIDER_INTERNAL_ERROR,
694 "%s", _("Internal error"));
695 }
696 else if (type == GDA_TYPE_BINARY) {
697 /*
698 * Requires PQunescapeBytea in libpq (present since 7.3.x)
699 */
700 guchar *unescaped;
701 size_t pqlength = 0;
702 PostgresConnectionData *cdata;
703 gboolean valueset = FALSE;
704
705 cdata = (PostgresConnectionData*) gda_connection_internal_get_provider_data_error (cnc, error);
706 if (cdata) {
707 if ((thevalue[0] == '\\') && (thevalue[1] == 'x')) {
708 guint len;
709 len = strlen (thevalue + 2);
710 if (!(len % 2)) {
711 guint i;
712 const gchar *ptr;
713 pqlength = len / 2;
714 unescaped = g_new (guchar, pqlength);
715 for (i = 0, ptr = thevalue + 2; *ptr; i++, ptr += 2) {
716 gchar c;
717 c = ptr[0];
718 if ((c >= 'a') && (c <= 'z'))
719 unescaped [i] = c - 'a' + 10;
720 else if ((c >= 'A') && (c <= 'Z'))
721 unescaped [i] = c - 'A' + 10;
722 else if ((c >= '0') && (c <= '9'))
723 unescaped [i] = c - '0';
724 else
725 break;
726 unescaped [i] <<= 4;
727
728 c = ptr[1];
729 if ((c >= 'a') && (c <= 'z'))
730 unescaped [i] += c - 'a' + 10;
731 else if ((c >= 'A') && (c <= 'Z'))
732 unescaped [i] += c - 'A' + 10;
733 else if ((c >= '0') && (c <= '9'))
734 unescaped [i] += c - '0';
735 else
736 break;
737 }
738 if (! *ptr) {
739 GdaBinary *bin;
740 bin = g_new (GdaBinary, 1);
741 bin->data = unescaped;
742 bin->binary_length = pqlength;
743 gda_value_take_binary (value, bin);
744 valueset = TRUE;
745 }
746 }
747 }
748 else {
749 unescaped = PQunescapeBytea ((guchar*) thevalue, &pqlength);
750 if (unescaped) {
751 GdaBinary bin;
752 bin.data = unescaped;
753 bin.binary_length = pqlength;
754 gda_value_set_binary (value, &bin);
755 PQfreemem (unescaped);
756 valueset = TRUE;
757 }
758 }
759 }
760 if (!valueset) {
761 gchar *tmp;
762 tmp = g_strndup (thevalue, 20);
763 gda_row_invalidate_value (row, value);
764 g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
765 GDA_SERVER_PROVIDER_DATA_ERROR,
766 _("Invalid binary string representation '%s ...'"),
767 tmp);
768 g_free (tmp);
769 }
770 }
771 else if (type == GDA_TYPE_BLOB) {
772 GdaBlob *blob;
773 GdaBlobOp *op;
774 blob = g_new0 (GdaBlob, 1);
775 op = gda_postgres_blob_op_new_with_id (cnc, thevalue);
776 gda_blob_set_op (blob, op);
777 g_object_unref (op);
778
779 gda_value_take_blob (value, blob);
780 }
781 else if (type == G_TYPE_GTYPE)
782 g_value_set_gtype (value, gda_g_type_from_string (thevalue));
783 else {
784 /*g_warning ("Type %s not translated for value '%s' => set as string", g_type_name (type), thevalue);*/
785 gda_value_reset_with_type (value, G_TYPE_STRING);
786 g_value_set_string (value, thevalue);
787 }
788 }
789
790 static gboolean
row_is_in_current_pg_res(GdaPostgresRecordset * model,gint row)791 row_is_in_current_pg_res (GdaPostgresRecordset *model, gint row)
792 {
793 if ((model->priv->pg_res) && (model->priv->pg_res_size > 0) &&
794 (row >= model->priv->pg_res_inf) && (row < model->priv->pg_res_inf + model->priv->pg_res_size))
795 return TRUE;
796 else
797 return FALSE;
798 }
799
800 static void
set_prow_with_pg_res(GdaPostgresRecordset * imodel,GdaRow * prow,gint pg_res_rownum,GError ** error)801 set_prow_with_pg_res (GdaPostgresRecordset *imodel, GdaRow *prow, gint pg_res_rownum, GError **error)
802 {
803 gchar *thevalue;
804 gint col;
805
806 for (col = 0; col < ((GdaDataSelect*) imodel)->prep_stmt->ncols; col++) {
807 thevalue = PQgetvalue (imodel->priv->pg_res, pg_res_rownum, col);
808 if (thevalue && (*thevalue != '\0' ? FALSE : PQgetisnull (imodel->priv->pg_res, pg_res_rownum, col)))
809 gda_value_set_null (gda_row_get_value (prow, col));
810 else
811 set_value (gda_data_select_get_connection ((GdaDataSelect*) imodel),
812 prow, gda_row_get_value (prow, col),
813 ((GdaDataSelect*) imodel)->prep_stmt->types [col],
814 thevalue,
815 PQgetlength (imodel->priv->pg_res, pg_res_rownum, col), error);
816 }
817 }
818
819 static GdaRow *
new_row_from_pg_res(GdaPostgresRecordset * imodel,gint pg_res_rownum,GError ** error)820 new_row_from_pg_res (GdaPostgresRecordset *imodel, gint pg_res_rownum, GError **error)
821 {
822 GdaRow *prow;
823
824 prow = gda_row_new (((GdaDataSelect*) imodel)->prep_stmt->ncols);
825 set_prow_with_pg_res (imodel, prow, pg_res_rownum, error);
826 return prow;
827 }
828
829 static gboolean
fetch_next_chunk(GdaPostgresRecordset * model,gboolean * fetch_error,GError ** error)830 fetch_next_chunk (GdaPostgresRecordset *model, gboolean *fetch_error, GError **error)
831 {
832 if (model->priv->pg_res) {
833 PQclear (model->priv->pg_res);
834 model->priv->pg_res = NULL;
835 }
836 *fetch_error = FALSE;
837
838 if (model->priv->pg_pos == G_MAXINT)
839 return FALSE;
840
841 gchar *str;
842 gboolean retval = TRUE;
843 int status;
844
845 str = g_strdup_printf ("FETCH FORWARD %d FROM %s;",
846 model->priv->chunk_size, model->priv->cursor_name);
847 #ifdef GDA_PG_DEBUG
848 g_print ("QUERY: %s\n", str);
849 #endif
850 model->priv->pg_res = PQexec (model->priv->pconn, str);
851 g_free (str);
852 status = PQresultStatus (model->priv->pg_res);
853 model->priv->chunks_read ++;
854 if (status != PGRES_TUPLES_OK) {
855 _gda_postgres_make_error (gda_data_select_get_connection ((GdaDataSelect*) model),
856 model->priv->pconn, model->priv->pg_res, error);
857 PQclear (model->priv->pg_res);
858 model->priv->pg_res = NULL;
859 model->priv->pg_res_size = 0;
860 retval = FALSE;
861 *fetch_error = TRUE;
862 }
863 else {
864 #ifdef GDA_PG_DEBUG
865 dump_pg_res (model->priv->pg_res);
866 #endif
867
868 //PQntuples() returns the number of rows in the result:
869 const gint nbtuples = PQntuples (model->priv->pg_res);
870 model->priv->pg_res_size = nbtuples;
871
872 if (nbtuples > 0) {
873 /* model->priv->pg_res_inf */
874 if (model->priv->pg_pos == G_MININT)
875 model->priv->pg_res_inf = 0;
876 else
877 model->priv->pg_res_inf = model->priv->pg_pos + 1;
878
879 /* GDA_DATA_SELECT (model)->advertized_nrows and model->priv->pg_pos */
880 if (nbtuples < model->priv->chunk_size) {
881 if (model->priv->pg_pos == G_MININT)
882 GDA_DATA_SELECT (model)->advertized_nrows = nbtuples;
883 else
884 GDA_DATA_SELECT (model)->advertized_nrows = model->priv->pg_pos + nbtuples + 1;
885
886 model->priv->pg_pos = G_MAXINT;
887 }
888 else {
889 if (model->priv->pg_pos == G_MININT)
890 model->priv->pg_pos = nbtuples - 1;
891 else
892 model->priv->pg_pos += nbtuples;
893 }
894 }
895 else {
896 if (model->priv->pg_pos == G_MININT)
897 GDA_DATA_SELECT (model)->advertized_nrows = 0;
898 else
899 GDA_DATA_SELECT (model)->advertized_nrows = model->priv->pg_pos + 1; /* total number of rows */
900 model->priv->pg_pos = G_MAXINT;
901 retval = FALSE;
902 }
903 }
904
905 #ifdef GDA_PG_DEBUG
906 g_print ("--> SIZE = %d (inf = %d) nrows = %d, pg_pos = %d\n", model->priv->pg_res_size, model->priv->pg_res_inf,
907 GDA_DATA_SELECT (model)->advertized_nrows, model->priv->pg_pos);
908 #endif
909
910 return retval;
911 }
912
913 static gboolean
fetch_prev_chunk(GdaPostgresRecordset * model,gboolean * fetch_error,GError ** error)914 fetch_prev_chunk (GdaPostgresRecordset *model, gboolean *fetch_error, GError **error)
915 {
916 if (model->priv->pg_res) {
917 PQclear (model->priv->pg_res);
918 model->priv->pg_res = NULL;
919 }
920 *fetch_error = FALSE;
921
922 if (model->priv->pg_pos == G_MININT)
923 return FALSE;
924 else if (model->priv->pg_pos == G_MAXINT)
925 g_assert (GDA_DATA_SELECT (model)->advertized_nrows >= 0); /* total number of rows MUST be known at this point */
926
927 gchar *str;
928 gboolean retval = TRUE;
929 int status;
930 gint noffset;
931
932 if (model->priv->pg_pos == G_MAXINT)
933 noffset = model->priv->chunk_size + 1;
934 else
935 noffset = model->priv->pg_res_size + model->priv->chunk_size;
936 str = g_strdup_printf ("MOVE BACKWARD %d FROM %s; FETCH FORWARD %d FROM %s;",
937 noffset, model->priv->cursor_name,
938 model->priv->chunk_size, model->priv->cursor_name);
939 #ifdef GDA_PG_DEBUG
940 g_print ("QUERY: %s\n", str);
941 #endif
942 model->priv->pg_res = PQexec (model->priv->pconn, str);
943 g_free (str);
944 status = PQresultStatus (model->priv->pg_res);
945 model->priv->chunks_read ++;
946 if (status != PGRES_TUPLES_OK) {
947 _gda_postgres_make_error (gda_data_select_get_connection ((GdaDataSelect*) model),
948 model->priv->pconn, model->priv->pg_res, error);
949 PQclear (model->priv->pg_res);
950 model->priv->pg_res = NULL;
951 model->priv->pg_res_size = 0;
952 retval = FALSE;
953 *fetch_error = TRUE;
954 }
955 else {
956 #ifdef GDA_PG_DEBUG
957 dump_pg_res (model->priv->pg_res);
958 #endif
959
960 //PQntuples() returns the number of rows in the result:
961 const gint nbtuples = PQntuples (model->priv->pg_res);
962 model->priv->pg_res_size = nbtuples;
963
964 if (nbtuples > 0) {
965 /* model->priv->pg_res_inf */
966 if (model->priv->pg_pos == G_MAXINT)
967 model->priv->pg_res_inf = GDA_DATA_SELECT (model)->advertized_nrows - nbtuples;
968 else
969 model->priv->pg_res_inf =
970 MAX (model->priv->pg_res_inf - (noffset - model->priv->chunk_size), 0);
971
972 /* model->priv->pg_pos */
973 if (nbtuples < model->priv->chunk_size) {
974 model->priv->pg_pos = G_MAXINT;
975 }
976 else {
977 if (model->priv->pg_pos == G_MAXINT)
978 model->priv->pg_pos = GDA_DATA_SELECT (model)->advertized_nrows - 1;
979 else
980 model->priv->pg_pos = MAX (model->priv->pg_pos - noffset, -1) + nbtuples;
981 }
982 }
983 else {
984 model->priv->pg_pos = G_MAXINT;
985 retval = FALSE;
986 }
987 }
988
989 #ifdef GDA_PG_DEBUG
990 g_print ("<-- SIZE = %d (inf = %d) nrows = %d, pg_pos = %d\n", model->priv->pg_res_size, model->priv->pg_res_inf,
991 GDA_DATA_SELECT (model)->advertized_nrows, model->priv->pg_pos);
992 #endif
993
994 return retval;
995 }
996
997 static gboolean
fetch_row_number_chunk(GdaPostgresRecordset * model,int row_index,gboolean * fetch_error,GError ** error)998 fetch_row_number_chunk (GdaPostgresRecordset *model, int row_index, gboolean *fetch_error, GError **error)
999 {
1000 if (model->priv->pg_res) {
1001 PQclear (model->priv->pg_res);
1002 model->priv->pg_res = NULL;
1003 }
1004 *fetch_error = FALSE;
1005
1006 gchar *str;
1007 gboolean retval = TRUE;
1008 int status;
1009
1010 /* Postgres's FETCH ABSOLUTE seems to use a 1-based index: */
1011 str = g_strdup_printf ("FETCH ABSOLUTE %d FROM %s;",
1012 row_index + 1, model->priv->cursor_name);
1013 #ifdef GDA_PG_DEBUG
1014 g_print ("QUERY: %s\n", str);
1015 #endif
1016 model->priv->pg_res = PQexec (model->priv->pconn, str);
1017 g_free (str);
1018 status = PQresultStatus (model->priv->pg_res);
1019 model->priv->chunks_read ++; /* Not really correct, because we are only fetching 1 row, not a whole chunk of rows. */
1020 if (status != PGRES_TUPLES_OK) {
1021 _gda_postgres_make_error (gda_data_select_get_connection ((GdaDataSelect*) model),
1022 model->priv->pconn, model->priv->pg_res, error);
1023 PQclear (model->priv->pg_res);
1024 model->priv->pg_res = NULL;
1025 model->priv->pg_res_size = 0;
1026 retval = FALSE;
1027 *fetch_error = TRUE;
1028 }
1029 else {
1030 #ifdef GDA_PG_DEBUG
1031 dump_pg_res (model->priv->pg_res);
1032 #endif
1033
1034 //PQntuples() returns the number of rows in the result:
1035 const gint nbtuples = PQntuples (model->priv->pg_res);
1036 model->priv->pg_res_size = nbtuples;
1037
1038 if (nbtuples > 0) {
1039 /* Remember the row number for the start of this chunk:
1040 * (actually a chunk of just 1 record in this case.) */
1041 model->priv->pg_res_inf = row_index;
1042
1043 /* don't change model->priv->nrows because we can't know if we have reached the end */
1044 model->priv->pg_pos = row_index;
1045 }
1046 else {
1047 model->priv->pg_pos = G_MAXINT;
1048 retval = FALSE;
1049 }
1050 }
1051
1052 #ifdef GDA_PG_DEBUG
1053 g_print ("--> SIZE = %d (inf = %d) nrows = %d, pg_pos = %d\n", model->priv->pg_res_size, model->priv->pg_res_inf,
1054 model->priv->nrows, model->priv->pg_pos);
1055 #endif
1056
1057 return retval;
1058 }
1059