mysql2 0.3.18 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/mysql2/result.c CHANGED
@@ -50,8 +50,24 @@ static rb_encoding *binaryEncoding;
50
50
  #define MYSQL2_MIN_TIME 62171150401ULL
51
51
  #endif
52
52
 
53
+ #define GET_RESULT(obj) \
54
+ mysql2_result_wrapper *wrapper; \
55
+ Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
56
+
57
+ typedef struct {
58
+ int symbolizeKeys;
59
+ int asArray;
60
+ int castBool;
61
+ int cacheRows;
62
+ int cast;
63
+ int streaming;
64
+ ID db_timezone;
65
+ ID app_timezone;
66
+ VALUE block_given;
67
+ } result_each_args;
68
+
69
+ VALUE cBigDecimal, cDateTime, cDate;
53
70
  static VALUE cMysql2Result;
54
- static VALUE cBigDecimal, cDate, cDateTime;
55
71
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
56
72
  extern VALUE mMysql2, cMysql2Client, cMysql2Error;
57
73
  static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
@@ -59,6 +75,7 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_a
59
75
  sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
60
76
  static ID intern_merge;
61
77
 
78
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
62
79
  static void rb_mysql_result_mark(void * wrapper) {
63
80
  mysql2_result_wrapper * w = wrapper;
64
81
  if (w) {
@@ -66,13 +83,44 @@ static void rb_mysql_result_mark(void * wrapper) {
66
83
  rb_gc_mark(w->rows);
67
84
  rb_gc_mark(w->encoding);
68
85
  rb_gc_mark(w->client);
86
+ rb_gc_mark(w->statement);
69
87
  }
70
88
  }
71
89
 
72
90
  /* this may be called manually or during GC */
73
91
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
74
- if (wrapper && wrapper->resultFreed != 1) {
92
+ if (!wrapper) return;
93
+
94
+ if (wrapper->resultFreed != 1) {
95
+ if (wrapper->stmt_wrapper) {
96
+ mysql_stmt_free_result(wrapper->stmt_wrapper->stmt);
97
+
98
+ /* MySQL BUG? If the statement handle was previously used, and so
99
+ * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
100
+ * MySQL still thinks the result set buffer is available and will prefetch the
101
+ * first result in mysql_stmt_execute. This will corrupt or crash the program.
102
+ * By setting bind_result_done back to 0, we make MySQL think that a result set
103
+ * has never been bound to this statement handle before to prevent the prefetch.
104
+ */
105
+ wrapper->stmt_wrapper->stmt->bind_result_done = 0;
106
+
107
+ if (wrapper->result_buffers) {
108
+ unsigned int i;
109
+ for (i = 0; i < wrapper->numberOfFields; i++) {
110
+ if (wrapper->result_buffers[i].buffer) {
111
+ xfree(wrapper->result_buffers[i].buffer);
112
+ }
113
+ }
114
+ xfree(wrapper->result_buffers);
115
+ xfree(wrapper->is_null);
116
+ xfree(wrapper->error);
117
+ xfree(wrapper->length);
118
+ }
119
+ /* Clue that the next statement execute will need to allocate a new result buffer. */
120
+ wrapper->result_buffers = NULL;
121
+ }
75
122
  /* FIXME: this may call flush_use_result, which can hit the socket */
123
+ /* For prepared statements, wrapper->result is the result metadata */
76
124
  mysql_free_result(wrapper->result);
77
125
  wrapper->resultFreed = 1;
78
126
  }
@@ -80,7 +128,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
80
128
 
81
129
  /* this is called during GC */
82
130
  static void rb_mysql_result_free(void *ptr) {
83
- mysql2_result_wrapper * wrapper = ptr;
131
+ mysql2_result_wrapper *wrapper = ptr;
84
132
  rb_mysql_result_free_result(wrapper);
85
133
 
86
134
  // If the GC gets to client first it will be nil
@@ -88,6 +136,10 @@ static void rb_mysql_result_free(void *ptr) {
88
136
  decr_mysql2_client(wrapper->client_wrapper);
89
137
  }
90
138
 
139
+ if (wrapper->statement != Qnil) {
140
+ decr_mysql2_stmt(wrapper->stmt_wrapper);
141
+ }
142
+
91
143
  xfree(wrapper);
92
144
  }
93
145
 
@@ -102,10 +154,17 @@ static void *nogvl_fetch_row(void *ptr) {
102
154
  return mysql_fetch_row(result);
103
155
  }
104
156
 
157
+ static void *nogvl_stmt_fetch(void *ptr) {
158
+ MYSQL_STMT *stmt = ptr;
159
+ uintptr_t r = mysql_stmt_fetch(stmt);
160
+
161
+ return (void *)r;
162
+ }
163
+
164
+
105
165
  static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
106
- mysql2_result_wrapper * wrapper;
107
166
  VALUE rb_field;
108
- GetMysql2Result(self, wrapper);
167
+ GET_RESULT(self);
109
168
 
110
169
  if (wrapper->fields == Qnil) {
111
170
  wrapper->numberOfFields = mysql_num_fields(wrapper->result);
@@ -182,7 +241,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
182
241
  */
183
242
  static unsigned int msec_char_to_uint(char *msec_char, size_t len)
184
243
  {
185
- int i;
244
+ size_t i;
186
245
  for (i = 0; i < (len - 1); i++) {
187
246
  if (msec_char[i] == '\0') {
188
247
  msec_char[i] = '0';
@@ -191,9 +250,284 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
191
250
  return (unsigned int)strtoul(msec_char, NULL, 10);
192
251
  }
193
252
 
194
- static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) {
253
+ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) {
254
+ unsigned int i;
255
+ GET_RESULT(self);
256
+
257
+ if (wrapper->result_buffers != NULL) return;
258
+
259
+ wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND));
260
+ wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
261
+ wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
262
+ wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long));
263
+
264
+ for (i = 0; i < wrapper->numberOfFields; i++) {
265
+ wrapper->result_buffers[i].buffer_type = fields[i].type;
266
+
267
+ // mysql type | C type
268
+ switch(fields[i].type) {
269
+ case MYSQL_TYPE_NULL: // NULL
270
+ break;
271
+ case MYSQL_TYPE_TINY: // signed char
272
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(signed char));
273
+ wrapper->result_buffers[i].buffer_length = sizeof(signed char);
274
+ break;
275
+ case MYSQL_TYPE_SHORT: // short int
276
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int));
277
+ wrapper->result_buffers[i].buffer_length = sizeof(short int);
278
+ break;
279
+ case MYSQL_TYPE_INT24: // int
280
+ case MYSQL_TYPE_LONG: // int
281
+ case MYSQL_TYPE_YEAR: // int
282
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int));
283
+ wrapper->result_buffers[i].buffer_length = sizeof(int);
284
+ break;
285
+ case MYSQL_TYPE_LONGLONG: // long long int
286
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(long long int));
287
+ wrapper->result_buffers[i].buffer_length = sizeof(long long int);
288
+ break;
289
+ case MYSQL_TYPE_FLOAT: // float
290
+ case MYSQL_TYPE_DOUBLE: // double
291
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(double));
292
+ wrapper->result_buffers[i].buffer_length = sizeof(double);
293
+ break;
294
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
295
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
296
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
297
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
298
+ case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME
299
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME));
300
+ wrapper->result_buffers[i].buffer_length = sizeof(MYSQL_TIME);
301
+ break;
302
+ case MYSQL_TYPE_DECIMAL: // char[]
303
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
304
+ case MYSQL_TYPE_STRING: // char[]
305
+ case MYSQL_TYPE_VAR_STRING: // char[]
306
+ case MYSQL_TYPE_VARCHAR: // char[]
307
+ case MYSQL_TYPE_TINY_BLOB: // char[]
308
+ case MYSQL_TYPE_BLOB: // char[]
309
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
310
+ case MYSQL_TYPE_LONG_BLOB: // char[]
311
+ case MYSQL_TYPE_BIT: // char[]
312
+ case MYSQL_TYPE_SET: // char[]
313
+ case MYSQL_TYPE_ENUM: // char[]
314
+ case MYSQL_TYPE_GEOMETRY: // char[]
315
+ wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length);
316
+ wrapper->result_buffers[i].buffer_length = fields[i].max_length;
317
+ break;
318
+ default:
319
+ rb_raise(cMysql2Error, "unhandled mysql type: %d", fields[i].type);
320
+ }
321
+
322
+ wrapper->result_buffers[i].is_null = &wrapper->is_null[i];
323
+ wrapper->result_buffers[i].length = &wrapper->length[i];
324
+ wrapper->result_buffers[i].error = &wrapper->error[i];
325
+ wrapper->result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0);
326
+ }
327
+ }
328
+
329
+ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
330
+ {
331
+ VALUE rowVal;
332
+ unsigned int i = 0;
333
+
334
+ #ifdef HAVE_RUBY_ENCODING_H
335
+ rb_encoding *default_internal_enc;
336
+ rb_encoding *conn_enc;
337
+ #endif
338
+ GET_RESULT(self);
339
+
340
+ #ifdef HAVE_RUBY_ENCODING_H
341
+ default_internal_enc = rb_default_internal_encoding();
342
+ conn_enc = rb_to_encoding(wrapper->encoding);
343
+ #endif
344
+
345
+ if (args->asArray) {
346
+ rowVal = rb_ary_new2(wrapper->numberOfFields);
347
+ } else {
348
+ rowVal = rb_hash_new();
349
+ }
350
+ if (wrapper->fields == Qnil) {
351
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
352
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
353
+ }
354
+
355
+ if (wrapper->result_buffers == NULL) {
356
+ rb_mysql_result_alloc_result_buffers(self, fields);
357
+ }
358
+
359
+ if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) {
360
+ rb_raise_mysql2_stmt_error2(wrapper->stmt_wrapper->stmt
361
+ #ifdef HAVE_RUBY_ENCODING_H
362
+ , conn_enc
363
+ #endif
364
+ );
365
+ }
366
+
367
+ {
368
+ switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt_wrapper->stmt, RUBY_UBF_IO, 0)) {
369
+ case 0:
370
+ /* success */
371
+ break;
372
+
373
+ case 1:
374
+ /* error */
375
+ rb_raise_mysql2_stmt_error2(wrapper->stmt_wrapper->stmt
376
+ #ifdef HAVE_RUBY_ENCODING_H
377
+ , conn_enc
378
+ #endif
379
+ );
380
+
381
+ case MYSQL_NO_DATA:
382
+ /* no more row */
383
+ return Qnil;
384
+
385
+ case MYSQL_DATA_TRUNCATED:
386
+ rb_raise(cMysql2Error, "IMPLBUG: caught MYSQL_DATA_TRUNCATED. should not come here as buffer_length is set to fields[i].max_length.");
387
+ }
388
+ }
389
+
390
+ for (i = 0; i < wrapper->numberOfFields; i++) {
391
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
392
+ VALUE val = Qnil;
393
+ MYSQL_TIME *ts;
394
+
395
+ if (wrapper->is_null[i]) {
396
+ val = Qnil;
397
+ } else {
398
+ const MYSQL_BIND* const result_buffer = &wrapper->result_buffers[i];
399
+
400
+ switch(result_buffer->buffer_type) {
401
+ case MYSQL_TYPE_TINY: // signed char
402
+ if (args->castBool && fields[i].length == 1) {
403
+ val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse;
404
+ break;
405
+ }
406
+ if (result_buffer->is_unsigned) {
407
+ val = UINT2NUM(*((unsigned char*)result_buffer->buffer));
408
+ } else {
409
+ val = INT2NUM(*((signed char*)result_buffer->buffer));
410
+ }
411
+ break;
412
+ case MYSQL_TYPE_SHORT: // short int
413
+ if (result_buffer->is_unsigned) {
414
+ val = UINT2NUM(*((unsigned short int*)result_buffer->buffer));
415
+ } else {
416
+ val = INT2NUM(*((short int*)result_buffer->buffer));
417
+ }
418
+ break;
419
+ case MYSQL_TYPE_INT24: // int
420
+ case MYSQL_TYPE_LONG: // int
421
+ case MYSQL_TYPE_YEAR: // int
422
+ if (result_buffer->is_unsigned) {
423
+ val = UINT2NUM(*((unsigned int*)result_buffer->buffer));
424
+ } else {
425
+ val = INT2NUM(*((int*)result_buffer->buffer));
426
+ }
427
+ break;
428
+ case MYSQL_TYPE_LONGLONG: // long long int
429
+ if (result_buffer->is_unsigned) {
430
+ val = ULL2NUM(*((unsigned long long int*)result_buffer->buffer));
431
+ } else {
432
+ val = LL2NUM(*((long long int*)result_buffer->buffer));
433
+ }
434
+ break;
435
+ case MYSQL_TYPE_FLOAT: // float
436
+ val = rb_float_new((double)(*((float*)result_buffer->buffer)));
437
+ break;
438
+ case MYSQL_TYPE_DOUBLE: // double
439
+ val = rb_float_new((double)(*((double*)result_buffer->buffer)));
440
+ break;
441
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
442
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
443
+ ts = (MYSQL_TIME*)result_buffer->buffer;
444
+ val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day));
445
+ break;
446
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
447
+ ts = (MYSQL_TIME*)result_buffer->buffer;
448
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part));
449
+ if (!NIL_P(args->app_timezone)) {
450
+ if (args->app_timezone == intern_local) {
451
+ val = rb_funcall(val, intern_localtime, 0);
452
+ } else { // utc
453
+ val = rb_funcall(val, intern_utc, 0);
454
+ }
455
+ }
456
+ break;
457
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
458
+ case MYSQL_TYPE_TIMESTAMP: { // MYSQL_TIME
459
+ uint64_t seconds;
460
+
461
+ ts = (MYSQL_TIME*)result_buffer->buffer;
462
+ seconds = (ts->year*31557600ULL) + (ts->month*2592000ULL) + (ts->day*86400ULL) + (ts->hour*3600ULL) + (ts->minute*60ULL) + ts->second;
463
+
464
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
465
+ VALUE offset = INT2NUM(0);
466
+ if (args->db_timezone == intern_local) {
467
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
468
+ }
469
+ val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), offset);
470
+ if (!NIL_P(args->app_timezone)) {
471
+ if (args->app_timezone == intern_local) {
472
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
473
+ val = rb_funcall(val, intern_new_offset, 1, offset);
474
+ } else { // utc
475
+ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
476
+ }
477
+ }
478
+ } else {
479
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(ts->year), UINT2NUM(ts->month), UINT2NUM(ts->day), UINT2NUM(ts->hour), UINT2NUM(ts->minute), UINT2NUM(ts->second), ULONG2NUM(ts->second_part));
480
+ if (!NIL_P(args->app_timezone)) {
481
+ if (args->app_timezone == intern_local) {
482
+ val = rb_funcall(val, intern_localtime, 0);
483
+ } else { // utc
484
+ val = rb_funcall(val, intern_utc, 0);
485
+ }
486
+ }
487
+ }
488
+ break;
489
+ }
490
+ case MYSQL_TYPE_DECIMAL: // char[]
491
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
492
+ val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length)));
493
+ break;
494
+ case MYSQL_TYPE_STRING: // char[]
495
+ case MYSQL_TYPE_VAR_STRING: // char[]
496
+ case MYSQL_TYPE_VARCHAR: // char[]
497
+ case MYSQL_TYPE_TINY_BLOB: // char[]
498
+ case MYSQL_TYPE_BLOB: // char[]
499
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
500
+ case MYSQL_TYPE_LONG_BLOB: // char[]
501
+ case MYSQL_TYPE_BIT: // char[]
502
+ case MYSQL_TYPE_SET: // char[]
503
+ case MYSQL_TYPE_ENUM: // char[]
504
+ case MYSQL_TYPE_GEOMETRY: // char[]
505
+ val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
506
+ #ifdef HAVE_RUBY_ENCODING_H
507
+ val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
508
+ #endif
509
+ break;
510
+ default:
511
+ rb_raise(cMysql2Error, "unhandled buffer type: %d",
512
+ result_buffer->buffer_type);
513
+ break;
514
+ }
515
+ }
516
+
517
+ if (args->asArray) {
518
+ rb_ary_push(rowVal, val);
519
+ } else {
520
+ rb_hash_aset(rowVal, field, val);
521
+ }
522
+ }
523
+
524
+ return rowVal;
525
+ }
526
+
527
+
528
+ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
529
+ {
195
530
  VALUE rowVal;
196
- mysql2_result_wrapper * wrapper;
197
531
  MYSQL_ROW row;
198
532
  unsigned int i = 0;
199
533
  unsigned long * fieldLengths;
@@ -202,7 +536,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
202
536
  rb_encoding *default_internal_enc;
203
537
  rb_encoding *conn_enc;
204
538
  #endif
205
- GetMysql2Result(self, wrapper);
539
+ GET_RESULT(self);
206
540
 
207
541
  #ifdef HAVE_RUBY_ENCODING_H
208
542
  default_internal_enc = rb_default_internal_encoding();
@@ -215,7 +549,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
215
549
  return Qnil;
216
550
  }
