1 /*
2  * Copyright (C) 2001 - 2005 Rodrigo Moya <rodrigo@gnome-db.org>
3  * Copyright (C) 2002 Andrew Hill <andru@src.gnome.org>
4  * Copyright (C) 2002 - 2003 Gonzalo Paniagua Javier <gonzalo@ximian.com>
5  * Copyright (C) 2002 Holger Thon <holger.thon@gnome-db.org>
6  * Copyright (C) 2002 Zbigniew Chyla <cyba@gnome.pl>
7  * Copyright (C) 2003 Chris Silles <csilles@src.gnome.org>
8  * Copyright (C) 2003 Laurent Sansonetti <lrz@gnome.org>
9  * Copyright (C) 2003 Paisa Seeluangsawat <paisa@users.sf.net>
10  * Copyright (C) 2004 - 2013 Vivien Malerba <malerba@gnome-db.org>
11  * Copyright (C) 2005 Alan Knowles <alan@akbkhome.com>
12  * Copyright (C) 2005 - 2009 Bas Driessen <bas.driessen@xobas.com>
13  * Copyright (C) 2005 Mike Fisk <mfisk@woozle.org>
14  * Copyright (C) 2005 Álvaro Peña <alvaropg@telefonica.net>
15  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
16  *
17  * This library is free software; you can redistribute it and/or
18  * modify it under the terms of the GNU Lesser General Public
19  * License as published by the Free Software Foundation; either
20  * version 2 of the License, or (at your option) any later version.
21  *
22  * This library is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  * Lesser General Public License for more details.
26  *
27  * You should have received a copy of the GNU Lesser General Public
28  * License along with this library; if not, write to the
29  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
30  * Boston, MA  02110-1301, USA.
31  */
32 
33 #include <stdarg.h>
34 #include <string.h>
35 #include <glib/gi18n-lib.h>
36 #include <libgda/gda-util.h>
37 #include <libgda/gda-connection-private.h>
38 #include <libgda/providers-support/gda-data-select-priv.h>
39 #include "gda-mysql.h"
40 #include "gda-mysql-recordset.h"
41 #include "gda-mysql-provider.h"
42 #include "gda-mysql-util.h"
43 #include <libgda/libgda-global-variables.h>
44 #ifdef HAVE_LOCALE_H
45 #include <locale.h>
46 #endif
47 
48 #define _GDA_PSTMT(x) ((GdaPStmt*)(x))
49 
50 enum
51 {
52 	PROP_0,
53 	PROP_CHUNK_SIZE,
54 	PROP_CHUNKS_READ
55 };
56 
57 static void
58 gda_mysql_recordset_class_init (GdaMysqlRecordsetClass  *klass);
59 static void
60 gda_mysql_recordset_init (GdaMysqlRecordset       *recset,
61 			  GdaMysqlRecordsetClass  *klass);
62 static void
63 gda_mysql_recordset_dispose (GObject  *object);
64 
65 /* virtual methods */
66 static gint
67 gda_mysql_recordset_fetch_nb_rows (GdaDataSelect  *model);
68 static gboolean
69 gda_mysql_recordset_fetch_random (GdaDataSelect  *model,
70 				  GdaRow        **row,
71 				  gint            rownum,
72 				  GError        **error);
73 static gboolean
74 gda_mysql_recordset_fetch_next (GdaDataSelect  *model,
75 				GdaRow        **row,
76 				gint            rownum,
77 				GError        **error);
78 static gboolean
79 gda_mysql_recordset_fetch_prev (GdaDataSelect  *model,
80 				GdaRow        **row,
81 				gint            rownum,
82 				GError        **error);
83 static gboolean
84 gda_mysql_recordset_fetch_at (GdaDataSelect  *model,
85 			      GdaRow        **row,
86 			      gint            rownum,
87 			      GError        **error);
88 
89 struct _GdaMysqlRecordsetPrivate {
90 	GdaConnection  *cnc;
91 
92 	MYSQL_STMT     *mysql_stmt;
93 
94 	gint            chunk_size;    /* Number of rows to fetch at a time when iterating forward/backward. */
95 	gint            chunks_read;   /* Number of times that we've iterated forward/backward. */
96 	GdaRow         *tmp_row;       /* Used in cursor mode to store a reference to the latest #GdaRow. */
97 
98 	/* if no prepared statement available */
99 	gint          ncols;
100         GType        *types;
101 };
102 static GObjectClass *parent_class = NULL;
103 
104 /*
105  * Object init and finalize
106  */
107 static void
gda_mysql_recordset_init(GdaMysqlRecordset * recset,G_GNUC_UNUSED GdaMysqlRecordsetClass * klass)108 gda_mysql_recordset_init (GdaMysqlRecordset       *recset,
109 			  G_GNUC_UNUSED GdaMysqlRecordsetClass  *klass)
110 {
111 	g_return_if_fail (GDA_IS_MYSQL_RECORDSET (recset));
112 	recset->priv = g_new0 (GdaMysqlRecordsetPrivate, 1);
113 	recset->priv->cnc = NULL;
114 
115 	/* initialize specific information */
116 	recset->priv->chunk_size = 1;
117 	recset->priv->chunks_read = 0;
118 
119 	recset->priv->ncols = 0;
120 	recset->priv->types = NULL;
121 }
122 
123 
124 gint
gda_mysql_recordset_get_chunk_size(GdaMysqlRecordset * recset)125 gda_mysql_recordset_get_chunk_size (GdaMysqlRecordset  *recset)
126 {
127 	g_return_val_if_fail (GDA_IS_MYSQL_RECORDSET (recset), -1);
128 	return recset->priv->chunk_size;
129 }
130 
131 void
gda_mysql_recordset_set_chunk_size(GdaMysqlRecordset * recset,gint chunk_size)132 gda_mysql_recordset_set_chunk_size (GdaMysqlRecordset  *recset,
133 				    gint                chunk_size)
134 {
135 	g_return_if_fail (GDA_IS_MYSQL_RECORDSET (recset));
136 
137 	if (recset->priv->mysql_stmt == NULL)  // Creation is in progress so it's not set.
138 		return;
139 
140 #if MYSQL_VERSION_ID >= 50002
141 	const unsigned long prefetch_rows = chunk_size;
142 	if (mysql_stmt_attr_set (recset->priv->mysql_stmt, STMT_ATTR_PREFETCH_ROWS,
143 				 (void *) &prefetch_rows)) {
144 		g_warning ("%s: %s\n", __func__, mysql_stmt_error (recset->priv->mysql_stmt));
145 		return;
146 	}
147 	recset->priv->chunk_size = chunk_size;
148 	g_object_notify (G_OBJECT(recset), "chunk-size");
149 #else
150 	g_warning (_("Could not use CURSOR. Mysql version 5.0 at least is required. "
151 		     "Chunk size ignored."));
152 #endif
153 }
154 
155 gint
gda_mysql_recordset_get_chunks_read(GdaMysqlRecordset * recset)156 gda_mysql_recordset_get_chunks_read (GdaMysqlRecordset  *recset)
157 {
158 	g_return_val_if_fail (GDA_IS_MYSQL_RECORDSET (recset), -1);
159 	return recset->priv->chunks_read;
160 }
161 
162 
163 static void
gda_mysql_recordset_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)164 gda_mysql_recordset_set_property (GObject       *object,
165 				  guint          param_id,
166 				  const GValue  *value,
167 				  GParamSpec    *pspec)
168 {
169 	GdaMysqlRecordset *recordset;
170 
171 	g_return_if_fail (object != NULL);
172 	g_return_if_fail (GDA_IS_MYSQL_RECORDSET(object));
173 	g_return_if_fail (GDA_MYSQL_RECORDSET(object)->priv != NULL);
174 
175 	recordset = GDA_MYSQL_RECORDSET(object);
176 
177 	switch (param_id) {
178 	case PROP_CHUNK_SIZE:
179 		gda_mysql_recordset_set_chunk_size (recordset,
180 						    g_value_get_int (value));
181 		break;
182 	case PROP_CHUNKS_READ:
183 		break;
184 	default:
185 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
186 	}
187 }
188 
189 static void
gda_mysql_recordset_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)190 gda_mysql_recordset_get_property (GObject     *object,
191 				  guint        param_id,
192 				  GValue      *value,
193 				  GParamSpec  *pspec)
194 {
195 	GdaMysqlRecordset *recordset;
196 
197 	g_return_if_fail (object != NULL);
198 	g_return_if_fail (GDA_IS_MYSQL_RECORDSET(object));
199 	g_return_if_fail (GDA_MYSQL_RECORDSET(object)->priv != NULL);
200 
201 	recordset = GDA_MYSQL_RECORDSET(object);
202 
203 	switch (param_id) {
204 	case PROP_CHUNK_SIZE:
205 		g_value_set_int (value, recordset->priv->chunk_size);
206 		break;
207 	case PROP_CHUNKS_READ:
208 		g_value_set_int (value, recordset->priv->chunks_read);
209 		break;
210 	default:
211 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
212 	}
213 }
214 
215 
216 static void
gda_mysql_recordset_class_init(GdaMysqlRecordsetClass * klass)217 gda_mysql_recordset_class_init (GdaMysqlRecordsetClass  *klass)
218 {
219 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
220 	GdaDataSelectClass *pmodel_class = GDA_DATA_SELECT_CLASS (klass);
221 
222 	parent_class = g_type_class_peek_parent (klass);
223 
224 	object_class->dispose = gda_mysql_recordset_dispose;
225 	pmodel_class->fetch_nb_rows = gda_mysql_recordset_fetch_nb_rows;
226 	pmodel_class->fetch_random = gda_mysql_recordset_fetch_random;
227 
228 	pmodel_class->fetch_next = gda_mysql_recordset_fetch_next;
229 	pmodel_class->fetch_prev = gda_mysql_recordset_fetch_prev;
230 	pmodel_class->fetch_at = gda_mysql_recordset_fetch_at;
231 
232 	/* Properties. */
233 	object_class->set_property = gda_mysql_recordset_set_property;
234 	object_class->get_property = gda_mysql_recordset_get_property;
235 
236 	g_object_class_install_property
237 		(object_class,
238 		 PROP_CHUNK_SIZE,
239 		 g_param_spec_int ("chunk-size", _("Number of rows fetched at a time"),
240 				   NULL,
241 				   1, G_MAXINT - 1, 1,
242 				   (G_PARAM_CONSTRUCT | G_PARAM_WRITABLE | G_PARAM_READABLE)));
243 
244 	g_object_class_install_property
245 		(object_class,
246 		 PROP_CHUNKS_READ,
247 		 g_param_spec_int ("chunks-read", _("Number of row chunks read since the object creation"),
248 				   NULL,
249 				   0, G_MAXINT - 1, 0,
250 				   (G_PARAM_CONSTRUCT | G_PARAM_WRITABLE | G_PARAM_READABLE)));
251 
252 }
253 
254 static void
gda_mysql_recordset_dispose(GObject * object)255 gda_mysql_recordset_dispose (GObject  *object)
256 {
257 	GdaMysqlRecordset *recset = (GdaMysqlRecordset *) object;
258 
259 	g_return_if_fail (GDA_IS_MYSQL_RECORDSET (recset));
260 
261 	if (recset->priv) {
262 		GDA_MYSQL_PSTMT (GDA_DATA_SELECT (object)->prep_stmt)->stmt_used = FALSE;
263 
264 		if (recset->priv->cnc) {
265 			g_object_unref (G_OBJECT(recset->priv->cnc));
266 			recset->priv->cnc = NULL;
267 		}
268 		if (recset->priv->tmp_row) {
269 			g_object_unref (G_OBJECT(recset->priv->tmp_row));
270 			recset->priv->tmp_row = NULL;
271 		}
272 		if (recset->priv->types)
273 			g_free (recset->priv->types);
274 
275 		g_free (recset->priv);
276 		recset->priv = NULL;
277 	}
278 
279 	parent_class->dispose (object);
280 }
281 
282 /*
283  * Public functions
284  */
285 
286 GType
gda_mysql_recordset_get_type(void)287 gda_mysql_recordset_get_type (void)
288 {
289 	static GType type = 0;
290 
291 	if (G_UNLIKELY (type == 0)) {
292 		static GMutex registering;
293 		static const GTypeInfo info = {
294 			sizeof (GdaMysqlRecordsetClass),
295 			(GBaseInitFunc) NULL,
296 			(GBaseFinalizeFunc) NULL,
297 			(GClassInitFunc) gda_mysql_recordset_class_init,
298 			NULL,
299 			NULL,
300 			sizeof (GdaMysqlRecordset),
301 			0,
302 			(GInstanceInitFunc) gda_mysql_recordset_init,
303 			NULL
304 		};
305 		g_mutex_lock (&registering);
306 		if (type == 0)
307 			type = g_type_register_static (GDA_TYPE_DATA_SELECT, "GdaMysqlRecordset", &info, 0);
308 		g_mutex_unlock (&registering);
309 	}
310 
311 	return type;
312 }
313 
314 static GType
_gda_mysql_type_to_gda(G_GNUC_UNUSED MysqlConnectionData * cdata,enum enum_field_types mysql_type,unsigned int charsetnr)315 _gda_mysql_type_to_gda (G_GNUC_UNUSED MysqlConnectionData *cdata,
316 			enum enum_field_types  mysql_type, unsigned int charsetnr)
317 {
318 	GType gtype;
319 	switch (mysql_type) {
320 	case MYSQL_TYPE_TINY:
321 	case MYSQL_TYPE_SHORT:
322 	case MYSQL_TYPE_LONG:
323 	case MYSQL_TYPE_INT24:
324 	case MYSQL_TYPE_YEAR:
325 		gtype = G_TYPE_INT;
326 		break;
327 	case MYSQL_TYPE_LONGLONG:
328 		gtype = G_TYPE_LONG;
329 		break;
330 	case MYSQL_TYPE_FLOAT:
331 		gtype = G_TYPE_FLOAT;
332 		break;
333 	case MYSQL_TYPE_DECIMAL:
334 	case MYSQL_TYPE_NEWDECIMAL:
335 		gtype = GDA_TYPE_NUMERIC;
336 		break;
337 	case MYSQL_TYPE_DOUBLE:
338 		gtype = G_TYPE_DOUBLE;
339 		break;
340 	case MYSQL_TYPE_TIMESTAMP:
341 	case MYSQL_TYPE_DATETIME:
342 		gtype = GDA_TYPE_TIMESTAMP;
343 		break;
344 	case MYSQL_TYPE_DATE:
345 		gtype = G_TYPE_DATE;
346 		break;
347 	case MYSQL_TYPE_TIME:
348 		gtype = GDA_TYPE_TIME;
349 		break;
350 	case MYSQL_TYPE_NULL:
351 		gtype = GDA_TYPE_NULL;
352 		break;
353 	case MYSQL_TYPE_STRING:
354 	case MYSQL_TYPE_VAR_STRING:
355 	case MYSQL_TYPE_SET:
356 	case MYSQL_TYPE_ENUM:
357 	case MYSQL_TYPE_GEOMETRY:
358 	case MYSQL_TYPE_BIT:
359 	case MYSQL_TYPE_BLOB:
360 	default:
361 		if (charsetnr == 63)
362 			gtype = GDA_TYPE_BLOB;
363 		else
364 			gtype = G_TYPE_STRING;
365 		break;
366 	}
367 
368 	/* g_print ("%s: ", __func__); */
369 	/* switch (mysql_type) { */
370 	/* case MYSQL_TYPE_TINY:  g_print ("MYSQL_TYPE_TINY");  break; */
371 	/* case MYSQL_TYPE_SHORT:  g_print ("MYSQL_TYPE_SHORT");  break; */
372 	/* case MYSQL_TYPE_LONG:  g_print ("MYSQL_TYPE_LONG");  break; */
373 	/* case MYSQL_TYPE_INT24:  g_print ("MYSQL_TYPE_INT24");  break; */
374 	/* case MYSQL_TYPE_YEAR:  g_print ("MYSQL_TYPE_YEAR");  break; */
375 	/* case MYSQL_TYPE_LONGLONG:  g_print ("MYSQL_TYPE_LONGLONG");  break; */
376 	/* case MYSQL_TYPE_FLOAT:  g_print ("MYSQL_TYPE_FLOAT");  break; */
377 	/* case MYSQL_TYPE_DECIMAL:  g_print ("MYSQL_TYPE_DECIMAL");  break; */
378 	/* case MYSQL_TYPE_NEWDECIMAL:  g_print ("MYSQL_TYPE_NEWDECIMAL");  break; */
379 	/* case MYSQL_TYPE_DOUBLE:  g_print ("MYSQL_TYPE_DOUBLE");  break; */
380 	/* case MYSQL_TYPE_BIT:  g_print ("MYSQL_TYPE_BIT");  break; */
381 	/* case MYSQL_TYPE_BLOB:  g_print ("MYSQL_TYPE_BLOB");  break; */
382 	/* case MYSQL_TYPE_TIMESTAMP:  g_print ("MYSQL_TYPE_TIMESTAMP");  break; */
383 	/* case MYSQL_TYPE_DATETIME:  g_print ("MYSQL_TYPE_DATETIME");  break; */
384 	/* case MYSQL_TYPE_DATE:  g_print ("MYSQL_TYPE_DATE");  break; */
385 	/* case MYSQL_TYPE_TIME:  g_print ("MYSQL_TYPE_TIME");  break; */
386 	/* case MYSQL_TYPE_NULL:  g_print ("MYSQL_TYPE_NULL");  break; */
387 	/* case MYSQL_TYPE_STRING:  g_print ("MYSQL_TYPE_STRING");  break; */
388 	/* case MYSQL_TYPE_VAR_STRING:  g_print ("MYSQL_TYPE_VAR_STRING");  break; */
389 	/* case MYSQL_TYPE_SET:  g_print ("MYSQL_TYPE_SET");  break; */
390 	/* case MYSQL_TYPE_ENUM:  g_print ("MYSQL_TYPE_ENUM");  break; */
391 	/* case MYSQL_TYPE_GEOMETRY:  g_print ("MYSQL_TYPE_GEOMETRY");  break; */
392 	/* default:  g_print ("UNKNOWN %d: MYSQL_TYPE_STRING", mysql_type);  break; */
393 	/* } */
394 	/* g_print ("\n"); */
395 
396 	return gtype;
397 }
398 
399 GdaDataModel *
gda_mysql_recordset_new_direct(GdaConnection * cnc,GdaDataModelAccessFlags flags,GType * col_types)400 gda_mysql_recordset_new_direct (GdaConnection *cnc, GdaDataModelAccessFlags flags,
401 				GType *col_types)
402 {
403 	GdaMysqlRecordset *model;
404         MysqlConnectionData *cdata;
405         gint i;
406 	GdaDataModelAccessFlags rflags;
407 	GSList *columns = NULL;
408 
409         g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
410 
411 	cdata = (MysqlConnectionData*) gda_connection_internal_get_provider_data (cnc);
412 	if (!cdata)
413 		return NULL;
414 
415 	/* determine access mode: RANDOM or CURSOR FORWARD are the only supported */
416 	if (flags & GDA_DATA_MODEL_ACCESS_RANDOM)
417 		rflags = GDA_DATA_MODEL_ACCESS_RANDOM;
418 	else
419 		rflags = GDA_DATA_MODEL_ACCESS_CURSOR_FORWARD;
420 
421 	/* create data model */
422         model = g_object_new (GDA_TYPE_MYSQL_RECORDSET,
423 			      "connection", cnc,
424 			      "model-usage", rflags,
425 			      NULL);
426         model->priv->cnc = cnc;
427 	g_object_ref (G_OBJECT(cnc));
428 
429 	/* columns & types */
430 	model->priv->ncols = mysql_field_count (cdata->mysql);
431 	model->priv->types = g_new0 (GType, model->priv->ncols);
432 
433 	/* create columns */
434 	for (i = 0; i < model->priv->ncols; i++)
435 		columns = g_slist_prepend (columns, gda_column_new ());
436 	columns = g_slist_reverse (columns);
437 
438 	if (col_types) {
439 		for (i = 0; ; i++) {
440 			if (col_types [i] > 0) {
441 				if (col_types [i] == G_TYPE_NONE)
442 					break;
443 				if (i >= model->priv->ncols) {
444 					g_warning (_("Column %d out of range (0-%d), ignoring its specified type"), i,
445 						   model->priv->ncols - 1);
446 					break;
447 				}
448 				else
449 					model->priv->types [i] = col_types [i];
450 			}
451 		}
452 	}
453 
454 	/* fill bind result */
455 	MYSQL_RES *mysql_res = mysql_store_result (cdata->mysql);
456 	MYSQL_FIELD *mysql_fields = mysql_fetch_fields (mysql_res);
457 	GSList *list;
458 
459 	((GdaDataSelect *) model)->advertized_nrows = mysql_affected_rows (cdata->mysql);
460 	for (i=0, list = columns;
461 	     i < model->priv->ncols;
462 	     i++, list = list->next) {
463 		GdaColumn *column = GDA_COLUMN (list->data);
464 
465 		/* use C API to set columns' information using gda_column_set_*() */
466 		MYSQL_FIELD *field = &mysql_fields[i];
467 
468 		GType gtype = model->priv->types [i];
469 		if (gtype == GDA_TYPE_NULL) {
470 			gtype = _gda_mysql_type_to_gda (cdata, field->type, field->charsetnr);
471 			model->priv->types [i] = gtype;
472 		}
473 		gda_column_set_g_type (column, gtype);
474 		gda_column_set_name (column, field->name);
475 		gda_column_set_description (column, field->name);
476 	}
477 	gda_data_select_set_columns (GDA_DATA_SELECT (model), columns);
478 
479 	/* load ALL data */
480 	MYSQL_ROW mysql_row;
481 	gint rownum;
482 	GdaServerProvider *prov;
483 	prov = gda_connection_get_provider (cnc);
484 	for (mysql_row = mysql_fetch_row (mysql_res), rownum = 0;
485 	     mysql_row;
486 	     mysql_row = mysql_fetch_row (mysql_res), rownum++) {
487 		GdaRow *row = gda_row_new (model->priv->ncols);
488 		gint col;
489 		for (col = 0; col < model->priv->ncols; col++) {
490 			gint i = col;
491 
492 			GValue *value = gda_row_get_value (row, i);
493 			GType type = model->priv->types[i];
494 			char *data = mysql_row[i];
495 
496 			if (!data || (type == GDA_TYPE_NULL))
497 				continue;
498 
499 			gda_value_reset_with_type (value, type);
500 			if (type == G_TYPE_STRING)
501 				g_value_set_string (value, data);
502 			else {
503 				GdaDataHandler *dh;
504 				gboolean valueset = FALSE;
505 				dh = gda_server_provider_get_data_handler_g_type (prov, cnc, type);
506 				if (dh) {
507 					GValue *tmpvalue;
508 					tmpvalue = gda_data_handler_get_value_from_str (dh, data, type);
509 					if (tmpvalue) {
510 						*value = *tmpvalue;
511 						g_free (tmpvalue);
512 						valueset = TRUE;
513 					}
514 				}
515 				if (!valueset)
516 					gda_row_invalidate_value (row, value);
517 			}
518 		}
519 		gda_data_select_take_row ((GdaDataSelect*) model, row, rownum);
520 	}
521 	mysql_free_result (mysql_res);
522 
523         return GDA_DATA_MODEL (model);
524 }
525 
526 
527 /*
528  * the @ps struct is modified and transferred to the new data model created in
529  * this function
530  *
531  * See MySQL's documentation "C API Prepared Statement Type Codes":
532  *     http://docs.oracle.com/cd/E17952_01/refman-5.5-en/c-api-prepared-statement-type-codes.html
533  */
534 GdaDataModel *
gda_mysql_recordset_new(GdaConnection * cnc,GdaMysqlPStmt * ps,GdaSet * exec_params,GdaDataModelAccessFlags flags,GType * col_types)535 gda_mysql_recordset_new (GdaConnection            *cnc,
536 			 GdaMysqlPStmt            *ps,
537 			 GdaSet                   *exec_params,
538 			 GdaDataModelAccessFlags   flags,
539 			 GType                    *col_types)
540 {
541 	GdaMysqlRecordset *model;
542         MysqlConnectionData *cdata;
543         gint i;
544 	GdaDataModelAccessFlags rflags;
545 
546         g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
547         g_return_val_if_fail (ps != NULL, NULL);
548 
549 	cdata = (MysqlConnectionData*) gda_connection_internal_get_provider_data (cnc);
550 	if (!cdata)
551 		return NULL;
552 
553 	g_assert (ps->mysql_stmt);
554 
555 	/* make sure @ps reports the correct number of columns using the API*/
556         if (_GDA_PSTMT (ps)->ncols < 0)
557 		_GDA_PSTMT(ps)->ncols = mysql_stmt_field_count (ps->mysql_stmt);
558 
559         /* completing @ps if not yet done */
560 	g_assert (! ps->stmt_used);
561         ps->stmt_used = TRUE;
562         if (!_GDA_PSTMT (ps)->types && (_GDA_PSTMT (ps)->ncols > 0)) {
563 		/* create prepared statement's columns */
564 		for (i = 0; i < _GDA_PSTMT (ps)->ncols; i++)
565 			_GDA_PSTMT (ps)->tmpl_columns = g_slist_prepend (_GDA_PSTMT (ps)->tmpl_columns,
566 									 gda_column_new ());
567 		_GDA_PSTMT (ps)->tmpl_columns = g_slist_reverse (_GDA_PSTMT (ps)->tmpl_columns);
568 
569 		/* create prepared statement's types, all types are initialized to GDA_TYPE_NULL */
570 		_GDA_PSTMT (ps)->types = g_new (GType, _GDA_PSTMT (ps)->ncols);
571 		for (i = 0; i < _GDA_PSTMT (ps)->ncols; i++)
572 			_GDA_PSTMT (ps)->types [i] = GDA_TYPE_NULL;
573 
574 		if (col_types) {
575 			for (i = 0; ; i++) {
576 				if (col_types [i] > 0) {
577 					if (col_types [i] == G_TYPE_NONE)
578 						break;
579 					if (i >= _GDA_PSTMT (ps)->ncols) {
580 						g_warning (_("Column %d out of range (0-%d), ignoring its specified type"), i,
581 							   _GDA_PSTMT (ps)->ncols - 1);
582 						break;
583 					}
584 					else
585 						_GDA_PSTMT (ps)->types [i] = col_types [i];
586 				}
587 			}
588 		}
589 	}
590 
591 	/* get rid of old bound result if any */
592 	if (ps->mysql_bind_result) {
593 		gint i;
594 		for (i = 0; i < ((GdaPStmt *) ps)->ncols; ++i) {
595 			g_free (ps->mysql_bind_result[i].buffer);
596 			g_free (ps->mysql_bind_result[i].is_null);
597 			g_free (ps->mysql_bind_result[i].length);
598 		}
599 		g_free (ps->mysql_bind_result);
600 		ps->mysql_bind_result = NULL;
601 	}
602 
603 	/* fill bind result */
604 	MYSQL_RES *mysql_res = mysql_stmt_result_metadata (ps->mysql_stmt);
605 	MYSQL_FIELD *mysql_fields = mysql_fetch_fields (mysql_res);
606 
607 	MYSQL_BIND *mysql_bind_result = g_new0 (MYSQL_BIND, GDA_PSTMT (ps)->ncols);
608 	GSList *list;
609 
610 	for (i=0, list = _GDA_PSTMT (ps)->tmpl_columns;
611 	     i < GDA_PSTMT (ps)->ncols;
612 	     i++, list = list->next) {
613 		GdaColumn *column = GDA_COLUMN (list->data);
614 
615 		/* use C API to set columns' information using gda_column_set_*() */
616 		MYSQL_FIELD *field = &mysql_fields[i];
617 
618 		GType gtype = _GDA_PSTMT(ps)->types[i];
619 		if (gtype == GDA_TYPE_NULL) {
620 			gtype = _gda_mysql_type_to_gda (cdata, field->type, field->charsetnr);
621 			_GDA_PSTMT(ps)->types[i] = gtype;
622 		}
623 		gda_column_set_g_type (column, gtype);
624 		gda_column_set_name (column, field->name);
625 		gda_column_set_description (column, field->name);
626 
627 		/* binding results with types */
628 		mysql_bind_result[i].buffer_type = field->type;
629 		mysql_bind_result[i].is_unsigned = field->flags & UNSIGNED_FLAG ? TRUE : FALSE;
630 		mysql_bind_result[i].is_null = g_malloc0 (sizeof (my_bool));
631 
632 		switch (mysql_bind_result[i].buffer_type) {
633 		case MYSQL_TYPE_TINY:
634 			mysql_bind_result[i].buffer = g_malloc0 (sizeof (signed char));
635 			break;
636 		case MYSQL_TYPE_SHORT:
637 			mysql_bind_result[i].buffer = g_malloc0 (sizeof (short int));
638 			break;
639 		case MYSQL_TYPE_INT24:
640 		case MYSQL_TYPE_LONG:
641 		case MYSQL_TYPE_YEAR:
642 			mysql_bind_result[i].buffer = g_malloc0 (sizeof (int));
643 			break;
644 		case MYSQL_TYPE_LONGLONG:
645 			mysql_bind_result[i].buffer = g_malloc0 (sizeof (long long));
646 			break;
647 		case MYSQL_TYPE_NULL:
648 			break;
649 		case MYSQL_TYPE_TIME:
650 		case MYSQL_TYPE_DATE:
651 		case MYSQL_TYPE_DATETIME:
652 		case MYSQL_TYPE_TIMESTAMP:
653 			mysql_bind_result[i].buffer = g_malloc0 (sizeof (MYSQL_TIME));
654 			break;
655 		case MYSQL_TYPE_FLOAT:
656 		case MYSQL_TYPE_DOUBLE:
657 			mysql_bind_result[i].buffer = g_malloc0 (sizeof (double));
658 			break;
659 		case MYSQL_TYPE_STRING:
660 		case MYSQL_TYPE_VAR_STRING:
661 		case MYSQL_TYPE_BLOB:
662 		case MYSQL_TYPE_TINY_BLOB:
663 		case MYSQL_TYPE_MEDIUM_BLOB:
664 		case MYSQL_TYPE_LONG_BLOB:
665 		case MYSQL_TYPE_DECIMAL:
666 		case MYSQL_TYPE_NEWDECIMAL:
667 		case MYSQL_TYPE_BIT:
668 			mysql_bind_result[i].buffer = g_malloc0 (field->max_length + 1);
669 			mysql_bind_result[i].buffer_length = field->max_length + 1;
670 			mysql_bind_result[i].length = g_malloc0 (sizeof (unsigned long));
671 			break;
672 		default:
673 			g_warning (_("Invalid column bind data type. %d\n"),
674 				   mysql_bind_result[i].buffer_type);
675 		}
676 		/*g_print ("%s(): NAME=%s, TYPE=%d, GTYPE=%s, unsigned: %d\n",
677 			 __FUNCTION__, field->name, field->type, g_type_name (gtype),
678 			 field->flags & UNSIGNED_FLAG);*/
679 	}
680 
681 	if (mysql_stmt_bind_result (ps->mysql_stmt, mysql_bind_result)) {
682 		g_warning ("mysql_stmt_bind_result failed: %s\n",
683 			   mysql_stmt_error (ps->mysql_stmt));
684 	}
685 
686 	mysql_free_result (mysql_res);
687 	ps->mysql_bind_result = mysql_bind_result;
688 
689 	/* determine access mode: RANDOM or CURSOR FORWARD are the only supported */
690 	if (flags & GDA_DATA_MODEL_ACCESS_RANDOM)
691 		rflags = GDA_DATA_MODEL_ACCESS_RANDOM;
692 	else
693 		rflags = GDA_DATA_MODEL_ACCESS_CURSOR_FORWARD;
694 
695 	/* create data model */
696         model = g_object_new (GDA_TYPE_MYSQL_RECORDSET,
697 			      "connection", cnc,
698 			      "prepared-stmt", ps,
699 			      "model-usage", rflags,
700 			      "exec-params", exec_params,
701 			      NULL);
702         model->priv->cnc = cnc;
703 	g_object_ref (G_OBJECT(cnc));
704 
705 	model->priv->mysql_stmt = ps->mysql_stmt;
706 
707 	((GdaDataSelect *) model)->advertized_nrows = mysql_stmt_affected_rows (ps->mysql_stmt);
708 
709         return GDA_DATA_MODEL (model);
710 }
711 
712 
713 /*
714  * Get the number of rows in @model, if possible
715  */
716 static gint
gda_mysql_recordset_fetch_nb_rows(GdaDataSelect * model)717 gda_mysql_recordset_fetch_nb_rows (GdaDataSelect *model)
718 {
719 	GdaMysqlRecordset *imodel;
720 
721 	imodel = GDA_MYSQL_RECORDSET (model);
722 	if (model->advertized_nrows >= 0)
723 		return model->advertized_nrows;
724 
725 	model->advertized_nrows = mysql_stmt_affected_rows (imodel->priv->mysql_stmt);
726 
727 	return model->advertized_nrows;
728 }
729 
730 static GdaRow *
new_row_from_mysql_stmt(GdaMysqlRecordset * imodel,G_GNUC_UNUSED gint rownum,GError ** error)731 new_row_from_mysql_stmt (GdaMysqlRecordset *imodel, G_GNUC_UNUSED gint rownum, GError **error)
732 {
733 	//g_print ("%s(): NCOLS=%d  ROWNUM=%d\n", __func__, ((GdaDataSelect *) imodel)->prep_stmt->ncols, rownum);
734 	int res;
735 	MYSQL_BIND *mysql_bind_result;
736 	g_return_val_if_fail (imodel->priv->mysql_stmt != NULL, NULL);
737 
738 	mysql_bind_result = ((GdaMysqlPStmt *) ((GdaDataSelect *) imodel)->prep_stmt)->mysql_bind_result;
739 	g_assert (mysql_bind_result);
740 
741 	res = mysql_stmt_fetch (imodel->priv->mysql_stmt);
742 	if (res == MYSQL_NO_DATA) {
743 		/* should not happen */
744 		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR,
745 			     "%s", "No more data, please report this bug to "
746 			     "http://bugzilla.gnome.org/ for the \"libgda\" product and the MySQL provider.");
747 	}
748 	else if (res == MYSQL_DATA_TRUNCATED) {
749 		GString *string;
750 
751 		string = g_string_new ("Truncated data, please report this bug to "
752 				       "http://bugzilla.gnome.org/ for the \"libgda\" product and the MySQL provider.");
753 
754 		gint col;
755 		for (col = 0; col < ((GdaDataSelect *) imodel)->prep_stmt->ncols; ++col) {
756 			my_bool truncated;
757 			mysql_bind_result[col].error = &truncated;
758 			mysql_stmt_fetch_column (imodel->priv->mysql_stmt, &(mysql_bind_result[col]),
759 						 (unsigned int)col, 0);
760 			if (truncated)
761 				g_string_append_printf (string, "\n  column %d is truncated\n", col);
762 			mysql_bind_result[col].error = NULL;
763 		}
764 		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_ACCESS_ERROR, "%s", string->str);
765 		g_string_free (string, TRUE);
766 
767 		return NULL;
768 	}
769 	else if (res) {
770 		_gda_mysql_make_error (imodel->priv->cnc, NULL, imodel->priv->mysql_stmt, error);
771 		return NULL;
772 	}
773 
774 	/* g_print ("%s: SQL=%s\n", __func__, ((GdaDataSelect *) imodel)->prep_stmt->sql); */
775 
776 
777 	GdaRow *row = gda_row_new (((GdaDataSelect *) imodel)->prep_stmt->ncols);
778 	gint col;
779 	for (col = 0; col < ((GdaDataSelect *) imodel)->prep_stmt->ncols; ++col) {
780 		gint i = col;
781 
782 		GValue *value = gda_row_get_value (row, i);
783 		GType type = ((GdaDataSelect *) imodel)->prep_stmt->types[i];
784 
785 		/*g_print ("%s: #%d : TYPE=%d, GTYPE=%s\n", __func__, i, mysql_bind_result[i].buffer_type, g_type_name (type));*/
786 
787 		my_bool is_null = FALSE;
788 		unsigned long length;
789 
790 		memmove (&is_null, mysql_bind_result[i].is_null, sizeof (my_bool));
791 		if (is_null) {
792 			gda_value_set_null (value);
793 			continue;
794 		}
795 		else
796 			gda_value_reset_with_type (value, type);
797 
798 		switch (mysql_bind_result[i].buffer_type) {
799 		case MYSQL_TYPE_SHORT: {
800 			short int bvalue = 0;
801 			memmove (&bvalue, mysql_bind_result[i].buffer, sizeof (bvalue));
802 			g_value_set_int (value, bvalue);
803 			break;
804 		}
805 		case MYSQL_TYPE_TINY: {
806 			signed char bvalue = 0;
807 			memmove (&bvalue, mysql_bind_result[i].buffer, sizeof (bvalue));
808 			g_value_set_int (value, bvalue);
809 			break;
810 		}
811 		case MYSQL_TYPE_INT24:
812 		case MYSQL_TYPE_LONG:
813 		case MYSQL_TYPE_YEAR: {
814 			int bvalue = 0;
815 			memmove (&bvalue, mysql_bind_result[i].buffer, sizeof (bvalue));
816 
817 			if (type == G_TYPE_INT)
818 				g_value_set_int (value, bvalue);
819 			else if (type == G_TYPE_LONG)
820 				g_value_set_long (value, (long) bvalue);
821 			else if (type == G_TYPE_BOOLEAN)
822 				g_value_set_boolean (value, bvalue ? TRUE : FALSE);
823 			else {
824 				gda_row_invalidate_value (row, value);
825 				g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
826 					     GDA_SERVER_PROVIDER_DATA_ERROR,
827 					     _("Type %s not mapped for value %d"),
828 					     g_type_name (type), bvalue);
829 			}
830 			break;
831 		}
832 		case MYSQL_TYPE_LONGLONG: {
833 			long long bvalue = 0;
834 			memmove (&bvalue, mysql_bind_result[i].buffer, sizeof (bvalue));
835 
836 			if (type == G_TYPE_BOOLEAN)
837 				g_value_set_boolean (value, bvalue ? TRUE : FALSE);
838 			else if (type == G_TYPE_INT)
839 				g_value_set_int (value, bvalue);
840 			else if (type == G_TYPE_LONG)
841 				g_value_set_long (value, bvalue);
842 			else {
843 				gda_row_invalidate_value (row, value);
844 				g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
845 					     GDA_SERVER_PROVIDER_DATA_ERROR,
846 					     _("Type %s not mapped for value %lld"),
847 					     g_type_name (type), bvalue);
848 			}
849 			break;
850 		}
851 		case MYSQL_TYPE_NULL:
852 			gda_value_set_null (value);
853 			break;
854 		case MYSQL_TYPE_TIME:
855 		case MYSQL_TYPE_DATE:
856 		case MYSQL_TYPE_DATETIME:
857 		case MYSQL_TYPE_TIMESTAMP: {
858 			MYSQL_TIME bvalue = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
859 			memmove (&bvalue, mysql_bind_result[i].buffer, sizeof (bvalue));
860 
861 			if (type == GDA_TYPE_TIME) {
862 				GdaTime time = {
863 					.hour = bvalue.hour,
864 					.minute = bvalue.minute,
865 					.second = bvalue.second,
866 					.fraction = bvalue.second_part,
867 					.timezone = 0 /* GMT */
868 				};
869 				gda_value_set_time (value, &time);
870 			}
871 			else if (type == G_TYPE_DATE) {
872 				GDate *date = g_date_new_dmy
873 					((bvalue.day != 0) ? bvalue.day : 1,
874 					 (bvalue.month != 0) ? bvalue.month : 1,
875 					 (bvalue.year != 0) ? bvalue.year : 1970);
876 				g_value_take_boxed (value, date);
877 			}
878 			else if (type == GDA_TYPE_TIMESTAMP) {
879 				GdaTimestamp timestamp = {
880 					.year = bvalue.year,
881 					.month = bvalue.month,
882 					.day = bvalue.day,
883 					.hour = bvalue.hour,
884 					.minute = bvalue.minute,
885 					.second = bvalue.second,
886 					.fraction = bvalue.second_part,
887 					.timezone = 0 /* GMT */
888 				};
889 				gda_value_set_timestamp (value, &timestamp);
890 			}
891 			else {
892 				gda_row_invalidate_value (row, value);
893 				g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
894 					     GDA_SERVER_PROVIDER_DATA_ERROR,
895 					     _("Type %s not mapped for value %d/%d/%d %d:%d:%d.%lu"),
896 					     g_type_name (type), bvalue.year, bvalue.month,
897 					     bvalue.day, bvalue.hour, bvalue.minute,
898 					     bvalue.second, bvalue.second_part);
899 			}
900 			break;
901 		}
902 		case MYSQL_TYPE_FLOAT: {
903 			float bvalue = 0.;
904 			memmove (&bvalue, mysql_bind_result[i].buffer, sizeof (bvalue));
905 
906 			if (type == G_TYPE_FLOAT)
907 				g_value_set_float (value, (float) bvalue);
908 			else {
909 				gda_row_invalidate_value (row, value);
910 				g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
911 					     GDA_SERVER_PROVIDER_DATA_ERROR,
912 					     _("Type %s not mapped for value %f"),
913 					     g_type_name (type), bvalue);
914 			}
915 			break;
916 		}
917 		case MYSQL_TYPE_DOUBLE: {
918 			double bvalue = 0.0;
919 			memmove (&bvalue, mysql_bind_result[i].buffer, sizeof (bvalue));
920 
921 			if (type == G_TYPE_DOUBLE)
922 				g_value_set_double (value, bvalue);
923 			else {
924 				gda_row_invalidate_value (row, value);
925 				g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
926 					     GDA_SERVER_PROVIDER_DATA_ERROR,
927 					     _("Type %s not mapped for value %f"),
928 					     g_type_name (type), bvalue);
929 			}
930 			break;
931 		}
932 		case MYSQL_TYPE_STRING:
933 		case MYSQL_TYPE_VAR_STRING:
934 		case MYSQL_TYPE_BLOB:
935 		case MYSQL_TYPE_TINY_BLOB:
936 		case MYSQL_TYPE_MEDIUM_BLOB:
937 		case MYSQL_TYPE_LONG_BLOB:
938 		case MYSQL_TYPE_NEWDECIMAL:
939 		case MYSQL_TYPE_DECIMAL:
940 		case MYSQL_TYPE_BIT: {
941 			char *bvalue = NULL;
942 			memmove (&length, mysql_bind_result[i].length, sizeof (unsigned long));
943 			if (length > 0) {
944 				bvalue = g_malloc (length + 1);
945 				memcpy (bvalue, mysql_bind_result[i].buffer, length);
946 				bvalue [length] = 0;
947 			}
948 
949 			if (type == G_TYPE_STRING)
950 				g_value_set_string (value, bvalue);
951 			else if (type == GDA_TYPE_BINARY) {
952 				GdaBinary binary = {
953 					.data = (guchar*) bvalue,
954 					.binary_length = length
955 				};
956 				gda_value_set_binary (value, &binary);
957 			}
958 			else if (type == GDA_TYPE_BLOB) {
959 				/* we don't use GdaMysqlBlobOp because it looks like the MySQL
960 				 * API does not support BLOBs accessed in a random way,
961 				 * so we return the whole BLOB at once */
962 				GdaBlob blob = { {(guchar*) bvalue, length}, NULL };
963 				gda_value_set_blob (value, &blob);
964 			}
965 			else if (type == GDA_TYPE_NUMERIC) {
966 				if (length > 0) {
967 					GdaNumeric *numeric;
968 					numeric = gda_numeric_new ();
969 					gda_numeric_set_from_string (numeric, bvalue);
970 					gda_numeric_set_precision (numeric, 6);
971 					gda_numeric_set_width (numeric, length);
972 					gda_value_set_numeric (value, numeric);
973 					gda_numeric_free (numeric);
974 				}
975 			}
976 			else if (type == G_TYPE_DOUBLE) {
977 				if (length > 0)
978 					g_value_set_double (value, g_ascii_strtod (bvalue, NULL));
979 				else {
980 					/* error: wrong column type */
981 					gda_row_invalidate_value (row, value);
982 					g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
983 						     GDA_SERVER_PROVIDER_DATA_ERROR,
984 						     _("Invalid column bind data type. %d\n"),
985 						     mysql_bind_result[i].buffer_type);
986 					break;
987 				}
988 			}
989 			else if (type == G_TYPE_INT) {
990 				if (length > 0)
991 					g_value_set_int (value, atoi (bvalue));
992 				else {
993 					/* error: wrong column type */
994 					gda_row_invalidate_value (row, value);
995 					g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
996 						     GDA_SERVER_PROVIDER_DATA_ERROR,
997 						     _("Invalid column bind data type. %d\n"),
998 						     mysql_bind_result[i].buffer_type);
999 					break;
1000 				}
1001 			}
1002 			else if (type == G_TYPE_BOOLEAN) {
1003 				if (length > 0)
1004 					g_value_set_boolean (value, atoi (bvalue));
1005 				else {
1006 					/* error: wrong column type */
1007 					gda_row_invalidate_value (row, value);
1008 					g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
1009 						     GDA_SERVER_PROVIDER_DATA_ERROR,
1010 						     _("Invalid column bind data type. %d\n"),
1011 						     mysql_bind_result[i].buffer_type);
1012 					break;
1013 				}
1014 			}
1015 			else {
1016 				gda_row_invalidate_value (row, value);
1017 				g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
1018 					     GDA_SERVER_PROVIDER_DATA_ERROR,
1019 					     _("Type %s not mapped for value %s"),
1020 					     g_type_name (type), bvalue);
1021 			}
1022 			g_free (bvalue);
1023 			break;
1024 		}
1025 		default:
1026 			gda_row_invalidate_value (row, value);
1027 			g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
1028 				     GDA_SERVER_PROVIDER_DATA_ERROR,
1029 				     _("Invalid column bind data type. %d\n"),
1030 				     mysql_bind_result[i].buffer_type);
1031 		}
1032 
1033 	}
1034 	return row;
1035 }
1036 
1037 
1038 /*
1039  * Create a new filled #GdaRow object for the row at position @rownum, and put it into *row.
1040  *
1041  * Each new GdaRow is given to @model using gda_data_select_take_row().
1042  */
1043 static gboolean
gda_mysql_recordset_fetch_random(GdaDataSelect * model,GdaRow ** row,gint rownum,GError ** error)1044 gda_mysql_recordset_fetch_random (GdaDataSelect  *model,
1045 				  GdaRow        **row,
1046 				  gint            rownum,
1047 				  GError        **error)
1048 {
1049 	GdaMysqlRecordset *imodel;
1050 
1051 	imodel = GDA_MYSQL_RECORDSET (model);
1052 
1053 	*row = new_row_from_mysql_stmt (imodel, rownum, error);
1054 	if (!*row)
1055 		return TRUE;
1056 
1057 	gda_data_select_take_row (model, *row, rownum);
1058 
1059 	if (model->nb_stored_rows == model->advertized_nrows) {
1060 		/* All the row have been converted.  We could free result now */
1061 		/* but it was done before provided no field-based API functions */
1062 		/* that process result set meta data was needed in the middle. */
1063 	}
1064 
1065 	return TRUE;
1066 }
1067 
1068 /*
1069  * Create a new filled #GdaRow object for the next cursor row, and put it into *row.
1070  *
1071  * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
1072  * never keeps a reference to it).
1073  * Before a new #GdaRow gets created, the previous one, if set, is discarded.
1074  */
1075 static gboolean
gda_mysql_recordset_fetch_next(GdaDataSelect * model,GdaRow ** row,gint rownum,GError ** error)1076 gda_mysql_recordset_fetch_next (GdaDataSelect  *model,
1077 				GdaRow        **row,
1078 				gint            rownum,
1079 				GError        **error)
1080 {
1081 	GdaMysqlRecordset *imodel = (GdaMysqlRecordset*) model;
1082 
1083 	if (imodel->priv->tmp_row)
1084 		g_object_unref (G_OBJECT(imodel->priv->tmp_row));
1085 	*row = new_row_from_mysql_stmt (imodel, rownum, error);
1086 	imodel->priv->tmp_row = *row;
1087 
1088 	return TRUE;
1089 }
1090 
1091 /*
1092  * Create a new filled #GdaRow object for the previous cursor row, and put it into *prow.
1093  *
1094  * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
1095  * never keeps a reference to it).
1096  * Before a new #GdaRow gets created, the previous one, if set, is discarded.
1097  */
1098 static gboolean
gda_mysql_recordset_fetch_prev(GdaDataSelect * model,GdaRow ** row,gint rownum,GError ** error)1099 gda_mysql_recordset_fetch_prev (GdaDataSelect  *model,
1100 				GdaRow        **row,
1101 				gint            rownum,
1102 				GError        **error)
1103 {
1104 	GdaMysqlRecordset *imodel = (GdaMysqlRecordset*) model;
1105 
1106 	if (imodel->priv->tmp_row)
1107 		g_object_unref (G_OBJECT(imodel->priv->tmp_row));
1108 	*row = new_row_from_mysql_stmt (imodel, rownum, error);
1109 	imodel->priv->tmp_row = *row;
1110 
1111 	return TRUE;
1112 }
1113 
1114 /*
1115  * Create a new filled #GdaRow object for the cursor row at position @rownum, and put it into *row.
1116  *
1117  * Each new #GdaRow created is referenced only by imodel->priv->tmp_row (the #GdaDataSelect implementation
1118  * never keeps a reference to it).
1119  * Before a new #GdaRow gets created, the previous one, if set, is discarded.
1120  */
1121 static gboolean
gda_mysql_recordset_fetch_at(GdaDataSelect * model,GdaRow ** row,gint rownum,GError ** error)1122 gda_mysql_recordset_fetch_at (GdaDataSelect  *model,
1123 			      GdaRow        **row,
1124 			      gint            rownum,
1125 			      GError        **error)
1126 {
1127 	GdaMysqlRecordset *imodel = (GdaMysqlRecordset*) model;
1128 
1129 	if (imodel->priv->tmp_row)
1130 		g_object_unref (G_OBJECT(imodel->priv->tmp_row));
1131 	*row = new_row_from_mysql_stmt (imodel, rownum, error);
1132 	imodel->priv->tmp_row = *row;
1133 
1134 	return TRUE;
1135 }
1136 
1137