mysql2 0.3.20 → 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 +323 -140
  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 +491 -97
  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 +82 -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 +38 -18
  26. data/spec/configuration.yml.example +0 -6
  27. data/spec/em/em_spec.rb +22 -21
  28. data/spec/mysql2/client_spec.rb +498 -380
  29. data/spec/mysql2/error_spec.rb +38 -39
  30. data/spec/mysql2/result_spec.rb +260 -229
  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,16 +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
- }
535
-
536
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
818
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
537
819
  /* we've already read the entire dataset from the C result into our */
538
820
  /* internal array. Lets hand that over to the user since it's ready to go */
539
821
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -546,11 +828,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
546
828
 
547
829
  for (i = 0; i < wrapper->numberOfRows; i++) {
548
830
  VALUE row;
549
- if (cacheRows && i < rowsProcessed) {
831
+ if (args->cacheRows && i < rowsProcessed) {
550
832
  row = rb_ary_entry(wrapper->rows, i);
551
833
  } else {
552
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
553
- if (cacheRows) {
834
+ row = fetch_row_func(self, fields, args);
835
+ if (args->cacheRows) {
554
836
  rb_ary_store(wrapper->rows, i, row);
555
837
  }
556
838
  wrapper->lastRowProcessed++;
@@ -558,24 +840,119 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
558
840
 
559
841
  if (row == Qnil) {
560
842
  /* we don't need the mysql C dataset around anymore, peace it */
561
- rb_mysql_result_free_result(wrapper);
843
+ if (args->cacheRows) {
844
+ rb_mysql_result_free_result(wrapper);
845
+ }
562
846
  return Qnil;
563
847
  }
564
848
 
565
- if (block != Qnil) {
849
+ if (args->block_given != Qnil) {
566
850
  rb_yield(row);
567
851
  }
568
852
  }
569
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
853
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows && args->cacheRows) {
570
854
  /* we don't need the mysql C dataset around anymore, peace it */
571
855
  rb_mysql_result_free_result(wrapper);
572
856
  }
573
857
  }
574
858
  }
575
859
 
860
+ // FIXME return Enumerator instead?
861
+ // return rb_ary_each(wrapper->rows);
576
862
  return wrapper->rows;
577
863
  }
578
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
+
579
956
  static VALUE rb_mysql_result_count(VALUE self) {
580
957
  GET_RESULT(self);
581
958
 
@@ -589,12 +966,16 @@ static VALUE rb_mysql_result_count(VALUE self) {
589
966
  return LONG2NUM(RARRAY_LEN(wrapper->rows));
590
967
  } else {
591
968
  /* MySQL returns an unsigned 64-bit long here */
592
- 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
+ }
593
974
  }
594
975
  }
595
976
 
596
977
  /* Mysql2::Result */
597
- 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) {
598
979
  VALUE obj;
599
980
  mysql2_result_wrapper * wrapper;
600
981
 
@@ -611,9 +992,21 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
611
992
  wrapper->client = client;
612
993
  wrapper->client_wrapper = DATA_PTR(client);
613
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
+ }
614
1008
 
615
1009
  rb_obj_call_init(obj, 0, NULL);
616
-
617
1010
  rb_iv_set(obj, "@query_options", options);
618
1011
 
619
1012
  /* Options that cannot be changed in results.each(...) { |row| }
@@ -631,6 +1024,7 @@ void init_mysql2_result() {
631
1024
  cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
632
1025
  rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
633
1026
  rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
1027
+ rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0);
634
1028
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
635
1029
  rb_define_alias(cMysql2Result, "size", "count");
636
1030