mysql2 0.3.20 → 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.
data/ext/mysql2/result.c CHANGED
@@ -1,68 +1,49 @@
1
1
  #include <mysql2_ext.h>
2
2
 
3
- #include <stdint.h>
4
-
5
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]))
6
5
 
7
- #ifdef HAVE_RUBY_ENCODING_H
8
6
  static rb_encoding *binaryEncoding;
9
- #endif
10
7
 
11
- #if (SIZEOF_INT < SIZEOF_LONG) || defined(HAVE_RUBY_ENCODING_H)
12
8
  /* on 64bit platforms we can handle dates way outside 2038-01-19T03:14:07
13
9
  *
14
10
  * (9999*31557600) + (12*2592000) + (31*86400) + (11*3600) + (59*60) + 59
15
11
  */
16
12
  #define MYSQL2_MAX_TIME 315578267999ULL
17
- #else
18
- /**
19
- * On 32bit platforms the maximum date the Time class can handle is 2038-01-19T03:14:07
20
- * 2038 years + 1 month + 19 days + 3 hours + 14 minutes + 7 seconds = 64318634047 seconds
21
- *
22
- * (2038*31557600) + (1*2592000) + (19*86400) + (3*3600) + (14*60) + 7
23
- */
24
- #define MYSQL2_MAX_TIME 64318634047ULL
25
- #endif
26
13
 
27
- #if defined(HAVE_RUBY_ENCODING_H)
28
14
  /* 0000-1-1 00:00:00 UTC
29
15
  *
30
16
  * (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
31
17
  */
32
18
  #define MYSQL2_MIN_TIME 2678400ULL
33
- #elif SIZEOF_INT < SIZEOF_LONG /* 64bit Ruby 1.8 */
34
- /* 0139-1-1 00:00:00 UTC
35
- *
36
- * (139*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
37
- */
38
- #define MYSQL2_MIN_TIME 4389184800ULL
39
- #elif defined(NEGATIVE_TIME_T)
40
- /* 1901-12-13 20:45:52 UTC : The oldest time in 32-bit signed time_t.
41
- *
42
- * (1901*31557600) + (12*2592000) + (13*86400) + (20*3600) + (45*60) + 52
43
- */
44
- #define MYSQL2_MIN_TIME 60023299552ULL
45
- #else
46
- /* 1970-01-01 00:00:01 UTC : The Unix epoch - the oldest time in portable time_t.
47
- *
48
- * (1970*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 1
49
- */
50
- #define MYSQL2_MIN_TIME 62171150401ULL
51
- #endif
52
19
 
53
- #define GET_RESULT(obj) \
20
+ #define GET_RESULT(self) \
54
21
  mysql2_result_wrapper *wrapper; \
55
22
  Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
56
23
 
57
- static VALUE cMysql2Result;
58
- static VALUE cBigDecimal, cDate, cDateTime;
59
- static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
60
- extern VALUE mMysql2, cMysql2Client, cMysql2Error;
61
- static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
62
- static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
63
- sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
64
- static ID intern_merge;
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;
65
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. */
66
47
  static void rb_mysql_result_mark(void * wrapper) {
67
48
  mysql2_result_wrapper * w = wrapper;
68
49
  if (w) {
@@ -70,13 +51,50 @@ static void rb_mysql_result_mark(void * wrapper) {
70
51
  rb_gc_mark(w->rows);
71
52
  rb_gc_mark(w->encoding);
72
53
  rb_gc_mark(w->client);
54
+ rb_gc_mark(w->statement);
73
55
  }
74
56
  }
75
57
 
76
58
  /* this may be called manually or during GC */
77
59
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
78
- 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
+ }
79
96
  /* FIXME: this may call flush_use_result, which can hit the socket */
97
+ /* For prepared statements, wrapper->result is the result metadata */
80
98
  mysql_free_result(wrapper->result);
81
99
  wrapper->resultFreed = 1;
82
100
  }
@@ -84,7 +102,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
84
102
 
85
103
  /* this is called during GC */
