mysql2 0.3.18 → 0.4.7

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