mysql2 0.3.18 → 0.4.2

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +63 -12
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +170 -175
  8. data/ext/mysql2/client.h +21 -1
  9. data/ext/mysql2/extconf.rb +95 -35
  10. data/ext/mysql2/infile.c +2 -2
  11. data/ext/mysql2/mysql2_ext.c +1 -0
  12. data/ext/mysql2/mysql2_ext.h +5 -6
  13. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  14. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  15. data/ext/mysql2/result.c +494 -132
  16. data/ext/mysql2/result.h +12 -6
  17. data/ext/mysql2/statement.c +494 -0
  18. data/ext/mysql2/statement.h +19 -0
  19. data/lib/mysql2/client.rb +68 -22
  20. data/lib/mysql2/console.rb +1 -1
  21. data/lib/mysql2/em.rb +5 -6
  22. data/lib/mysql2/error.rb +18 -27
  23. data/lib/mysql2/field.rb +3 -0
  24. data/lib/mysql2/statement.rb +17 -0
  25. data/lib/mysql2/version.rb +1 -1
  26. data/lib/mysql2.rb +38 -18
  27. data/spec/em/em_spec.rb +21 -21
  28. data/spec/mysql2/client_spec.rb +393 -351
  29. data/spec/mysql2/error_spec.rb +37 -36
  30. data/spec/mysql2/result_spec.rb +213 -208
  31. data/spec/mysql2/statement_spec.rb +684 -0
  32. data/spec/spec_helper.rb +7 -0
  33. data/spec/ssl/ca-cert.pem +17 -0
  34. data/spec/ssl/ca-key.pem +27 -0
  35. data/spec/ssl/ca.cnf +22 -0
  36. data/spec/ssl/cert.cnf +22 -0
  37. data/spec/ssl/client-cert.pem +17 -0
  38. data/spec/ssl/client-key.pem +27 -0
  39. data/spec/ssl/client-req.pem +15 -0
  40. data/spec/ssl/gen_certs.sh +48 -0
  41. data/spec/ssl/pkcs8-client-key.pem +28 -0
  42. data/spec/ssl/pkcs8-server-key.pem +28 -0
  43. data/spec/ssl/server-cert.pem +17 -0
  44. data/spec/ssl/server-key.pem +27 -0
  45. data/spec/ssl/server-req.pem +15 -0
  46. data/support/mysql_enc_to_ruby.rb +7 -8
  47. data/support/ruby_enc_to_mysql.rb +1 -1
  48. metadata +41 -46
data/ext/mysql2/result.c CHANGED
@@ -1,7 +1,5 @@
1
1
  #include <mysql2_ext.h>
2
2
 
3
- #include <stdint.h>
4
-
5
3
  #include "mysql_enc_to_ruby.h"
6
4
 
7
5
  #ifdef HAVE_RUBY_ENCODING_H
@@ -50,8 +48,24 @@ static rb_encoding *binaryEncoding;
50
48
  #define MYSQL2_MIN_TIME 62171150401ULL
51
49
  #endif
52
50
 
51
+ #define GET_RESULT(self) \
52
+ mysql2_result_wrapper *wrapper; \
53
+ Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
54
+
55
+ typedef struct {
56
+ int symbolizeKeys;
57
+ int asArray;
58
+ int castBool;
59
+ int cacheRows;
60
+ int cast;
61
+ int streaming;
62
+ ID db_timezone;
63
+ ID app_timezone;
64
+ VALUE block_given;
65
+ } result_each_args;
66
+
67
+ VALUE cBigDecimal, cDateTime, cDate;
53
68
  static VALUE cMysql2Result;
54
- static VALUE cBigDecimal, cDate, cDateTime;
55
69
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
56
70
  extern VALUE mMysql2, cMysql2Client, cMysql2Error;
57
71
  static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
@@ -59,6 +73,7 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_a
59
73
  sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
60
74
  static ID intern_merge;
61
75
 
