mysql2 0.3.21 → 0.4.10

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +132 -55
  4. data/examples/eventmachine.rb +1 -1
  5. data/examples/threaded.rb +4 -6
  6. data/ext/mysql2/client.c +314 -118
  7. data/ext/mysql2/client.h +13 -3
  8. data/ext/mysql2/extconf.rb +111 -44
  9. data/ext/mysql2/infile.c +2 -2
  10. data/ext/mysql2/mysql2_ext.c +1 -0
  11. data/ext/mysql2/mysql2_ext.h +5 -10
  12. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  13. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  14. data/ext/mysql2/result.c +489 -101
  15. data/ext/mysql2/result.h +12 -4
  16. data/ext/mysql2/statement.c +595 -0
  17. data/ext/mysql2/statement.h +19 -0
  18. data/lib/mysql2/client.rb +70 -27
  19. data/lib/mysql2/console.rb +1 -1
  20. data/lib/mysql2/em.rb +5 -6
  21. data/lib/mysql2/error.rb +17 -26
  22. data/lib/mysql2/field.rb +3 -0
  23. data/lib/mysql2/statement.rb +17 -0
  24. data/lib/mysql2/version.rb +1 -1
  25. data/lib/mysql2.rb +37 -35
  26. data/spec/configuration.yml.example +0 -6
  27. data/spec/em/em_spec.rb +22 -21
  28. data/spec/mysql2/client_spec.rb +484 -346
  29. data/spec/mysql2/error_spec.rb +38 -39
  30. data/spec/mysql2/result_spec.rb +256 -230
  31. data/spec/mysql2/statement_spec.rb +776 -0
  32. data/spec/spec_helper.rb +80 -59
  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/5072E1F5.asc +432 -0
  47. data/support/mysql_enc_to_ruby.rb +7 -8
  48. data/support/ruby_enc_to_mysql.rb +1 -1
  49. metadata +50 -55
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,12 +48,24 @@ static rb_encoding *binaryEncoding;
50
48
  #define MYSQL2_MIN_TIME 62171150401ULL
51
49
  #endif
52
50
 
53
- #define GET_RESULT(obj) \
51
+ #define GET_RESULT(self) \
54
52
  mysql2_result_wrapper *wrapper; \
55
53
  Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
56
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;
57
68
  static VALUE cMysql2Result;
58
- static VALUE cBigDecimal, cDate, cDateTime;
59
69
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
60
70
  extern VALUE mMysql2, cMysql2Client, cMysql2Error;
61
71
  static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
@@ -63,6 +73,7 @@ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_a
63
73
  sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
64
74
  static ID intern_merge;
65
75
 
76
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
66
77
  static void rb_mysql_result_mark(void * wrapper) {
67
78
  mysql2_result_wrapper * w = wrapper;
68
79
  if (w) {
@@ -70,13 +81,50 @@ static void rb_mysql_result_mark(void * wrapper) {
70
81
  rb_gc_mark(w->rows);
71
82
  rb_gc_mark(w->encoding);
72
83
  rb_gc_mark(w->client);
84
+ rb_gc_mark(w->statement);
73
85
  }
74
86
  }
75
87
 
76
88
  /* this may be called manually or during GC */
77
89
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
78
- 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->statement != Qnil) {
108
+ decr_mysql2_stmt(wrapper->stmt_wrapper);
109
+ }
110
+
111
+ if (wrapper->result_buffers) {
112
+ unsigned int i;
113
+ for (i = 0; i < wrapper->numberOfFields; i++) {
114
+ if (wrapper->result_buffers[i].buffer) {
115
+ xfree(wrapper->result_buffers[i].buffer);
116
+ }
117
+ }
118
+ xfree(wrapper->result_buffers);
119
+ xfree(wrapper->is_null);
120
+ xfree(wrapper->error);
121
+ xfree(wrapper->length);
122
+ }
123
+ /* Clue that the next statement execute will need to allocate a new result buffer. */
124
+ wrapper->result_buffers = NULL;
125
+ }
79
126
  /* FIXME: this may call flush_use_result, which can hit the socket */
