mysql2 0.3.18 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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