76
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
62
77
  static void rb_mysql_result_mark(void * wrapper) {
63
78
  mysql2_result_wrapper * w = wrapper;
64
79
  if (w) {
@@ -66,13 +81,46 @@ static void rb_mysql_result_mark(void * wrapper) {
66
81
  rb_gc_mark(w->rows);
67
82
  rb_gc_mark(w->encoding);
68
83
  rb_gc_mark(w->client);
84
+ rb_gc_mark(w->statement);
69
85
  }
70
86
  }
71
87
 
72
88
  /* this may be called manually or during GC */
73
89
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
74
- if (wrapper && wrapper->resultFreed != 1) {
90
+ if (!wrapper) return;
91
+
92
+ if (wrapper->resultFreed != 1) {
93
+ if (wrapper->stmt_wrapper) {
94
+ if (!wrapper->stmt_wrapper->closed) {
95
+ mysql_stmt_free_result(wrapper->stmt_wrapper->stmt);
96
+
97
+ /* MySQL BUG? If the statement handle was previously used, and so
98
+ * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
99
+ * MySQL still thinks the result set buffer is available and will prefetch the
100
+ * first result in mysql_stmt_execute. This will corrupt or crash the program.
101
+ * By setting bind_result_done back to 0, we make MySQL think that a result set
102
+ * has never been bound to this statement handle before to prevent the prefetch.
103
+ */
104
+ wrapper->stmt_wrapper->stmt->bind_result_done = 0;
105
+ }
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,16 @@ static void *nogvl_fetch_row(void *ptr) {
102
154
  return mysql_fetch_row(result);
103
155
  }
104
156
 
105
- static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
106
- mysql2_result_wrapper * wrapper;
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
+ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) {
107
165
  VALUE rb_field;
108
- GetMysql2Result(self, wrapper);
166
+ GET_RESULT(self);
109
167
 
110
168
  if (wrapper->fields == Qnil) {
111
169
  wrapper->numberOfFields = mysql_num_fields(wrapper->result);
@@ -147,7 +205,7 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
147
205
 
148
206
  #ifdef HAVE_RUBY_ENCODING_H
149
207
  static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
150
- /* if binary flag is set, respect it's wishes */
208
+ /* if binary flag is set, respect its wishes */
151
209
  if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
152
210
  rb_enc_associate(val, binaryEncoding);
153
211
  } else if (!field.charsetnr) {
@@ -182,7 +240,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
182
240
  */
183
241
  static unsigned int msec_char_to_uint(char *msec_char, size_t len)
184
242
  {
185
- int i;
243
+ size_t i;
186
244
  for (i = 0; i < (len - 1); i++) {
187
245
  if (msec_char[i] == '\0') {
188
246
  msec_char[i] = '0';
@@ -191,9 +249,272 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
191
249
  return (unsigned int)strtoul(msec_char, NULL, 10);
192
250
  }
193
251
 
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) {
252
+ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) {
253
+ unsigned int i;
254
+ GET_RESULT(self);
255
+
256
+ if (wrapper->result_buffers != NULL) return;
257
+
258
+ wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND));
259
+ wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
260
+ wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
261
+ wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long));
262
+
263
+ for (i = 0; i < wrapper->numberOfFields; i++) {
264
+ wrapper->result_buffers[i].buffer_type = fields[i].type;
265
+
266
+ // mysql type | C type
267
+ switch(fields[i].type) {
268
+ case MYSQL_TYPE_NULL: // NULL
269
+ break;
270
+ case MYSQL_TYPE_TINY: // signed char
271
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(signed char));
272
+ wrapper->result_buffers[i].buffer_length = sizeof(signed char);
273
+ break;
274
+ case MYSQL_TYPE_SHORT: // short int
275
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int));
276
+ wrapper->result_buffers[i].buffer_length = sizeof(short int);
277
+ break;
278
+ case MYSQL_TYPE_INT24: // int
279
+ case MYSQL_TYPE_LONG: // int
280
+ case MYSQL_TYPE_YEAR: // int
281
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int));
282
+ wrapper->result_buffers[i].buffer_length = sizeof(int);
283
+ break;
284
+ case MYSQL_TYPE_LONGLONG: // long long int
285
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(long long int));
286
+ wrapper->result_buffers[i].buffer_length = sizeof(long long int);
287
+ break;
288
+ case MYSQL_TYPE_FLOAT: // float
289
+ case MYSQL_TYPE_DOUBLE: // double
290
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(double));
291
+ wrapper->result_buffers[i].buffer_length = sizeof(double);
292
+ break;
293
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
294
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
295
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
296
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
297
+ case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME
298
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME));
299
+ wrapper->result_buffers[i].buffer_length = sizeof(MYSQL_TIME);
300
+ break;
301
+ case MYSQL_TYPE_DECIMAL: // char[]
302
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
303
+ case MYSQL_TYPE_STRING: // char[]
304
+ case MYSQL_TYPE_VAR_STRING: // char[]
305
+ case MYSQL_TYPE_VARCHAR: // char[]
306
+ case MYSQL_TYPE_TINY_BLOB: // char[]
307
+ case MYSQL_TYPE_BLOB: // char[]
308
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
309
+ case MYSQL_TYPE_LONG_BLOB: // char[]
310
+ case MYSQL_TYPE_BIT: // char[]
311
+ case MYSQL_TYPE_SET: // char[]
312
+ case MYSQL_TYPE_ENUM: // char[]
313
+ case MYSQL_TYPE_GEOMETRY: // char[]
314
+ default:
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
+ }
319
+
320
+ wrapper->result_buffers[i].is_null = &wrapper->is_null[i];
321
+ wrapper->result_buffers[i].length = &wrapper->length[i];
322
+ wrapper->result_buffers[i].error = &wrapper->error[i];
323
+ wrapper->result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0);
324
+ }
325
+ }
326
+
327
+ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
328
+ {
329
+ VALUE rowVal;
330
+ unsigned int i = 0;
331
+
332
+ #ifdef HAVE_RUBY_ENCODING_H
333
+ rb_encoding *default_internal_enc;
334
+ rb_encoding *conn_enc;
335
+ #endif
336
+ GET_RESULT(self);
337
+
338
+ #ifdef HAVE_RUBY_ENCODING_H
339
+ default_internal_enc = rb_default_internal_encoding();
340
+ conn_enc = rb_to_encoding(wrapper->encoding);
341
+ #endif
342
+
343
+ if (args->asArray) {
344
+ rowVal = rb_ary_new2(wrapper->numberOfFields);
345
+ } else {
346
+ rowVal = rb_hash_new();
347
+ }
348
+ if (wrapper->fields == Qnil) {
349
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
350
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
351
+ }
352
+
353
+ if (wrapper->result_buffers == NULL) {
354
+ rb_mysql_result_alloc_result_buffers(self, fields);
355
+ }
356
+
357
+ if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) {
358
+ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper);
359
+ }
360
+
361
+ {
362
+ switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt_wrapper->stmt, RUBY_UBF_IO, 0)) {
363
+ case 0:
364
+ /* success */
365
+ break;
366
+
367
+ case 1:
368
+ /* error */
369
+ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper);
370
+
371
+ case MYSQL_NO_DATA:
372
+ /* no more row */
373
+ return Qnil;
374
+
375
+ case MYSQL_DATA_TRUNCATED:
376
+ rb_raise(cMysql2Error, "IMPLBUG: caught MYSQL_DATA_TRUNCATED. should not come here as buffer_length is set to fields[i].max_length.");
377
+ }
378
+ }
379
+
380
+ for (i = 0; i < wrapper->numberOfFields; i++) {
381
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
382
+ VALUE val = Qnil;
383
+ MYSQL_TIME *ts;
384
+
385
+ if (wrapper->is_null[i]) {
386
+ val = Qnil;
387
+ } else {
388
+ const MYSQL_BIND* const result_buffer = &wrapper->result_buffers[i];
389
+
390
+ switch(result_buffer->buffer_type) {
391
+ case MYSQL_TYPE_TINY: // signed char
392
+ if (args->castBool && fields[i].length == 1) {
393
+ val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse;
394
+ break;
395
+ }
396
+ if (result_buffer->is_unsigned) {
397
+ val = UINT2NUM(*((unsigned char*)result_buffer->buffer));
398
+ } else {
399
+ val = INT2NUM(*((signed char*)result_buffer->buffer));
400
+ }
401
+ break;
402
+ case MYSQL_TYPE_SHORT: // short int
403
+ if (result_buffer->is_unsigned) {
404
+ val = UINT2NUM(*((unsigned short int*)result_buffer->buffer));
405
+ } else {
406
+ val = INT2NUM(*((short int*)result_buffer->buffer));
407
+ }
408
+ break;
409
+ case MYSQL_TYPE_INT24: // int
410
+ case MYSQL_TYPE_LONG: // int
411
+ case MYSQL_TYPE_YEAR: // int
412
+ if (result_buffer->is_unsigned) {
413
+ val = UINT2NUM(*((unsigned int*)result_buffer->buffer));
414
+ } else {
415
+ val = INT2NUM(*((int*)result_buffer->buffer));
416
+ }
417
+ break;
418
+ case MYSQL_TYPE_LONGLONG: // long long int
419
+ if (result_buffer->is_unsigned) {
420
+ val = ULL2NUM(*((unsigned long long int*)result_buffer->buffer));
421
+ } else {
422
+ val = LL2NUM(*((long long int*)result_buffer->buffer));
423
+ }
424
+ break;
425
+ case MYSQL_TYPE_FLOAT: // float
426
+ val = rb_float_new((double)(*((float*)result_buffer->buffer)));
427
+ break;
428
+ case MYSQL_TYPE_DOUBLE: // double
429
+ val = rb_float_new((double)(*((double*)result_buffer->buffer)));
430
+ break;
431
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
432
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
433
+ ts = (MYSQL_TIME*)result_buffer->buffer;
434
+ val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day));
435
+ break;
436
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
437
+ ts = (MYSQL_TIME*)result_buffer->buffer;
438
+ 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));
439
+ if (!NIL_P(args->app_timezone)) {
440
+ if (args->app_timezone == intern_local) {
441
+ val = rb_funcall(val, intern_localtime, 0);
442
+ } else { // utc
443
+ val = rb_funcall(val, intern_utc, 0);
444
+ }
445
+ }
446
+ break;
447
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
448
+ case MYSQL_TYPE_TIMESTAMP: { // MYSQL_TIME
449
+ uint64_t seconds;
450
+
451
+ ts = (MYSQL_TIME*)result_buffer->buffer;
452
+ seconds = (ts->year*31557600ULL) + (ts->month*2592000ULL) + (ts->day*86400ULL) + (ts->hour*3600ULL) + (ts->minute*60ULL) + ts->second;
453
+
454
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
455
+ VALUE offset = INT2NUM(0);
456
+ if (args->db_timezone == intern_local) {
457
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
458
+ }
459
+ 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);
460
+ if (!NIL_P(args->app_timezone)) {
461
+ if (args->app_timezone == intern_local) {
462
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
463
+ val = rb_funcall(val, intern_new_offset, 1, offset);
464
+ } else { // utc
465
+ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
466
+ }
467
+ }
468
+ } else {
469
+ 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));
470
+ if (!NIL_P(args->app_timezone)) {
471
+ if (args->app_timezone == intern_local) {
472
+ val = rb_funcall(val, intern_localtime, 0);
473
+ } else { // utc
474
+ val = rb_funcall(val, intern_utc, 0);
475
+ }
476
+ }
477
+ }
478
+ break;
479
+ }
480
+ case MYSQL_TYPE_DECIMAL: // char[]
481
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
482
+ val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length)));
483
+ break;
484
+ case MYSQL_TYPE_STRING: // char[]
485
+ case MYSQL_TYPE_VAR_STRING: // char[]
486
+ case MYSQL_TYPE_VARCHAR: // char[]
487
+ case MYSQL_TYPE_TINY_BLOB: // char[]
488
+ case MYSQL_TYPE_BLOB: // char[]
489
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
490
+ case MYSQL_TYPE_LONG_BLOB: // char[]
491
+ case MYSQL_TYPE_BIT: // char[]
492
+ case MYSQL_TYPE_SET: // char[]
493
+ case MYSQL_TYPE_ENUM: // char[]
494
+ case MYSQL_TYPE_GEOMETRY: // char[]
495
+ default:
496
+ val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
497
+ #ifdef HAVE_RUBY_ENCODING_H
498
+ val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
499
+ #endif
500
+ break;
501
+ }
502
+ }
503
+
504
+ if (args->asArray) {
505
+ rb_ary_push(rowVal, val);
506
+ } else {
507
+ rb_hash_aset(rowVal, field, val);
508
+ }
509
+ }
510
+
511
+ return rowVal;
512
+ }
513
+
514
+
515
+ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
516
+ {
195
517
  VALUE rowVal;
196
- mysql2_result_wrapper * wrapper;
197
518
  MYSQL_ROW row;
198
519
  unsigned int i = 0;
199
520
  unsigned long * fieldLengths;
@@ -202,7 +523,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
202
523
  rb_encoding *default_internal_enc;
203
524
  rb_encoding *conn_enc;
204
525
  #endif
205
- GetMysql2Result(self, wrapper);
526
+ GET_RESULT(self);
206
527
 
207
528
  #ifdef HAVE_RUBY_ENCODING_H
208
529
  default_internal_enc = rb_default_internal_encoding();
@@ -215,7 +536,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
215
536
  return Qnil;
216
537
  }