127
+ /* For prepared statements, wrapper->result is the result metadata */
80
128
  mysql_free_result(wrapper->result);
81
129
  wrapper->resultFreed = 1;
82
130
  }
@@ -84,7 +132,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
84
132
 
85
133
  /* this is called during GC */
86
134
  static void rb_mysql_result_free(void *ptr) {
87
- mysql2_result_wrapper * wrapper = ptr;
135
+ mysql2_result_wrapper *wrapper = ptr;
88
136
  rb_mysql_result_free_result(wrapper);
89
137
 
90
138
  // If the GC gets to client first it will be nil
@@ -95,6 +143,12 @@ static void rb_mysql_result_free(void *ptr) {
95
143
  xfree(wrapper);
96
144
  }
97
145
 
146
+ static VALUE rb_mysql_result_free_(VALUE self) {
147
+ GET_RESULT(self);
148
+ rb_mysql_result_free_result(wrapper);
149
+ return Qnil;
150
+ }
151
+
98
152
  /*
99
153
  * for small results, this won't hit the network, but there's no
100
154
  * reliable way for us to tell this so we'll always release the GVL
@@ -106,7 +160,14 @@ static void *nogvl_fetch_row(void *ptr) {
106
160
  return mysql_fetch_row(result);
107
161
  }
108
162
 
109
- static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
163
+ static void *nogvl_stmt_fetch(void *ptr) {
164
+ MYSQL_STMT *stmt = ptr;
165
+ uintptr_t r = mysql_stmt_fetch(stmt);
166
+
167
+ return (void *)r;
168
+ }
169
+
170
+ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) {
110
171
  VALUE rb_field;
111
172
  GET_RESULT(self);
112
173
 
@@ -150,7 +211,7 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
150
211
 
151
212
  #ifdef HAVE_RUBY_ENCODING_H
152
213
  static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
153
- /* if binary flag is set, respect it's wishes */
214
+ /* if binary flag is set, respect its wishes */
154
215
  if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
155
216
  rb_enc_associate(val, binaryEncoding);
156
217
  } else if (!field.charsetnr) {
@@ -185,7 +246,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
185
246
  */
186
247
  static unsigned int msec_char_to_uint(char *msec_char, size_t len)
187
248
  {
188
- int i;
249
+ size_t i;
189
250
  for (i = 0; i < (len - 1); i++) {
190
251
  if (msec_char[i] == '\0') {
191
252
  msec_char[i] = '0';
@@ -194,7 +255,276 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
194
255
  return (unsigned int)strtoul(msec_char, NULL, 10);
195
256
  }
196
257
 
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) {
258
+ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) {
259
+ unsigned int i;
260
+ GET_RESULT(self);
261
+
262
+ if (wrapper->result_buffers != NULL) return;
263
+
264
+ wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND));
265
+ wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(bool));
266
+ wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(bool));
267
+ wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long));
268
+
269
+ for (i = 0; i < wrapper->numberOfFields; i++) {
270
+ wrapper->result_buffers[i].buffer_type = fields[i].type;
271
+
272
+ // mysql type | C type
273
+ switch(fields[i].type) {
274
+ case MYSQL_TYPE_NULL: // NULL
275
+ break;
276
+ case MYSQL_TYPE_TINY: // signed char
277
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(signed char));
278
+ wrapper->result_buffers[i].buffer_length = sizeof(signed char);
279
+ break;
280
+ case MYSQL_TYPE_SHORT: // short int
281
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int));
282
+ wrapper->result_buffers[i].buffer_length = sizeof(short int);
283
+ break;
284
+ case MYSQL_TYPE_INT24: // int
285
+ case MYSQL_TYPE_LONG: // int
286
+ case MYSQL_TYPE_YEAR: // int
287
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int));
288
+ wrapper->result_buffers[i].buffer_length = sizeof(int);
289
+ break;
290
+ case MYSQL_TYPE_LONGLONG: // long long int
291
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(long long int));
292
+ wrapper->result_buffers[i].buffer_length = sizeof(long long int);
293
+ break;
294
+ case MYSQL_TYPE_FLOAT: // float
295
+ case MYSQL_TYPE_DOUBLE: // double
296
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(double));
297
+ wrapper->result_buffers[i].buffer_length = sizeof(double);
298
+ break;
299
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
300
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
301
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
302
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
303
+ case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME
304
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME));
305
+ wrapper->result_buffers[i].buffer_length = sizeof(MYSQL_TIME);
306
+ break;
307
+ case MYSQL_TYPE_DECIMAL: // char[]
308
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
309
+ case MYSQL_TYPE_STRING: // char[]
310
+ case MYSQL_TYPE_VAR_STRING: // char[]
311
+ case MYSQL_TYPE_VARCHAR: // char[]
312
+ case MYSQL_TYPE_TINY_BLOB: // char[]
313
+ case MYSQL_TYPE_BLOB: // char[]
314
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
315
+ case MYSQL_TYPE_LONG_BLOB: // char[]
316
+ case MYSQL_TYPE_BIT: // char[]
317
+ case MYSQL_TYPE_SET: // char[]
318
+ case MYSQL_TYPE_ENUM: // char[]
319
+ case MYSQL_TYPE_GEOMETRY: // char[]
320
+ default:
321
+ wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length);
322
+ wrapper->result_buffers[i].buffer_length = fields[i].max_length;
323
+ break;
324
+ }
325
+
326
+ wrapper->result_buffers[i].is_null = &wrapper->is_null[i];
327
+ wrapper->result_buffers[i].length = &wrapper->length[i];
328
+ wrapper->result_buffers[i].error = &wrapper->error[i];
329
+ wrapper->result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0);
330
+ }
331
+ }
332
+
333
+ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
334
+ {
335
+ VALUE rowVal;
336
+ unsigned int i = 0;
337
+
338
+ #ifdef HAVE_RUBY_ENCODING_H
339
+ rb_encoding *default_internal_enc;
340
+ rb_encoding *conn_enc;
341
+ #endif
342
+ GET_RESULT(self);
343
+
344
+ #ifdef HAVE_RUBY_ENCODING_H
345
+ default_internal_enc = rb_default_internal_encoding();
346
+ conn_enc = rb_to_encoding(wrapper->encoding);
347
+ #endif
348
+
349
+ if (wrapper->fields == Qnil) {
350
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
351
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
352
+ }
353
+ if (args->asArray) {
354
+ rowVal = rb_ary_new2(wrapper->numberOfFields);
355
+ } else {
356
+ rowVal = rb_hash_new();
357
+ }
358
+
359
+ if (wrapper->result_buffers == NULL) {
360
+ rb_mysql_result_alloc_result_buffers(self, fields);
361
+ }
362
+
363
+ if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) {
364
+ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper);
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_error(wrapper->stmt_wrapper);
376
+
377
+ case MYSQL_NO_DATA:
378
+ /* no more row */
379
+ return Qnil;
380
+
381
+ case MYSQL_DATA_TRUNCATED:
382
+ rb_raise(cMysql2Error, "IMPLBUG: caught MYSQL_DATA_TRUNCATED. should not come here as buffer_length is set to fields[i].max_length.");
383
+ }
384
+ }
385
+
386
+ for (i = 0; i < wrapper->numberOfFields; i++) {
387
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
388
+ VALUE val = Qnil;
389
+ MYSQL_TIME *ts;
390
+
391
+ if (wrapper->is_null[i]) {
392
+ val = Qnil;
393
+ } else {
394
+ const MYSQL_BIND* const result_buffer = &wrapper->result_buffers[i];
395
+
396
+ switch(result_buffer->buffer_type) {
397
+ case MYSQL_TYPE_TINY: // signed char
398
+ if (args->castBool && fields[i].length == 1) {
399
+ val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse;
400
+ break;
401
+ }
402
+ if (result_buffer->is_unsigned) {
403
+ val = UINT2NUM(*((unsigned char*)result_buffer->buffer));
404
+ } else {
405
+ val = INT2NUM(*((signed char*)result_buffer->buffer));
406
+ }
407
+ break;
408
+ case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
409
+ if (args->castBool && fields[i].length == 1) {
410
+ val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse;
411
+ }else{
412
+ val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
413
+ }
414
+ break;
415
+ case MYSQL_TYPE_SHORT: // short int
416
+ if (result_buffer->is_unsigned) {
417
+ val = UINT2NUM(*((unsigned short int*)result_buffer->buffer));
418
+ } else {
419
+ val = INT2NUM(*((short int*)result_buffer->buffer));
420
+ }
421
+ break;
422
+ case MYSQL_TYPE_INT24: // int
423
+ case MYSQL_TYPE_LONG: // int
424
+ case MYSQL_TYPE_YEAR: // int
425
+ if (result_buffer->is_unsigned) {
426
+ val = UINT2NUM(*((unsigned int*)result_buffer->buffer));
427
+ } else {
428
+ val = INT2NUM(*((int*)result_buffer->buffer));
429
+ }
430
+ break;
431
+ case MYSQL_TYPE_LONGLONG: // long long int
432
+ if (result_buffer->is_unsigned) {
433
+ val = ULL2NUM(*((unsigned long long int*)result_buffer->buffer));
434
+ } else {
435
+ val = LL2NUM(*((long long int*)result_buffer->buffer));
436
+ }
437
+ break;
438
+ case MYSQL_TYPE_FLOAT: // float
439
+ val = rb_float_new((double)(*((float*)result_buffer->buffer)));
440
+ break;
441
+ case MYSQL_TYPE_DOUBLE: // double
442
+ val = rb_float_new((double)(*((double*)result_buffer->buffer)));
443
+ break;
444
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
445
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
446
+ ts = (MYSQL_TIME*)result_buffer->buffer;
447
+ val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day));
448
+ break;
449
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
450
+ ts = (MYSQL_TIME*)result_buffer->buffer;
451
+ 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));
452
+ if (!NIL_P(args->app_timezone)) {
453
+ if (args->app_timezone == intern_local) {
454
+ val = rb_funcall(val, intern_localtime, 0);
455
+ } else { // utc
456
+ val = rb_funcall(val, intern_utc, 0);
457
+ }
458
+ }
459
+ break;
460
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
461
+ case MYSQL_TYPE_TIMESTAMP: { // MYSQL_TIME
462
+ uint64_t seconds;
463
+
464
+ ts = (MYSQL_TIME*)result_buffer->buffer;
465
+ seconds = (ts->year*31557600ULL) + (ts->month*2592000ULL) + (ts->day*86400ULL) + (ts->hour*3600ULL) + (ts->minute*60ULL) + ts->second;
466
+
467
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
468
+ VALUE offset = INT2NUM(0);
469
+ if (args->db_timezone == intern_local) {
470
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
471
+ }
472
+ 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);
473
+ if (!NIL_P(args->app_timezone)) {
474
+ if (args->app_timezone == intern_local) {
475
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
476
+ val = rb_funcall(val, intern_new_offset, 1, offset);
477
+ } else { // utc
478
+ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
479
+ }
480
+ }
481
+ } else {
482
+ 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));
483
+ if (!NIL_P(args->app_timezone)) {
484
+ if (args->app_timezone == intern_local) {
485
+ val = rb_funcall(val, intern_localtime, 0);
486
+ } else { // utc
487
+ val = rb_funcall(val, intern_utc, 0);
488
+ }
489
+ }
490
+ }
491
+ break;
492
+ }
493
+ case MYSQL_TYPE_DECIMAL: // char[]
494
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
495
+ val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length)));
496
+ break;
497
+ case MYSQL_TYPE_STRING: // char[]
498
+ case MYSQL_TYPE_VAR_STRING: // char[]
499
+ case MYSQL_TYPE_VARCHAR: // char[]
500
+ case MYSQL_TYPE_TINY_BLOB: // char[]
501
+ case MYSQL_TYPE_BLOB: // char[]
502
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
503
+ case MYSQL_TYPE_LONG_BLOB: // char[]
504
+ case MYSQL_TYPE_SET: // char[]
505
+ case MYSQL_TYPE_ENUM: // char[]
506
+ case MYSQL_TYPE_GEOMETRY: // char[]
507
+ default:
508
+ val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
509
+ #ifdef HAVE_RUBY_ENCODING_H
510
+ val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
511
+ #endif
512
+ break;
513
+ }
514
+ }
515
+
516
+ if (args->asArray) {
517
+ rb_ary_push(rowVal, val);
518
+ } else {
519
+ rb_hash_aset(rowVal, field, val);
520
+ }
521
+ }
522
+
523
+ return rowVal;
524
+ }
525
+
526
+ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
527
+ {
198
528
  VALUE rowVal;
199
529
  MYSQL_ROW row;
200
530
  unsigned int i = 0;
@@ -217,24 +547,24 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
217
547
  return Qnil;
218
548
  }
