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 (®istering);
306 if (type == 0)
307 type = g_type_register_static (GDA_TYPE_DATA_SELECT, "GdaMysqlRecordset", &info, 0);
308 g_mutex_unlock (®istering);
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, ×tamp);
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