86
104
  static void rb_mysql_result_free(void *ptr) {
87
- mysql2_result_wrapper * wrapper = ptr;
105
+ mysql2_result_wrapper *wrapper = ptr;
88
106
  rb_mysql_result_free_result(wrapper);
89
107
 
90
108
  // If the GC gets to client first it will be nil
@@ -95,6 +113,12 @@ static void rb_mysql_result_free(void *ptr) {
95
113
  xfree(wrapper);
96
114
  }
97
115
 
116
+ static VALUE rb_mysql_result_free_(VALUE self) {
117
+ GET_RESULT(self);
118
+ rb_mysql_result_free_result(wrapper);
119
+ return Qnil;
120
+ }
121
+
98
122
  /*
99
123
  * for small results, this won't hit the network, but there's no
100
124
  * reliable way for us to tell this so we'll always release the GVL
@@ -106,7 +130,14 @@ static void *nogvl_fetch_row(void *ptr) {
106
130
  return mysql_fetch_row(result);
107
131
  }
108
132
 
109
- static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
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) {
110
141
  VALUE rb_field;
111
142
  GET_RESULT(self);
112
143
 
@@ -118,29 +149,19 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
118
149
  rb_field = rb_ary_entry(wrapper->fields, idx);
119
150
  if (rb_field == Qnil) {
120
151
  MYSQL_FIELD *field = NULL;
121
- #ifdef HAVE_RUBY_ENCODING_H
122
152
  rb_encoding *default_internal_enc = rb_default_internal_encoding();
123
153
  rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
124
- #endif
125
154
 
126
155
  field = mysql_fetch_field_direct(wrapper->result, idx);
127
156
  if (symbolize_keys) {
128
- #ifdef HAVE_RB_INTERN3
129
157
  rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding());
130
158
  rb_field = ID2SYM(rb_field);
131
- #else
132
- VALUE colStr;
133
- colStr = rb_str_new(field->name, field->name_length);
134
- rb_field = ID2SYM(rb_to_id(colStr));
135
- #endif
136
159
  } else {
137
160
  rb_field = rb_str_new(field->name, field->name_length);
138
- #ifdef HAVE_RUBY_ENCODING_H
139
161
  rb_enc_associate(rb_field, conn_enc);
140
162
  if (default_internal_enc) {
141
163
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
142
164
  }
143
- #endif
144
165
  }
145
166
  rb_ary_store(wrapper->fields, idx, rb_field);
146
167
  }
@@ -148,9 +169,8 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
148
169
  return rb_field;
149
170
  }
150
171
 
151
- #ifdef HAVE_RUBY_ENCODING_H
152
172
  static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_encoding *default_internal_enc, rb_encoding *conn_enc) {
153
- /* if binary flag is set, respect it's wishes */
173
+ /* if binary flag is set, respect its wishes */
154
174
  if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
155
175
  rb_enc_associate(val, binaryEncoding);
156
176
  } else if (!field.charsetnr) {
@@ -161,7 +181,8 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
161
181
  const char *enc_name;
162
182
  int enc_index;
163
183
 
164
- enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1];
184
+ enc_name = (field.charsetnr-1 < MYSQL2_CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL;
185
+
165
186
  if (enc_name != NULL) {
166
187
  /* use the field encoding we were able to match */
167
188
  enc_index = rb_enc_find_index(enc_name);
@@ -177,7 +198,6 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
177
198
  }
178
199
  return val;
179
200
  }
180
- #endif
181
201
 
182
202
  /* Interpret microseconds digits left-aligned in fixed-width field.
183
203
  * e.g. 10.123 seconds means 10 seconds and 123000 microseconds,
@@ -185,7 +205,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
185
205
  */
186
206
  static unsigned int msec_char_to_uint(char *msec_char, size_t len)