219
549
 
220
- if (asArray) {
550
+ if (wrapper->fields == Qnil) {
551
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
552
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
553
+ }
554
+ if (args->asArray) {
221
555
  rowVal = rb_ary_new2(wrapper->numberOfFields);
222
556
  } else {
223
557
  rowVal = rb_hash_new();
224
558
  }
225
559
  fieldLengths = mysql_fetch_lengths(wrapper->result);
226
- if (wrapper->fields == Qnil) {
227
- wrapper->numberOfFields = mysql_num_fields(wrapper->result);
228
- wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
229
- }
230
560
 
231
561
  for (i = 0; i < wrapper->numberOfFields; i++) {
232
- VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
562
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
233
563
  if (row[i]) {
234
564
  VALUE val = Qnil;
235
565
  enum enum_field_types type = fields[i].type;
236
566
 
237
- if (!cast) {
567
+ if (!args->cast) {
238
568
  if (type == MYSQL_TYPE_NULL) {
239
569
  val = Qnil;
240
570
  } else {
@@ -249,14 +579,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
249
579
  val = Qnil;
250
580
  break;
251
581
  case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
252
- if (castBool && fields[i].length == 1) {
582
+ if (args->castBool && fields[i].length == 1) {
253
583
  val = *row[i] == 1 ? Qtrue : Qfalse;
254
584
  }else{
255
585
  val = rb_str_new(row[i], fieldLengths[i]);
256
586
  }
257
587
  break;
258
588
  case MYSQL_TYPE_TINY: /* TINYINT field */
259
- if (castBool && fields[i].length == 1) {
589
+ if (args->castBool && fields[i].length == 1) {
260
590
  val = *row[i] != '0' ? Qtrue : Qfalse;
261
591
  break;
262
592
  }
@@ -299,9 +629,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
299
629
  break;
300
630
  }
301
631
  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) {
632
+ 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));
633
+ if (!NIL_P(args->app_timezone)) {
634
+ if (args->app_timezone == intern_local) {
305
635
  val = rb_funcall(val, intern_localtime, 0);
306
636
  } else { /* utc */
307
637
  val = rb_funcall(val, intern_utc, 0);
@@ -332,12 +662,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
332
662
  } else {
333
663
  if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
334
664
  VALUE offset = INT2NUM(0);
335
- if (db_timezone == intern_local) {
665
+ if (args->db_timezone == intern_local) {
336
666
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
337
667
  }
338
668
  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) {
669
+ if (!NIL_P(args->app_timezone)) {
670
+ if (args->app_timezone == intern_local) {
341
671
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
342
672
  val = rb_funcall(val, intern_new_offset, 1, offset);
343
673
  } else { /* utc */
@@ -346,9 +676,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
346
676
  }
347
677
  } else {
348
678
  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) {
679
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
680
+ if (!NIL_P(args->app_timezone)) {
681
+ if (args->app_timezone == intern_local) {
352
682
  val = rb_funcall(val, intern_localtime, 0);
353
683
  } else { /* utc */
354
684
  val = rb_funcall(val, intern_utc, 0);
@@ -398,13 +728,13 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
398
728
  break;
399
729
  }
400
730
  }
401
- if (asArray) {
731
+ if (args->asArray) {
402
732
  rb_ary_push(rowVal, val);
403
733
  } else {
404
734
  rb_hash_aset(rowVal, field, val);
405
735
  }
406
736
  } else {
407
- if (asArray) {
737
+ if (args->asArray) {
408
738
  rb_ary_push(rowVal, Qnil);
409
739
  } else {
410
740
  rb_hash_aset(rowVal, field, Qnil);
@@ -432,7 +762,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
432
762
  wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
433
763
  }
434
764
 
435
- if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
765
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
436
766
  for (i=0; i<wrapper->numberOfFields; i++) {
437
767
  rb_mysql_result_fetch_field(self, i, symbolizeKeys);
438
768
  }
@@ -441,55 +771,16 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
441
771
  return wrapper->fields;
442
772
  }
443
773
 
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;
774
+ static VALUE rb_mysql_result_each_(VALUE self,
775
+ VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args),
776
+ const result_each_args *args)
777
+ {
447
778
  unsigned long i;
448
- const char * errstr;
449
- int symbolizeKeys, asArray, castBool, cacheRows, cast;
450
- MYSQL_FIELD * fields = NULL;
779
+ const char *errstr;
780
+ MYSQL_FIELD *fields = NULL;
451
781
 
452
782
  GET_RESULT(self);
453
783
 
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
784
  if (wrapper->is_streaming) {
494
785
  /* When streaming, we will only yield rows, not return them. */
495
786
  if (wrapper->rows == Qnil) {
@@ -502,10 +793,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
502
793
  fields = mysql_fetch_fields(wrapper->result);
503
794
 
504
795
  do {
505
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
796
+ row = fetch_row_func(self, fields, args);
506
797
  if (row != Qnil) {
507
798
  wrapper->numberOfRows++;
508
- if (block != Qnil) {
799
+ if (args->block_given != Qnil) {
509
800
  rb_yield(row);
510
801
  }
511
802
  }
@@ -524,20 +815,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
524
815
  rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
525
816
  }
526
817
  } 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) {
818
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
541
819
  /* we've already read the entire dataset from the C result into our */
542
820
  /* internal array. Lets hand that over to the user since it's ready to go */
543
821
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -550,11 +828,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
550
828
 
551
829
  for (i = 0; i < wrapper->numberOfRows; i++) {
552
830
  VALUE row;
553
- if (cacheRows && i < rowsProcessed) {
831
+ if (args->cacheRows && i < rowsProcessed) {
554
832
  row = rb_ary_entry(wrapper->rows, i);
555
833
  } else {
556
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
557
- if (cacheRows) {
834
+ row = fetch_row_func(self, fields, args);
835
+ if (args->cacheRows) {
558
836
  rb_ary_store(wrapper->rows, i, row);
559
837
  }
560
838
  wrapper->lastRowProcessed++;
@@ -562,26 +840,119 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
562
840
 
563
841
  if (row == Qnil) {
564
842
  /* we don't need the mysql C dataset around anymore, peace it */
565
- if (cacheRows) {
843
+ if (args->cacheRows) {
566
844
  rb_mysql_result_free_result(wrapper);
567
845
  }
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 && args->cacheRows) {
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
+ if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) {
874
+ rb_raise(cMysql2Error, "Statement handle already closed");
875
+ }
876
+
877
+ defaults = rb_iv_get(self, "@query_options");
878
+ Check_Type(defaults, T_HASH);
879
+ if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
880
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
881
+ } else {
882
+ opts = defaults;
883
+ }
884
+
885
+ symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
886
+ asArray = rb_hash_aref(opts, sym_as) == sym_array;
887
+ castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
888
+ cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
889
+ cast = RTEST(rb_hash_aref(opts, sym_cast));
890
+
891
+ if (wrapper->is_streaming && cacheRows) {
892
+ rb_warn(":cache_rows is ignored if :stream is true");
893
+ }
894
+
895
+ if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) {
896
+ rb_warn(":cache_rows is forced for prepared statements (if not streaming)");
897
+ cacheRows = 1;
898
+ }
899
+
900
+ if (wrapper->stmt_wrapper && !cast) {
901
+ rb_warn(":cast is forced for prepared statements");
902
+ }
903
+
904
+ dbTz = rb_hash_aref(opts, sym_database_timezone);
905
+ if (dbTz == sym_local) {
906
+ db_timezone = intern_local;
907
+ } else if (dbTz == sym_utc) {
908
+ db_timezone = intern_utc;
909
+ } else {
910
+ if (!NIL_P(dbTz)) {
911
+ rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
912
+ }
913
+ db_timezone = intern_local;
914
+ }
915
+
916
+ appTz = rb_hash_aref(opts, sym_application_timezone);
917
+ if (appTz == sym_local) {
918
+ app_timezone = intern_local;
919
+ } else if (appTz == sym_utc) {
920
+ app_timezone = intern_utc;
921
+ } else {
922
+ app_timezone = Qnil;
923
+ }
924
+
925
+ if (wrapper->rows == Qnil && !wrapper->is_streaming) {
926
+ wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
927
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
928
+ } else if (wrapper->rows && !cacheRows) {
929
+ if (wrapper->resultFreed) {
930
+ rb_raise(cMysql2Error, "Result set has already been freed");
931
+ }
932
+ mysql_data_seek(wrapper->result, 0);
933
+ wrapper->lastRowProcessed = 0;
934
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
935
+ }
936
+
937
+ // Backward compat
938
+ args.symbolizeKeys = symbolizeKeys;
939
+ args.asArray = asArray;
940
+ args.castBool = castBool;
941
+ args.cacheRows = cacheRows;
942
+ args.cast = cast;
943
+ args.db_timezone = db_timezone;
944
+ args.app_timezone = app_timezone;
945
+ args.block_given = block;
946
+
947
+ if (wrapper->stmt_wrapper) {
948
+ fetch_row_func = rb_mysql_result_fetch_row_stmt;
949
+ } else {
950
+ fetch_row_func = rb_mysql_result_fetch_row;
951
+ }
952
+
953
+ return rb_mysql_result_each_(self, fetch_row_func, &args);
954
+ }
955
+
585
956
  static VALUE rb_mysql_result_count(VALUE self) {
586
957
  GET_RESULT(self);
587
958
 
@@ -595,12 +966,16 @@ static VALUE rb_mysql_result_count(VALUE self) {
595
966
  return LONG2NUM(RARRAY_LEN(wrapper->rows));
596
967
  } else {
597
968
  /* MySQL returns an unsigned 64-bit long here */
598
- return ULL2NUM(mysql_num_rows(wrapper->result));
969
+ if (wrapper->stmt_wrapper) {
970
+ return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt));
971
+ } else {
972
+ return ULL2NUM(mysql_num_rows(wrapper->result));
973
+ }
599
974
  }
600
975
  }
601
976
 
602
977
  /* Mysql2::Result */
603
- VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
978
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) {
604
979
  VALUE obj;
605
980
  mysql2_result_wrapper * wrapper;
606
981
 
@@ -617,9 +992,21 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
617
992
  wrapper->client = client;
618
993
  wrapper->client_wrapper = DATA_PTR(client);
619
994
  wrapper->client_wrapper->refcount++;
995
+ wrapper->result_buffers = NULL;
996
+ wrapper->is_null = NULL;
997
+ wrapper->error = NULL;
998
+ wrapper->length = NULL;
999
+
1000
+ /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
1001
+ wrapper->statement = statement;
1002
+ if (statement != Qnil) {
1003
+ wrapper->stmt_wrapper = DATA_PTR(statement);
1004
+ wrapper->stmt_wrapper->refcount++;
1005
+ } else {
1006
+ wrapper->stmt_wrapper = NULL;
1007
+ }
620
1008
 
621
1009
  rb_obj_call_init(obj, 0, NULL);
622
-
623
1010
  rb_iv_set(obj, "@query_options", options);
624
1011
 
625
1012
  /* Options that cannot be changed in results.each(...) { |row| }
@@ -637,6 +1024,7 @@ void init_mysql2_result() {
637
1024
  cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
638
1025
  rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
639
1026
  rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
1027
+ rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0);
640
1028
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
641
1029
  rb_define_alias(cMysql2Result, "size", "count");
642
1030