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 (&registering);
255 		if (type == 0)
256 			type = g_type_register_static (GDA_TYPE_DATA_SELECT, "GdaPostgresRecordset", &info, 0);
257 		g_mutex_unlock (&registering);
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 (&timestamp, 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, &timestamp);
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