mysql2 0.3.8 → 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 (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);