217
551
 
218
- if (asArray) {
552
+ if (args->asArray) {
219
553
  rowVal = rb_ary_new2(wrapper->numberOfFields);
220
554
  } else {
221
555
  rowVal = rb_hash_new();
@@ -227,12 +561,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
227
561
  }
228
562
 
229
563
  for (i = 0; i < wrapper->numberOfFields; i++) {
230
- VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
564
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
231
565
  if (row[i]) {
232
566
  VALUE val = Qnil;
233
567
  enum enum_field_types type = fields[i].type;
234
568
 
235
- if (!cast) {
569
+ if (!args->cast) {
236
570
  if (type == MYSQL_TYPE_NULL) {
237
571
  val = Qnil;
238
572
  } else {
@@ -247,14 +581,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
247
581
  val = Qnil;
248
582
  break;
249
583
  case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
250
- if (castBool && fields[i].length == 1) {
584
+ if (args->castBool && fields[i].length == 1) {
251
585
  val = *row[i] == 1 ? Qtrue : Qfalse;
252
586
  }else{
253
587
  val = rb_str_new(row[i], fieldLengths[i]);
254
588
  }
255
589
  break;
256
590
  case MYSQL_TYPE_TINY: /* TINYINT field */
257
- if (castBool && fields[i].length == 1) {
591
+ if (args->castBool && fields[i].length == 1) {
258
592
  val = *row[i] != '0' ? Qtrue : Qfalse;
259
593
  break;
260
594
  }
@@ -297,9 +631,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
297
631
  break;
298
632
  }
299
633
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
300
- val = rb_funcall(rb_cTime, db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
301
- if (!NIL_P(app_timezone)) {
302
- if (app_timezone == intern_local) {
634
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
635
+ if (!NIL_P(args->app_timezone)) {
636
+ if (args->app_timezone == intern_local) {
303
637
  val = rb_funcall(val, intern_localtime, 0);
304
638
  } else { /* utc */
305
639
  val = rb_funcall(val, intern_utc, 0);
@@ -330,12 +664,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
330
664
  } else {
331
665
  if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
332
666
  VALUE offset = INT2NUM(0);
333
- if (db_timezone == intern_local) {
667
+ if (args->db_timezone == intern_local) {
334
668
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
335
669
  }
336
670
  val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset);
337
- if (!NIL_P(app_timezone)) {
338
- if (app_timezone == intern_local) {
671
+ if (!NIL_P(args->app_timezone)) {
672
+ if (args->app_timezone == intern_local) {
339
673
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
340
674
  val = rb_funcall(val, intern_new_offset, 1, offset);
341
675
  } else { /* utc */
@@ -344,9 +678,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
344
678
  }
345
679
  } else {
346
680
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
347
- val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
348
- if (!NIL_P(app_timezone)) {
349
- if (app_timezone == intern_local) {
681
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
682
+ if (!NIL_P(args->app_timezone)) {
683
+ if (args->app_timezone == intern_local) {
350
684
  val = rb_funcall(val, intern_localtime, 0);
351
685
  } else { /* utc */
352
686
  val = rb_funcall(val, intern_utc, 0);
@@ -396,13 +730,13 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
396
730
  break;
397
731
  }
398
732
  }
399
- if (asArray) {
733
+ if (args->asArray) {
400
734
  rb_ary_push(rowVal, val);
401
735
  } else {
402
736
  rb_hash_aset(rowVal, field, val);
403
737
  }
404
738
  } else {
405
- if (asArray) {
739
+ if (args->asArray) {
406
740
  rb_ary_push(rowVal, Qnil);
407
741
  } else {
408
742
  rb_hash_aset(rowVal, field, Qnil);
@@ -413,12 +747,11 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
413
747
  }
414
748
 
415
749
  static VALUE rb_mysql_result_fetch_fields(VALUE self) {
416
- mysql2_result_wrapper * wrapper;
417
750
  unsigned int i = 0;
418
751
  short int symbolizeKeys = 0;
419
752
  VALUE defaults;
420
753
 
421
- GetMysql2Result(self, wrapper);
754
+ GET_RESULT(self);
422
755
 
423
756
  defaults = rb_iv_get(self, "@query_options");
424
757
  Check_Type(defaults, T_HASH);
@@ -431,7 +764,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
431
764
  wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
432
765
  }
433
766
 
434
- if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
767
+ if ((unsigned)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
435
768
  for (i=0; i<wrapper->numberOfFields; i++) {
436
769
  rb_mysql_result_fetch_field(self, i, symbolizeKeys);
437
770
  }
@@ -440,108 +773,38 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
440
773
  return wrapper->fields;
441
774
  }
442
775
 
443
- static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
444
- VALUE defaults, opts, block;
445
- ID db_timezone, app_timezone, dbTz, appTz;
446
- mysql2_result_wrapper * wrapper;
776
+ static VALUE rb_mysql_result_each_(VALUE self,
777
+ VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args),
778
+ const result_each_args *args)
779
+ {
447
780
  unsigned long i;
448
- const char * errstr;
449
- int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0;
450
- MYSQL_FIELD * fields = NULL;
781
+ const char *errstr;
782
+ MYSQL_FIELD *fields = NULL;
451
783
 
452
- GetMysql2Result(self, wrapper);
453
-
454
- defaults = rb_iv_get(self, "@query_options");
455
- Check_Type(defaults, T_HASH);
456
- if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
457
- opts = rb_funcall(defaults, intern_merge, 1, opts);
458
- } else {
459
- opts = defaults;
460
- }
461
-
462
- if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
463
- symbolizeKeys = 1;
464
- }
784
+ GET_RESULT(self);
465
785
 
466
- if (rb_hash_aref(opts, sym_as) == sym_array) {
467
- asArray = 1;
468
- }
469
-
470
- if (rb_hash_aref(opts, sym_cast_booleans) == Qtrue) {
471
- castBool = 1;
472
- }
473
-
474
- if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
475
- cacheRows = 0;
476
- }
477
-
478
- if (rb_hash_aref(opts, sym_cast) == Qfalse) {
479
- cast = 0;
480
- }
481
-
482
- if (rb_hash_aref(opts, sym_stream) == Qtrue) {
483
- streaming = 1;
484
- }
485
-
486
- if (streaming && cacheRows) {
487
- rb_warn("cacheRows is ignored if streaming is true");
488
- }
489
-
490
- dbTz = rb_hash_aref(opts, sym_database_timezone);
491
- if (dbTz == sym_local) {
492
- db_timezone = intern_local;
493
- } else if (dbTz == sym_utc) {
494
- db_timezone = intern_utc;
495
- } else {
496
- if (!NIL_P(dbTz)) {
497
- rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
498
- }
499
- db_timezone = intern_local;
500
- }
501
-
502
- appTz = rb_hash_aref(opts, sym_application_timezone);
503
- if (appTz == sym_local) {
504
- app_timezone = intern_local;
505
- } else if (appTz == sym_utc) {
506
- app_timezone = intern_utc;
507
- } else {
508
- app_timezone = Qnil;
509
- }
510
-
511
- if (wrapper->lastRowProcessed == 0) {
512
- if (streaming) {
513
- /* We can't get number of rows if we're streaming, */
514
- /* until we've finished fetching all rows */
515
- wrapper->numberOfRows = 0;
786
+ if (wrapper->is_streaming) {
787
+ /* When streaming, we will only yield rows, not return them. */
788
+ if (wrapper->rows == Qnil) {
516
789
  wrapper->rows = rb_ary_new();
517
- } else {
518
- wrapper->numberOfRows = mysql_num_rows(wrapper->result);
519
- if (wrapper->numberOfRows == 0) {
520
- wrapper->rows = rb_ary_new();
521
- return wrapper->rows;
522
- }
523
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
524
790
  }
525
- }
526
791
 
527
- if (streaming) {
528
792
  if (!wrapper->streamingComplete) {
529
793
  VALUE row;
530
794
 
531
795
  fields = mysql_fetch_fields(wrapper->result);
532
796
 
533
797
  do {
534
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
535
-
536
- if (block != Qnil && row != Qnil) {
537
- rb_yield(row);
538
- wrapper->lastRowProcessed++;
798
+ row = fetch_row_func(self, fields, args);
799
+ if (row != Qnil) {
800
+ wrapper->numberOfRows++;
801
+ if (args->block_given != Qnil) {
802
+ rb_yield(row);
803
+ }
539
804
  }
540
805
  } while(row != Qnil);
541
806
 
542
807
  rb_mysql_result_free_result(wrapper);
543
-
544
- wrapper->numberOfRows = wrapper->lastRowProcessed;
545
808
  wrapper->streamingComplete = 1;
546
809
 
547
810
  // Check for errors, the connection might have gone out from under us
@@ -554,7 +817,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
554
817
  rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
555
818
  }
556
819
  } else {
557
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
820
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
558
821
  /* we've already read the entire dataset from the C result into our */
559
822
  /* internal array. Lets hand that over to the user since it's ready to go */
560
823
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -567,11 +830,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
567
830
 
568
831
  for (i = 0; i < wrapper->numberOfRows; i++) {
569
832
  VALUE row;
570
- if (cacheRows && i < rowsProcessed) {
833
+ if (args->cacheRows && i < rowsProcessed) {
571
834
  row = rb_ary_entry(wrapper->rows, i);
572
835
  } else {
573
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
574
- if (cacheRows) {
836
+ row = fetch_row_func(self, fields, args);
837
+ if (args->cacheRows) {
575
838
  rb_ary_store(wrapper->rows, i, row);
576
839
  }
577
840
  wrapper->lastRowProcessed++;
@@ -583,7 +846,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
583
846
  return Qnil;
584
847
  }
585
848
 
586
- if (block != Qnil) {
849
+ if (args->block_given != Qnil) {
587
850
  rb_yield(row);
588
851
  }
589
852
  }
@@ -594,28 +857,120 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
594
857
  }
595
858
  }
596
859
 
860
+ // FIXME return Enumerator instead?
861
+ // return rb_ary_each(wrapper->rows);
597
862
  return wrapper->rows;
598
863
  }
599
864
 
865
+ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
866
+ result_each_args args;
867
+ VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
868
+ ID db_timezone, app_timezone, dbTz, appTz;
869
+ int symbolizeKeys, asArray, castBool, cacheRows, cast;
870
+
871
+ GET_RESULT(self);
872
+
873
+ defaults = rb_iv_get(self, "@query_options");
874
+ Check_Type(defaults, T_HASH);
875
+ if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
876
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
877
+ } else {
878
+ opts = defaults;
879
+ }
880
+
881
+ symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
882
+ asArray = rb_hash_aref(opts, sym_as) == sym_array;
883
+ castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
884
+ cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
885
+ cast = RTEST(rb_hash_aref(opts, sym_cast));
886
+
887
+ if (wrapper->is_streaming && cacheRows) {
888
+ rb_warn(":cache_rows is ignored if :stream is true");
889
+ }
890
+
891
+ if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) {
892
+ rb_warn(":cache_rows is forced for prepared statements (if not streaming)");
893
+ }
894
+
895
+ if (wrapper->stmt_wrapper && !cast) {
896
+ rb_warn(":cast is forced for prepared statements");
897
+ }
898
+
899
+ dbTz = rb_hash_aref(opts, sym_database_timezone);
900
+ if (dbTz == sym_local) {
901
+ db_timezone = intern_local;
902
+ } else if (dbTz == sym_utc) {
903
+ db_timezone = intern_utc;
904
+ } else {
905
+ if (!NIL_P(dbTz)) {
906
+ rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
907
+ }
908
+ db_timezone = intern_local;
909
+ }
910
+
911
+ appTz = rb_hash_aref(opts, sym_application_timezone);
912
+ if (appTz == sym_local) {
913
+ app_timezone = intern_local;
914
+ } else if (appTz == sym_utc) {
915
+ app_timezone = intern_utc;
916
+ } else {
917
+ app_timezone = Qnil;
918
+ }
919
+
920
+ if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) {
921
+ wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
922
+ if (wrapper->numberOfRows == 0) {
923
+ wrapper->rows = rb_ary_new();
924
+ return wrapper->rows;
925
+ }
926
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
927
+ }
928
+
929
+ // Backward compat
930
+ args.symbolizeKeys = symbolizeKeys;
931
+ args.asArray = asArray;
932
+ args.castBool = castBool;
933
+ args.cacheRows = cacheRows;
934
+ args.cast = cast;
935
+ args.db_timezone = db_timezone;
936
+ args.app_timezone = app_timezone;
937
+ args.block_given = block;
938
+
939
+ if (wrapper->stmt_wrapper) {
940
+ fetch_row_func = rb_mysql_result_fetch_row_stmt;
941
+ } else {
942
+ fetch_row_func = rb_mysql_result_fetch_row;
943
+ }
944
+
945
+ return rb_mysql_result_each_(self, fetch_row_func, &args);
946
+ }
947
+
600
948
  static VALUE rb_mysql_result_count(VALUE self) {
601
- mysql2_result_wrapper *wrapper;
949
+ GET_RESULT(self);
950
+
951
+ if (wrapper->is_streaming) {
952
+ /* This is an unsigned long per result.h */
953
+ return ULONG2NUM(wrapper->numberOfRows);
954
+ }
602
955
 
603
- GetMysql2Result(self, wrapper);
604
956
  if (wrapper->resultFreed) {
605
- if (wrapper->streamingComplete){
606
- return LONG2NUM(wrapper->numberOfRows);
957
+ /* Ruby arrays have platform signed long length */
958
+ return LONG2NUM(RARRAY_LEN(wrapper->rows));
959
+ } else {
960
+ /* MySQL returns an unsigned 64-bit long here */
961
+ if (wrapper->stmt_wrapper) {
962
+ return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt));
607
963
  } else {
608
- return LONG2NUM(RARRAY_LEN(wrapper->rows));
964
+ return ULL2NUM(mysql_num_rows(wrapper->result));
609
965
  }
610
- } else {
611
- return INT2FIX(mysql_num_rows(wrapper->result));
612
966
  }
613
967
  }
614
968
 
615
969
  /* Mysql2::Result */
616
- VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
970
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) {
617
971
  VALUE obj;
618
972
  mysql2_result_wrapper * wrapper;
973
+
619
974
  obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
620
975
  wrapper->numberOfFields = 0;
621
976
  wrapper->numberOfRows = 0;
@@ -629,11 +984,27 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
629
984
  wrapper->client = client;
630
985
  wrapper->client_wrapper = DATA_PTR(client);
631
986
  wrapper->client_wrapper->refcount++;
987
+ wrapper->result_buffers = NULL;
988
+ wrapper->is_null = NULL;
989
+ wrapper->error = NULL;
990
+ wrapper->length = NULL;
991
+
992
+ /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
993
+ wrapper->statement = statement;
994
+ if (statement != Qnil) {
995
+ wrapper->stmt_wrapper = DATA_PTR(statement);
996
+ wrapper->stmt_wrapper->refcount++;
997
+ } else {
998
+ wrapper->stmt_wrapper = NULL;
999
+ }
632
1000
 
633
1001
  rb_obj_call_init(obj, 0, NULL);
634
-
635
1002
  rb_iv_set(obj, "@query_options", options);
636
1003
 
1004
+ /* Options that cannot be changed in results.each(...) { |row| }
1005
+ * should be processed here. */
1006
+ wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0);
1007
+
637
1008
  return obj;
638
1009
  }
639
1010