mysql2 0.3.8 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -220
  3. data/LICENSE +21 -0
  4. data/README.md +370 -79
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +1017 -305
  8. data/ext/mysql2/client.h +35 -11
  9. data/ext/mysql2/extconf.rb +222 -34
  10. data/ext/mysql2/infile.c +122 -0
  11. data/ext/mysql2/infile.h +1 -0
  12. data/ext/mysql2/mysql2_ext.c +1 -0
  13. data/ext/mysql2/mysql2_ext.h +12 -14
  14. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  15. data/ext/mysql2/mysql_enc_to_ruby.h +249 -0
  16. data/ext/mysql2/result.c +664 -166
  17. data/ext/mysql2/result.h +16 -6
  18. data/ext/mysql2/statement.c +595 -0
  19. data/ext/mysql2/statement.h +19 -0
  20. data/lib/mysql2/client.rb +118 -211
  21. data/lib/mysql2/console.rb +5 -0
  22. data/lib/mysql2/em.rb +23 -5
  23. data/lib/mysql2/error.rb +62 -6
  24. data/lib/mysql2/field.rb +3 -0
  25. data/lib/mysql2/statement.rb +17 -0
  26. data/lib/mysql2/version.rb +1 -1
  27. data/lib/mysql2.rb +66 -3
  28. data/spec/configuration.yml.example +11 -0
  29. data/spec/em/em_spec.rb +96 -10
  30. data/spec/my.cnf.example +9 -0
  31. data/spec/mysql2/client_spec.rb +779 -205
  32. data/spec/mysql2/error_spec.rb +58 -45
  33. data/spec/mysql2/result_spec.rb +316 -159
  34. data/spec/mysql2/statement_spec.rb +776 -0
  35. data/spec/spec_helper.rb +97 -56
  36. data/spec/ssl/ca-cert.pem +17 -0
  37. data/spec/ssl/ca-key.pem +27 -0
  38. data/spec/ssl/ca.cnf +22 -0
  39. data/spec/ssl/cert.cnf +22 -0
  40. data/spec/ssl/client-cert.pem +17 -0
  41. data/spec/ssl/client-key.pem +27 -0
  42. data/spec/ssl/client-req.pem +15 -0
  43. data/spec/ssl/gen_certs.sh +48 -0
  44. data/spec/ssl/pkcs8-client-key.pem +28 -0
  45. data/spec/ssl/pkcs8-server-key.pem +28 -0
  46. data/spec/ssl/server-cert.pem +17 -0
  47. data/spec/ssl/server-key.pem +27 -0
  48. data/spec/ssl/server-req.pem +15 -0
  49. data/spec/test_data +1 -0
  50. data/support/5072E1F5.asc +432 -0
  51. data/support/libmysql.def +219 -0
  52. data/support/mysql_enc_to_ruby.rb +81 -0
  53. data/support/ruby_enc_to_mysql.rb +61 -0
  54. metadata +77 -196
  55. data/.gitignore +0 -12
  56. data/.rspec +0 -3
  57. data/.rvmrc +0 -1
  58. data/.travis.yml +0 -7
  59. data/Gemfile +0 -3
  60. data/MIT-LICENSE +0 -20
  61. data/Rakefile +0 -5
  62. data/benchmark/active_record.rb +0 -51
  63. data/benchmark/active_record_threaded.rb +0 -42
  64. data/benchmark/allocations.rb +0 -33
  65. data/benchmark/escape.rb +0 -36
  66. data/benchmark/query_with_mysql_casting.rb +0 -80
  67. data/benchmark/query_without_mysql_casting.rb +0 -56
  68. data/benchmark/sequel.rb +0 -37
  69. data/benchmark/setup_db.rb +0 -119
  70. data/benchmark/threaded.rb +0 -44
  71. data/mysql2.gemspec +0 -29
  72. data/tasks/benchmarks.rake +0 -20
  73. data/tasks/compile.rake +0 -71
  74. data/tasks/rspec.rake +0 -16
  75. data/tasks/vendor_mysql.rake +0 -40
data/ext/mysql2/result.c CHANGED
@@ -1,5 +1,6 @@
1
1
  #include <mysql2_ext.h>
2
- #include <stdint.h>
2
+
3
+ #include "mysql_enc_to_ruby.h"
3
4
 
4
5
  #ifdef HAVE_RUBY_ENCODING_H
5
6
  static rb_encoding *binaryEncoding;
