mysql2 0.4.2 → 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 +4 -4
- data/README.md +103 -59
- data/examples/eventmachine.rb +0 -2
- data/examples/threaded.rb +2 -4
- data/ext/mysql2/client.c +334 -94
- data/ext/mysql2/client.h +3 -51
- data/ext/mysql2/extconf.rb +45 -18
- data/ext/mysql2/mysql2_ext.c +2 -1
- data/ext/mysql2/mysql2_ext.h +8 -8
- data/ext/mysql2/mysql_enc_to_ruby.h +10 -0
- data/ext/mysql2/result.c +53 -94
- data/ext/mysql2/statement.c +191 -83
- data/ext/mysql2/statement.h +0 -2
- data/ext/mysql2/wait_for_single_fd.h +2 -1
- data/lib/mysql2/client.rb +50 -31
- data/lib/mysql2/em.rb +2 -4
- data/lib/mysql2/error.rb +49 -20
- data/lib/mysql2/result.rb +2 -0
- data/lib/mysql2/statement.rb +3 -9
- data/lib/mysql2/version.rb +1 -1
- data/lib/mysql2.rb +14 -15
- data/spec/configuration.yml.example +0 -6
- data/spec/em/em_spec.rb +6 -6
- data/spec/mysql2/client_spec.rb +372 -239
- data/spec/mysql2/error_spec.rb +4 -10
- data/spec/mysql2/result_spec.rb +132 -157
- data/spec/mysql2/statement_spec.rb +205 -177
- data/spec/spec_helper.rb +79 -61
- data/spec/ssl/gen_certs.sh +1 -1
- data/support/5072E1F5.asc +432 -0
- data/support/mysql_enc_to_ruby.rb +2 -2
- data/support/ruby_enc_to_mysql.rb +5 -5
- metadata +16 -14
data/ext/mysql2/statement.c
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
#include <mysql2_ext.h>
|
2
2
|
|
3
|
-
VALUE
|
4
|
-
|
5
|
-
static VALUE sym_stream, intern_new_with_args, intern_each;
|
6
|
-
static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
|
3
|
+
extern VALUE mMysql2, cMysql2Error;
|
4
|
+
static VALUE cMysql2Statement, cBigDecimal, cDateTime, cDate;
|
5
|
+
static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang;
|
6
|
+
static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
|
7
7
|
|
8
8
|
#define GET_STATEMENT(self) \
|
9
9
|
mysql_stmt_wrapper *stmt_wrapper; \
|
@@ -11,7 +11,6 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter
|
|
11
11
|
if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
|
12
12
|
if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
|
13
13
|
|
14
|
-
|
15
14
|
static void rb_mysql_stmt_mark(void * ptr) {
|
16
15
|
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
17
16
|
if (!stmt_wrapper) return;
|
@@ -19,7 +18,7 @@ static void rb_mysql_stmt_mark(void * ptr) {
|
|
19
18
|
rb_gc_mark(stmt_wrapper->client);
|
20
19
|
}
|
21
20
|
|
22
|
-
static void *nogvl_stmt_close(void *
|
21
|
+
static void *nogvl_stmt_close(void *ptr) {
|
23
22
|
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
24
23
|
if (stmt_wrapper->stmt) {
|
25
24
|
mysql_stmt_close(stmt_wrapper->stmt);
|
@@ -28,7 +27,7 @@ static void *nogvl_stmt_close(void * ptr) {
|
|
28
27
|
return NULL;
|
29
28
|
}
|
30
29
|
|
31
|
-
static void rb_mysql_stmt_free(void *
|
30
|
+
static void rb_mysql_stmt_free(void *ptr) {
|
32
31
|
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
33
32
|
decr_mysql2_stmt(stmt_wrapper);
|
34
33
|
}
|
@@ -42,14 +41,12 @@ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
|
|
42
41
|
}
|
43
42
|
}
|
44
43
|
|
45
|
-
|
46
44
|
void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
|
47
45
|
VALUE e;
|
48
46
|
GET_CLIENT(stmt_wrapper->client);
|
49
47
|
VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
|
50
48
|
VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
|
51
49
|
|
52
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
53
50
|
rb_encoding *conn_enc;
|
54
51
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
55
52
|
|
@@ -61,7 +58,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
|
|
61
58
|
rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
|
62
59
|
rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
|
63
60
|
}
|
64
|
-
#endif
|
65
61
|
|
66
62
|
e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
|
67
63
|
rb_error_msg,
|
@@ -71,7 +67,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
|
|
71
67
|
rb_exc_raise(e);
|
72
68
|
}
|
73
69
|
|
74
|
-
|
75
70
|
/*
|
76
71
|
* used to pass all arguments to mysql_stmt_prepare while inside
|
77
72
|
* nogvl_prepare_statement_args
|
@@ -96,9 +91,7 @@ static void *nogvl_prepare_statement(void *ptr) {
|
|
96
91
|
VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
97
92
|
mysql_stmt_wrapper *stmt_wrapper;
|
98
93
|
VALUE rb_stmt;
|
99
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
100
94
|
rb_encoding *conn_enc;
|
101
|
-
#endif
|
102
95
|
|
103
96
|
Check_Type(sql, T_STRING);
|
104
97
|
|
@@ -114,9 +107,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
|
114
107
|
{
|
115
108
|
GET_CLIENT(rb_client);
|
116
109
|
stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
|
117
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
118
110
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
119
|
-
#endif
|
120
111
|
}
|
121
112
|
if (stmt_wrapper->stmt == NULL) {
|
122
113
|
rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
|
@@ -134,11 +125,8 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
|
134
125
|
{
|
135
126
|
struct nogvl_prepare_statement_args args;
|
136
127
|
args.stmt = stmt_wrapper->stmt;
|
137
|
-
args.sql = sql;
|
138
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
139
128
|
// ensure the string is in the encoding the connection is expecting
|
140
|
-
args.sql = rb_str_export_to_enc(
|
141
|
-
#endif
|
129
|
+
args.sql = rb_str_export_to_enc(sql, conn_enc);
|
142
130
|
args.sql_ptr = RSTRING_PTR(sql);
|
143
131
|
args.sql_len = RSTRING_LEN(sql);
|
144
132
|
|
@@ -154,7 +142,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
|
154
142
|
*
|
155
143
|
* Returns the number of parameters the prepared statement expects.
|
156
144
|
*/
|
157
|
-
static VALUE
|
145
|
+
static VALUE rb_mysql_stmt_param_count(VALUE self) {
|
158
146
|
GET_STATEMENT(self);
|
159
147
|
|
160
148
|
return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
|
@@ -164,13 +152,13 @@ static VALUE param_count(VALUE self) {
|
|
164
152
|
*
|
165
153
|
* Returns the number of fields the prepared statement returns.
|
166
154
|
*/
|
167
|
-
static VALUE
|
155
|
+
static VALUE rb_mysql_stmt_field_count(VALUE self) {
|
168
156
|
GET_STATEMENT(self);
|
169
157
|
|
170
158
|
return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
|
171
159
|
}
|
172
160
|
|
173
|
-
static void *
|
161
|
+
static void *nogvl_stmt_execute(void *ptr) {
|
174
162
|
MYSQL_STMT *stmt = ptr;
|
175
163
|
|
176
164
|
if (mysql_stmt_execute(stmt)) {
|
@@ -180,21 +168,23 @@ static void *nogvl_execute(void *ptr) {
|
|
180
168
|
}
|
181
169
|
}
|
182
170
|
|
183
|
-
static void *
|
184
|
-
|
171
|
+
static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
|
172
|
+
unsigned long length;
|
185
173
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
174
|
+
bind_buffer->buffer = RSTRING_PTR(string);
|
175
|
+
|
176
|
+
length = RSTRING_LEN(string);
|
177
|
+
bind_buffer->buffer_length = length;
|
178
|
+
*length_buffer = length;
|
179
|
+
|
180
|
+
bind_buffer->length = length_buffer;
|
191
181
|
}
|
192
182
|
|
193
183
|
/* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
|
194
184
|
* the buffer is a Ruby string pointer and not our memory to manage.
|
195
185
|
*/
|
196
186
|
#define FREE_BINDS \
|
197
|
-
for (i = 0; i <
|
187
|
+
for (i = 0; i < bind_count; i++) { \
|
198
188
|
if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
|
199
189
|
xfree(bind_buffers[i].buffer); \
|
200
190
|
} \
|
@@ -204,48 +194,94 @@ static void *nogvl_stmt_store_result(void *ptr) {
|
|
204
194
|
xfree(length_buffers); \
|
205
195
|
}
|
206
196
|
|
197
|
+
/* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */
|
198
|
+
static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
|
199
|
+
{
|
200
|
+
unsigned LONG_LONG num;
|
201
|
+
size_t len;
|
202
|
+
// rb_absint_size was added in 2.1.0. See:
|
203
|
+
// https://github.com/ruby/ruby/commit/9fea875
|
204
|
+
#ifdef HAVE_RB_ABSINT_SIZE
|
205
|
+
int nlz_bits = 0;
|
206
|
+
len = rb_absint_size(bignum, &nlz_bits);
|
207
|
+
#else
|
208
|
+
len = RBIGNUM_LEN(bignum) * SIZEOF_BDIGITS;
|
209
|
+
#endif
|
210
|
+
if (len > sizeof(LONG_LONG)) goto overflow;
|
211
|
+
if (RBIGNUM_POSITIVE_P(bignum)) {
|
212
|
+
num = rb_big2ull(bignum);
|
213
|
+
if (num > LLONG_MAX)
|
214
|
+
goto overflow;
|
215
|
+
*ptr = num;
|
216
|
+
}
|
217
|
+
else {
|
218
|
+
if (len == 8 &&
|
219
|
+
#ifdef HAVE_RB_ABSINT_SIZE
|
220
|
+
nlz_bits == 0 &&
|
221
|
+
#endif
|
222
|
+
// rb_absint_singlebit_p was added in 2.1.0. See:
|
223
|
+
// https://github.com/ruby/ruby/commit/e5ff9d5
|
224
|
+
#if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
|
225
|
+
/* Optimized to avoid object allocation for Ruby 2.1+
|
226
|
+
* only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
|
227
|
+
*/
|
228
|
+
!rb_absint_singlebit_p(bignum)
|
229
|
+
#else
|
230
|
+
rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
|
231
|
+
#endif
|
232
|
+
) {
|
233
|
+
goto overflow;
|
234
|
+
}
|
235
|
+
*ptr = rb_big2ll(bignum);
|
236
|
+
}
|
237
|
+
return 0;
|
238
|
+
overflow:
|
239
|
+
return 1;
|
240
|
+
}
|
241
|
+
|
207
242
|
/* call-seq: stmt.execute
|
208
243
|
*
|
209
244
|
* Executes the current prepared statement, returns +result+.
|
210
245
|
*/
|
211
|
-
static VALUE
|
246
|
+
static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
|
212
247
|
MYSQL_BIND *bind_buffers = NULL;
|
213
248
|
unsigned long *length_buffers = NULL;
|
214
249
|
unsigned long bind_count;
|
215
|
-
long i;
|
250
|
+
unsigned long i;
|
216
251
|
MYSQL_STMT *stmt;
|
217
252
|
MYSQL_RES *metadata;
|
253
|
+
VALUE opts;
|
218
254
|
VALUE current;
|
219
255
|
VALUE resultObj;
|
220
|
-
VALUE *params_enc;
|
256
|
+
VALUE *params_enc = NULL;
|
221
257
|
int is_streaming;
|
222
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
223
258
|
rb_encoding *conn_enc;
|
224
|
-
#endif
|
225
259
|
|
226
260
|
GET_STATEMENT(self);
|
227
261
|
GET_CLIENT(stmt_wrapper->client);
|
228
262
|
|
229
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
230
263
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
231
|
-
#endif
|
232
|
-
|
233
|
-
/* Scratch space for string encoding exports, allocate on the stack. */
|
234
|
-
params_enc = alloca(sizeof(VALUE) * argc);
|
235
264
|
|
236
265
|
stmt = stmt_wrapper->stmt;
|
237
|
-
|
238
266
|
bind_count = mysql_stmt_param_count(stmt);
|
239
|
-
|
240
|
-
|
267
|
+
|
268
|
+
// Get count of ordinary arguments, and extract hash opts/keyword arguments
|
269
|
+
// Use a local scope to avoid leaking the temporary count variable
|
270
|
+
{
|
271
|
+
int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
|
272
|
+
if (c != (long)bind_count) {
|
273
|
+
rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
|
274
|
+
}
|
241
275
|
}
|
242
276
|
|
243
277
|
// setup any bind variables in the query
|
244
278
|
if (bind_count > 0) {
|
279
|
+
// Scratch space for string encoding exports, allocate on the stack
|
280
|
+
params_enc = alloca(sizeof(VALUE) * bind_count);
|
245
281
|
bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
|
246
282
|
length_buffers = xcalloc(bind_count, sizeof(unsigned long));
|
247
283
|
|
248
|
-
for (i = 0; i <
|
284
|
+
for (i = 0; i < bind_count; i++) {
|
249
285
|
bind_buffers[i].buffer = NULL;
|
250
286
|
params_enc[i] = Qnil;
|
251
287
|
|
@@ -265,9 +301,19 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
265
301
|
#endif
|
266
302
|
break;
|
267
303
|
case T_BIGNUM:
|
268
|
-
|
269
|
-
|
270
|
-
|
304
|
+
{
|
305
|
+
LONG_LONG num;
|
306
|
+
if (my_big2ll(argv[i], &num) == 0) {
|
307
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
|
308
|
+
bind_buffers[i].buffer = xmalloc(sizeof(long long int));
|
309
|
+
*(LONG_LONG*)(bind_buffers[i].buffer) = num;
|
310
|
+
} else {
|
311
|
+
/* The bignum was larger than we can fit in LONG_LONG, send it as a string */
|
312
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
|
313
|
+
params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
|
314
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
315
|
+
}
|
316
|
+
}
|
271
317
|
break;
|
272
318
|
case T_FLOAT:
|
273
319
|
bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
|
@@ -275,17 +321,21 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
275
321
|
*(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
|
276
322
|
break;
|
277
323
|
case T_STRING:
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
324
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
|
325
|
+
|
326
|
+
params_enc[i] = argv[i];
|
327
|
+
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
328
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
329
|
+
break;
|
330
|
+
case T_TRUE:
|
331
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
|
332
|
+
bind_buffers[i].buffer = xmalloc(sizeof(signed char));
|
333
|
+
*(signed char*)(bind_buffers[i].buffer) = 1;
|
334
|
+
break;
|
335
|
+
case T_FALSE:
|
336
|
+
bind_buffers[i].buffer_type = MYSQL_TYPE_TINY;
|
337
|
+
bind_buffers[i].buffer = xmalloc(sizeof(signed char));
|
338
|
+
*(signed char*)(bind_buffers[i].buffer) = 0;
|
289
339
|
break;
|
290
340
|
default:
|
291
341
|
// TODO: what Ruby type should support MYSQL_TYPE_TIME
|
@@ -298,7 +348,13 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
298
348
|
|
299
349
|
memset(&t, 0, sizeof(MYSQL_TIME));
|
300
350
|
t.neg = 0;
|
301
|
-
|
351
|
+
|
352
|
+
if (CLASS_OF(argv[i]) == rb_cTime) {
|
353
|
+
t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
|
354
|
+
} else if (CLASS_OF(argv[i]) == cDateTime) {
|
355
|
+
t.second_part = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
|
356
|
+
}
|
357
|
+
|
302
358
|
t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
|
303
359
|
t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
|
304
360
|
t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
|
@@ -324,6 +380,17 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
324
380
|
*(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
|
325
381
|
} else if (CLASS_OF(argv[i]) == cBigDecimal) {
|
326
382
|
bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
|
383
|
+
|
384
|
+
// DECIMAL are represented with the "string representation of the
|
385
|
+
// original server-side value", see
|
386
|
+
// https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-conversions.html
|
387
|
+
// This should be independent of the locale used both on the server
|
388
|
+
// and the client side.
|
389
|
+
VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
|
390
|
+
|
391
|
+
params_enc[i] = rb_val_as_string;
|
392
|
+
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
393
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
327
394
|
}
|
328
395
|
break;
|
329
396
|
}
|
@@ -336,7 +403,40 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
336
403
|
}
|
337
404
|
}
|
338
405
|
|
339
|
-
|
406
|
+
// Duplicate the options hash, merge! extra opts, put the copy into the Result object
|
407
|
+
current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
|
408
|
+
(void)RB_GC_GUARD(current);
|
409
|
+
Check_Type(current, T_HASH);
|
410
|
+
|
411
|
+
// Merge in hash opts/keyword arguments
|
412
|
+
if (!NIL_P(opts)) {
|
413
|
+
rb_funcall(current, intern_merge_bang, 1, opts);
|
414
|
+
}
|
415
|
+
|
416
|
+
is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
|
417
|
+
|
418
|
+
// From stmt_execute to mysql_stmt_result_metadata to stmt_store_result, no
|
419
|
+
// Ruby API calls are allowed so that GC is not invoked. If the connection is
|
420
|
+
// in results-streaming-mode for Statement A, and in the middle Statement B
|
421
|
+
// gets garbage collected, a message will be sent to the server notifying it
|
422
|
+
// to release Statement B, resulting in the following error:
|
423
|
+
// Commands out of sync; you can't run this command now
|
424
|
+
//
|
425
|
+
// In streaming mode, statement execute must return a cursor because we
|
426
|
+
// cannot prevent other Statement objects from being garbage collected
|
427
|
+
// between fetches of each row of the result set. The following error
|
428
|
+
// occurs if cursor mode is not set:
|
429
|
+
// Row retrieval was canceled by mysql_stmt_close
|
430
|
+
|
431
|
+
if (is_streaming) {
|
432
|
+
unsigned long type = CURSOR_TYPE_READ_ONLY;
|
433
|
+
if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &type)) {
|
434
|
+
FREE_BINDS;
|
435
|
+
rb_raise(cMysql2Error, "Unable to stream prepared statement, could not set CURSOR_TYPE_READ_ONLY");
|
436
|
+
}
|
437
|
+
}
|
438
|
+
|
439
|
+
if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
|
340
440
|
FREE_BINDS;
|
341
441
|
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
342
442
|
}
|
@@ -347,30 +447,26 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
347
447
|
if (metadata == NULL) {
|
348
448
|
if (mysql_stmt_errno(stmt) != 0) {
|
349
449
|
// either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
|
350
|
-
|
351
|
-
MARK_CONN_INACTIVE(stmt_wrapper->client);
|
450
|
+
wrapper->active_thread = Qnil;
|
352
451
|
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
353
452
|
}
|
354
453
|
// no data and no error, so query was not a SELECT
|
355
454
|
return Qnil;
|
356
455
|
}
|
357
456
|
|
358
|
-
current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
|
359
|
-
(void)RB_GC_GUARD(current);
|
360
|
-
Check_Type(current, T_HASH);
|
361
|
-
|
362
|
-
is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
|
363
457
|
if (!is_streaming) {
|
364
458
|
// recieve the whole result set from the server
|
365
|
-
if (
|
459
|
+
if (mysql_stmt_store_result(stmt)) {
|
366
460
|
mysql_free_result(metadata);
|
367
461
|
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
368
462
|
}
|
369
|
-
|
463
|
+
wrapper->active_thread = Qnil;
|
370
464
|
}
|
371
465
|
|
372
466
|
resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
|
373
467
|
|
468
|
+
rb_mysql_set_server_query_flags(wrapper->client, resultObj);
|
469
|
+
|
374
470
|
if (!is_streaming) {
|
375
471
|
// cache all result
|
376
472
|
rb_funcall(resultObj, intern_each, 0);
|
@@ -383,42 +479,47 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
383
479
|
*
|
384
480
|
* Returns a list of fields that will be returned by this statement.
|
385
481
|
*/
|
386
|
-
static VALUE
|
482
|
+
static VALUE rb_mysql_stmt_fields(VALUE self) {
|
387
483
|
MYSQL_FIELD *fields;
|
388
484
|
MYSQL_RES *metadata;
|
389
485
|
unsigned int field_count;
|
390
486
|
unsigned int i;
|
391
487
|
VALUE field_list;
|
392
488
|
MYSQL_STMT* stmt;
|
393
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
394
489
|
rb_encoding *default_internal_enc, *conn_enc;
|
395
|
-
#endif
|
396
490
|
GET_STATEMENT(self);
|
491
|
+
GET_CLIENT(stmt_wrapper->client);
|
397
492
|
stmt = stmt_wrapper->stmt;
|
398
493
|
|
399
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
400
494
|
default_internal_enc = rb_default_internal_encoding();
|
401
495
|
{
|
402
496
|
GET_CLIENT(stmt_wrapper->client);
|
403
497
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
404
498
|
}
|
405
|
-
#endif
|
406
499
|
|
407
|
-
metadata
|
500
|
+
metadata = mysql_stmt_result_metadata(stmt);
|
501
|
+
if (metadata == NULL) {
|
502
|
+
if (mysql_stmt_errno(stmt) != 0) {
|
503
|
+
// either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
|
504
|
+
wrapper->active_thread = Qnil;
|
505
|
+
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
506
|
+
}
|
507
|
+
// no data and no error, so query was not a SELECT
|
508
|
+
return Qnil;
|
509
|
+
}
|
510
|
+
|
408
511
|
fields = mysql_fetch_fields(metadata);
|
409
512
|
field_count = mysql_stmt_field_count(stmt);
|
410
513
|
field_list = rb_ary_new2((long)field_count);
|
411
514
|
|
412
|
-
for(i = 0; i < field_count; i++) {
|
515
|
+
for (i = 0; i < field_count; i++) {
|
413
516
|
VALUE rb_field;
|
414
517
|
|
415
518
|
rb_field = rb_str_new(fields[i].name, fields[i].name_length);
|
416
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
417
519
|
rb_enc_associate(rb_field, conn_enc);
|
418
520
|
if (default_internal_enc) {
|
419
521
|
rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
|
420
522
|
}
|
421
|
-
#endif
|
422
523
|
|
423
524
|
rb_ary_store(field_list, (long)i, rb_field);
|
424
525
|
}
|
@@ -469,12 +570,15 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
|
|
469
570
|
}
|
470
571
|
|
471
572
|
void init_mysql2_statement() {
|
472
|
-
|
573
|
+
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
574
|
+
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
575
|
+
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
473
576
|
|
474
|
-
|
475
|
-
rb_define_method(cMysql2Statement, "
|
476
|
-
rb_define_method(cMysql2Statement, "
|
477
|
-
rb_define_method(cMysql2Statement, "
|
577
|
+
cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
|
578
|
+
rb_define_method(cMysql2Statement, "param_count", rb_mysql_stmt_param_count, 0);
|
579
|
+
rb_define_method(cMysql2Statement, "field_count", rb_mysql_stmt_field_count, 0);
|
580
|
+
rb_define_method(cMysql2Statement, "_execute", rb_mysql_stmt_execute, -1);
|
581
|
+
rb_define_method(cMysql2Statement, "fields", rb_mysql_stmt_fields, 0);
|
478
582
|
rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
|
479
583
|
rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
|
480
584
|
rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
|
@@ -484,6 +588,7 @@ void init_mysql2_statement() {
|
|
484
588
|
intern_new_with_args = rb_intern("new_with_args");
|
485
589
|
intern_each = rb_intern("each");
|
486
590
|
|
591
|
+
intern_sec_fraction = rb_intern("sec_fraction");
|
487
592
|
intern_usec = rb_intern("usec");
|
488
593
|
intern_sec = rb_intern("sec");
|
489
594
|
intern_min = rb_intern("min");
|
@@ -491,4 +596,7 @@ void init_mysql2_statement() {
|
|
491
596
|
intern_day = rb_intern("day");
|
492
597
|
intern_month = rb_intern("month");
|
493
598
|
intern_year = rb_intern("year");
|
599
|
+
|
600
|
+
intern_to_s = rb_intern("to_s");
|
601
|
+
intern_merge_bang = rb_intern("merge!");
|
494
602
|
}
|
data/ext/mysql2/statement.h
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
/*
|
2
|
-
* backwards compatibility for
|
2
|
+
* backwards compatibility for Rubinius. See
|
3
|
+
* https://github.com/rubinius/rubinius/issues/3771.
|
3
4
|
*
|
4
5
|
* Ruby 1.9.3 provides this API which allows the use of ppoll() on Linux
|
5
6
|
* to minimize select() and malloc() overhead on high-numbered FDs.
|
data/lib/mysql2/client.rb
CHANGED
@@ -4,21 +4,22 @@ module Mysql2
|
|
4
4
|
|
5
5
|
def self.default_query_options
|
6
6
|
@default_query_options ||= {
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
7
|
+
as: :hash, # the type of object you want each row back as; also supports :array (an array of values)
|
8
|
+
async: false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
|
9
|
+
cast_booleans: false, # cast tinyint(1) fields as true/false in ruby
|
10
|
+
symbolize_keys: false, # return field names as symbols instead of strings
|
11
|
+
database_timezone: :local, # timezone Mysql2 will assume datetime objects are stored in
|
12
|
+
application_timezone: nil, # timezone Mysql2 will convert to before handing the object back to the caller
|
13
|
+
cache_rows: true, # tells Mysql2 to use its internal row cache for results
|
14
|
+
connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | CONNECT_ATTRS,
|
15
|
+
cast: true,
|
16
|
+
default_file: nil,
|
17
|
+
default_group: nil,
|
18
18
|
}
|
19
19
|
end
|
20
20
|
|
21
21
|
def initialize(opts = {})
|
22
|
+
raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
|
22
23
|
opts = Mysql2::Util.key_hash_as_symbols(opts)
|
23
24
|
@read_timeout = nil
|
24
25
|
@query_options = self.class.default_query_options.dup
|
@@ -30,13 +31,13 @@ module Mysql2
|
|
30
31
|
opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
|
31
32
|
|
32
33
|
# TODO: stricter validation rather than silent massaging
|
33
|
-
[
|
34
|
+
%i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin].each do |key|
|
34
35
|
next unless opts.key?(key)
|
35
36
|
case key
|
36
|
-
when :reconnect, :local_infile, :secure_auth
|
37
|
+
when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
|
37
38
|
send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
|
38
39
|
when :connect_timeout, :read_timeout, :write_timeout
|
39
|
-
send(:"#{key}=", opts[key].
|
40
|
+
send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
|
40
41
|
else
|
41
42
|
send(:"#{key}=", opts[key])
|
42
43
|
end
|
@@ -46,25 +47,26 @@ module Mysql2
|
|
46
47
|
self.charset_name = opts[:encoding] || 'utf8'
|
47
48
|
|
48
49
|
ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
|
49
|
-
ssl_set(*ssl_options) if ssl_options.any?
|
50
|
+
ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify)
|
51
|
+
self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
|
50
52
|
|
51
|
-
case opts[:flags]
|
53
|
+
flags = case opts[:flags]
|
52
54
|
when Array
|
53
|
-
|
55
|
+
parse_flags_array(opts[:flags], @query_options[:connect_flags])
|
54
56
|
when String
|
55
|
-
|
57
|
+
parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
|
56
58
|
when Integer
|
57
|
-
|
59
|
+
@query_options[:connect_flags] | opts[:flags]
|
58
60
|
else
|
59
|
-
|
61
|
+
@query_options[:connect_flags]
|
60
62
|
end
|
61
63
|
|
62
64
|
# SSL verify is a connection flag rather than a mysql_ssl_set option
|
63
|
-
flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
|
65
|
+
flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
|
64
66
|
|
65
|
-
if [
|
67
|
+
if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
|
66
68
|
warn "============= WARNING FROM mysql2 ============="
|
67
|
-
warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be
|
69
|
+
warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
|
68
70
|
warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
|
69
71
|
warn "============= END WARNING FROM mysql2 ========="
|
70
72
|
end
|
@@ -83,8 +85,20 @@ module Mysql2
|
|
83
85
|
port = port.to_i unless port.nil?
|
84
86
|
database = database.to_s unless database.nil?
|
85
87
|
socket = socket.to_s unless socket.nil?
|
88
|
+
conn_attrs = parse_connect_attrs(opts[:connect_attrs])
|
86
89
|
|
87
|
-
connect user, pass, host, port, database, socket, flags
|
90
|
+
connect user, pass, host, port, database, socket, flags, conn_attrs
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_ssl_mode(mode)
|
94
|
+
m = mode.to_s.upcase
|
95
|
+
if m.start_with?('SSL_MODE_')
|
96
|
+
return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
|
97
|
+
else
|
98
|
+
x = 'SSL_MODE_' + m
|
99
|
+
return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
|
100
|
+
end
|
101
|
+
warn "Unknown MySQL ssl_mode flag: #{mode}"
|
88
102
|
end
|
89
103
|
|
90
104
|
def parse_flags_array(flags, initial = 0)
|
@@ -101,14 +115,19 @@ module Mysql2
|
|
101
115
|
end
|
102
116
|
end
|
103
117
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
118
|
+
# Set default program_name in performance_schema.session_connect_attrs
|
119
|
+
# and performance_schema.session_account_connect_attrs
|
120
|
+
def parse_connect_attrs(conn_attrs)
|
121
|
+
return {} if Mysql2::Client::CONNECT_ATTRS.zero?
|
122
|
+
conn_attrs ||= {}
|
123
|
+
conn_attrs[:program_name] ||= $PROGRAM_NAME
|
124
|
+
conn_attrs.each_with_object({}) do |(key, value), hash|
|
125
|
+
hash[key.to_s] = value.to_s
|
109
126
|
end
|
110
|
-
|
111
|
-
|
127
|
+
end
|
128
|
+
|
129
|
+
def query(sql, options = {})
|
130
|
+
Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
|
112
131
|
_query(sql, @query_options.merge(options))
|
113
132
|
end
|
114
133
|
end
|