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