mysql2 0.2.24 → 0.5.4

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 (46) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +237 -85
  5. data/ext/mysql2/client.c +582 -249
  6. data/ext/mysql2/client.h +10 -38
  7. data/ext/mysql2/extconf.rb +217 -66
  8. data/ext/mysql2/infile.c +2 -2
  9. data/ext/mysql2/mysql2_ext.c +9 -2
  10. data/ext/mysql2/mysql2_ext.h +13 -14
  11. data/ext/mysql2/mysql_enc_name_to_ruby.h +62 -58
  12. data/ext/mysql2/mysql_enc_to_ruby.h +82 -18
  13. data/ext/mysql2/result.c +736 -200
  14. data/ext/mysql2/result.h +13 -6
  15. data/ext/mysql2/statement.c +612 -0
  16. data/ext/mysql2/statement.h +17 -0
  17. data/ext/mysql2/wait_for_single_fd.h +2 -1
  18. data/lib/mysql2/client.rb +110 -28
  19. data/lib/mysql2/console.rb +1 -1
  20. data/lib/mysql2/em.rb +15 -9
  21. data/lib/mysql2/error.rb +57 -36
  22. data/lib/mysql2/field.rb +3 -0
  23. data/lib/mysql2/result.rb +2 -0
  24. data/lib/mysql2/statement.rb +9 -0
  25. data/lib/mysql2/version.rb +1 -1
  26. data/lib/mysql2.rb +66 -15
  27. data/support/3A79BD29.asc +49 -0
  28. data/support/5072E1F5.asc +432 -0
  29. data/support/libmysql.def +219 -0
  30. data/support/mysql_enc_to_ruby.rb +15 -11
  31. data/support/ruby_enc_to_mysql.rb +8 -6
  32. metadata +30 -94
  33. data/MIT-LICENSE +0 -20
  34. data/examples/eventmachine.rb +0 -21
  35. data/examples/threaded.rb +0 -20
  36. data/lib/active_record/connection_adapters/mysql2_adapter.rb +0 -635
  37. data/lib/arel/engines/sql/compilers/mysql2_compiler.rb +0 -11
  38. data/spec/configuration.yml.example +0 -17
  39. data/spec/em/em_spec.rb +0 -114
  40. data/spec/my.cnf.example +0 -9
  41. data/spec/mysql2/client_spec.rb +0 -897
  42. data/spec/mysql2/error_spec.rb +0 -83
  43. data/spec/mysql2/result_spec.rb +0 -505
  44. data/spec/rcov.opts +0 -3
  45. data/spec/spec_helper.rb +0 -87
  46. data/spec/test_data +0 -1
data/ext/mysql2/result.c CHANGED
@@ -1,64 +1,63 @@
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
19
+
20
+ #define MYSQL2_MAX_BYTES_PER_CHAR 3
21
+
22
+ /* From Mysql documentations:
23
+ * To distinguish between binary and nonbinary data for string data types,
24
+ * check whether the charsetnr value is 63. If so, the character set is binary,
25
+ * which indicates binary rather than nonbinary data. This enables you to distinguish BINARY
26
+ * from CHAR, VARBINARY from VARCHAR, and the BLOB types from the TEXT types.
49
27
  */
50
- #define MYSQL2_MIN_TIME 62171150401ULL
28
+ #define MYSQL2_BINARY_CHARSET 63
29
+
30
+ #ifndef MYSQL_TYPE_JSON
31
+ #define MYSQL_TYPE_JSON 245
51
32
  #endif
52
33
 
53
- static VALUE cMysql2Result;
54
- static VALUE cBigDecimal, cDate, cDateTime;
55
- static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
56
- extern VALUE mMysql2, cMysql2Client, cMysql2Error;
57
- static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
58
- static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
59
- sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream, sym_name;
60
- static ID intern_merge;
34
+ #define GET_RESULT(self) \
35
+ mysql2_result_wrapper *wrapper; \
36
+ Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
37
+
38
+ typedef struct {
39
+ int symbolizeKeys;
40
+ int asArray;
41
+ int castBool;
42
+ int cacheRows;
43
+ int cast;
44
+ int streaming;
45
+ ID db_timezone;
46
+ ID app_timezone;
47
+ int block_given; /* boolean */
48
+ } result_each_args;
61
49
 
