mysql2 0.2.24 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
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
  }