mysql2 0.4.6 → 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 +82 -52
- data/examples/eventmachine.rb +0 -2
- data/examples/threaded.rb +2 -4
- data/ext/mysql2/client.c +171 -75
- data/ext/mysql2/client.h +2 -41
- data/ext/mysql2/extconf.rb +30 -23
- 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 +24 -77
- data/ext/mysql2/result.h +2 -3
- data/ext/mysql2/statement.c +101 -73
- data/ext/mysql2/statement.h +0 -2
- data/ext/mysql2/wait_for_single_fd.h +2 -1
- data/lib/mysql2/client.rb +37 -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/em/em_spec.rb +6 -6
- data/spec/mysql2/client_spec.rb +300 -215
- data/spec/mysql2/error_spec.rb +3 -9
- data/spec/mysql2/result_spec.rb +124 -158
- data/spec/mysql2/statement_spec.rb +138 -185
- data/spec/spec_helper.rb +79 -61
- 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,12 +1,9 @@
|
|
1
1
|
#include <mysql2_ext.h>
|
2
2
|
|
3
|
-
VALUE
|
4
|
-
|
5
|
-
static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s;
|
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
6
|
static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
|
7
|
-
#ifndef HAVE_RB_BIG_CMP
|
8
|
-
static ID id_cmp;
|
9
|
-
#endif
|
10
7
|
|
11
8
|
#define GET_STATEMENT(self) \
|
12
9
|
mysql_stmt_wrapper *stmt_wrapper; \
|
@@ -21,7 +18,7 @@ static void rb_mysql_stmt_mark(void * ptr) {
|
|
21
18
|
rb_gc_mark(stmt_wrapper->client);
|
22
19
|
}
|
23
20
|
|
24
|
-
static void *nogvl_stmt_close(void *
|
21
|
+
static void *nogvl_stmt_close(void *ptr) {
|
25
22
|
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
26
23
|
if (stmt_wrapper->stmt) {
|
27
24
|
mysql_stmt_close(stmt_wrapper->stmt);
|
@@ -30,7 +27,7 @@ static void *nogvl_stmt_close(void * ptr) {
|
|
30
27
|
return NULL;
|
31
28
|
}
|
32
29
|
|
33
|
-
static void rb_mysql_stmt_free(void *
|
30
|
+
static void rb_mysql_stmt_free(void *ptr) {
|
34
31
|
mysql_stmt_wrapper *stmt_wrapper = ptr;
|
35
32
|
decr_mysql2_stmt(stmt_wrapper);
|
36
33
|
}
|
@@ -50,7 +47,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
|
|
50
47
|
VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt_wrapper->stmt));
|
51
48
|
VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt_wrapper->stmt));
|
52
49
|
|
53
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
54
50
|
rb_encoding *conn_enc;
|
55
51
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
56
52
|
|
@@ -62,7 +58,6 @@ void rb_raise_mysql2_stmt_error(mysql_stmt_wrapper *stmt_wrapper) {
|
|
62
58
|
rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
|
63
59
|
rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
|
64
60
|
}
|
65
|
-
#endif
|
66
61
|
|
67
62
|
e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
|
68
63
|
rb_error_msg,
|
@@ -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");
|
@@ -124,7 +115,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
|
|
124
115
|
|
125
116
|
// set STMT_ATTR_UPDATE_MAX_LENGTH attr
|
126
117
|
{
|
127
|
-
|
118
|
+
my_bool truth = 1;
|
128
119
|
if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
|
129
120
|
rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
|
130
121
|
}
|
@@ -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)) {
|
@@ -196,7 +184,7 @@ static void set_buffer_for_string(MYSQL_BIND* bind_buffer, unsigned long *length
|
|
196
184
|
* the buffer is a Ruby string pointer and not our memory to manage.
|
197
185
|
*/
|
198
186
|
#define FREE_BINDS \
|
199
|
-
for (i = 0; i <
|
187
|
+
for (i = 0; i < bind_count; i++) { \
|
200
188
|
if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
|
201
189
|
xfree(bind_buffers[i].buffer); \
|
202
190
|
} \
|
@@ -211,6 +199,8 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
|
|
211
199
|
{
|
212
200
|
unsigned LONG_LONG num;
|
213
201
|
size_t len;
|
202
|
+
// rb_absint_size was added in 2.1.0. See:
|
203
|
+
// https://github.com/ruby/ruby/commit/9fea875
|
214
204
|
#ifdef HAVE_RB_ABSINT_SIZE
|
215
205
|
int nlz_bits = 0;
|
216
206
|
len = rb_absint_size(bignum, &nlz_bits);
|
@@ -229,16 +219,15 @@ static int my_big2ll(VALUE bignum, LONG_LONG *ptr)
|
|
229
219
|
#ifdef HAVE_RB_ABSINT_SIZE
|
230
220
|
nlz_bits == 0 &&
|
231
221
|
#endif
|
222
|
+
// rb_absint_singlebit_p was added in 2.1.0. See:
|
223
|
+
// https://github.com/ruby/ruby/commit/e5ff9d5
|
232
224
|
#if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
|
233
225
|
/* Optimized to avoid object allocation for Ruby 2.1+
|
234
226
|
* only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
|
235
227
|
*/
|
236
228
|
!rb_absint_singlebit_p(bignum)
|
237
|
-
#elif defined(HAVE_RB_BIG_CMP)
|
238
|
-
rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
|
239
229
|
#else
|
240
|
-
|
241
|
-
rb_funcall(bignum, id_cmp, 1, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
|
230
|
+
rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
|
242
231
|
#endif
|
243
232
|
) {
|
244
233
|
goto overflow;
|
@@ -254,44 +243,45 @@ overflow:
|
|
254
243
|
*
|
255
244
|
* Executes the current prepared statement, returns +result+.
|
256
245
|
*/
|
257
|
-
static VALUE
|
246
|
+
static VALUE rb_mysql_stmt_execute(int argc, VALUE *argv, VALUE self) {
|
258
247
|
MYSQL_BIND *bind_buffers = NULL;
|
259
248
|
unsigned long *length_buffers = NULL;
|
260
249
|
unsigned long bind_count;
|
261
|
-
long i;
|
250
|
+
unsigned long i;
|
262
251
|
MYSQL_STMT *stmt;
|
263
252
|
MYSQL_RES *metadata;
|
253
|
+
VALUE opts;
|
264
254
|
VALUE current;
|
265
255
|
VALUE resultObj;
|
266
|
-
VALUE *params_enc;
|
256
|
+
VALUE *params_enc = NULL;
|
267
257
|
int is_streaming;
|
268
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
269
258
|
rb_encoding *conn_enc;
|
270
|
-
#endif
|
271
259
|
|
272
260
|
GET_STATEMENT(self);
|
273
261
|
GET_CLIENT(stmt_wrapper->client);
|
274
262
|
|
275
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
276
263
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
277
|
-
#endif
|
278
|
-
|
279
|
-
/* Scratch space for string encoding exports, allocate on the stack. */
|
280
|
-
params_enc = alloca(sizeof(VALUE) * argc);
|
281
264
|
|
282
265
|
stmt = stmt_wrapper->stmt;
|
283
|
-
|
284
266
|
bind_count = mysql_stmt_param_count(stmt);
|
285
|
-
|
286
|
-
|
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
|
+
}
|
287
275
|
}
|
288
276
|
|
289
277
|
// setup any bind variables in the query
|
290
278
|
if (bind_count > 0) {
|
279
|
+
// Scratch space for string encoding exports, allocate on the stack
|
280
|
+
params_enc = alloca(sizeof(VALUE) * bind_count);
|
291
281
|
bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
|
292
282
|
length_buffers = xcalloc(bind_count, sizeof(unsigned long));
|
293
283
|
|
294
|
-
for (i = 0; i <
|
284
|
+
for (i = 0; i < bind_count; i++) {
|
295
285
|
bind_buffers[i].buffer = NULL;
|
296
286
|
params_enc[i] = Qnil;
|
297
287
|
|
@@ -319,12 +309,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
319
309
|
*(LONG_LONG*)(bind_buffers[i].buffer) = num;
|
320
310
|
} else {
|
321
311
|
/* The bignum was larger than we can fit in LONG_LONG, send it as a string */
|
322
|
-
VALUE rb_val_as_string = rb_big2str(argv[i], 10);
|
323
312
|
bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
|
324
|
-
params_enc[i] =
|
325
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
326
|
-
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
327
|
-
#endif
|
313
|
+
params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
|
328
314
|
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
329
315
|
}
|
330
316
|
}
|
@@ -338,11 +324,19 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
338
324
|
bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
|
339
325
|
|
340
326
|
params_enc[i] = argv[i];
|
341
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
342
327
|
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
343
|
-
#endif
|
344
328
|
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
345
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;
|
339
|
+
break;
|
346
340
|
default:
|
347
341
|
// TODO: what Ruby type should support MYSQL_TYPE_TIME
|
348
342
|
if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
|
@@ -395,9 +389,7 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
395
389
|
VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
|
396
390
|
|
397
391
|
params_enc[i] = rb_val_as_string;
|
398
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
399
392
|
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
400
|
-
#endif
|
401
393
|
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
402
394
|
}
|
403
395
|
break;
|
@@ -411,7 +403,40 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
411
403
|
}
|
412
404
|
}
|
413
405
|
|
414
|
-
|
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) {
|
415
440
|
FREE_BINDS;
|
416
441
|
rb_raise_mysql2_stmt_error(stmt_wrapper);
|
417
442
|
}
|
@@ -429,11 +454,6 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
429
454
|
return Qnil;
|
430
455
|
}
|
431
456
|
|
432
|
-
current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
|
433
|
-
(void)RB_GC_GUARD(current);
|
434
|
-
Check_Type(current, T_HASH);
|
435
|
-
|
436
|
-
is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
|
437
457
|
if (!is_streaming) {
|
438
458
|
// recieve the whole result set from the server
|
439
459
|
if (mysql_stmt_store_result(stmt)) {
|
@@ -445,6 +465,8 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
445
465
|
|
446
466
|
resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
|
447
467
|
|
468
|
+
rb_mysql_set_server_query_flags(wrapper->client, resultObj);
|
469
|
+
|
448
470
|
if (!is_streaming) {
|
449
471
|
// cache all result
|
450
472
|
rb_funcall(resultObj, intern_each, 0);
|
@@ -457,42 +479,47 @@ static VALUE execute(int argc, VALUE *argv, VALUE self) {
|
|
457
479
|
*
|
458
480
|
* Returns a list of fields that will be returned by this statement.
|
459
481
|
*/
|
460
|
-
static VALUE
|
482
|
+
static VALUE rb_mysql_stmt_fields(VALUE self) {
|
461
483
|
MYSQL_FIELD *fields;
|
462
484
|
MYSQL_RES *metadata;
|
463
485
|
unsigned int field_count;
|
464
486
|
unsigned int i;
|
465
487
|
VALUE field_list;
|
466
488
|
MYSQL_STMT* stmt;
|
467
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
468
489
|
rb_encoding *default_internal_enc, *conn_enc;
|
469
|
-
#endif
|
470
490
|
GET_STATEMENT(self);
|
491
|
+
GET_CLIENT(stmt_wrapper->client);
|
471
492
|
stmt = stmt_wrapper->stmt;
|
472
493
|
|
473
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
474
494
|
default_internal_enc = rb_default_internal_encoding();
|
475
495
|
{
|
476
496
|
GET_CLIENT(stmt_wrapper->client);
|
477
497
|
conn_enc = rb_to_encoding(wrapper->encoding);
|
478
498
|
}
|
479
|
-
#endif
|
480
499
|
|
481
|
-
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
|
+
|
482
511
|
fields = mysql_fetch_fields(metadata);
|
483
512
|
field_count = mysql_stmt_field_count(stmt);
|
484
513
|
field_list = rb_ary_new2((long)field_count);
|
485
514
|
|
486
|
-
for(i = 0; i < field_count; i++) {
|
515
|
+
for (i = 0; i < field_count; i++) {
|
487
516
|
VALUE rb_field;
|
488
517
|
|
489
518
|
rb_field = rb_str_new(fields[i].name, fields[i].name_length);
|
490
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
491
519
|
rb_enc_associate(rb_field, conn_enc);
|
492
520
|
if (default_internal_enc) {
|
493
521
|
rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
|
494
522
|
}
|
495
|
-
#endif
|
496
523
|
|
497
524
|
rb_ary_store(field_list, (long)i, rb_field);
|
498
525
|
}
|
@@ -543,12 +570,15 @@ static VALUE rb_mysql_stmt_close(VALUE self) {
|
|
543
570
|
}
|
544
571
|
|
545
572
|
void init_mysql2_statement() {
|
546
|
-
|
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"));
|
547
576
|
|
548
|
-
|
549
|
-
rb_define_method(cMysql2Statement, "
|
550
|
-
rb_define_method(cMysql2Statement, "
|
551
|
-
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);
|
552
582
|
rb_define_method(cMysql2Statement, "last_id", rb_mysql_stmt_last_id, 0);
|
553
583
|
rb_define_method(cMysql2Statement, "affected_rows", rb_mysql_stmt_affected_rows, 0);
|
554
584
|
rb_define_method(cMysql2Statement, "close", rb_mysql_stmt_close, 0);
|
@@ -568,7 +598,5 @@ void init_mysql2_statement() {
|
|
568
598
|
intern_year = rb_intern("year");
|
569
599
|
|
570
600
|
intern_to_s = rb_intern("to_s");
|
571
|
-
|
572
|
-
id_cmp = rb_intern("<=>");
|
573
|
-
#endif
|
601
|
+
intern_merge_bang = rb_intern("merge!");
|
574
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,22 +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
|
-
|
22
|
+
raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
|
23
23
|
opts = Mysql2::Util.key_hash_as_symbols(opts)
|
24
24
|
@read_timeout = nil
|
25
25
|
@query_options = self.class.default_query_options.dup
|
@@ -31,10 +31,10 @@ module Mysql2
|
|
31
31
|
opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
|
32
32
|
|
33
33
|
# TODO: stricter validation rather than silent massaging
|
34
|
-
[
|
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|
|
35
35
|
next unless opts.key?(key)
|
36
36
|
case key
|
37
|
-
when :reconnect, :local_infile, :secure_auth, :automatic_close
|
37
|
+
when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin
|
38
38
|
send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
|
39
39
|
when :connect_timeout, :read_timeout, :write_timeout
|
40
40
|
send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
|
@@ -47,26 +47,26 @@ module Mysql2
|
|
47
47
|
self.charset_name = opts[:encoding] || 'utf8'
|
48
48
|
|
49
49
|
ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
|
50
|
-
ssl_set(*ssl_options) if ssl_options.any?
|
50
|
+
ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify)
|
51
51
|
self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
|
52
52
|
|
53
|
-
case opts[:flags]
|
53
|
+
flags = case opts[:flags]
|
54
54
|
when Array
|
55
|
-
|
55
|
+
parse_flags_array(opts[:flags], @query_options[:connect_flags])
|
56
56
|
when String
|
57
|
-
|
57
|
+
parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
|
58
58
|
when Integer
|
59
|
-
|
59
|
+
@query_options[:connect_flags] | opts[:flags]
|
60
60
|
else
|
61
|
-
|
61
|
+
@query_options[:connect_flags]
|
62
62
|
end
|
63
63
|
|
64
64
|
# SSL verify is a connection flag rather than a mysql_ssl_set option
|
65
|
-
flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
|
65
|
+
flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
|
66
66
|
|
67
|
-
if [
|
67
|
+
if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
|
68
68
|
warn "============= WARNING FROM mysql2 ============="
|
69
|
-
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."
|
70
70
|
warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
|
71
71
|
warn "============= END WARNING FROM mysql2 ========="
|
72
72
|
end
|
@@ -85,8 +85,9 @@ module Mysql2
|
|
85
85
|
port = port.to_i unless port.nil?
|
86
86
|
database = database.to_s unless database.nil?
|
87
87
|
socket = socket.to_s unless socket.nil?
|
88
|
+
conn_attrs = parse_connect_attrs(opts[:connect_attrs])
|
88
89
|
|
89
|
-
connect user, pass, host, port, database, socket, flags
|
90
|
+
connect user, pass, host, port, database, socket, flags, conn_attrs
|
90
91
|
end
|
91
92
|
|
92
93
|
def parse_ssl_mode(mode)
|
@@ -114,14 +115,19 @@ module Mysql2
|
|
114
115
|
end
|
115
116
|
end
|
116
117
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
122
126
|
end
|
123
|
-
|
124
|
-
|
127
|
+
end
|
128
|
+
|
129
|
+
def query(sql, options = {})
|
130
|
+
Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
|
125
131
|
_query(sql, @query_options.merge(options))
|
126
132
|
end
|
127
133
|
end
|
data/lib/mysql2/em.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'eventmachine'
|
4
2
|
require 'mysql2'
|
5
3
|
|
@@ -17,7 +15,7 @@ module Mysql2
|
|
17
15
|
detach
|
18
16
|
begin
|
19
17
|
result = @client.async_result
|
20
|
-
rescue => e
|
18
|
+
rescue StandardError => e
|
21
19
|
@deferable.fail(e)
|
22
20
|
else
|
23
21
|
@deferable.succeed(result)
|
@@ -41,7 +39,7 @@ module Mysql2
|
|
41
39
|
|
42
40
|
def query(sql, opts = {})
|
43
41
|
if ::EM.reactor_running?
|
44
|
-
super(sql, opts.merge(:
|
42
|
+
super(sql, opts.merge(async: true))
|
45
43
|
deferable = ::EM::DefaultDeferrable.new
|
46
44
|
@watch = ::EM.watch(socket, Watcher, self, deferable)
|
47
45
|
@watch.notify_readable = true
|
data/lib/mysql2/error.rb
CHANGED
@@ -1,32 +1,65 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
1
|
module Mysql2
|
4
2
|
class Error < StandardError
|
5
3
|
ENCODE_OPTS = {
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
4
|
+
undef: :replace,
|
5
|
+
invalid: :replace,
|
6
|
+
replace: '?'.freeze,
|
7
|
+
}.freeze
|
8
|
+
|
9
|
+
ConnectionError = Class.new(Error)
|
10
|
+
TimeoutError = Class.new(Error)
|
11
|
+
|
12
|
+
CODES = {
|
13
|
+
1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT
|
14
|
+
|
15
|
+
1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR
|
16
|
+
1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR
|
17
|
+
1152 => ConnectionError, # ER_ABORTING_CONNECTION
|
18
|
+
1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE
|
19
|
+
1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE
|
20
|
+
1155 => ConnectionError, # ER_NET_FCNTL_ERROR
|
21
|
+
1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER
|
22
|
+
1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR
|
23
|
+
1158 => ConnectionError, # ER_NET_READ_ERROR
|
24
|
+
1159 => ConnectionError, # ER_NET_READ_INTERRUPTED
|
25
|
+
1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE
|
26
|
+
1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED
|
27
|
+
|
28
|
+
2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR
|
29
|
+
2002 => ConnectionError, # CR_CONNECTION_ERROR
|
30
|
+
2003 => ConnectionError, # CR_CONN_HOST_ERROR
|
31
|
+
2004 => ConnectionError, # CR_IPSOCK_ERROR
|
32
|
+
2005 => ConnectionError, # CR_UNKNOWN_HOST
|
33
|
+
2006 => ConnectionError, # CR_SERVER_GONE_ERROR
|
34
|
+
2007 => ConnectionError, # CR_VERSION_ERROR
|
35
|
+
2009 => ConnectionError, # CR_WRONG_HOST_INFO
|
36
|
+
2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR
|
37
|
+
2013 => ConnectionError, # CR_SERVER_LOST
|
38
|
+
2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE
|
39
|
+
2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR
|
40
|
+
2027 => ConnectionError, # CR_MALFORMED_PACKET
|
41
|
+
2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL
|
42
|
+
2048 => ConnectionError, # CR_INVALID_CONN_HANDLE
|
43
|
+
2049 => ConnectionError, # CR_UNUSED_1
|
9
44
|
}.freeze
|
10
45
|
|
11
46
|
attr_reader :error_number, :sql_state
|
12
47
|
|
13
48
|
# Mysql gem compatibility
|
14
|
-
|
15
|
-
|
49
|
+
alias errno error_number
|
50
|
+
alias error message
|
16
51
|
|
17
|
-
def initialize(msg)
|
18
|
-
@server_version
|
52
|
+
def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
|
53
|
+
@server_version = server_version
|
54
|
+
@error_number = error_number
|
55
|
+
@sql_state = sql_state ? sql_state.encode(ENCODE_OPTS) : nil
|
19
56
|
|
20
57
|
super(clean_message(msg))
|
21
58
|
end
|
22
59
|
|
23
60
|
def self.new_with_args(msg, server_version, error_number, sql_state)
|
24
|
-
|
25
|
-
|
26
|
-
err.instance_variable_set('@error_number', error_number)
|
27
|
-
err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state)
|
28
|
-
err.send(:initialize, msg)
|
29
|
-
err
|
61
|
+
error_class = CODES.fetch(error_number, self)
|
62
|
+
error_class.new(msg, server_version, error_number, sql_state)
|
30
63
|
end
|
31
64
|
|
32
65
|
private
|
@@ -55,12 +88,8 @@ module Mysql2
|
|
55
88
|
# encoding, we'll assume UTF-8 and clean the string of anything that's not a
|
56
89
|
# valid UTF-8 character.
|
57
90
|
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
# Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8
|
91
|
+
# Returns a valid UTF-8 string.
|
61
92
|
def clean_message(message)
|
62
|
-
return message unless message.respond_to?(:encode)
|
63
|
-
|
64
93
|
if @server_version && @server_version > 50500
|
65
94
|
message.encode(ENCODE_OPTS)
|
66
95
|
else
|