mysql2 0.3.10 → 0.5.3

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