50
+ extern VALUE mMysql2, cMysql2Client, cMysql2Error;
51
+ static VALUE cMysql2Result, cDateTime, cDate;
52
+ static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
53
+ static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset,
54
+ intern_civil, intern_new_offset, intern_merge, intern_BigDecimal,
55
+ intern_query_options;
56
+ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone,
57
+ sym_application_timezone, sym_local, sym_utc, sym_cast_booleans,
58
+ sym_cache_rows, sym_cast, sym_stream, sym_name;
59
+
60
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
62
61
  static void rb_mysql_result_mark(void * wrapper) {
63
62
  mysql2_result_wrapper * w = wrapper;
64
63
  if (w) {
@@ -66,13 +65,50 @@ static void rb_mysql_result_mark(void * wrapper) {
66
65
  rb_gc_mark(w->rows);
67
66
  rb_gc_mark(w->encoding);
68
67
  rb_gc_mark(w->client);
68
+ rb_gc_mark(w->statement);
69
69
  }
70
70
  }
71
71
 
72
72
  /* this may be called manually or during GC */
73
73
  static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
74
- if (wrapper && wrapper->resultFreed != 1) {
74
+ if (!wrapper) return;
75
+
76
+ if (wrapper->resultFreed != 1) {
77
+ if (wrapper->stmt_wrapper) {
78
+ if (!wrapper->stmt_wrapper->closed) {
79
+ mysql_stmt_free_result(wrapper->stmt_wrapper->stmt);
80
+
81
+ /* MySQL BUG? If the statement handle was previously used, and so
82
+ * mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
83
+ * MySQL still thinks the result set buffer is available and will prefetch the
84
+ * first result in mysql_stmt_execute. This will corrupt or crash the program.
85
+ * By setting bind_result_done back to 0, we make MySQL think that a result set
86
+ * has never been bound to this statement handle before to prevent the prefetch.
87
+ */
88
+ wrapper->stmt_wrapper->stmt->bind_result_done = 0;
89
+ }
90
+
91
+ if (wrapper->statement != Qnil) {
92
+ decr_mysql2_stmt(wrapper->stmt_wrapper);
93
+ }
94
+
95
+ if (wrapper->result_buffers) {
96
+ unsigned int i;
97
+ for (i = 0; i < wrapper->numberOfFields; i++) {
98
+ if (wrapper->result_buffers[i].buffer) {
99
+ xfree(wrapper->result_buffers[i].buffer);
100
+ }
101
+ }
102
+ xfree(wrapper->result_buffers);
103
+ xfree(wrapper->is_null);
104
+ xfree(wrapper->error);
105
+ xfree(wrapper->length);
106
+ }
107
+ /* Clue that the next statement execute will need to allocate a new result buffer. */
108
+ wrapper->result_buffers = NULL;
109
+ }
75
110
  /* FIXME: this may call flush_use_result, which can hit the socket */
111
+ /* For prepared statements, wrapper->result is the result metadata */
76
112
  mysql_free_result(wrapper->result);
77
113
  wrapper->resultFreed = 1;
78
114
  }
@@ -80,7 +116,7 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
80
116
 
81
117
  /* this is called during GC */
82
118
  static void rb_mysql_result_free(void *ptr) {
83
- mysql2_result_wrapper * wrapper = ptr;
119
+ mysql2_result_wrapper *wrapper = ptr;
84
120
  rb_mysql_result_free_result(wrapper);
85
121
 
86
122
  // If the GC gets to client first it will be nil
@@ -91,6 +127,12 @@ static void rb_mysql_result_free(void *ptr) {
91
127
  xfree(wrapper);
92
128
  }
93
129
 
130
+ static VALUE rb_mysql_result_free_(VALUE self) {
131
+ GET_RESULT(self);
132
+ rb_mysql_result_free_result(wrapper);
133
+ return Qnil;
134
+ }
135
+
94
136
  /*
95
137
  * for small results, this won't hit the network, but there's no
96
138
  * reliable way for us to tell this so we'll always release the GVL
@@ -102,10 +144,16 @@ static void *nogvl_fetch_row(void *ptr) {
102
144
  return mysql_fetch_row(result);
103
145
  }
104
146
 
105
- static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int symbolize_keys) {
106
- mysql2_result_wrapper * wrapper;
147
+ static void *nogvl_stmt_fetch(void *ptr) {
148
+ MYSQL_STMT *stmt = ptr;
149
+ uintptr_t r = mysql_stmt_fetch(stmt);
150
+
151
+ return (void *)r;
152
+ }
153
+
154
+ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) {
107
155
  VALUE rb_field;
108
- GetMysql2Result(self, wrapper);
156
+ GET_RESULT(self);
109
157
 
110
158
  if (wrapper->fields == Qnil) {
111
159
  wrapper->numberOfFields = mysql_num_fields(wrapper->result);
@@ -115,28 +163,25 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
115
163
  rb_field = rb_ary_entry(wrapper->fields, idx);
116
164
  if (rb_field == Qnil) {
117
165
  MYSQL_FIELD *field = NULL;
118
- #ifdef HAVE_RUBY_ENCODING_H
119
166
  rb_encoding *default_internal_enc = rb_default_internal_encoding();
120
167
  rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
121
- #endif
122
168
 
123
169
  field = mysql_fetch_field_direct(wrapper->result, idx);
124
170
  if (symbolize_keys) {
125
- #ifdef HAVE_RB_INTERN3
126
171
  rb_field = rb_intern3(field->name, field->name_length, rb_utf8_encoding());
127
172
  rb_field = ID2SYM(rb_field);
128
- #else
129
- VALUE colStr;
130
- colStr = rb_str_new(field->name, field->name_length);
131
- rb_field = ID2SYM(rb_to_id(colStr));
132
- #endif
133
173
  } else {
134
- rb_field = rb_str_new(field->name, field->name_length);
135
- #ifdef HAVE_RUBY_ENCODING_H
136
- rb_enc_associate(rb_field, conn_enc);
137
- if (default_internal_enc) {
174
+ #ifdef HAVE_RB_ENC_INTERNED_STR
175
+ rb_field = rb_enc_interned_str(field->name, field->name_length, conn_enc);
176
+ if (default_internal_enc && default_internal_enc != conn_enc) {
177
+ rb_field = rb_str_to_interned_str(rb_str_export_to_enc(rb_field, default_internal_enc));
178
+ }
179
+ #else
180
+ rb_field = rb_enc_str_new(field->name, field->name_length, conn_enc);
181
+ if (default_internal_enc && default_internal_enc != conn_enc) {
138
182
  rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
139
183
  }
184
+ rb_obj_freeze(rb_field);
140
185
  #endif
141
186
  }
142
187
  rb_ary_store(wrapper->fields, idx, rb_field);
@@ -145,10 +190,171 @@ static VALUE rb_mysql_result_fetch_field(VALUE self, unsigned int idx, short int
145
190
  return rb_field;
146
191
  }
147
192
 
148
- #ifdef HAVE_RUBY_ENCODING_H
193
+ static VALUE rb_mysql_result_fetch_field_type(VALUE self, unsigned int idx) {
194
+ VALUE rb_field_type;
195
+ GET_RESULT(self);
196
+
197
+ if (wrapper->fieldTypes == Qnil) {
198
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
199
+ wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
200
+ }
201
+
202
+ rb_field_type = rb_ary_entry(wrapper->fieldTypes, idx);
203
+ if (rb_field_type == Qnil) {
204
+ MYSQL_FIELD *field = NULL;
205
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
206
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
207
+ int precision;
208
+
209
+ field = mysql_fetch_field_direct(wrapper->result, idx);
210
+
211
+ switch(field->type) {
212
+ case MYSQL_TYPE_NULL: // NULL
213
+ rb_field_type = rb_str_new_cstr("null");
214
+ break;
215
+ case MYSQL_TYPE_TINY: // signed char
216
+ rb_field_type = rb_sprintf("tinyint(%ld)", field->length);
217
+ break;
218
+ case MYSQL_TYPE_SHORT: // short int
219
+ rb_field_type = rb_sprintf("smallint(%ld)", field->length);
220
+ break;
221
+ case MYSQL_TYPE_YEAR: // short int
222
+ rb_field_type = rb_sprintf("year(%ld)", field->length);
223
+ break;
224
+ case MYSQL_TYPE_INT24: // int
225
+ rb_field_type = rb_sprintf("mediumint(%ld)", field->length);
226
+ break;
227
+ case MYSQL_TYPE_LONG: // int
228
+ rb_field_type = rb_sprintf("int(%ld)", field->length);
229
+ break;
230
+ case MYSQL_TYPE_LONGLONG: // long long int
231
+ rb_field_type = rb_sprintf("bigint(%ld)", field->length);
232
+ break;
233
+ case MYSQL_TYPE_FLOAT: // float
234
+ rb_field_type = rb_sprintf("float(%ld,%d)", field->length, field->decimals);
235
+ break;
236
+ case MYSQL_TYPE_DOUBLE: // double
237
+ rb_field_type = rb_sprintf("double(%ld,%d)", field->length, field->decimals);
238
+ break;
239
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
240
+ rb_field_type = rb_str_new_cstr("time");
241
+ break;
242
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
243
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
244
+ rb_field_type = rb_str_new_cstr("date");
245
+ break;
246
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
247
+ rb_field_type = rb_str_new_cstr("datetime");
248
+ break;
249
+ case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME
250
+ rb_field_type = rb_str_new_cstr("timestamp");
251
+ break;
252
+ case MYSQL_TYPE_DECIMAL: // char[]
253
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
254
+ /*
255
+ Handle precision similar to this line from mysql's code:
256
+ https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/field.cc#L2246
257
+ */
258
+ precision = field->length - (field->decimals > 0 ? 2 : 1);
259
+ rb_field_type = rb_sprintf("decimal(%d,%d)", precision, field->decimals);
260
+ break;
261
+ case MYSQL_TYPE_STRING: // char[]
262
+ if (field->flags & ENUM_FLAG) {
263
+ rb_field_type = rb_str_new_cstr("enum");
264
+ } else if (field->flags & SET_FLAG) {
265
+ rb_field_type = rb_str_new_cstr("set");
266
+ } else {
267
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
268
+ rb_field_type = rb_sprintf("binary(%ld)", field->length);
269
+ } else {
270
+ rb_field_type = rb_sprintf("char(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
271
+ }
272
+ }
273
+ break;
274
+ case MYSQL_TYPE_VAR_STRING: // char[]
275
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
276
+ rb_field_type = rb_sprintf("varbinary(%ld)", field->length);
277
+ } else {
278
+ rb_field_type = rb_sprintf("varchar(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
279
+ }
280
+ break;
281
+ case MYSQL_TYPE_VARCHAR: // char[]
282
+ rb_field_type = rb_sprintf("varchar(%ld)", field->length / MYSQL2_MAX_BYTES_PER_CHAR);
283
+ break;
284
+ case MYSQL_TYPE_TINY_BLOB: // char[]
285
+ rb_field_type = rb_str_new_cstr("tinyblob");
286
+ break;
287
+ case MYSQL_TYPE_BLOB: // char[]
288
+ if (field->charsetnr == MYSQL2_BINARY_CHARSET) {
289
+ switch(field->length) {
290
+ case 255:
291
+ rb_field_type = rb_str_new_cstr("tinyblob");
292
+ break;
293
+ case 65535:
294
+ rb_field_type = rb_str_new_cstr("blob");
295
+ break;
296
+ case 16777215:
297
+ rb_field_type = rb_str_new_cstr("mediumblob");
298
+ break;
299
+ case 4294967295:
300
+ rb_field_type = rb_str_new_cstr("longblob");
301
+ default:
302
+ break;
303
+ }
304
+ } else {
305
+ if (field->length == (255 * MYSQL2_MAX_BYTES_PER_CHAR)) {
306
+ rb_field_type = rb_str_new_cstr("tinytext");
307
+ } else if (field->length == (65535 * MYSQL2_MAX_BYTES_PER_CHAR)) {
308
+ rb_field_type = rb_str_new_cstr("text");
309
+ } else if (field->length == (16777215 * MYSQL2_MAX_BYTES_PER_CHAR)) {
310
+ rb_field_type = rb_str_new_cstr("mediumtext");
311
+ } else if (field->length == 4294967295) {
312
+ rb_field_type = rb_str_new_cstr("longtext");
313
+ } else {
314
+ rb_field_type = rb_sprintf("text(%ld)", field->length);
315
+ }
316
+ }
317
+ break;
318
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
319
+ rb_field_type = rb_str_new_cstr("mediumblob");
320
+ break;
321
+ case MYSQL_TYPE_LONG_BLOB: // char[]
322
+ rb_field_type = rb_str_new_cstr("longblob");
323
+ break;
324
+ case MYSQL_TYPE_BIT: // char[]
325
+ rb_field_type = rb_sprintf("bit(%ld)", field->length);
326
+ break;
327
+ case MYSQL_TYPE_SET: // char[]
328
+ rb_field_type = rb_str_new_cstr("set");
329
+ break;
330
+ case MYSQL_TYPE_ENUM: // char[]
331
+ rb_field_type = rb_str_new_cstr("enum");
332
+ break;
333
+ case MYSQL_TYPE_GEOMETRY: // char[]
334
+ rb_field_type = rb_str_new_cstr("geometry");
335
+ break;
336
+ case MYSQL_TYPE_JSON: // json
337
+ rb_field_type = rb_str_new_cstr("json");
338
+ break;
339
+ default:
340
+ rb_field_type = rb_str_new_cstr("unknown");
341
+ break;
342
+ }
343
+
344
+ rb_enc_associate(rb_field_type, conn_enc);
345
+ if (default_internal_enc) {
346
+ rb_field_type = rb_str_export_to_enc(rb_field_type, default_internal_enc);
347
+ }
348
+
349
+ rb_ary_store(wrapper->fieldTypes, idx, rb_field_type);
350
+ }
351
+
352
+ return rb_field_type;
353
+ }
354
+
149
355
  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 */