@@ -27,7 +28,7 @@ static rb_encoding *binaryEncoding;
27
28
  * (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
28
29
  */
29
30
  #define MYSQL2_MIN_TIME 2678400ULL
30
- #elif SIZEOF_INT < SIZEOF_LONG // 64bit Ruby 1.8
31
+ #elif SIZEOF_INT < SIZEOF_LONG /* 64bit Ruby 1.8 */
31
32
  /* 0139-1-1 00:00:00 UTC
32
33
  *
33
34
  * (139*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
@@ -47,40 +48,105 @@ static rb_encoding *binaryEncoding;
47
48
  #define MYSQL2_MIN_TIME 62171150401ULL
48
49
  #endif
49
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;
50
68
  static VALUE cMysql2Result;
51
- static VALUE cBigDecimal, cDate, cDateTime;
52
69
  static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
53
70
  extern VALUE mMysql2, cMysql2Client, cMysql2Error;
54
- static VALUE intern_encoding_from_charset;
55
- static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
56
- intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
71
+ static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
57
72
  static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
58
- sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast;
73
+ sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
59
74
  static ID intern_merge;
60
75
 
76
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
61
77
  static void rb_mysql_result_mark(void * wrapper) {
62
78
  mysql2_result_wrapper * w = wrapper;
63
79
  if (w) {
64
80
  rb_gc_mark(w->fields);
65
81
  rb_gc_mark(w->rows);
66
82
  rb_gc_mark(w->encoding);
83
+ rb_gc_mark(w->client);
84
+ rb_gc_mark(w->statement);
67
85
  }
68
86
  }
69
87
 
70
88
  /* this may be called manually or during GC */
71
89
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
72
- 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
+ }
126
+ /* FIXME: this may call flush_use_result, which can hit the socket */
127
+ /* For prepared statements, wrapper->result is the result metadata */
73
128
  mysql_free_result(wrapper->result);
74
129
  wrapper->resultFreed = 1;
75
130
  }
76
131
  }
77
132
 
78
133
  /* this is called during GC */
