mysql2 0.3.21 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,4 +9,5 @@ void Init_mysql2() {
9
9
 
10
10
  init_mysql2_client();
11
11
  init_mysql2_result();
12
+ init_mysql2_statement();
12
13
  }
@@ -39,6 +39,7 @@ typedef unsigned int uint;
39
39
  #endif
40
40
 
41
41
  #include <client.h>
42
+ #include <statement.h>
42
43
  #include <result.h>
43
44
  #include <infile.h>
44
45
 
@@ -40,9 +40,9 @@ inline
40
40
  #endif
41
41
  #endif
42
42
  static unsigned int
43
- mysql2_mysql_enc_name_to_rb_hash (str, len)
43
+ mysql2_mysql_enc_name_to_rb_hash(str, len)
44
44
  register const char *str;
45
- register unsigned int len;
45
+ register const unsigned int len;
46
46
  {
47
47
  static const unsigned char asso_values[] =
48
48
  {
@@ -83,9 +83,9 @@ __attribute__ ((__gnu_inline__))
83
83
  #endif
84
84
  #endif
85
85
  const struct mysql2_mysql_enc_name_to_rb_map *
86
- mysql2_mysql_enc_name_to_rb (str, len)
86
+ mysql2_mysql_enc_name_to_rb(str, len)
87
87
  register const char *str;
88
- register unsigned int len;
88
+ register const unsigned int len;
89
89
  {
90
90
  enum
91
91
  {
@@ -154,9 +154,9 @@ mysql2_mysql_enc_name_to_rb (str, len)
154
154
 
155
155
  if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
156
156
  {
157
- register int key = mysql2_mysql_enc_name_to_rb_hash (str, len);
157
+ register const unsigned int key = mysql2_mysql_enc_name_to_rb_hash(str, len);
158
158
 
159
- if (key <= MAX_HASH_VALUE && key >= 0)
159
+ if (key <= MAX_HASH_VALUE)
160
160
  {
161
161
  register const char *s = wordlist[key].name;
162
162
 
@@ -54,8 +54,20 @@ static rb_encoding *binaryEncoding;
54
54
  mysql2_result_wrapper *wrapper; \
55
55
  Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
56
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;
57
70
  static VALUE cMysql2Result;
58
- static VALUE cBigDecimal, cDate, cDateTime;
59
71
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
60
72
  extern VALUE mMysql2, cMysql2Client, cMysql2Error;
61
73
  static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
@@ -63,6 +75,7 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_a
63
75
  sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
64
76
  static ID intern_merge;
65
77
 
78
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
66
79
  static void rb_mysql_result_mark(void * wrapper) {
67
80
  mysql2_result_wrapper * w = wrapper;
68
81
  if (w) {
@@ -70,13 +83,44 @@ static void rb_mysql_result_mark(void * wrapper) {
70
83
  rb_gc_mark(w->rows);
71
84
  rb_gc_mark(w->encoding);
72
85
  rb_gc_mark(w->client);
86
+ rb_gc_mark(w->statement);
73
87
  }
74
88
  }
75
89
 
76
90
  /* this may be called manually or during GC */
77
91
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
78
- 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
+ }
79
122
  /* FIXME: this may call flush_use_result, which can hit the socket */
123
+ /* For prepared statements, wrapper->result is the result metadata */
80
124
  mysql_free_result(wrapper->result);
81
125
  wrapper->resultFreed = 1;
82
126
  }
@@ -84,7 +128,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
84
128
 
85
129
  /* this is called during GC */
86
130
  static void rb_mysql_result_free(void *ptr) {
87
- mysql2_result_wrapper * wrapper = ptr;
131
+ mysql2_result_wrapper *wrapper = ptr;
88
132
  rb_mysql_result_free_result(wrapper);
89
133
 
90
134
  // If the GC gets to client first it will be nil
@@ -92,6 +136,10 @@ static void rb_mysql_result_free(void *ptr) {
92
136
  decr_mysql2_client(wrapper->client_wrapper);
93
137
  }
94
138
 
139
+ if (wrapper->statement != Qnil) {
140
+ decr_mysql2_stmt(wrapper->stmt_wrapper);
141
+ }
142
+
95
143
  xfree(wrapper);
96
144
  }
97
145
 
@@ -106,6 +154,14 @@ static void *nogvl_fetch_row(void *ptr) {
106
154
  return mysql_fetch_row(result);
107
155
  }
108
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
+
109
165
  static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
110
166
  VALUE rb_field;
111
167
  GET_RESULT(self);
@@ -185,7 +241,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
185
241
  */
186
242
  static unsigned int msec_char_to_uint(char *msec_char, size_t len)
187
243
  {
188
- int i;
244
+ size_t i;
189
245
  for (i = 0; i < (len - 1); i++) {
190
246
  if (msec_char[i] == '\0') {
191
247
  msec_char[i] = '0';
@@ -194,7 +250,283 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
194
250
  return (unsigned int)strtoul(msec_char, NULL, 10);
195
251
  }
196
252
 
197
- 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
+ {
198
530
  VALUE rowVal;
199
531
  MYSQL_ROW row;
200
532
  unsigned int i = 0;
@@ -217,7 +549,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
217
549
  return Qnil;
218
550
  }
219
551
 
220
- if (asArray) {
552
+ if (args->asArray) {
221
553
  rowVal = rb_ary_new2(wrapper->numberOfFields);
222
554
  } else {
223
555
  rowVal = rb_hash_new();
@@ -229,12 +561,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
229
561
  }
230
562
 
231
563
  for (i = 0; i < wrapper->numberOfFields; i++) {
232
- VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
564
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
233
565
  if (row[i]) {
234
566
  VALUE val = Qnil;
235
567
  enum enum_field_types type = fields[i].type;
236
568
 
237
- if (!cast) {
569
+ if (!args->cast) {
238
570
  if (type == MYSQL_TYPE_NULL) {
239
571
  val = Qnil;
240
572
  } else {
@@ -249,14 +581,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
249
581
  val = Qnil;
250
582
  break;
251
583
  case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
252
- if (castBool && fields[i].length == 1) {
584
+ if (args->castBool && fields[i].length == 1) {
253
585
  val = *row[i] == 1 ? Qtrue : Qfalse;
254
586
  }else{
255
587
  val = rb_str_new(row[i], fieldLengths[i]);
256
588
  }
257
589
  break;
258
590
  case MYSQL_TYPE_TINY: /* TINYINT field */
259
- if (castBool && fields[i].length == 1) {
591
+ if (args->castBool && fields[i].length == 1) {
260
592
  val = *row[i] != '0' ? Qtrue : Qfalse;
261
593
  break;
262
594
  }
@@ -299,9 +631,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
299
631
  break;
300
632
  }
301
633
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
302
- 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));
303
- if (!NIL_P(app_timezone)) {
304
- 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) {
305
637
  val = rb_funcall(val, intern_localtime, 0);
306
638
  } else { /* utc */
307
639
  val = rb_funcall(val, intern_utc, 0);
@@ -332,12 +664,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
332
664
  } else {
333
665
  if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
334
666
  VALUE offset = INT2NUM(0);
335
- if (db_timezone == intern_local) {
667
+ if (args->db_timezone == intern_local) {
336
668
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
337
669
  }
338
670
  val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset);
339
- if (!NIL_P(app_timezone)) {
340
- if (app_timezone == intern_local) {
671
+ if (!NIL_P(args->app_timezone)) {
672
+ if (args->app_timezone == intern_local) {
341
673
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
342
674
  val = rb_funcall(val, intern_new_offset, 1, offset);
343
675
  } else { /* utc */
@@ -346,9 +678,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
346
678
  }
347
679
  } else {
348
680
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
349
- val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
350
- if (!NIL_P(app_timezone)) {
351
- 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) {
352
684
  val = rb_funcall(val, intern_localtime, 0);
353
685
  } else { /* utc */
354
686
  val = rb_funcall(val, intern_utc, 0);
@@ -398,13 +730,13 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
398
730
  break;
399
731
  }
400
732
  }
401
- if (asArray) {
733
+ if (args->asArray) {
402
734
  rb_ary_push(rowVal, val);
403
735
  } else {
404
736
  rb_hash_aset(rowVal, field, val);
405
737
  }
406
738
  } else {
407
- if (asArray) {
739
+ if (args->asArray) {
408
740
  rb_ary_push(rowVal, Qnil);
409
741
  } else {
410
742
  rb_hash_aset(rowVal, field, Qnil);
@@ -432,7 +764,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
432
764
  wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
433
765
  }
434
766
 
435
- if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
767
+ if ((unsigned)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
436
768
  for (i=0; i<wrapper->numberOfFields; i++) {
437
769
  rb_mysql_result_fetch_field(self, i, symbolizeKeys);
438
770
  }
@@ -441,55 +773,16 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
441
773
  return wrapper->fields;
442
774
  }
443
775
 
444
- static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
445
- VALUE defaults, opts, block;
446
- ID db_timezone, app_timezone, dbTz, appTz;
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, asArray, castBool, cacheRows, cast;
450
- MYSQL_FIELD * fields = NULL;
781
+ const char *errstr;
782
+ MYSQL_FIELD *fields = NULL;
451
783
 
452
784
  GET_RESULT(self);
453
785
 
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
- symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
463
- asArray = rb_hash_aref(opts, sym_as) == sym_array;
464
- castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
465
- cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
466
- cast = RTEST(rb_hash_aref(opts, sym_cast));
467
-
468
- if (wrapper->is_streaming && cacheRows) {
469
- rb_warn(":cache_rows is ignored if :stream is true");
470
- }
471
-
472
- dbTz = rb_hash_aref(opts, sym_database_timezone);
473
- if (dbTz == sym_local) {
474
- db_timezone = intern_local;
475
- } else if (dbTz == sym_utc) {
476
- db_timezone = intern_utc;
477
- } else {
478
- if (!NIL_P(dbTz)) {
479
- rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
480
- }
481
- db_timezone = intern_local;
482
- }
483
-
484
- appTz = rb_hash_aref(opts, sym_application_timezone);
485
- if (appTz == sym_local) {
486
- app_timezone = intern_local;
487
- } else if (appTz == sym_utc) {
488
- app_timezone = intern_utc;
489
- } else {
490
- app_timezone = Qnil;
491
- }
492
-
493
786
  if (wrapper->is_streaming) {
494
787
  /* When streaming, we will only yield rows, not return them. */
495
788
  if (wrapper->rows == Qnil) {
@@ -502,10 +795,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
502
795
  fields = mysql_fetch_fields(wrapper->result);
503
796
 
504
797
  do {
505
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
798
+ row = fetch_row_func(self, fields, args);
506
799
  if (row != Qnil) {
507
800
  wrapper->numberOfRows++;
508
- if (block != Qnil) {
801
+ if (args->block_given != Qnil) {
509
802
  rb_yield(row);
510
803
  }
511
804
  }
@@ -524,20 +817,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
524
817
  rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
525
818
  }
526
819
  } else {
527
- if (wrapper->lastRowProcessed == 0) {
528
- wrapper->numberOfRows = mysql_num_rows(wrapper->result);
529
- if (wrapper->numberOfRows == 0) {
530
- wrapper->rows = rb_ary_new();
531
- return wrapper->rows;
532
- }
533
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
534
- } else if (!cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
535
- mysql_data_seek(wrapper->result, 0);
536
- wrapper->lastRowProcessed = 0;
537
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
538
- }
539
-
540
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
820
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
541
821
  /* we've already read the entire dataset from the C result into our */
542
822
  /* internal array. Lets hand that over to the user since it's ready to go */
543
823
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -550,11 +830,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
550
830
 
551
831
  for (i = 0; i < wrapper->numberOfRows; i++) {
552
832
  VALUE row;
553
- if (cacheRows && i < rowsProcessed) {
833
+ if (args->cacheRows && i < rowsProcessed) {
554
834
  row = rb_ary_entry(wrapper->rows, i);
555
835
  } else {
556
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
557
- if (cacheRows) {
836
+ row = fetch_row_func(self, fields, args);
837
+ if (args->cacheRows) {
558
838
  rb_ary_store(wrapper->rows, i, row);
559
839
  }
560
840
  wrapper->lastRowProcessed++;
@@ -562,26 +842,109 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
562
842
 
563
843
  if (row == Qnil) {
564
844
  /* we don't need the mysql C dataset around anymore, peace it */
565
- if (cacheRows) {
566
- rb_mysql_result_free_result(wrapper);
567
- }
845
+ rb_mysql_result_free_result(wrapper);
568
846
  return Qnil;
569
847
  }
570
848
 
571
- if (block != Qnil) {
849
+ if (args->block_given != Qnil) {
572
850
  rb_yield(row);
573
851
  }
574
852
  }
575
- if (wrapper->lastRowProcessed == wrapper->numberOfRows && cacheRows) {
853
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
576
854
  /* we don't need the mysql C dataset around anymore, peace it */
577
855
  rb_mysql_result_free_result(wrapper);
578
856
  }
579
857
  }
580
858
  }
581
859
 
860
+ // FIXME return Enumerator instead?
861
+ // return rb_ary_each(wrapper->rows);
582
862
  return wrapper->rows;
583
863
  }
584
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
+
585
948
  static VALUE rb_mysql_result_count(VALUE self) {
586
949
  GET_RESULT(self);
587
950
 
@@ -595,12 +958,16 @@ static VALUE rb_mysql_result_count(VALUE self) {
595
958
  return LONG2NUM(RARRAY_LEN(wrapper->rows));
596
959
  } else {
597
960
  /* MySQL returns an unsigned 64-bit long here */
598
- return ULL2NUM(mysql_num_rows(wrapper->result));
961
+ if (wrapper->stmt_wrapper) {
962
+ return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt));
963
+ } else {
964
+ return ULL2NUM(mysql_num_rows(wrapper->result));
965
+ }
599
966
  }
600
967
  }
601
968
 
602
969
  /* Mysql2::Result */
603
- 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) {
604
971
  VALUE obj;
605
972
  mysql2_result_wrapper * wrapper;
606
973
 
@@ -617,9 +984,21 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
617
984
  wrapper->client = client;
618
985
  wrapper->client_wrapper = DATA_PTR(client);
619
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
+ }
620
1000
 
621
1001
  rb_obj_call_init(obj, 0, NULL);
622
-
623
1002
  rb_iv_set(obj, "@query_options", options);
624
1003
 
625
1004
  /* Options that cannot be changed in results.each(...) { |row| }