217
538
 
218
- if (asArray) {
539
+ if (args->asArray) {
219
540
  rowVal = rb_ary_new2(wrapper->numberOfFields);
220
541
  } else {
221
542
  rowVal = rb_hash_new();
@@ -227,12 +548,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
227
548
  }
228
549
 
229
550
  for (i = 0; i < wrapper->numberOfFields; i++) {
230
- VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
551
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
231
552
  if (row[i]) {
232
553
  VALUE val = Qnil;
233
554
  enum enum_field_types type = fields[i].type;
234
555
 
235
- if (!cast) {
556
+ if (!args->cast) {
236
557
  if (type == MYSQL_TYPE_NULL) {
237
558
  val = Qnil;
238
559
  } else {
@@ -247,14 +568,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
247
568
  val = Qnil;
248
569
  break;
249
570
  case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
250
- if (castBool && fields[i].length == 1) {
571
+ if (args->castBool && fields[i].length == 1) {
251
572
  val = *row[i] == 1 ? Qtrue : Qfalse;
252
573
  }else{
253
574
  val = rb_str_new(row[i], fieldLengths[i]);
254
575
  }
255
576
  break;
256
577
  case MYSQL_TYPE_TINY: /* TINYINT field */
257
- if (castBool && fields[i].length == 1) {
578
+ if (args->castBool && fields[i].length == 1) {
258
579
  val = *row[i] != '0' ? Qtrue : Qfalse;
259
580
  break;
260
581
  }
@@ -297,9 +618,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
297
618
  break;
298
619
  }
299
620
  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) {
621
+ 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));
622
+ if (!NIL_P(args->app_timezone)) {
623
+ if (args->app_timezone == intern_local) {
303
624
  val = rb_funcall(val, intern_localtime, 0);
304
625
  } else { /* utc */
305
626
  val = rb_funcall(val, intern_utc, 0);
@@ -330,12 +651,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
330
651
  } else {
331
652
  if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
332
653
  VALUE offset = INT2NUM(0);
333
- if (db_timezone == intern_local) {
654
+ if (args->db_timezone == intern_local) {
334
655
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
335
656
  }
336
657
  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) {
658
+ if (!NIL_P(args->app_timezone)) {
659
+ if (args->app_timezone == intern_local) {
339
660
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
340
661
  val = rb_funcall(val, intern_new_offset, 1, offset);
341
662
  } else { /* utc */
@@ -344,9 +665,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
344
665
  }
345
666
  } else {
346
667
  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) {
668
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
669
+ if (!NIL_P(args->app_timezone)) {
670
+ if (args->app_timezone == intern_local) {
350
671
  val = rb_funcall(val, intern_localtime, 0);
351
672
  } else { /* utc */
352
673
  val = rb_funcall(val, intern_utc, 0);
@@ -396,13 +717,13 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
396
717
  break;
397
718
  }
398
719
  }
399
- if (asArray) {
720
+ if (args->asArray) {
400
721
  rb_ary_push(rowVal, val);
401
722
  } else {
402
723
  rb_hash_aset(rowVal, field, val);
403
724
  }
404
725
  } else {
405
- if (asArray) {
726
+ if (args->asArray) {
406
727
  rb_ary_push(rowVal, Qnil);
407
728
  } else {
408
729
  rb_hash_aset(rowVal, field, Qnil);
@@ -413,12 +734,11 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
413
734
  }
414
735
 
415
736
  static VALUE rb_mysql_result_fetch_fields(VALUE self) {
416
- mysql2_result_wrapper * wrapper;
417
737
  unsigned int i = 0;
418
738
  short int symbolizeKeys = 0;
419
739
  VALUE defaults;
420
740
 
421
- GetMysql2Result(self, wrapper);
741
+ GET_RESULT(self);
422
742
 
423
743
  defaults = rb_iv_get(self, "@query_options");
424
744
  Check_Type(defaults, T_HASH);
@@ -431,7 +751,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
431
751
  wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
432
752
  }
433
753
 
434
- if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
754
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
435
755
  for (i=0; i<wrapper->numberOfFields; i++) {
436
756
  rb_mysql_result_fetch_field(self, i, symbolizeKeys);
437
757
  }
@@ -440,108 +760,38 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
440
760
  return wrapper->fields;
441
761
  }
442
762
 
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;
763
+ static VALUE rb_mysql_result_each_(VALUE self,
764
+ VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args),
765
+ const result_each_args *args)
766
+ {
447
767
  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;
768
+ const char *errstr;
769
+ MYSQL_FIELD *fields = NULL;
451
770
 
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
- }
465
-
466
- if (rb_hash_aref(opts, sym_as) == sym_array) {
467
- asArray = 1;
468
- }
771
+ GET_RESULT(self);
469
772
 
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;
773
+ if (wrapper->is_streaming) {
774
+ /* When streaming, we will only yield rows, not return them. */
775
+ if (wrapper->rows == Qnil) {
516
776
  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
777
  }
525
- }
526
778
 
527
- if (streaming) {
528
779
  if (!wrapper->streamingComplete) {
529
780
  VALUE row;
530
781
 
531
782
  fields = mysql_fetch_fields(wrapper->result);
532
783
 
533
784
  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++;
785
+ row = fetch_row_func(self, fields, args);
786
+ if (row != Qnil) {
787
+ wrapper->numberOfRows++;
788
+ if (args->block_given != Qnil) {
789
+ rb_yield(row);
790
+ }
539
791
  }
540
792
  } while(row != Qnil);
541
793
 
542
794
  rb_mysql_result_free_result(wrapper);
543
-
544
- wrapper->numberOfRows = wrapper->lastRowProcessed;
545
795
  wrapper->streamingComplete = 1;
546
796
 
547
797
  // Check for errors, the connection might have gone out from under us
@@ -554,7 +804,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
554
804
  rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
555
805
  }
556
806
  } else {
557
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
807
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
558
808
  /* we've already read the entire dataset from the C result into our */
559
809
  /* internal array. Lets hand that over to the user since it's ready to go */
560
810
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -567,11 +817,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
567
817
 
568
818
  for (i = 0; i < wrapper->numberOfRows; i++) {
569
819
  VALUE row;
570
- if (cacheRows && i < rowsProcessed) {
820
+ if (args->cacheRows && i < rowsProcessed) {
571
821
  row = rb_ary_entry(wrapper->rows, i);
572
822
  } else {
573
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
574
- if (cacheRows) {
823
+ row = fetch_row_func(self, fields, args);
824
+ if (args->cacheRows) {
575
825
  rb_ary_store(wrapper->rows, i, row);
576
826
  }
577
827
  wrapper->lastRowProcessed++;
@@ -583,7 +833,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
583
833
  return Qnil;
584
834
  }
585
835
 
586
- if (block != Qnil) {
836
+ if (args->block_given != Qnil) {
587
837
  rb_yield(row);
588
838
  }
589
839
  }
@@ -594,28 +844,124 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
594
844
  }
595
845
  }
596
846
 
847
+ // FIXME return Enumerator instead?
848
+ // return rb_ary_each(wrapper->rows);
597
849
  return wrapper->rows;
598
850
  }
599
851
 
852
+ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
853
+ result_each_args args;
854
+ VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
855
+ ID db_timezone, app_timezone, dbTz, appTz;
856
+ int symbolizeKeys, asArray, castBool, cacheRows, cast;
857
+
858
+ GET_RESULT(self);
859
+
860
+ if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) {
861
+ rb_raise(cMysql2Error, "Statement handle already closed");
862
+ }
863
+
864
+ defaults = rb_iv_get(self, "@query_options");
865
+ Check_Type(defaults, T_HASH);
866
+ if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
867
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
868
+ } else {
869
+ opts = defaults;
870
+ }
871
+
872
+ symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
873
+ asArray = rb_hash_aref(opts, sym_as) == sym_array;
874
+ castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
875
+ cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
876
+ cast = RTEST(rb_hash_aref(opts, sym_cast));
877
+
878
+ if (wrapper->is_streaming && cacheRows) {
879
+ rb_warn(":cache_rows is ignored if :stream is true");
880
+ }
881
+
882
+ if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) {
883
+ rb_warn(":cache_rows is forced for prepared statements (if not streaming)");
884
+ }
885
+
886
+ if (wrapper->stmt_wrapper && !cast) {
887
+ rb_warn(":cast is forced for prepared statements");
888
+ }
889
+
890
+ dbTz = rb_hash_aref(opts, sym_database_timezone);
891
+ if (dbTz == sym_local) {
892
+ db_timezone = intern_local;
893
+ } else if (dbTz == sym_utc) {
894
+ db_timezone = intern_utc;
895
+ } else {
896
+ if (!NIL_P(dbTz)) {
897
+ rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
898
+ }
899
+ db_timezone = intern_local;
900
+ }
901
+
902
+ appTz = rb_hash_aref(opts, sym_application_timezone);
903
+ if (appTz == sym_local) {
904
+ app_timezone = intern_local;
905
+ } else if (appTz == sym_utc) {
906
+ app_timezone = intern_utc;
907
+ } else {
908
+ app_timezone = Qnil;
909
+ }
910
+
911
+ if (wrapper->lastRowProcessed == 0 && !wrapper->is_streaming) {
912
+ wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
913
+ if (wrapper->numberOfRows == 0) {
914
+ wrapper->rows = rb_ary_new();
915
+ return wrapper->rows;
916
+ }
917
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
918
+ }
919
+
920
+ // Backward compat
921
+ args.symbolizeKeys = symbolizeKeys;
922
+ args.asArray = asArray;
923
+ args.castBool = castBool;
924
+ args.cacheRows = cacheRows;
925
+ args.cast = cast;
926
+ args.db_timezone = db_timezone;
927
+ args.app_timezone = app_timezone;
928
+ args.block_given = block;
929
+
930
+ if (wrapper->stmt_wrapper) {
931
+ fetch_row_func = rb_mysql_result_fetch_row_stmt;
932
+ } else {
933
+ fetch_row_func = rb_mysql_result_fetch_row;
934
+ }
935
+
936
+ return rb_mysql_result_each_(self, fetch_row_func, &args);
937
+ }
938
+
600
939
  static VALUE rb_mysql_result_count(VALUE self) {
601
- mysql2_result_wrapper *wrapper;
940
+ GET_RESULT(self);
941
+
942
+ if (wrapper->is_streaming) {
943
+ /* This is an unsigned long per result.h */
944
+ return ULONG2NUM(wrapper->numberOfRows);
945
+ }
602
946
 
603
- GetMysql2Result(self, wrapper);
604
947
  if (wrapper->resultFreed) {
605
- if (wrapper->streamingComplete){
606
- return LONG2NUM(wrapper->numberOfRows);
948
+ /* Ruby arrays have platform signed long length */
949
+ return LONG2NUM(RARRAY_LEN(wrapper->rows));
950
+ } else {
951
+ /* MySQL returns an unsigned 64-bit long here */
952
+ if (wrapper->stmt_wrapper) {
953
+ return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt));
607
954
  } else {
608
- return LONG2NUM(RARRAY_LEN(wrapper->rows));
955
+ return ULL2NUM(mysql_num_rows(wrapper->result));
609
956
  }
610
- } else {
611
- return INT2FIX(mysql_num_rows(wrapper->result));
612
957
  }
613
958
  }
614
959
 
615
960
  /* Mysql2::Result */
616
- VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
961
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) {
617
962
  VALUE obj;
618
963
  mysql2_result_wrapper * wrapper;
964
+
619
965
  obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
620
966
  wrapper->numberOfFields = 0;
621
967
  wrapper->numberOfRows = 0;
@@ -629,11 +975,27 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
629
975
  wrapper->client = client;
630
976
  wrapper->client_wrapper = DATA_PTR(client);
631
977
  wrapper->client_wrapper->refcount++;
978
+ wrapper->result_buffers = NULL;
979
+ wrapper->is_null = NULL;
980
+ wrapper->error = NULL;
981
+ wrapper->length = NULL;
982
+
983
+ /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
984
+ wrapper->statement = statement;
985
+ if (statement != Qnil) {
986
+ wrapper->stmt_wrapper = DATA_PTR(statement);
987
+ wrapper->stmt_wrapper->refcount++;
988
+ } else {
989
+ wrapper->stmt_wrapper = NULL;
990
+ }
632
991
 
633
992
  rb_obj_call_init(obj, 0, NULL);
634
-
635
993
  rb_iv_set(obj, "@query_options", options);
636
994
 
995
+ /* Options that cannot be changed in results.each(...) { |row| }
996
+ * should be processed here. */
997
+ wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0);
998
+
637
999
  return obj;
638
1000
  }
639
1001