mysql2 0.3.10 → 0.5.3

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