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