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