151
- if (field.flags & BINARY_FLAG && field.charsetnr == 63) {
356
+ /* if binary flag is set, respect its wishes */
357
+ if (field.flags & BINARY_FLAG && field.charsetnr == MYSQL2_BINARY_CHARSET) {
152
358
  rb_enc_associate(val, binaryEncoding);
153
359
  } else if (!field.charsetnr) {
154
360
  /* MySQL 4.x may not provide an encoding, binary will get the bytes through */
@@ -158,7 +364,8 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
158
364
  const char *enc_name;
159
365
  int enc_index;
160
366
 
161
- enc_name = mysql2_mysql_enc_to_rb[field.charsetnr-1];
367
+ enc_name = (field.charsetnr-1 < MYSQL2_CHARSETNR_SIZE) ? mysql2_mysql_enc_to_rb[field.charsetnr-1] : NULL;
368
+
162
369
  if (enc_name != NULL) {
163
370
  /* use the field encoding we were able to match */
164
371
  enc_index = rb_enc_find_index(enc_name);
@@ -174,7 +381,6 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
174
381
  }
175
382
  return val;
176
383
  }
177
- #endif
178
384
 
179
385
  /* Interpret microseconds digits left-aligned in fixed-width field.
180
386
  * e.g. 10.123 seconds means 10 seconds and 123000 microseconds,
@@ -182,7 +388,7 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
182
388
  */
183
389
  static unsigned int msec_char_to_uint(char *msec_char, size_t len)
184
390
  {
185
- int i;
391
+ size_t i;
186
392
  for (i = 0; i < (len - 1); i++) {
187
393
  if (msec_char[i] == '\0') {
188
394
  msec_char[i] = '0';
@@ -191,23 +397,281 @@ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
191
397
  return (unsigned int)strtoul(msec_char, NULL, 10);
192
398
  }
193
399
 
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) {
400
+ static void rb_mysql_result_alloc_result_buffers(VALUE self, MYSQL_FIELD *fields) {
401
+ unsigned int i;
402
+ GET_RESULT(self);
403
+
404
+ if (wrapper->result_buffers != NULL) return;
405
+
406
+ wrapper->result_buffers = xcalloc(wrapper->numberOfFields, sizeof(MYSQL_BIND));
407
+ wrapper->is_null = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
408
+ wrapper->error = xcalloc(wrapper->numberOfFields, sizeof(my_bool));
409
+ wrapper->length = xcalloc(wrapper->numberOfFields, sizeof(unsigned long));
410
+
411
+ for (i = 0; i < wrapper->numberOfFields; i++) {
412
+ wrapper->result_buffers[i].buffer_type = fields[i].type;
413
+
414
+ // mysql type | C type
415
+ switch(fields[i].type) {
416
+ case MYSQL_TYPE_NULL: // NULL
417
+ break;
418
+ case MYSQL_TYPE_TINY: // signed char
419
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(signed char));
420
+ wrapper->result_buffers[i].buffer_length = sizeof(signed char);
421
+ break;
422
+ case MYSQL_TYPE_SHORT: // short int
423
+ case MYSQL_TYPE_YEAR: // short int
424
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(short int));
425
+ wrapper->result_buffers[i].buffer_length = sizeof(short int);
426
+ break;
427
+ case MYSQL_TYPE_INT24: // int
428
+ case MYSQL_TYPE_LONG: // int
429
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(int));
430
+ wrapper->result_buffers[i].buffer_length = sizeof(int);
431
+ break;
432
+ case MYSQL_TYPE_LONGLONG: // long long int
433
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(long long int));
434
+ wrapper->result_buffers[i].buffer_length = sizeof(long long int);
435
+ break;
436
+ case MYSQL_TYPE_FLOAT: // float
437
+ case MYSQL_TYPE_DOUBLE: // double
438
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(double));
439
+ wrapper->result_buffers[i].buffer_length = sizeof(double);
440
+ break;
441
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
442
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
443
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
444
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
445
+ case MYSQL_TYPE_TIMESTAMP: // MYSQL_TIME
446
+ wrapper->result_buffers[i].buffer = xcalloc(1, sizeof(MYSQL_TIME));
447
+ wrapper->result_buffers[i].buffer_length = sizeof(MYSQL_TIME);
448
+ break;
449
+ case MYSQL_TYPE_DECIMAL: // char[]
450
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
451
+ case MYSQL_TYPE_STRING: // char[]
452
+ case MYSQL_TYPE_VAR_STRING: // char[]
453
+ case MYSQL_TYPE_VARCHAR: // char[]
454
+ case MYSQL_TYPE_TINY_BLOB: // char[]
455
+ case MYSQL_TYPE_BLOB: // char[]
456
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
457
+ case MYSQL_TYPE_LONG_BLOB: // char[]
458
+ case MYSQL_TYPE_BIT: // char[]
459
+ case MYSQL_TYPE_SET: // char[]
460
+ case MYSQL_TYPE_ENUM: // char[]
461
+ case MYSQL_TYPE_GEOMETRY: // char[]
462
+ default:
463
+ wrapper->result_buffers[i].buffer = xmalloc(fields[i].max_length);
464
+ wrapper->result_buffers[i].buffer_length = fields[i].max_length;
465
+ break;
466
+ }
467
+
468
+ wrapper->result_buffers[i].is_null = &wrapper->is_null[i];
469
+ wrapper->result_buffers[i].length = &wrapper->length[i];
470
+ wrapper->result_buffers[i].error = &wrapper->error[i];
471
+ wrapper->result_buffers[i].is_unsigned = ((fields[i].flags & UNSIGNED_FLAG) != 0);
472
+ }
473
+ }
474
+
475
+ static VALUE rb_mysql_result_fetch_row_stmt(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
476
+ {
477
+ VALUE rowVal;
478
+ unsigned int i = 0;
479
+
480
+ rb_encoding *default_internal_enc;
481
+ rb_encoding *conn_enc;
482
+ GET_RESULT(self);
483
+
484
+ default_internal_enc = rb_default_internal_encoding();
485
+ conn_enc = rb_to_encoding(wrapper->encoding);
486
+
487
+ if (wrapper->fields == Qnil) {
488
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
489
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
490
+ }
491
+ if (args->asArray) {
492
+ rowVal = rb_ary_new2(wrapper->numberOfFields);
493
+ } else {
494
+ rowVal = rb_hash_new();
495
+ }
496
+
497
+ if (wrapper->result_buffers == NULL) {
498
+ rb_mysql_result_alloc_result_buffers(self, fields);
499
+ }
500
+
501
+ if (mysql_stmt_bind_result(wrapper->stmt_wrapper->stmt, wrapper->result_buffers)) {
502
+ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper);
503
+ }
504
+
505
+ {
506
+ switch((uintptr_t)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper->stmt_wrapper->stmt, RUBY_UBF_IO, 0)) {
507
+ case 0:
508
+ /* success */
509
+ break;
510
+
511
+ case 1:
512
+ /* error */
513
+ rb_raise_mysql2_stmt_error(wrapper->stmt_wrapper);
514
+
515
+ case MYSQL_NO_DATA:
516
+ /* no more row */
517
+ return Qnil;
518
+
519
+ case MYSQL_DATA_TRUNCATED:
520
+ rb_raise(cMysql2Error, "IMPLBUG: caught MYSQL_DATA_TRUNCATED. should not come here as buffer_length is set to fields[i].max_length.");
521
+ }
522
+ }
523
+
524
+ for (i = 0; i < wrapper->numberOfFields; i++) {
525
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
526
+ VALUE val = Qnil;
527
+ MYSQL_TIME *ts;
528
+
529
+ if (wrapper->is_null[i]) {
530
+ val = Qnil;
531
+ } else {
532
+ const MYSQL_BIND* const result_buffer = &wrapper->result_buffers[i];
533
+
534
+ switch(result_buffer->buffer_type) {
535
+ case MYSQL_TYPE_TINY: // signed char
536
+ if (args->castBool && fields[i].length == 1) {
537
+ val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse;
538
+ break;
539
+ }
540
+ if (result_buffer->is_unsigned) {
541
+ val = UINT2NUM(*((unsigned char*)result_buffer->buffer));
542
+ } else {
543
+ val = INT2NUM(*((signed char*)result_buffer->buffer));
544
+ }
545
+ break;
546
+ case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
547
+ if (args->castBool && fields[i].length == 1) {
548
+ val = (*((unsigned char*)result_buffer->buffer) != 0) ? Qtrue : Qfalse;
549
+ }else{
550
+ val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
551
+ }
552
+ break;
553
+ case MYSQL_TYPE_SHORT: // short int
554
+ case MYSQL_TYPE_YEAR: // short int
555
+ if (result_buffer->is_unsigned) {
556
+ val = UINT2NUM(*((unsigned short int*)result_buffer->buffer));
557
+ } else {
558
+ val = INT2NUM(*((short int*)result_buffer->buffer));
559
+ }
560
+ break;
561
+ case MYSQL_TYPE_INT24: // int
562
+ case MYSQL_TYPE_LONG: // int
563
+ if (result_buffer->is_unsigned) {
564
+ val = UINT2NUM(*((unsigned int*)result_buffer->buffer));
565
+ } else {
566
+ val = INT2NUM(*((int*)result_buffer->buffer));
567
+ }
568
+ break;
569
+ case MYSQL_TYPE_LONGLONG: // long long int
570
+ if (result_buffer->is_unsigned) {
571
+ val = ULL2NUM(*((unsigned long long int*)result_buffer->buffer));
572
+ } else {
573
+ val = LL2NUM(*((long long int*)result_buffer->buffer));
574
+ }
575
+ break;
576
+ case MYSQL_TYPE_FLOAT: // float
577
+ val = rb_float_new((double)(*((float*)result_buffer->buffer)));
578
+ break;
579
+ case MYSQL_TYPE_DOUBLE: // double
580
+ val = rb_float_new((double)(*((double*)result_buffer->buffer)));
581
+ break;
582
+ case MYSQL_TYPE_DATE: // MYSQL_TIME
583
+ case MYSQL_TYPE_NEWDATE: // MYSQL_TIME
584
+ ts = (MYSQL_TIME*)result_buffer->buffer;
585
+ val = rb_funcall(cDate, intern_new, 3, INT2NUM(ts->year), INT2NUM(ts->month), INT2NUM(ts->day));
586
+ break;
587
+ case MYSQL_TYPE_TIME: // MYSQL_TIME
588
+ ts = (MYSQL_TIME*)result_buffer->buffer;
589
+ 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));
590
+ if (!NIL_P(args->app_timezone)) {
591
+ if (args->app_timezone == intern_local) {
592
+ val = rb_funcall(val, intern_localtime, 0);
593
+ } else { // utc
594
+ val = rb_funcall(val, intern_utc, 0);
595
+ }
596
+ }
597
+ break;
598
+ case MYSQL_TYPE_DATETIME: // MYSQL_TIME
599
+ case MYSQL_TYPE_TIMESTAMP: { // MYSQL_TIME
600
+ uint64_t seconds;
601
+
602
+ ts = (MYSQL_TIME*)result_buffer->buffer;
603
+ seconds = (ts->year*31557600ULL) + (ts->month*2592000ULL) + (ts->day*86400ULL) + (ts->hour*3600ULL) + (ts->minute*60ULL) + ts->second;
604
+
605
+ if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { // use DateTime instead
606
+ VALUE offset = INT2NUM(0);
607
+ if (args->db_timezone == intern_local) {
608
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
609
+ }
610
+ 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);
611
+ if (!NIL_P(args->app_timezone)) {
612
+ if (args->app_timezone == intern_local) {
613
+ offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
614
+ val = rb_funcall(val, intern_new_offset, 1, offset);
615
+ } else { // utc
616
+ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
617
+ }
618
+ }
619
+ } else {
620
+ 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));
621
+ if (!NIL_P(args->app_timezone)) {
622
+ if (args->app_timezone == intern_local) {
623
+ val = rb_funcall(val, intern_localtime, 0);
624
+ } else { // utc
625
+ val = rb_funcall(val, intern_utc, 0);
626
+ }
627
+ }
628
+ }
629
+ break;
630
+ }
631
+ case MYSQL_TYPE_DECIMAL: // char[]
632
+ case MYSQL_TYPE_NEWDECIMAL: // char[]
633
+ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(result_buffer->buffer, *(result_buffer->length)));
634
+ break;
635
+ case MYSQL_TYPE_STRING: // char[]
636
+ case MYSQL_TYPE_VAR_STRING: // char[]
637
+ case MYSQL_TYPE_VARCHAR: // char[]
638
+ case MYSQL_TYPE_TINY_BLOB: // char[]
639
+ case MYSQL_TYPE_BLOB: // char[]
640
+ case MYSQL_TYPE_MEDIUM_BLOB: // char[]
641
+ case MYSQL_TYPE_LONG_BLOB: // char[]
642
+ case MYSQL_TYPE_SET: // char[]
643
+ case MYSQL_TYPE_ENUM: // char[]
644
+ case MYSQL_TYPE_GEOMETRY: // char[]
645
+ default:
646
+ val = rb_str_new(result_buffer->buffer, *(result_buffer->length));
647
+ val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
648
+ break;
649
+ }
650
+ }
651
+
652
+ if (args->asArray) {
653
+ rb_ary_push(rowVal, val);
654
+ } else {
655
+ rb_hash_aset(rowVal, field, val);
656
+ }
657
+ }
658
+
659
+ return rowVal;
660
+ }
661
+
662
+ static VALUE rb_mysql_result_fetch_row(VALUE self, MYSQL_FIELD * fields, const result_each_args *args)
663
+ {
195
664
  VALUE rowVal;
196
- mysql2_result_wrapper * wrapper;
197
665
  MYSQL_ROW row;
198
666
  unsigned int i = 0;
199
667
  unsigned long * fieldLengths;
200
668
  void * ptr;
201
- #ifdef HAVE_RUBY_ENCODING_H
202
669
  rb_encoding *default_internal_enc;
203
670
  rb_encoding *conn_enc;
204
- #endif
205
- GetMysql2Result(self, wrapper);
671
+ GET_RESULT(self);
206
672
 
207
- #ifdef HAVE_RUBY_ENCODING_H
208
673
  default_internal_enc = rb_default_internal_encoding();
209
674
  conn_enc = rb_to_encoding(wrapper->encoding);
210
- #endif
211
675
 
212
676
  ptr = wrapper->result;
213
677
  row = (MYSQL_ROW)rb_thread_call_without_gvl(nogvl_fetch_row, ptr, RUBY_UBF_IO, 0);
@@ -215,31 +679,29 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
215
679
  return Qnil;
216
680
  }
