mysql2 0.3.1 → 0.5.2

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 (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
  }