mysql2 0.3.1 → 0.5.2

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