217
681
 
218
- if (asArray) {
682
+ if (wrapper->fields == Qnil) {
683
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
684
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
685
+ }
686
+ if (args->asArray) {
219
687
  rowVal = rb_ary_new2(wrapper->numberOfFields);
220
688
  } else {
221
689
  rowVal = rb_hash_new();
222
690
  }
223
691
  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
692
 
229
693
  for (i = 0; i < wrapper->numberOfFields; i++) {
230
- VALUE field = rb_mysql_result_fetch_field(self, i, symbolizeKeys);
694
+ VALUE field = rb_mysql_result_fetch_field(self, i, args->symbolizeKeys);
231
695
  if (row[i]) {
232
696
  VALUE val = Qnil;
233
697
  enum enum_field_types type = fields[i].type;
234
698
 
235
- if(!cast) {
699
+ if (!args->cast) {
236
700
  if (type == MYSQL_TYPE_NULL) {
237
701
  val = Qnil;
238
702
  } else {
239
703
  val = rb_str_new(row[i], fieldLengths[i]);
240
- #ifdef HAVE_RUBY_ENCODING_H
241
704
  val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
242
- #endif
243
705
  }
244
706
  } else {
245
707
  switch(type) {
@@ -247,14 +709,14 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
247
709
  val = Qnil;
248
710
  break;
249
711
  case MYSQL_TYPE_BIT: /* BIT field (MySQL 5.0.3 and up) */
250
- if (castBool && fields[i].length == 1) {
712
+ if (args->castBool && fields[i].length == 1) {
251
713
  val = *row[i] == 1 ? Qtrue : Qfalse;
252
714
  }else{
253
715
  val = rb_str_new(row[i], fieldLengths[i]);
254
716
  }
255
717
  break;
256
718
  case MYSQL_TYPE_TINY: /* TINYINT field */
257
- if (castBool && fields[i].length == 1) {
719
+ if (args->castBool && fields[i].length == 1) {
258
720
  val = *row[i] != '0' ? Qtrue : Qfalse;
259
721
  break;
260
722
  }
@@ -270,9 +732,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
270
732
  if (fields[i].decimals == 0) {
271
733
  val = rb_cstr2inum(row[i], 10);
272
734
  } else if (strtod(row[i], NULL) == 0.000000){
273
- val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero);
735
+ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero);
274
736
  }else{
275
- val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
737
+ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i]));
276
738
  }
277
739
  break;
278
740
  case MYSQL_TYPE_FLOAT: /* FLOAT field */
@@ -297,9 +759,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
297
759
  break;
298
760
  }
299
761
  msec = msec_char_to_uint(msec_char, sizeof(msec_char));
300
- val = rb_funcall(rb_cTime, db_timezone, 6, 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) {
762
+ 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));
763
+ if (!NIL_P(args->app_timezone)) {
764
+ if (args->app_timezone == intern_local) {
303
765
  val = rb_funcall(val, intern_localtime, 0);
304
766
  } else { /* utc */
305
767
  val = rb_funcall(val, intern_utc, 0);
@@ -330,12 +792,12 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
330
792
  } else {
331
793
  if (seconds < MYSQL2_MIN_TIME || seconds > MYSQL2_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
332
794
  VALUE offset = INT2NUM(0);
333
- if (db_timezone == intern_local) {
795
+ if (args->db_timezone == intern_local) {
334
796
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
335
797
  }
336
798
  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) {
799
+ if (!NIL_P(args->app_timezone)) {
800
+ if (args->app_timezone == intern_local) {
339
801
  offset = rb_funcall(cMysql2Client, intern_local_offset, 0);
340
802
  val = rb_funcall(val, intern_new_offset, 1, offset);
341
803
  } else { /* utc */
@@ -344,9 +806,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
344
806
  }
345
807
  } else {
346
808
  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) {
809
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
810
+ if (!NIL_P(args->app_timezone)) {
811
+ if (args->app_timezone == intern_local) {
350
812
  val = rb_funcall(val, intern_localtime, 0);
351
813
  } else { /* utc */
352
814
  val = rb_funcall(val, intern_utc, 0);
@@ -390,19 +852,17 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
390
852
  case MYSQL_TYPE_GEOMETRY: /* Spatial fielda */
391
853
  default:
392
854
  val = rb_str_new(row[i], fieldLengths[i]);
393
- #ifdef HAVE_RUBY_ENCODING_H
394
855
  val = mysql2_set_field_string_encoding(val, fields[i], default_internal_enc, conn_enc);
395
- #endif
396
856
  break;
397
857
  }
398
858
  }
399
- if (asArray) {
859
+ if (args->asArray) {
400
860
  rb_ary_push(rowVal, val);
401
861
  } else {
402
862
  rb_hash_aset(rowVal, field, val);
403
863
  }
404
864
  } else {
405
- if (asArray) {
865
+ if (args->asArray) {
406
866
  rb_ary_push(rowVal, Qnil);
407
867
  } else {
408
868
  rb_hash_aset(rowVal, field, Qnil);
@@ -413,14 +873,13 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
413
873
  }
414
874
 
415
875
  static VALUE rb_mysql_result_fetch_fields(VALUE self) {
416
- mysql2_result_wrapper * wrapper;
417
876
  unsigned int i = 0;
418
877
  short int symbolizeKeys = 0;
419
878
  VALUE defaults;
420
879
 
421
- GetMysql2Result(self, wrapper);
880
+ GET_RESULT(self);
422
881
 
423
- defaults = rb_iv_get(self, "@query_options");
882
+ defaults = rb_ivar_get(self, intern_query_options);
424
883
  Check_Type(defaults, T_HASH);
425
884
  if (rb_hash_aref(defaults, sym_symbolize_keys) == Qtrue) {
426
885
  symbolizeKeys = 1;
@@ -431,7 +890,7 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
431
890
  wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
432
891
  }
433
892
 
434
- if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
893
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
435
894
  for (i=0; i<wrapper->numberOfFields; i++) {
436
895
  rb_mysql_result_fetch_field(self, i, symbolizeKeys);
437
896
  }
@@ -440,108 +899,57 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
440
899
  return wrapper->fields;
441
900
  }
442
901
 
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;
447
- 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
- }
902
+ static VALUE rb_mysql_result_fetch_field_types(VALUE self) {
903
+ unsigned int i = 0;
469
904
 
470
- if (rb_hash_aref(opts, sym_cast_booleans) == Qtrue) {
471
- castBool = 1;
472
- }
905
+ GET_RESULT(self);
473
906
 
474
- if (rb_hash_aref(opts, sym_cache_rows) == Qfalse) {
475
- cacheRows = 0;
907
+ if (wrapper->fieldTypes == Qnil) {
908
+ wrapper->numberOfFields = mysql_num_fields(wrapper->result);
909
+ wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
476
910
  }
477
911
 
478
- if (rb_hash_aref(opts, sym_cast) == Qfalse) {
479
- cast = 0;
912
+ if ((my_ulonglong)RARRAY_LEN(wrapper->fieldTypes) != wrapper->numberOfFields) {
913
+ for (i=0; i<wrapper->numberOfFields; i++) {
914
+ rb_mysql_result_fetch_field_type(self, i);
915
+ }
480
916
  }
481
917
 
482
- if(rb_hash_aref(opts, sym_stream) == Qtrue) {
483
- streaming = 1;
484
- }
918
+ return wrapper->fieldTypes;
919
+ }
485
920
 
486
- if(streaming && cacheRows) {
487
- rb_warn("cacheRows is ignored if streaming is true");
488
- }
921
+ static VALUE rb_mysql_result_each_(VALUE self,
922
+ VALUE(*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args),
923
+ const result_each_args *args)
924
+ {
925
+ unsigned long i;
926
+ const char *errstr;
927
+ MYSQL_FIELD *fields = NULL;
489
928
 
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
- }
929
+ GET_RESULT(self);
501
930
 
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
- }
510
-
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;
931
+ if (wrapper->is_streaming) {
932
+ /* When streaming, we will only yield rows, not return them. */
933
+ if (wrapper->rows == Qnil) {
516
934
  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
935
  }
525
- }
526
936
 
527
- if (streaming) {
528
937
  if (!wrapper->streamingComplete) {
529
938
  VALUE row;
530
939
 
531
940
  fields = mysql_fetch_fields(wrapper->result);
532
941
 
533
942
  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++;
943
+ row = fetch_row_func(self, fields, args);
944
+ if (row != Qnil) {
945
+ wrapper->numberOfRows++;
946
+ if (args->block_given) {
947
+ rb_yield(row);
948
+ }
539
949
  }
540
950
  } while(row != Qnil);
541
951
 
542
952
  rb_mysql_result_free_result(wrapper);
543
-
544
- wrapper->numberOfRows = wrapper->lastRowProcessed;
545
953
  wrapper->streamingComplete = 1;
546
954
 
547
955
  // Check for errors, the connection might have gone out from under us
@@ -554,7 +962,7 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
554
962
  rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
555
963
  }
556
964
  } else {
557
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
965
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
558
966
  /* we've already read the entire dataset from the C result into our */
559
967
  /* internal array. Lets hand that over to the user since it's ready to go */
560
968
  for (i = 0; i < wrapper->numberOfRows; i++) {
@@ -567,11 +975,11 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
567
975
 
568
976
  for (i = 0; i < wrapper->numberOfRows; i++) {
569
977
  VALUE row;
570
- if (cacheRows && i < rowsProcessed) {
978
+ if (args->cacheRows && i < rowsProcessed) {
571
979
  row = rb_ary_entry(wrapper->rows, i);
572
980
  } else {
573
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
574
- if (cacheRows) {
981
+ row = fetch_row_func(self, fields, args);
982
+ if (args->cacheRows) {
575
983
  rb_ary_store(wrapper->rows, i, row);
576
984
  }
577
985
  wrapper->lastRowProcessed++;
@@ -579,43 +987,148 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
579
987
 
580
988
  if (row == Qnil) {
581
989
  /* we don't need the mysql C dataset around anymore, peace it */
582
- rb_mysql_result_free_result(wrapper);
990
+ if (args->cacheRows) {
991
+ rb_mysql_result_free_result(wrapper);
992
+ }
583
993
  return Qnil;
584
994
  }
585
995
 
586
- if (block != Qnil) {
996
+ if (args->block_given) {
587
997
  rb_yield(row);
588
998
  }
589
999
  }
590
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
1000
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows && args->cacheRows) {
591
1001
  /* we don't need the mysql C dataset around anymore, peace it */
592
1002
  rb_mysql_result_free_result(wrapper);
593
1003
  }
594
1004
  }
595
1005
  }
596
1006
 
1007
+ // FIXME return Enumerator instead?
1008
+ // return rb_ary_each(wrapper->rows);
597
1009
  return wrapper->rows;
598
1010
  }
599
1011
 
1012
+ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
1013
+ result_each_args args;
1014
+ VALUE defaults, opts, (*fetch_row_func)(VALUE, MYSQL_FIELD *fields, const result_each_args *args);
1015
+ ID db_timezone, app_timezone, dbTz, appTz;
1016
+ int symbolizeKeys, asArray, castBool, cacheRows, cast;
1017
+
1018
+ GET_RESULT(self);
1019
+
1020
+ if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) {
1021
+ rb_raise(cMysql2Error, "Statement handle already closed");
1022
+ }
1023
+
1024
+ defaults = rb_ivar_get(self, intern_query_options);
1025
+ Check_Type(defaults, T_HASH);
1026
+
1027
+ // A block can be passed to this method, but since we don't call the block directly from C,
1028
+ // we don't need to capture it into a variable here with the "&" scan arg.
1029
+ if (rb_scan_args(argc, argv, "01", &opts) == 1) {
1030
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
1031
+ } else {
1032
+ opts = defaults;
1033
+ }
1034
+
1035
+ symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
1036
+ asArray = rb_hash_aref(opts, sym_as) == sym_array;
1037
+ castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
1038
+ cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
1039
+ cast = RTEST(rb_hash_aref(opts, sym_cast));
1040
+
1041
+ if (wrapper->is_streaming && cacheRows) {
1042
+ rb_warn(":cache_rows is ignored if :stream is true");
1043
+ }
1044
+
1045
+ if (wrapper->stmt_wrapper && !cacheRows && !wrapper->is_streaming) {
1046
+ rb_warn(":cache_rows is forced for prepared statements (if not streaming)");
1047
+ cacheRows = 1;
1048
+ }
1049
+
1050
+ if (wrapper->stmt_wrapper && !cast) {
1051
+ rb_warn(":cast is forced for prepared statements");
1052
+ }
1053
+
1054
+ dbTz = rb_hash_aref(opts, sym_database_timezone);
1055
+ if (dbTz == sym_local) {
1056
+ db_timezone = intern_local;
1057
+ } else if (dbTz == sym_utc) {
1058
+ db_timezone = intern_utc;
1059
+ } else {
1060
+ if (!NIL_P(dbTz)) {
1061
+ rb_warn(":database_timezone option must be :utc or :local - defaulting to :local");
1062
+ }
1063
+ db_timezone = intern_local;
1064
+ }
1065
+
1066
+ appTz = rb_hash_aref(opts, sym_application_timezone);
1067
+ if (appTz == sym_local) {
1068
+ app_timezone = intern_local;
1069
+ } else if (appTz == sym_utc) {
1070
+ app_timezone = intern_utc;
1071
+ } else {
1072
+ app_timezone = Qnil;
1073
+ }
1074
+
1075
+ if (wrapper->rows == Qnil && !wrapper->is_streaming) {
1076
+ wrapper->numberOfRows = wrapper->stmt_wrapper ? mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt) : mysql_num_rows(wrapper->result);
1077
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
1078
+ } else if (wrapper->rows && !cacheRows) {
1079
+ if (wrapper->resultFreed) {
1080
+ rb_raise(cMysql2Error, "Result set has already been freed");
1081
+ }
1082
+ mysql_data_seek(wrapper->result, 0);
1083
+ wrapper->lastRowProcessed = 0;
1084
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
1085
+ }
1086
+
1087
+ // Backward compat
1088
+ args.symbolizeKeys = symbolizeKeys;
1089
+ args.asArray = asArray;
1090
+ args.castBool = castBool;
1091
+ args.cacheRows = cacheRows;
1092
+ args.cast = cast;
1093
+ args.db_timezone = db_timezone;
1094
+ args.app_timezone = app_timezone;
1095
+ args.block_given = rb_block_given_p();
1096
+
1097
+ if (wrapper->stmt_wrapper) {
1098
+ fetch_row_func = rb_mysql_result_fetch_row_stmt;
1099
+ } else {
1100
+ fetch_row_func = rb_mysql_result_fetch_row;
1101
+ }
1102
+
1103
+ return rb_mysql_result_each_(self, fetch_row_func, &args);
1104
+ }
1105
+
600
1106
  static VALUE rb_mysql_result_count(VALUE self) {
601
- mysql2_result_wrapper *wrapper;
1107
+ GET_RESULT(self);
1108
+
1109
+ if (wrapper->is_streaming) {
1110
+ /* This is an unsigned long per result.h */
1111
+ return ULONG2NUM(wrapper->numberOfRows);
1112
+ }
602
1113
 
603
- GetMysql2Result(self, wrapper);
604
- if(wrapper->resultFreed) {
605
- if (wrapper->streamingComplete){
606
- return LONG2NUM(wrapper->numberOfRows);
1114
+ if (wrapper->resultFreed) {
1115
+ /* Ruby arrays have platform signed long length */
1116
+ return LONG2NUM(RARRAY_LEN(wrapper->rows));
1117
+ } else {
1118
+ /* MySQL returns an unsigned 64-bit long here */
1119
+ if (wrapper->stmt_wrapper) {
1120
+ return ULL2NUM(mysql_stmt_num_rows(wrapper->stmt_wrapper->stmt));
607
1121
  } else {
608
- return LONG2NUM(RARRAY_LEN(wrapper->rows));
1122
+ return ULL2NUM(mysql_num_rows(wrapper->result));
609
1123
  }
610
- } else {
611
- return INT2FIX(mysql_num_rows(wrapper->result));
612
1124
  }
613
1125
  }
614
1126
 
615
1127
  /* Mysql2::Result */
616
- VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r) {
1128
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement) {
617
1129
  VALUE obj;
618
1130
  mysql2_result_wrapper * wrapper;
1131
+
619
1132
  obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
620
1133
  wrapper->numberOfFields = 0;
621
1134
  wrapper->numberOfRows = 0;
@@ -623,28 +1136,51 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
623
1136
  wrapper->resultFreed = 0;
624
1137
  wrapper->result = r;
625
1138
  wrapper->fields = Qnil;
1139
+ wrapper->fieldTypes = Qnil;
626
1140
  wrapper->rows = Qnil;
627
1141
  wrapper->encoding = encoding;
628
1142
  wrapper->streamingComplete = 0;
629
1143
  wrapper->client = client;
630
1144
  wrapper->client_wrapper = DATA_PTR(client);
631
1145
  wrapper->client_wrapper->refcount++;
1146
+ wrapper->result_buffers = NULL;
1147
+ wrapper->is_null = NULL;
1148
+ wrapper->error = NULL;
1149
+ wrapper->length = NULL;
1150
+
1151
+ /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
1152
+ wrapper->statement = statement;
1153
+ if (statement != Qnil) {
1154
+ wrapper->stmt_wrapper = DATA_PTR(statement);
1155
+ wrapper->stmt_wrapper->refcount++;
1156
+ } else {
1157
+ wrapper->stmt_wrapper = NULL;
1158
+ }
632
1159
 
633
1160
  rb_obj_call_init(obj, 0, NULL);
1161
+ rb_ivar_set(obj, intern_query_options, options);
634
1162
 
635
- rb_iv_set(obj, "@query_options", options);
1163
+ /* Options that cannot be changed in results.each(...) { |row| }
1164
+ * should be processed here. */
1165
+ wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0);
636
1166
 
637
1167
  return obj;
638
1168
  }
639
1169
 
640
1170
  void init_mysql2_result() {
641
- cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
642
1171
  cDate = rb_const_get(rb_cObject, rb_intern("Date"));
1172
+ rb_global_variable(&cDate);
643
1173
  cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
1174
+ rb_global_variable(&cDateTime);
644
1175
 
645
1176
  cMysql2Result = rb_define_class_under(mMysql2, "Result", rb_cObject);
1177
+ rb_undef_alloc_func(cMysql2Result);
1178
+ rb_global_variable(&cMysql2Result);
1179
+
646
1180
  rb_define_method(cMysql2Result, "each", rb_mysql_result_each, -1);
647
1181
  rb_define_method(cMysql2Result, "fields", rb_mysql_result_fetch_fields, 0);
1182
+ rb_define_method(cMysql2Result, "field_types", rb_mysql_result_fetch_field_types, 0);
1183
+ rb_define_method(cMysql2Result, "free", rb_mysql_result_free_, 0);
648
1184
  rb_define_method(cMysql2Result, "count", rb_mysql_result_count, 0);
649
1185
  rb_define_alias(cMysql2Result, "size", "count");
650
1186
 
@@ -656,6 +1192,8 @@ void init_mysql2_result() {
656
1192
  intern_local_offset = rb_intern("local_offset");
657
1193
  intern_civil = rb_intern("civil");
658
1194
  intern_new_offset = rb_intern("new_offset");
1195
+ intern_BigDecimal = rb_intern("BigDecimal");
1196
+ intern_query_options = rb_intern("@query_options");
659
1197
 
660
1198
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
661
1199
  sym_as = ID2SYM(rb_intern("as"));
@@ -678,7 +1216,5 @@ void init_mysql2_result() {
678
1216
  opt_time_month = INT2NUM(1);
679
1217
  opt_utc_offset = INT2NUM(0);
680
1218
 
681
- #ifdef HAVE_RUBY_ENCODING_H
682
1219
  binaryEncoding = rb_enc_find("binary");
683
- #endif
684
1220
  }