187
207
  {
188
- int i;
208
+ size_t i;
189
209
  for (i = 0; i < (len - 1); i++) {
190
210
  if (msec_char[i] == '\0') {
191
211
  msec_char[i] = '0';
@@ -194,22 +214,281 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
194
214
  return (unsigned int)strtoul(msec_char, NULL, 10);
195
215
  }
196
216
 
197
- static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) {
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
+ {
198
481
  VALUE rowVal;
199
482
  MYSQL_ROW row;
200
483
  unsigned int i = 0;
201
484
  unsigned long * fieldLengths;
202
485
  void * ptr;
203
- #ifdef HAVE_RUBY_ENCODING_H
204
486
  rb_encoding *default_internal_enc;
205
487
  rb_encoding *conn_enc;
206
- #endif
207
488
  GET_RESULT(self);
208
489
 
209
- #ifdef HAVE_RUBY_ENCODING_H
210
490
  default_internal_enc = rb_default_internal_encoding();
211
491
  conn_enc = rb_to_encoding(wrapper->encoding);
212
- #endif
213
492
 
214
493
  ptr = wrapper->result;
215
494
  row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
@@ -217,31 +496,29 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
217
496
  return Qnil;
218
497
  }
219
498
 
220
- 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) {
221
504
  rowVal = rb_ary_new2(wrapper->numberOfFields);
222
505
  } else {
223
506
  rowVal = rb_hash_new();
224
507
  }
225
508
  fieldLengths = mysql_fetch_lengths(wrapper->result);
226
- if (wrapper->fields == Qnil) {
227
- wrapper->numberOfFields = mysql_num_fields(wrapper->result);
228
- wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
229
- }
230
509
 
231
510
  for (i = 0; i < wrapper->numberOfFields; i++) {
232
- VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
511
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
233
512
  if (row[i]) {
234
513
  VALUE val = Qnil;
235
514
  enum enum_field_types type = fields[i].type;
236
515
 
237
- if (!cast) {
516
+ if (!args->cast) {
238
517
  if (type == MYSQL_TYPE_NULL) {
239
518
  val = Qnil;
240
519
  } else {
241
520
  val = rb_str_new(row[i], fieldLengths[i]);
242
- #ifdef HAVE_RUBY_ENCODING_H
243
521
  val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
244
- #endif
245
522
  }
246
523
  } else {
247
524
  switch(type) {
@@ -249,14 +526,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
249
526
  val = Qnil;
250
527
  break;
251
528
  case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
252
- if (castBool && fields[i].length == 1) {
529
+ if (args->castBool && fields[i].length == 1) {
253
530
  val = *row[i] == 1 ? Qtrue : Qfalse;
254
531
  }else{
255
532
  val = rb_str_new(row[i], fieldLengths[i]);
256
533
  }
257
534
  break;
258
535
  case MYSQL_TYPE_TINY: /* TINYINT field */
259
- if (castBool && fields[i].length == 1) {
536
+ if (args->castBool && fields[i].length == 1) {
260
537
  val = *row[i] != '0' ? Qtrue : Qfalse;
261
538
  break;
262
539
  }
@@ -272,9 +549,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
272
549
  if (fields[i].decimals == 0) {
273
550
  val = rb_cstr2inum(row[i], 10);
274
551
  } else if (strtod(row[i], NULL) == 0.000000){
275
- val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero);
552
+ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero);
276
553
  }else{
277
- 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]));
278
555
  }
279
556
  break;
280
557
  case MYSQL_TYPE_FLOAT: /* FLOAT field */
@@ -299,9 +576,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
299
576
  break;
300
577
  }
301
578
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
302
- val = rb_funcall(rb_cTime, db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
303
- if (!NIL_P(app_timezone)) {
304
- if (app_timezone == intern_local) {
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) {
305
582
  val = rb_funcall(val, intern_localtime, 0);
306
583
  } else { /* utc */
307
584
  val = rb_funcall(val, intern_utc, 0);
@@ -332,12 +609,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
332
609
  } else {
333
610
  if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
334
611
  VALUE offset = INT2NUM(0);
335
- if (db_timezone == intern_local) {
612
+ if (args->db_timezone == intern_local) {
336
613
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
337
614
  }
338
615
  val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset);
339
- if (!NIL_P(app_timezone)) {
340
- if (app_timezone == intern_local) {
616
+ if (!NIL_P(args->app_timezone)) {
617
+ if (args->app_timezone == intern_local) {
341
618
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
342
619
  val = rb_funcall(val, intern_new_offset, 1, offset);
343
620
  } else { /* utc */
@@ -346,9 +623,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
346
623
  }
347
624
  } else {
348
625
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
349
- val = rb_funcall(rb_cTime, db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
350
- if (!NIL_P(app_timezone)) {
351
- if (app_timezone == intern_local) {
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) {
352
629
  val = rb_funcall(val, intern_localtime, 0);
353
630
  } else { /* utc */
354
631
  val = rb_funcall(val, intern_utc, 0);
@@ -392,19 +669,17 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
392
669
  case MYSQL_TYPE_GEOMETRY: /* Spatial fielda */
393
670
  default:
394
671
  val = rb_str_new(row[i], fieldLengths[i]);
395
- #ifdef HAVE_RUBY_ENCODING_H
396
672
  val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
397
- #endif
398
673
  break;
399
674
  }
400
675
  }
401
- if (asArray) {
676
+ if (args->asArray) {
402
677
  rb_ary_push(rowVal, val);
403
678
  } else {
404
679
  rb_hash_aset(rowVal, field, val);
405
680
  }
406
681
  } else {
407
- if (asArray) {
682
+ if (args->asArray) {
408
683
  rb_ary_push(rowVal, Qnil);
409
684
  } else {
410
685
  rb_hash_aset(rowVal, field, Qnil);
@@ -421,7 +696,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
421
696
 
422
697
  GET_RESULT(self);
423
698
 
424
- defaults = rb_iv_get(self, "@query_options");
699
+ defaults = rb_ivar_get(self, intern_query_options);
425
700
  Check_Type(defaults, T_HASH);
426
701
  if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
427
702
  symbolizeKeys = 1;
@@ -432,7 +707,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
432
707
  wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
433
708
  }
434
709
 
435
- if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
710
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
436
711
  for (i=0; i<wrapper->numberOfFields; i++) {
437
712
  rb_mysql_result_fetch_field(self, i, symbolizeKeys);
438
713
  }
@@ -441,55 +716,16 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
441
716
  return wrapper->fields;
442
717
  }
443
718
 
444
- static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
445
- VALUE defaults, opts, block;
446
- ID db_timezone, app_timezone, dbTz, appTz;
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
+ {
447
723
  unsigned long i;
448
- const char * errstr;
449
- int symbolizeKeys, asArray, castBool, cacheRows, cast;
450
- MYSQL_FIELD * fields = NULL;
724
+ const char *errstr;
725
+ MYSQL_FIELD *fields = NULL;
451
726
 
452
727
  GET_RESULT(self);
453
728
 
454
- defaults = rb_iv_get(self, "@query_options");
455
- Check_Type(defaults, T_HASH);
456
- if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
457
- opts = rb_funcall(defaults, intern_merge, 1, opts);
458
- } else {
459
- opts = defaults;
460
- }
461
-
462
- symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
463
- asArray = rb_hash_aref(opts, sym_as) == sym_array;
464
- castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
465
- cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
466
- cast = RTEST(rb_hash_aref(opts, sym_cast));
467
-
468
- if (wrapper->is_streaming && cacheRows) {
469
- rb_warn(":cache_rows is ignored if :stream is true");
470
- }
471
-
472
- dbTz = rb_hash_aref(opts, sym_database_timezone);
473
- if (dbTz == sym_local) {
474
- db_timezone = intern_local;
475
- } else if (dbTz == sym_utc) {
476
- db_timezone = intern_utc;
477
- } else {
478
- if (!NIL_P(dbTz)) {
479
- rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
480
- }
481
- db_timezone = intern_local;
482
- }
483
-
484
- appTz = rb_hash_aref(opts, sym_application_timezone);
485
- if (appTz == sym_local) {
486
- app_timezone = intern_local;
487
- } else if (appTz == sym_utc) {
488
- app_timezone = intern_utc;
489
- } else {
490
- app_timezone = Qnil;
491
- }
492
-
493
729
  if (wrapper->is_streaming) {
494
730
  /* When streaming, we will only yield rows, not return them. */
495
731
  if (wrapper->rows == Qnil) {
@@ -502,10 +738,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
502
738
  fields = mysql_fetch_fields(wrapper->result);
503
739
 
504
740
  do {
505
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
741
+ row = fetch_row_func(self, fields, args);
506
742
  if (row != Qnil) {
507
743
  wrapper->numberOfRows++;
508
- if (block != Qnil) {
744
+ if (args->block_given) {
509
745
  rb_yield(row);
510
746
  }
511
747
  }
@@ -524,16 +760,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
524
760
  rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
525
761
  }
526
762
  } else {
527
- if (wrapper->lastRowProcessed == 0) {
528
- wrapper->numberOfRows = mysql_num_rows(wrapper->result);
529
- if (wrapper->numberOfRows == 0) {
530
- wrapper->rows = rb_ary_new();
531
- return wrapper->rows;
532
- }
533
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
534
- }
535
-
536
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
763
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
537
764
  /* we've already read the entire dataset from the C result into our */
538
765
  /* internal array. Lets hand that over to the user since it's ready to go */
539
766
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -546,11 +773,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
546
773
 
547
774
  for (i = 0; i < wrapper->numberOfRows; i++) {
548
775
  VALUE row;
549
- if (cacheRows && i < rowsProcessed) {
776
+ if (args->cacheRows && i < rowsProcessed) {
550
777
  row = rb_ary_entry(wrapper->rows, i);
551
778
  } else {
552
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
553
- if (cacheRows) {
779
+ row = fetch_row_func(self, fields, args);
780
+ if (args->cacheRows) {
554
781
  rb_ary_store(wrapper->rows, i, row);
555
782
  }
556
783
  wrapper->lastRowProcessed++;
@@ -558,24 +785,122 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
558
785
 
559
786
  if (row == Qnil) {
560
787
  /* we don't need the mysql C dataset around anymore, peace it */
561
- rb_mysql_result_free_result(wrapper);
788
+ if (args->cacheRows) {
789
+ rb_mysql_result_free_result(wrapper);
790
+ }
562
791
  return Qnil;
563
792
  }
564
793
 
565
- if (block != Qnil) {
794
+ if (args->block_given) {
566
795
  rb_yield(row);
567
796
  }
568
797
  }
569
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
798
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows && args->cacheRows) {
570
799
  /* we don't need the mysql C dataset around anymore, peace it */
571
800
  rb_mysql_result_free_result(wrapper);
572
801
  }
573
802
  }
574
803
  }
575
804
 
805
+ // FIXME return Enumerator instead?
806
+ // return rb_ary_each(wrapper->rows);
576
807
  return wrapper->rows;
577
808
  }
578
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");
820
+ }
821
+
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;
831
+ }
832
+
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");
841
+ }
842
+
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;
846
+ }
847
+
848
+ if (wrapper->stmt_wrapper && !cast) {
849
+ rb_warn(":cast is forced for prepared statements");
850
+ }
851
+
852
+ dbTz = rb_hash_aref(opts, sym_database_timezone);
853
+ if (dbTz == sym_local) {
854
+ db_timezone = intern_local;
855
+ } else if (dbTz == sym_utc) {
856
+ db_timezone = intern_utc;
857
+ } else {
858
+ if (!NIL_P(dbTz)) {
859
+ rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
860
+ }
861
+ db_timezone = intern_local;
862
+ }
863
+
864
+ appTz = rb_hash_aref(opts, sym_application_timezone);
865
+ if (appTz == sym_local) {
866
+ app_timezone = intern_local;
867
+ } else if (appTz == sym_utc) {
868
+ app_timezone = intern_utc;
869
+ } else {
870
+ app_timezone = Qnil;
871
+ }
872
+
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");
879
+ }
880
+ mysql_data_seek(wrapper->result, 0);
881
+ wrapper->lastRowProcessed = 0;
882
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
883
+ }
884
+
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;
897
+ } else {
898
+ fetch_row_func = rb_mysql_result_fetch_row;
899
+ }
900
+
901
+ return rb_mysql_result_each_(self, fetch_row_func, &args);
902
+ }
903
+
579
904
  static VALUE rb_mysql_result_count(VALUE self) {
580
905
  GET_RESULT(self);
581
906
 
@@ -589,12 +914,16 @@ static VALUE rb_mysql_result_count(VALUE self) {
589
914
  return LONG2NUM(RARRAY_LEN(wrapper->rows));
590
915
  } else {
591
916
  /* MySQL returns an unsigned 64-bit long here */
592
- return ULL2NUM(mysql_num_rows(wrapper->result));
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
+ }
593
922
  }
594
923
  }
595
924
 
596
925
  /* Mysql2::Result */
597
- VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
926
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) {
598
927
  VALUE obj;
599
928
  mysql2_result_wrapper * wrapper;
600
929
 
@@ -611,10 +940,22 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
611
940
  wrapper->client = client;
612
941
  wrapper->client_wrapper = DATA_PTR(client);
613
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
+ }
614
956
 
615
957
  rb_obj_call_init(obj, 0, NULL);
616
-
617
- rb_iv_set(obj, "@query_options", options);
958
+ rb_ivar_set(obj, intern_query_options, options);
618
959
 
619
960
  /* Options that cannot be changed in results.each(...) { |row| }
620
961
  * should be processed here. */
@@ -624,13 +965,13 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
624
965
  }
625
966
 
626
967
  void init_mysql2_result() {
627
- cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
628
968
  cDate = rb_const_get(rb_cObject, rb_intern("Date"));
629
969
  cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
630
970
 
631
971
  cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
632
972
  rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
633
973
  rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
974
+ rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0);
634
975
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
635
976
  rb_define_alias(cMysql2Result, "size", "count");
636
977
 
@@ -642,6 +983,8 @@ void init_mysql2_result() {
642
983
  intern_local_offset = rb_intern("local_offset");
643
984
  intern_civil = rb_intern("civil");
644
985
  intern_new_offset = rb_intern("new_offset");
986
+ intern_BigDecimal = rb_intern("BigDecimal");
987
+ intern_query_options = rb_intern("@query_options");
645
988
 
646
989
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
647
990
  sym_as = ID2SYM(rb_intern("as"));
@@ -664,7 +1007,5 @@ void init_mysql2_result() {
664
1007
  opt_time_month = INT2NUM(1);
665
1008
  opt_utc_offset = INT2NUM(0);
666
1009
 
667
- #ifdef HAVE_RUBY_ENCODING_H
668
1010
  binaryEncoding = rb_enc_find("binary");
669
- #endif
670
1011
  }