79
- static void rb_mysql_result_free(void * wrapper) {
80
- mysql2_result_wrapper * w = wrapper;
81
- /* FIXME: this may call flush_use_result, which can hit the socket */
82
- rb_mysql_result_free_result(w);
83
- free(wrapper);
134
+ static void rb_mysql_result_free(void *ptr) {
135
+ mysql2_result_wrapper *wrapper = ptr;
136
+ rb_mysql_result_free_result(wrapper);
137
+
138
+ // If the GC gets to client first it will be nil
139
+ if (wrapper->client != Qnil) {
140
+ decr_mysql2_client(wrapper->client_wrapper);
141
+ }
142
+
143
+ xfree(wrapper);
144
+ }
145
+
146
+ static VALUE rb_mysql_result_free_(VALUE self) {
147
+ GET_RESULT(self);
148
+ rb_mysql_result_free_result(wrapper);
149
+ return Qnil;
84
150
  }
85
151
 
86
152
  /*
@@ -88,16 +154,22 @@ static void rb_mysql_result_free(void * wrapper) {
88
154
  * reliable way for us to tell this so we'll always release the GVL
89
155
  * to be safe
90
156
  */
91
- static VALUE nogvl_fetch_row(void *ptr) {
157
+ static void *nogvl_fetch_row(void *ptr) {
92
158
  MYSQL_RES *result = ptr;
93
159
 
94
- return (VALUE)mysql_fetch_row(result);
160
+ return mysql_fetch_row(result);
95
161
  }
96
162
 
97
- static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
98
- mysql2_result_wrapper * wrapper;
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) {
99
171
  VALUE rb_field;
100
- GetMysql2Result(self, wrapper);
172
+ GET_RESULT(self);
101
173
 
102
174
  if (wrapper->fields == Qnil) {
103
175
  wrapper->numberOfFields = mysql_num_fields(wrapper->result);
@@ -114,15 +186,14 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
114
186
 
115
187
  field = mysql_fetch_field_direct(wrapper->result, idx);
116
188
  if (symbolize_keys) {
189
+ #ifdef HAVE_RB_INTERN3
190
+ rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding());
191
+ rb_field = ID2SYM(rb_field);
192
+ #else
117
193
  VALUE colStr;
118
- char buf[field->name_length+1];
119
- memcpy(buf, field->name, field->name_length);
120
- buf[field->name_length] = 0;
121
- colStr = rb_str_new2(buf);
122
- #ifdef HAVE_RUBY_ENCODING_H
123
- rb_enc_associate(colStr, rb_utf8_encoding());
124
- #endif
194
+ colStr = rb_str_new(field->name, field->name_length);
125
195
  rb_field = ID2SYM(rb_to_id(colStr));
196
+ #endif
126
197
  } else {
127
198
  rb_field = rb_str_new(field->name, field->name_length);
128
199
  #ifdef HAVE_RUBY_ENCODING_H
@@ -140,20 +211,27 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
140
211
 
141
212
  #ifdef HAVE_RUBY_ENCODING_H
142
213
  static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
143
- // if binary flag is set, respect it's wishes
214
+ /* if binary flag is set, respect its wishes */
144
215
  if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
145
216
  rb_enc_associate(val, binaryEncoding);
217
+ } else if (!field.charsetnr) {
218
+ /* MySQL 4.x may not provide an encoding, binary will get the bytes through */
219
+ rb_enc_associate(val, binaryEncoding);
146
220
  } else {
147
- // lookup the encoding configured on this field
148
- VALUE new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset_code, 1, INT2NUM(field.charsetnr));
149
- if (new_encoding != Qnil) {
150
- // use the field encoding we were able to match
151
- rb_encoding *enc = rb_to_encoding(new_encoding);
152
- rb_enc_associate(val, enc);
221
+ /* lookup the encoding configured on this field */
222
+ const char *enc_name;
223
+ int enc_index;
224
+
225
+ enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1];
226
+ if (enc_name != NULL) {
227
+ /* use the field encoding we were able to match */
228
+ enc_index = rb_enc_find_index(enc_name);
229
+ rb_enc_set_index(val, enc_index);
153
230
  } else {
154
- // otherwise fall-back to the connection's encoding
231
+ /* otherwise fall-back to the connection's encoding */
155
232
  rb_enc_associate(val, conn_enc);
156
233
  }
234
+
157
235
  if (default_internal_enc) {
158
236
  val = rb_str_export_to_enc(val, default_internal_enc);
159
237
  }
@@ -162,12 +240,293 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
162
240
  }
163
241
  #endif
164
242
 
243
+ /* Interpret microseconds digits left-aligned in fixed-width field.
244
+ * e.g. 10.123 seconds means 10 seconds and 123000 microseconds,
245
+ * because the microseconds are to the right of the decimal point.
246
+ */
247
+ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
248
+ {
249
+ size_t i;
250
+ for (i = 0; i < (len - 1); i++) {
251
+ if (msec_char[i] == '\0') {
252
+ msec_char[i] = '0';
253
+ }
254
+ }
255
+ return (unsigned int)strtoul(msec_char, NULL, 10);
256
+ }
165
257
 
166
- static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast) {
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
+ {
167
528
  VALUE rowVal;
168
- mysql2_result_wrapper * wrapper;
169
529
  MYSQL_ROW row;
170
- MYSQL_FIELD * fields = NULL;
171
530
  unsigned int i = 0;
172
531
  unsigned long * fieldLengths;
173
532
  void * ptr;
@@ -175,7 +534,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
175
534
  rb_encoding *default_internal_enc;
176
535
  rb_encoding *conn_enc;
177
536
  #endif
178
- GetMysql2Result(self, wrapper);
537
+ GET_RESULT(self);
179
538
 
180
539
  #ifdef HAVE_RUBY_ENCODING_H
181
540
  default_internal_enc = rb_default_internal_encoding();
@@ -183,30 +542,29 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
183
542
  #endif
184
543
 
185
544
  ptr = wrapper->result;
186
- row = (MYSQL_ROW)rb_thread_blocking_region(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
545
+ row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
187
546
  if (row == NULL) {
188
547
  return Qnil;
189
548
  }
190
549
 
191
- 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) {
192
555
  rowVal = rb_ary_new2(wrapper->numberOfFields);
193
556
  } else {
194
557
  rowVal = rb_hash_new();
195
558
  }
196
- fields = mysql_fetch_fields(wrapper->result);
197
559
  fieldLengths = mysql_fetch_lengths(wrapper->result);
198
- if (wrapper->fields == Qnil) {
199
- wrapper->numberOfFields = mysql_num_fields(wrapper->result);
200
- wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
201
- }
202
560
 
203
561
  for (i = 0; i < wrapper->numberOfFields; i++) {
204
- VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
562
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
205
563
  if (row[i]) {
206
564
  VALUE val = Qnil;
207
565
  enum enum_field_types type = fields[i].type;
208
566
 
209
- if(!cast) {
567
+ if (!args->cast) {
210
568
  if (type == MYSQL_TYPE_NULL) {
211
569
  val = Qnil;
212
570
  } else {
@@ -217,34 +575,40 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
217
575
  }
218
576
  } else {
219
577
  switch(type) {
220
- case MYSQL_TYPE_NULL: // NULL-type field
578
+ case MYSQL_TYPE_NULL: /* NULL-type field */
221
579
  val = Qnil;
222
580
  break;
223
- case MYSQL_TYPE_BIT: // BIT field (MySQL 5.0.3 and up)
224
- val = rb_str_new(row[i], fieldLengths[i]);
581
+ case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
582
+ if (args->castBool && fields[i].length == 1) {
583
+ val = *row[i] == 1 ? Qtrue : Qfalse;
584
+ }else{
585
+ val = rb_str_new(row[i], fieldLengths[i]);
586
+ }
225
587
  break;
226
- case MYSQL_TYPE_TINY: // TINYINT field
227
- if (castBool && fields[i].length == 1) {
228
- val = *row[i] == '1' ? Qtrue : Qfalse;
588
+ case MYSQL_TYPE_TINY: /* TINYINT field */
589
+ if (args->castBool && fields[i].length == 1) {
590
+ val = *row[i] != '0' ? Qtrue : Qfalse;
229
591
  break;
230
592
  }
231
- case MYSQL_TYPE_SHORT: // SMALLINT field
232
- case MYSQL_TYPE_LONG: // INTEGER field
233
- case MYSQL_TYPE_INT24: // MEDIUMINT field
234
- case MYSQL_TYPE_LONGLONG: // BIGINT field
235
- case MYSQL_TYPE_YEAR: // YEAR field
593
+ case MYSQL_TYPE_SHORT: /* SMALLINT field */
594
+ case MYSQL_TYPE_LONG: /* INTEGER field */
595
+ case MYSQL_TYPE_INT24: /* MEDIUMINT field */
596
+ case MYSQL_TYPE_LONGLONG: /* BIGINT field */
597
+ case MYSQL_TYPE_YEAR: /* YEAR field */
236
598
  val = rb_cstr2inum(row[i], 10);
237
599
  break;
238
- case MYSQL_TYPE_DECIMAL: // DECIMAL or NUMERIC field
239
- case MYSQL_TYPE_NEWDECIMAL: // Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up)
240
- if (strtod(row[i], NULL) == 0.000000){
600
+ case MYSQL_TYPE_DECIMAL: /* DECIMAL or NUMERIC field */
601
+ case MYSQL_TYPE_NEWDECIMAL: /* Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up) */
602
+ if (fields[i].decimals == 0) {
603
+ val = rb_cstr2inum(row[i], 10);
604
+ } else if (strtod(row[i], NULL) == 0.000000){
241
605
  val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero);
242
606
  }else{
243
607
  val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
244
608
  }
245
609
  break;
246
- case MYSQL_TYPE_FLOAT: // FLOAT field
247
- case MYSQL_TYPE_DOUBLE: { // DOUBLE or REAL field
610
+ case MYSQL_TYPE_FLOAT: /* FLOAT field */
611
+ case MYSQL_TYPE_DOUBLE: { /* DOUBLE or REAL field */
248
612
  double column_to_double;
249
613
  column_to_double = strtod(row[i], NULL);
250
614
  if (column_to_double == 0.000000){
@@ -254,54 +618,69 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
254
618
  }
255
619
  break;
256
620
  }
257
- case MYSQL_TYPE_TIME: { // TIME field
258
- int hour, min, sec, tokens;
259
- tokens = sscanf(row[i], "%2d:%2d:%2d", &hour, &min, &sec);
260
- val = rb_funcall(rb_cTime, db_timezone, 6, opt_time_year, opt_time_month, opt_time_month, INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
261
- if (!NIL_P(app_timezone)) {
262
- if (app_timezone == intern_local) {
621
+ case MYSQL_TYPE_TIME: { /* TIME field */
622
+ int tokens;
623
+ unsigned int hour=0, min=0, sec=0, msec=0;
624
+ char msec_char[7] = {'0','0','0','0','0','0','\0'};
625
+
626
+ tokens = sscanf(row[i], "%2u:%2u:%2u.%6s", &hour, &min, &sec, msec_char);
627
+ if (tokens < 3) {
628
+ val = Qnil;
629
+ break;
630
+ }
631
+ msec = msec_char_to_uint(msec_char, sizeof(msec_char));
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) {
263
635
  val = rb_funcall(val, intern_localtime, 0);
264
- } else { // utc
636
+ } else { /* utc */
265
637
  val = rb_funcall(val, intern_utc, 0);
266
638
  }
267
639
  }
268
640
  break;
269
641
  }
270
- case MYSQL_TYPE_TIMESTAMP: // TIMESTAMP field
271
- case MYSQL_TYPE_DATETIME: { // DATETIME field
272
- unsigned int year, month, day, hour, min, sec, tokens;
642
+ case MYSQL_TYPE_TIMESTAMP: /* TIMESTAMP field */
643
+ case MYSQL_TYPE_DATETIME: { /* DATETIME field */
644
+ int tokens;
645
+ unsigned int year=0, month=0, day=0, hour=0, min=0, sec=0, msec=0;
646
+ char msec_char[7] = {'0','0','0','0','0','0','\0'};
273
647
  uint64_t seconds;
274
648
 
275
- tokens = sscanf(row[i], "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
649
+ tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6s", &year, &month, &day, &hour, &min, &sec, msec_char);
650
+ if (tokens < 6) { /* msec might be empty */
651
+ val = Qnil;
652
+ break;
653
+ }
276
654
  seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec;
277
655
 
278
656
  if (seconds == 0) {
279
657
  val = Qnil;
280
658
  } else {
281
659
  if (month < 1 || day < 1) {
282
- rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
660
+ rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]);
283
661
  val = Qnil;
284
662
  } else {
285
- if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
663
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
286
664
  VALUE offset = INT2NUM(0);
287
- if (db_timezone == intern_local) {
665
+ if (args->db_timezone == intern_local) {
288
666
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
289
667
  }
290
- val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), offset);
291
- if (!NIL_P(app_timezone)) {
292
- if (app_timezone == intern_local) {
668
+ val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset);
669
+ if (!NIL_P(args->app_timezone)) {
670
+ if (args->app_timezone == intern_local) {
293
671
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
294
672
  val = rb_funcall(val, intern_new_offset, 1, offset);
295
- } else { // utc
673
+ } else { /* utc */
296
674
  val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
297
675
  }
298
676
  }
299
677
  } else {
300
- val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec));
301
- if (!NIL_P(app_timezone)) {
302
- if (app_timezone == intern_local) {
678
+ msec = msec_char_to_uint(msec_char, sizeof(msec_char));
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) {
303
682
  val = rb_funcall(val, intern_localtime, 0);
304
- } else { // utc
683
+ } else { /* utc */
305
684
  val = rb_funcall(val, intern_utc, 0);
306
685
  }
307
686
  }
@@ -310,18 +689,23 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
310
689
  }
311
690
  break;
312
691
  }
313
- case MYSQL_TYPE_DATE: // DATE field
314
- case MYSQL_TYPE_NEWDATE: { // Newer const used > 5.0
315
- int year, month, day, tokens;
316
- tokens = sscanf(row[i], "%4d-%2d-%2d", &year, &month, &day);
692
+ case MYSQL_TYPE_DATE: /* DATE field */
693
+ case MYSQL_TYPE_NEWDATE: { /* Newer const used > 5.0 */
694
+ int tokens;
695
+ unsigned int year=0, month=0, day=0;
696
+ tokens = sscanf(row[i], "%4u-%2u-%2u", &year, &month, &day);
697
+ if (tokens < 3) {
698
+ val = Qnil;
699
+ break;
700
+ }
317
701
  if (year+month+day == 0) {
318
702
  val = Qnil;
319
703
  } else {
320
704
  if (month < 1 || day < 1) {
321
- rb_raise(cMysql2Error, "Invalid date: %s", row[i]);
705
+ rb_raise(cMysql2Error, "Invalid date in field '%.*s': %s", fields[i].name_length, fields[i].name, row[i]);
322
706
  val = Qnil;
323
707
  } else {
324
- val = rb_funcall(cDate, intern_new, 3, INT2NUM(year), INT2NUM(month), INT2NUM(day));
708
+ val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day));
325
709
  }
326
710
  }
327
711
  break;
@@ -332,10 +716,10 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
332
716
  case MYSQL_TYPE_BLOB:
333
717
  case MYSQL_TYPE_VAR_STRING:
334
718
  case MYSQL_TYPE_VARCHAR:
335
- case MYSQL_TYPE_STRING: // CHAR or BINARY field
336
- case MYSQL_TYPE_SET: // SET field
337
- case MYSQL_TYPE_ENUM: // ENUM field
338
- case MYSQL_TYPE_GEOMETRY: // Spatial fielda
719
+ case MYSQL_TYPE_STRING: /* CHAR or BINARY field */
720
+ case MYSQL_TYPE_SET: /* SET field */
721
+ case MYSQL_TYPE_ENUM: /* ENUM field */
722
+ case MYSQL_TYPE_GEOMETRY: /* Spatial fielda */
339
723
  default:
340
724
  val = rb_str_new(row[i], fieldLengths[i]);
341
725
  #ifdef HAVE_RUBY_ENCODING_H
@@ -344,13 +728,13 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
344
728
  break;
345
729
  }
346
730
  }
347
- if (asArray) {
731
+ if (args->asArray) {
348
732
  rb_ary_push(rowVal, val);
349
733
  } else {
350
734
  rb_hash_aset(rowVal, field, val);
351
735
  }
352
736
  } else {
353
- if (asArray) {
737
+ if (args->asArray) {
354
738
  rb_ary_push(rowVal, Qnil);
355
739
  } else {
356
740
  rb_hash_aset(rowVal, field, Qnil);
@@ -361,14 +745,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
361
745
  }
362
746
 
363
747
  static VALUE rb_mysql_result_fetch_fields(VALUE self) {
364
- mysql2_result_wrapper * wrapper;
365
748
  unsigned int i = 0;
366
749
  short int symbolizeKeys = 0;
367
750
  VALUE defaults;
368
751
 
369
- GetMysql2Result(self, wrapper);
752
+ GET_RESULT(self);
370
753
 
371
754
  defaults = rb_iv_get(self, "@query_options");
755
+ Check_Type(defaults, T_HASH);
372
756
  if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
373
757
  symbolizeKeys = 1;
374
758
  }
@@ -378,7 +762,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
378
762
  wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
379
763
  }
380
764
 
381
- if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
765
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
382
766
  for (i=0; i<wrapper->numberOfFields; i++) {
383
767
  rb_mysql_result_fetch_field(self, i, symbolizeKeys);
384
768
  }
@@ -387,40 +771,134 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
387
771
  return wrapper->fields;
388
772
  }
389
773
 
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
+ {
778
+ unsigned long i;
779
+ const char *errstr;
780
+ MYSQL_FIELD *fields = NULL;
781
+
782
+ GET_RESULT(self);
783
+
784
+ if (wrapper->is_streaming) {
785
+ /* When streaming, we will only yield rows, not return them. */
786
+ if (wrapper->rows == Qnil) {
787
+ wrapper->rows = rb_ary_new();
788
+ }
789
+
790
+ if (!wrapper->streamingComplete) {
791
+ VALUE row;
792
+
793
+ fields = mysql_fetch_fields(wrapper->result);
794
+
795
+ do {
796
+ row = fetch_row_func(self, fields, args);
797
+ if (row != Qnil) {
798
+ wrapper->numberOfRows++;
799
+ if (args->block_given != Qnil) {
800
+ rb_yield(row);
801
+ }
802
+ }
803
+ } while(row != Qnil);
804
+
805
+ rb_mysql_result_free_result(wrapper);
806
+ wrapper->streamingComplete = 1;
807
+
808
+ // Check for errors, the connection might have gone out from under us
809
+ // mysql_error returns an empty string if there is no error
810
+ errstr = mysql_error(wrapper->client_wrapper->client);
811
+ if (errstr[0]) {
812
+ rb_raise(cMysql2Error, "%s", errstr);
813
+ }
814
+ } else {
815
+ rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
816
+ }
817
+ } else {
818
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
819
+ /* we've already read the entire dataset from the C result into our */
820
+ /* internal array. Lets hand that over to the user since it's ready to go */
821
+ for (i = 0; i < wrapper->numberOfRows; i++) {
822
+ rb_yield(rb_ary_entry(wrapper->rows, i));
823
+ }
824
+ } else {
825
+ unsigned long rowsProcessed = 0;
826
+ rowsProcessed = RARRAY_LEN(wrapper->rows);
827
+ fields = mysql_fetch_fields(wrapper->result);
828
+
829
+ for (i = 0; i < wrapper->numberOfRows; i++) {
830
+ VALUE row;
831
+ if (args->cacheRows && i < rowsProcessed) {
832
+ row = rb_ary_entry(wrapper->rows, i);
833
+ } else {
834
+ row = fetch_row_func(self, fields, args);
835
+ if (args->cacheRows) {
836
+ rb_ary_store(wrapper->rows, i, row);
837
+ }
838
+ wrapper->lastRowProcessed++;
839
+ }
840
+
841
+ if (row == Qnil) {
842
+ /* we don't need the mysql C dataset around anymore, peace it */
843
+ if (args->cacheRows) {
844
+ rb_mysql_result_free_result(wrapper);
845
+ }
846
+ return Qnil;
847
+ }
848
+
849
+ if (args->block_given != Qnil) {
850
+ rb_yield(row);
851
+ }
852
+ }
853
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows && args->cacheRows) {
854
+ /* we don't need the mysql C dataset around anymore, peace it */
855
+ rb_mysql_result_free_result(wrapper);
856
+ }
857
+ }
858
+ }
859
+
860
+ // FIXME return Enumerator instead?
861
+ // return rb_ary_each(wrapper->rows);
862
+ return wrapper->rows;
863
+ }
864
+
390
865
  static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
391
- VALUE defaults, opts, block;
866
+ result_each_args args;
867
+ VALUE defaults, opts, block, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
392
868
  ID db_timezone, app_timezone, dbTz, appTz;
393
- mysql2_result_wrapper * wrapper;
394
- unsigned long i;
395
- int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1;
869
+ int symbolizeKeys, asArray, castBool, cacheRows, cast;
870
+
871
+ GET_RESULT(self);
396
872
 
397
- GetMysql2Result(self, wrapper);
873
+ if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) {
874
+ rb_raise(cMysql2Error, "Statement handle already closed");
875
+ }
398
876
 
399
877
  defaults = rb_iv_get(self, "@query_options");
878
+ Check_Type(defaults, T_HASH);
400
879
  if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
401
880
  opts = rb_funcall(defaults, intern_merge, 1, opts);
402
881
  } else {
403
882
  opts = defaults;
404
883
  }
405
884
 
406
- if (rb_hash_aref(opts, sym_symbolize_keys) == Qtrue) {
407
- symbolizeKeys = 1;
408
- }
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));
409
890
 
410
- if (rb_hash_aref(opts, sym_as) == sym_array) {
411
- asArray = 1;
891
+ if (wrapper->is_streaming && cacheRows) {
892
+ rb_warn(":cache_rows is ignored if :stream is true");
412
893
  }
413
894
 
414
- if (rb_hash_aref(opts, sym_cast_booleans) == Qtrue) {
415
- castBool = 1;
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;
416
898
  }
417
899
 
418
- if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
419
- cacheRows = 0;
420
- }
421
-
422
- if (rb_hash_aref(opts, sym_cast) == Qfalse) {
423
- cast = 0;
900
+ if (wrapper->stmt_wrapper && !cast) {
901
+ rb_warn(":cast is forced for prepared statements");
424
902
  }
425
903
 
426
904
  dbTz = rb_hash_aref(opts, sym_database_timezone);
@@ -444,67 +922,63 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
444
922
  app_timezone = Qnil;
445
923
  }
446
924
 
447
- if (wrapper->lastRowProcessed == 0) {
448
- wrapper->numberOfRows = mysql_num_rows(wrapper->result);
449
- if (wrapper->numberOfRows == 0) {
450
- wrapper->rows = rb_ary_new();
451
- return wrapper->rows;
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");
452
931
  }
932
+ mysql_data_seek(wrapper->result, 0);
933
+ wrapper->lastRowProcessed = 0;
453
934
  wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
454
935
  }
455
936
 
456
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
457
- // we've already read the entire dataset from the C result into our
458
- // internal array. Lets hand that over to the user since it's ready to go
459
- for (i = 0; i < wrapper->numberOfRows; i++) {
460
- rb_yield(rb_ary_entry(wrapper->rows, i));
461
- }
462
- } else {
463
- unsigned long rowsProcessed = 0;
464
- rowsProcessed = RARRAY_LEN(wrapper->rows);
465
- for (i = 0; i < wrapper->numberOfRows; i++) {
466
- VALUE row;
467
- if (cacheRows && i < rowsProcessed) {
468
- row = rb_ary_entry(wrapper->rows, i);
469
- } else {
470
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast);
471
- if (cacheRows) {
472
- rb_ary_store(wrapper->rows, i, row);
473
- }
474
- wrapper->lastRowProcessed++;
475
- }
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;
476
946
 
477
- if (row == Qnil) {
478
- // we don't need the mysql C dataset around anymore, peace it
479
- rb_mysql_result_free_result(wrapper);
480
- return Qnil;
481
- }
482
-
483
- if (block != Qnil) {
484
- rb_yield(row);
485
- }
486
- }
487
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
488
- // we don't need the mysql C dataset around anymore, peace it
489
- rb_mysql_result_free_result(wrapper);
490
- }
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;
491
951
  }
492
952
 
493
- return wrapper->rows;
953
+ return rb_mysql_result_each_(self, fetch_row_func, &args);
494
954
  }
495
955
 
496
956
  static VALUE rb_mysql_result_count(VALUE self) {
497
- mysql2_result_wrapper *wrapper;
957
+ GET_RESULT(self);
498
958
 
499
- GetMysql2Result(self, wrapper);
959
+ if (wrapper->is_streaming) {
960
+ /* This is an unsigned long per result.h */
961
+ return ULONG2NUM(wrapper->numberOfRows);
962
+ }
500
963
 
501
- return INT2FIX(mysql_num_rows(wrapper->result));
964
+ if (wrapper->resultFreed) {
965
+ /* Ruby arrays have platform signed long length */
966
+ return LONG2NUM(RARRAY_LEN(wrapper->rows));
967
+ } else {
968
+ /* MySQL returns an unsigned 64-bit long here */
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
+ }
974
+ }
502
975
  }
503
976
 
504
977
  /* Mysql2::Result */
505
- VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
978
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) {
506
979
  VALUE obj;
507
980
  mysql2_result_wrapper * wrapper;
981
+
508
982
  obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
509
983
  wrapper->numberOfFields = 0;
510
984
  wrapper->numberOfRows = 0;
@@ -513,8 +987,32 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
513
987
  wrapper->result = r;
514
988
  wrapper->fields = Qnil;
515
989
  wrapper->rows = Qnil;
516
- wrapper->encoding = Qnil;
990
+ wrapper->encoding = encoding;
991
+ wrapper->streamingComplete = 0;
992
+ wrapper->client = client;
993
+ wrapper->client_wrapper = DATA_PTR(client);
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
+ }
1008
+
517
1009
  rb_obj_call_init(obj, 0, NULL);
1010
+ rb_iv_set(obj, "@query_options", options);
1011
+
1012
+ /* Options that cannot be changed in results.each(...) { |row| }
1013
+ * should be processed here. */
1014
+ wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0);
1015
+
518
1016
  return obj;
519
1017
  }
520
1018
 
@@ -526,12 +1024,10 @@ void init_mysql2_result() {
526
1024
  cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
527
1025
  rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
528
1026
  rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
1027
+ rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0);
529
1028
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
530
1029
  rb_define_alias(cMysql2Result, "size", "count");
531
1030
 
532
- intern_encoding_from_charset = rb_intern("encoding_from_charset");
533
- intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code");
534
-
535
1031
  intern_new = rb_intern("new");
536
1032
  intern_utc = rb_intern("utc");
537
1033
  intern_local = rb_intern("local");
@@ -551,9 +1047,11 @@ void init_mysql2_result() {
551
1047
  sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
552
1048
  sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
553
1049
  sym_cast = ID2SYM(rb_intern("cast"));
1050
+ sym_stream = ID2SYM(rb_intern("stream"));
1051
+ sym_name = ID2SYM(rb_intern("name"));
554
1052
 
555
1053
  opt_decimal_zero = rb_str_new2("0.0");
556
- rb_global_variable(&opt_decimal_zero); //never GC
1054
+ rb_global_variable(&opt_decimal_zero); /*never GC */
557
1055
  opt_float_zero = rb_float_new((double)0);
558
1056
  rb_global_variable(&opt_float_zero);
559
1057
  opt_time_year = INT2NUM(2000);