mysql2 0.3.18 → 0.4.4
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/CHANGELOG.md +1 -0
- data/LICENSE +21 -0
- data/README.md +69 -17
- data/examples/eventmachine.rb +1 -1
- data/examples/threaded.rb +4 -6
- data/ext/mysql2/client.c +230 -179
- data/ext/mysql2/client.h +18 -1
- data/ext/mysql2/extconf.rb +95 -35
- data/ext/mysql2/infile.c +2 -2
- data/ext/mysql2/mysql2_ext.c +1 -0
- data/ext/mysql2/mysql2_ext.h +5 -6
- data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
- data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
- data/ext/mysql2/result.c +509 -138
- data/ext/mysql2/result.h +12 -6
- data/ext/mysql2/statement.c +504 -0
- data/ext/mysql2/statement.h +19 -0
- data/lib/mysql2/client.rb +71 -25
- data/lib/mysql2/console.rb +1 -1
- data/lib/mysql2/em.rb +5 -6
- data/lib/mysql2/error.rb +18 -27
- data/lib/mysql2/field.rb +3 -0
- data/lib/mysql2/statement.rb +17 -0
- data/lib/mysql2/version.rb +1 -1
- data/lib/mysql2.rb +38 -18
- data/spec/em/em_spec.rb +21 -21
- data/spec/mysql2/client_spec.rb +456 -362
- data/spec/mysql2/error_spec.rb +37 -36
- data/spec/mysql2/result_spec.rb +222 -208
- data/spec/mysql2/statement_spec.rb +703 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/ssl/ca-cert.pem +17 -0
- data/spec/ssl/ca-key.pem +27 -0
- data/spec/ssl/ca.cnf +22 -0
- data/spec/ssl/cert.cnf +22 -0
- data/spec/ssl/client-cert.pem +17 -0
- data/spec/ssl/client-key.pem +27 -0
- data/spec/ssl/client-req.pem +15 -0
- data/spec/ssl/gen_certs.sh +48 -0
- data/spec/ssl/pkcs8-client-key.pem +28 -0
- data/spec/ssl/pkcs8-server-key.pem +28 -0
- data/spec/ssl/server-cert.pem +17 -0
- data/spec/ssl/server-key.pem +27 -0
- data/spec/ssl/server-req.pem +15 -0
- data/support/mysql_enc_to_ruby.rb +7 -8
- data/support/ruby_enc_to_mysql.rb +1 -1
- metadata +42 -47
data/ext/mysql2/client.c
CHANGED
@@ -16,12 +16,12 @@
|
|
16
16
|
|
17
17
|
VALUE cMysql2Client;
|
18
18
|
extern VALUE mMysql2, cMysql2Error;
|
19
|
-
static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
|
20
|
-
static ID intern_merge, intern_merge_bang,
|
19
|
+
static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
|
20
|
+
static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
|
21
21
|
|
22
22
|
#ifndef HAVE_RB_HASH_DUP
|
23
|
-
|
24
|
-
return rb_funcall(rb_cHash,
|
23
|
+
VALUE rb_hash_dup(VALUE other) {
|
24
|
+
return rb_funcall(rb_cHash, intern_brackets, 1, other);
|
25
25
|
}
|
26
26
|
#endif
|
27
27
|
|
@@ -30,25 +30,12 @@ static VALUE rb_hash_dup(VALUE other) {
|
|
30
30
|
rb_raise(cMysql2Error, "MySQL client is not initialized"); \
|
31
31
|
}
|
32
32
|
|
33
|
-
#define REQUIRE_CONNECTED(wrapper) \
|
34
|
-
REQUIRE_INITIALIZED(wrapper) \
|
35
|
-
if (!wrapper->connected && !wrapper->reconnect_enabled) { \
|
36
|
-
rb_raise(cMysql2Error, "closed MySQL connection"); \
|
37
|
-
}
|
38
|
-
|
39
33
|
#define REQUIRE_NOT_CONNECTED(wrapper) \
|
40
34
|
REQUIRE_INITIALIZED(wrapper) \
|
41
35
|
if (wrapper->connected) { \
|
42
36
|
rb_raise(cMysql2Error, "MySQL connection is already open"); \
|
43
37
|
}
|
44
38
|
|
45
|
-
#define MARK_CONN_INACTIVE(conn) \
|
46
|
-
wrapper->active_thread = Qnil;
|
47
|
-
|
48
|
-
#define GET_CLIENT(self) \
|
49
|
-
mysql_client_wrapper *wrapper; \
|
50
|
-
Data_Get_Struct(self, mysql_client_wrapper, wrapper)
|
51
|
-
|
52
39
|
/*
|
53
40
|
* compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
|
54
41
|
* variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
|
@@ -136,16 +123,17 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
|
|
136
123
|
rb_enc_associate(rb_sql_state, rb_usascii_encoding());
|
137
124
|
#endif
|
138
125
|
|
139
|
-
e = rb_funcall(cMysql2Error,
|
140
|
-
|
141
|
-
|
126
|
+
e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
|
127
|
+
rb_error_msg,
|
128
|
+
LONG2FIX(wrapper->server_version),
|
129
|
+
UINT2NUM(mysql_errno(wrapper->client)),
|
130
|
+
rb_sql_state);
|
142
131
|
rb_exc_raise(e);
|
143
|
-
return Qnil;
|
144
132
|
}
|
145
133
|
|
146
134
|
static void *nogvl_init(void *ptr) {
|
147
135
|
MYSQL *client;
|
148
|
-
mysql_client_wrapper *wrapper =
|
136
|
+
mysql_client_wrapper *wrapper = ptr;
|
149
137
|
|
150
138
|
/* may initialize embedded server and read /etc/services off disk */
|
151
139
|
client = mysql_init(wrapper->client);
|
@@ -182,23 +170,31 @@ static void *nogvl_connect(void *ptr) {
|
|
182
170
|
*/
|
183
171
|
static VALUE invalidate_fd(int clientfd)
|
184
172
|
{
|
185
|
-
#ifdef
|
173
|
+
#ifdef O_CLOEXEC
|
186
174
|
/* Atomically set CLOEXEC on the new FD in case another thread forks */
|
187
175
|
int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC);
|
188
|
-
if (sockfd < 0) {
|
189
|
-
/* Maybe SOCK_CLOEXEC is defined but not available on this kernel */
|
190
|
-
int sockfd = open("/dev/null", O_RDWR);
|
191
|
-
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
192
|
-
}
|
193
176
|
#else
|
194
|
-
/* Well we don't have
|
195
|
-
int sockfd =
|
196
|
-
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
177
|
+
/* Well we don't have O_CLOEXEC, trigger the fallback code below */
|
178
|
+
int sockfd = -1;
|
197
179
|
#endif
|
198
180
|
|
199
181
|
if (sockfd < 0) {
|
200
|
-
/*
|
201
|
-
*
|
182
|
+
/* Either O_CLOEXEC wasn't defined at compile time, or it was defined at
|
183
|
+
* compile time, but isn't available at run-time. So we'll just be quick
|
184
|
+
* about setting FD_CLOEXEC now.
|
185
|
+
*/
|
186
|
+
int flags;
|
187
|
+
sockfd = open("/dev/null", O_RDWR);
|
188
|
+
flags = fcntl(sockfd, F_GETFD);
|
189
|
+
/* Do the flags dance in case there are more defined flags in the future */
|
190
|
+
if (flags != -1) {
|
191
|
+
flags |= FD_CLOEXEC;
|
192
|
+
fcntl(sockfd, F_SETFD, flags);
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
if (sockfd < 0) {
|
197
|
+
/* Cannot raise here, because one or both of the following may be true:
|
202
198
|
* a) we have no GVL (in C Ruby)
|
203
199
|
* b) are running as a GC finalizer
|
204
200
|
*/
|
@@ -213,43 +209,47 @@ static VALUE invalidate_fd(int clientfd)
|
|
213
209
|
#endif /* _WIN32 */
|
214
210
|
|
215
211
|
static void *nogvl_close(void *ptr) {
|
216
|
-
mysql_client_wrapper *wrapper;
|
217
|
-
wrapper = ptr;
|
218
|
-
if (wrapper->connected) {
|
219
|
-
wrapper->active_thread = Qnil;
|
220
|
-
wrapper->connected = 0;
|
221
|
-
#ifndef _WIN32
|
222
|
-
/* Invalidate the socket before calling mysql_close(). This prevents
|
223
|
-
* mysql_close() from sending a mysql-QUIT or from calling shutdown() on
|
224
|
-
* the socket. The difference is that invalidate_fd will drop this
|
225
|
-
* process's reference to the socket only, while a QUIT or shutdown()
|
226
|
-
* would render the underlying connection unusable, interrupting other
|
227
|
-
* processes which share this object across a fork().
|
228
|
-
*/
|
229
|
-
if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
|
230
|
-
fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, leaking some memory\n");
|
231
|
-
close(wrapper->client->net.fd);
|
232
|
-
return NULL;
|
233
|
-
}
|
234
|
-
#endif
|
212
|
+
mysql_client_wrapper *wrapper = ptr;
|
235
213
|
|
236
|
-
|
214
|
+
if (wrapper->client) {
|
215
|
+
mysql_close(wrapper->client);
|
216
|
+
xfree(wrapper->client);
|
217
|
+
wrapper->client = NULL;
|
218
|
+
wrapper->connected = 0;
|
219
|
+
wrapper->active_thread = Qnil;
|
237
220
|
}
|
238
221
|
|
239
222
|
return NULL;
|
240
223
|
}
|
241
224
|
|
225
|
+
/* this is called during GC */
|
242
226
|
static void rb_mysql_client_free(void *ptr) {
|
243
|
-
mysql_client_wrapper *wrapper =
|
227
|
+
mysql_client_wrapper *wrapper = ptr;
|
244
228
|
decr_mysql2_client(wrapper);
|
245
229
|
}
|
246
230
|
|
247
231
|
void decr_mysql2_client(mysql_client_wrapper *wrapper)
|
248
232
|
{
|
249
233
|
wrapper->refcount--;
|
234
|
+
|
250
235
|
if (wrapper->refcount == 0) {
|
236
|
+
#ifndef _WIN32
|
237
|
+
if (wrapper->connected && !wrapper->automatic_close) {
|
238
|
+
/* The client is being garbage collected while connected. Prevent
|
239
|
+
* mysql_close() from sending a mysql-QUIT or from calling shutdown() on
|
240
|
+
* the socket by invalidating it. invalidate_fd() will drop this
|
241
|
+
* process's reference to the socket only, while a QUIT or shutdown()
|
242
|
+
* would render the underlying connection unusable, interrupting other
|
243
|
+
* processes which share this object across a fork().
|
244
|
+
*/
|
245
|
+
if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
|
246
|
+
fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
|
247
|
+
close(wrapper->client->net.fd);
|
248
|
+
}
|
249
|
+
}
|
250
|
+
#endif
|
251
|
+
|
251
252
|
nogvl_close(wrapper);
|
252
|
-
xfree(wrapper->client);
|
253
253
|
xfree(wrapper);
|
254
254
|
}
|
255
255
|
}
|
@@ -260,6 +260,7 @@ static VALUE allocate(VALUE klass) {
|
|
260
260
|
obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
|
261
261
|
wrapper->encoding = Qnil;
|
262
262
|
wrapper->active_thread = Qnil;
|
263
|
+
wrapper->automatic_close = 1;
|
263
264
|
wrapper->server_version = 0;
|
264
265
|
wrapper->reconnect_enabled = 0;
|
265
266
|
wrapper->connect_timeout = 0;
|
@@ -267,6 +268,7 @@ static VALUE allocate(VALUE klass) {
|
|
267
268
|
wrapper->initialized = 0; /* means that that the wrapper is initialized */
|
268
269
|
wrapper->refcount = 1;
|
269
270
|
wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
|
271
|
+
|
270
272
|
return obj;
|
271
273
|
}
|
272
274
|
|
@@ -287,7 +289,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
|
|
287
289
|
oldLen = RSTRING_LEN(str);
|
288
290
|
newStr = xmalloc(oldLen*2+1);
|
289
291
|
|
290
|
-
newLen = mysql_escape_string((char *)newStr,
|
292
|
+
newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen);
|
291
293
|
if (newLen == oldLen) {
|
292
294
|
/* no need to return a new ruby string if nothing changed */
|
293
295
|
xfree(newStr);
|
@@ -330,20 +332,39 @@ static VALUE rb_mysql_info(VALUE self) {
|
|
330
332
|
return rb_str;
|
331
333
|
}
|
332
334
|
|
335
|
+
static VALUE rb_mysql_get_ssl_cipher(VALUE self)
|
336
|
+
{
|
337
|
+
const char *cipher;
|
338
|
+
VALUE rb_str;
|
339
|
+
GET_CLIENT(self);
|
340
|
+
|
341
|
+
cipher = mysql_get_ssl_cipher(wrapper->client);
|
342
|
+
|
343
|
+
if (cipher == NULL) {
|
344
|
+
return Qnil;
|
345
|
+
}
|
346
|
+
|
347
|
+
rb_str = rb_str_new2(cipher);
|
348
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
349
|
+
rb_enc_associate(rb_str, rb_utf8_encoding());
|
350
|
+
#endif
|
351
|
+
|
352
|
+
return rb_str;
|
353
|
+
}
|
354
|
+
|
333
355
|
static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
|
334
356
|
struct nogvl_connect_args args;
|
335
|
-
time_t start_time, end_time;
|
336
|
-
unsigned int elapsed_time, connect_timeout;
|
357
|
+
time_t start_time, end_time, elapsed_time, connect_timeout;
|
337
358
|
VALUE rv;
|
338
359
|
GET_CLIENT(self);
|
339
360
|
|
340
|
-
args.host
|
341
|
-
args.unix_socket = NIL_P(socket)
|
342
|
-
args.port
|
343
|
-
args.user
|
344
|
-
args.passwd
|
345
|
-
args.db
|
346
|
-
args.mysql
|
361
|
+
args.host = NIL_P(host) ? NULL : StringValueCStr(host);
|
362
|
+
args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket);
|
363
|
+
args.port = NIL_P(port) ? 0 : NUM2INT(port);
|
364
|
+
args.user = NIL_P(user) ? NULL : StringValueCStr(user);
|
365
|
+
args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass);
|
366
|
+
args.db = NIL_P(database) ? NULL : StringValueCStr(database);
|
367
|
+
args.mysql = wrapper->client;
|
347
368
|
args.client_flag = NUM2ULONG(flags);
|
348
369
|
|
349
370
|
if (wrapper->connect_timeout)
|
@@ -360,7 +381,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
360
381
|
/* avoid an early timeout due to time truncating milliseconds off the start time */
|
361
382
|
if (elapsed_time > 0)
|
362
383
|
elapsed_time--;
|
363
|
-
if (elapsed_time >= wrapper->connect_timeout)
|
384
|
+
if (elapsed_time >= (time_t)wrapper->connect_timeout)
|
364
385
|
break;
|
365
386
|
connect_timeout = wrapper->connect_timeout - elapsed_time;
|
366
387
|
mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
|
@@ -372,7 +393,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
372
393
|
if (wrapper->connect_timeout)
|
373
394
|
mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
|
374
395
|
if (rv == Qfalse)
|
375
|
-
|
396
|
+
rb_raise_mysql2_error(wrapper);
|
376
397
|
}
|
377
398
|
|
378
399
|
wrapper->server_version = mysql_get_server_version(wrapper->client);
|
@@ -381,10 +402,12 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
|
|
381
402
|
}
|
382
403
|
|
383
404
|
/*
|
384
|
-
* Immediately disconnect from the server
|
405
|
+
* Immediately disconnect from the server; normally the garbage collector
|
385
406
|
* will disconnect automatically when a connection is no longer needed.
|
386
407
|
* Explicitly closing this will free up server resources sooner than waiting
|
387
408
|
* for the garbage collector.
|
409
|
+
*
|
410
|
+
* @return [nil]
|
388
411
|
*/
|
389
412
|
static VALUE rb_mysql_client_close(VALUE self) {
|
390
413
|
GET_CLIENT(self);
|
@@ -415,8 +438,8 @@ static VALUE do_send_query(void *args) {
|
|
415
438
|
mysql_client_wrapper *wrapper = query_args->wrapper;
|
416
439
|
if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
|
417
440
|
/* an error occurred, we're not active anymore */
|
418
|
-
|
419
|
-
|
441
|
+
wrapper->active_thread = Qnil;
|
442
|
+
rb_raise_mysql2_error(wrapper);
|
420
443
|
}
|
421
444
|
return Qnil;
|
422
445
|
}
|
@@ -434,10 +457,9 @@ static void *nogvl_read_query_result(void *ptr) {
|
|
434
457
|
}
|
435
458
|
|
436
459
|
static void *nogvl_do_result(void *ptr, char use_result) {
|
437
|
-
mysql_client_wrapper *wrapper;
|
460
|
+
mysql_client_wrapper *wrapper = ptr;
|
438
461
|
MYSQL_RES *result;
|
439
462
|
|
440
|
-
wrapper = (mysql_client_wrapper *)ptr;
|
441
463
|
if (use_result) {
|
442
464
|
result = mysql_use_result(wrapper->client);
|
443
465
|
} else {
|
@@ -478,8 +500,8 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
|
|
478
500
|
REQUIRE_CONNECTED(wrapper);
|
479
501
|
if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
|
480
502
|
/* an error occurred, mark this connection inactive */
|
481
|
-
|
482
|
-
|
503
|
+
wrapper->active_thread = Qnil;
|
504
|
+
rb_raise_mysql2_error(wrapper);
|
483
505
|
}
|
484
506
|
|
485
507
|
is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
|
@@ -491,7 +513,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
|
|
491
513
|
|
492
514
|
if (result == NULL) {
|
493
515
|
if (mysql_errno(wrapper->client) != 0) {
|
494
|
-
|
516
|
+
wrapper->active_thread = Qnil;
|
495
517
|
rb_raise_mysql2_error(wrapper);
|
496
518
|
}
|
497
519
|
/* no data and no error, so query was not a SELECT */
|
@@ -499,9 +521,9 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
|
|
499
521
|
}
|
500
522
|
|
501
523
|
current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
|
502
|
-
RB_GC_GUARD(current);
|
524
|
+
(void)RB_GC_GUARD(current);
|
503
525
|
Check_Type(current, T_HASH);
|
504
|
-
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
|
526
|
+
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
|
505
527
|
|
506
528
|
return resultObj;
|
507
529
|
}
|
@@ -527,19 +549,16 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
|
|
527
549
|
}
|
528
550
|
|
529
551
|
rb_exc_raise(error);
|
530
|
-
|
531
|
-
return Qnil;
|
532
552
|
}
|
533
553
|
|
534
554
|
static VALUE do_query(void *args) {
|
535
|
-
struct async_query_args *async_args;
|
555
|
+
struct async_query_args *async_args = args;
|
536
556
|
struct timeval tv;
|
537
|
-
struct timeval*
|
557
|
+
struct timeval *tvp;
|
538
558
|
long int sec;
|
539
559
|
int retval;
|
540
560
|
VALUE read_timeout;
|
541
561
|
|
542
|
-
async_args = (struct async_query_args *)args;
|
543
562
|
read_timeout = rb_iv_get(async_args->self, "@read_timeout");
|
544
563
|
|
545
564
|
tvp = NULL;
|
@@ -577,11 +596,9 @@ static VALUE do_query(void *args) {
|
|
577
596
|
}
|
578
597
|
#else
|
579
598
|
static VALUE finish_and_mark_inactive(void *args) {
|
580
|
-
VALUE self;
|
599
|
+
VALUE self = args;
|
581
600
|
MYSQL_RES *result;
|
582
601
|
|
583
|
-
self = (VALUE)args;
|
584
|
-
|
585
602
|
GET_CLIENT(self);
|
586
603
|
|
587
604
|
if (!NIL_P(wrapper->active_thread)) {
|
@@ -598,6 +615,24 @@ static VALUE finish_and_mark_inactive(void *args) {
|
|
598
615
|
}
|
599
616
|
#endif
|
600
617
|
|
618
|
+
void rb_mysql_client_set_active_thread(VALUE self) {
|
619
|
+
VALUE thread_current = rb_thread_current();
|
620
|
+
GET_CLIENT(self);
|
621
|
+
|
622
|
+
// see if this connection is still waiting on a result from a previous query
|
623
|
+
if (NIL_P(wrapper->active_thread)) {
|
624
|
+
// mark this connection active
|
625
|
+
wrapper->active_thread = thread_current;
|
626
|
+
} else if (wrapper->active_thread == thread_current) {
|
627
|
+
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
|
628
|
+
} else {
|
629
|
+
VALUE inspect = rb_inspect(wrapper->active_thread);
|
630
|
+
const char *thr = StringValueCStr(inspect);
|
631
|
+
|
632
|
+
rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
|
633
|
+
}
|
634
|
+
}
|
635
|
+
|
601
636
|
/* call-seq:
|
602
637
|
* client.abandon_results!
|
603
638
|
*
|
@@ -632,74 +667,47 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
|
|
632
667
|
* client.query(sql, options = {})
|
633
668
|
*
|
634
669
|
* Query the database with +sql+, with optional +options+. For the possible
|
635
|
-
* options, see
|
670
|
+
* options, see default_query_options on the Mysql2::Client class.
|
636
671
|
*/
|
637
|
-
static VALUE
|
672
|
+
static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
|
638
673
|
#ifndef _WIN32
|
639
674
|
struct async_query_args async_args;
|
640
675
|
#endif
|
641
676
|
struct nogvl_send_query_args args;
|
642
|
-
int async = 0;
|
643
|
-
VALUE opts, current;
|
644
|
-
VALUE thread_current = rb_thread_current();
|
645
|
-
#ifdef HAVE_RUBY_ENCODING_H
|
646
|
-
rb_encoding *conn_enc;
|
647
|
-
#endif
|
648
677
|
GET_CLIENT(self);
|
649
678
|
|
650
679
|
REQUIRE_CONNECTED(wrapper);
|
651
680
|
args.mysql = wrapper->client;
|
652
681
|
|
653
|
-
|
654
|
-
RB_GC_GUARD(current);
|
682
|
+
(void)RB_GC_GUARD(current);
|
655
683
|
Check_Type(current, T_HASH);
|
656
684
|
rb_iv_set(self, "@current_query_options", current);
|
657
685
|
|
658
|
-
|
659
|
-
rb_funcall(current, intern_merge_bang, 1, opts);
|
660
|
-
|
661
|
-
if (rb_hash_aref(current, sym_async) == Qtrue) {
|
662
|
-
async = 1;
|
663
|
-
}
|
664
|
-
}
|
665
|
-
|
666
|
-
Check_Type(args.sql, T_STRING);
|
686
|
+
Check_Type(sql, T_STRING);
|
667
687
|
#ifdef HAVE_RUBY_ENCODING_H
|
668
|
-
conn_enc = rb_to_encoding(wrapper->encoding);
|
669
688
|
/* ensure the string is in the encoding the connection is expecting */
|
670
|
-
args.sql = rb_str_export_to_enc(
|
689
|
+
args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
|
690
|
+
#else
|
691
|
+
args.sql = sql;
|
671
692
|
#endif
|
672
|
-
args.sql_ptr =
|
693
|
+
args.sql_ptr = RSTRING_PTR(args.sql);
|
673
694
|
args.sql_len = RSTRING_LEN(args.sql);
|
674
|
-
|
675
|
-
/* see if this connection is still waiting on a result from a previous query */
|
676
|
-
if (NIL_P(wrapper->active_thread)) {
|
677
|
-
/* mark this connection active */
|
678
|
-
wrapper->active_thread = thread_current;
|
679
|
-
} else if (wrapper->active_thread == thread_current) {
|
680
|
-
rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
|
681
|
-
} else {
|
682
|
-
VALUE inspect = rb_inspect(wrapper->active_thread);
|
683
|
-
const char *thr = StringValueCStr(inspect);
|
684
|
-
|
685
|
-
rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
|
686
|
-
RB_GC_GUARD(inspect);
|
687
|
-
}
|
688
|
-
|
689
695
|
args.wrapper = wrapper;
|
690
696
|
|
697
|
+
rb_mysql_client_set_active_thread(self);
|
698
|
+
|
691
699
|
#ifndef _WIN32
|
692
700
|
rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
|
693
701
|
|
694
|
-
if (
|
702
|
+
if (rb_hash_aref(current, sym_async) == Qtrue) {
|
703
|
+
return Qnil;
|
704
|
+
} else {
|
695
705
|
async_args.fd = wrapper->client->net.fd;
|
696
706
|
async_args.self = self;
|
697
707
|
|
698
708
|
rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
|
699
709
|
|
700
710
|
return rb_mysql_client_async_result(self);
|
701
|
-
} else {
|
702
|
-
return Qnil;
|
703
711
|
}
|
704
712
|
#else
|
705
713
|
do_send_query(&args);
|
@@ -736,9 +744,14 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
|
|
736
744
|
oldLen = RSTRING_LEN(str);
|
737
745
|
newStr = xmalloc(oldLen*2+1);
|
738
746
|
|
739
|
-
newLen = mysql_real_escape_string(wrapper->client, (char *)newStr,
|
747
|
+
newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
|
740
748
|
if (newLen == oldLen) {
|
741
749
|
/* no need to return a new ruby string if nothing changed */
|
750
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
751
|
+
if (default_internal_enc) {
|
752
|
+
str = rb_str_export_to_enc(str, default_internal_enc);
|
753
|
+
}
|
754
|
+
#endif
|
742
755
|
xfree(newStr);
|
743
756
|
return str;
|
744
757
|
} else {
|
@@ -800,17 +813,17 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
|
|
800
813
|
break;
|
801
814
|
|
802
815
|
case MYSQL_READ_DEFAULT_FILE:
|
803
|
-
charval = (const char *)
|
816
|
+
charval = (const char *)StringValueCStr(value);
|
804
817
|
retval = charval;
|
805
818
|
break;
|
806
819
|
|
807
820
|
case MYSQL_READ_DEFAULT_GROUP:
|
808
|
-
charval = (const char *)
|
821
|
+
charval = (const char *)StringValueCStr(value);
|
809
822
|
retval = charval;
|
810
823
|
break;
|
811
824
|
|
812
825
|
case MYSQL_INIT_COMMAND:
|
813
|
-
charval = (const char *)
|
826
|
+
charval = (const char *)StringValueCStr(value);
|
814
827
|
retval = charval;
|
815
828
|
break;
|
816
829
|
|
@@ -843,30 +856,23 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
|
|
843
856
|
*
|
844
857
|
* Returns a string that represents the client library version.
|
845
858
|
*/
|
846
|
-
static VALUE rb_mysql_client_info(VALUE
|
847
|
-
VALUE version,
|
848
|
-
|
849
|
-
rb_encoding *default_internal_enc;
|
850
|
-
rb_encoding *conn_enc;
|
851
|
-
GET_CLIENT(self);
|
852
|
-
#endif
|
853
|
-
version = rb_hash_new();
|
859
|
+
static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
|
860
|
+
VALUE version_info, version, header_version;
|
861
|
+
version_info = rb_hash_new();
|
854
862
|
|
855
|
-
|
856
|
-
|
857
|
-
conn_enc = rb_to_encoding(wrapper->encoding);
|
858
|
-
#endif
|
863
|
+
version = rb_str_new2(mysql_get_client_info());
|
864
|
+
header_version = rb_str_new2(MYSQL_LINK_VERSION);
|
859
865
|
|
860
|
-
rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
|
861
|
-
client_info = rb_str_new2(mysql_get_client_info());
|
862
866
|
#ifdef HAVE_RUBY_ENCODING_H
|
863
|
-
rb_enc_associate(
|
864
|
-
|
865
|
-
client_info = rb_str_export_to_enc(client_info, default_internal_enc);
|
866
|
-
}
|
867
|
+
rb_enc_associate(version, rb_usascii_encoding());
|
868
|
+
rb_enc_associate(header_version, rb_usascii_encoding());
|
867
869
|
#endif
|
868
|
-
|
869
|
-
|
870
|
+
|
871
|
+
rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
|
872
|
+
rb_hash_aset(version_info, sym_version, version);
|
873
|
+
rb_hash_aset(version_info, sym_header_version, header_version);
|
874
|
+
|
875
|
+
return version_info;
|
870
876
|
}
|
871
877
|
|
872
878
|
/* call-seq:
|
@@ -906,19 +912,17 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
|
|
906
912
|
*
|
907
913
|
* Return the file descriptor number for this client.
|
908
914
|
*/
|
915
|
+
#ifndef _WIN32
|
909
916
|
static VALUE rb_mysql_client_socket(VALUE self) {
|
910
917
|
GET_CLIENT(self);
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
REQUIRE_CONNECTED(wrapper);
|
915
|
-
fd_set_fd = wrapper->client->net.fd;
|
916
|
-
return INT2NUM(fd_set_fd);
|
917
|
-
}
|
918
|
+
REQUIRE_CONNECTED(wrapper);
|
919
|
+
return INT2NUM(wrapper->client->net.fd);
|
920
|
+
}
|
918
921
|
#else
|
922
|
+
static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
|
919
923
|
rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
|
920
|
-
#endif
|
921
924
|
}
|
925
|
+
#endif
|
922
926
|
|
923
927
|
/* call-seq:
|
924
928
|
* client.last_id
|
@@ -987,7 +991,7 @@ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db)
|
|
987
991
|
REQUIRE_CONNECTED(wrapper);
|
988
992
|
|
989
993
|
args.mysql = wrapper->client;
|
990
|
-
args.db =
|
994
|
+
args.db = StringValueCStr(db);
|
991
995
|
|
992
996
|
if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse)
|
993
997
|
rb_raise_mysql2_error(wrapper);
|
@@ -1027,10 +1031,10 @@ static VALUE rb_mysql_client_ping(VALUE self) {
|
|
1027
1031
|
static VALUE rb_mysql_client_more_results(VALUE self)
|
1028
1032
|
{
|
1029
1033
|
GET_CLIENT(self);
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
+
if (mysql_more_results(wrapper->client) == 0)
|
1035
|
+
return Qfalse;
|
1036
|
+
else
|
1037
|
+
return Qtrue;
|
1034
1038
|
}
|
1035
1039
|
|
1036
1040
|
/* call-seq:
|
@@ -1078,9 +1082,9 @@ static VALUE rb_mysql_client_store_result(VALUE self)
|
|
1078
1082
|
}
|
1079
1083
|
|
1080
1084
|
current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
|
1081
|
-
RB_GC_GUARD(current);
|
1085
|
+
(void)RB_GC_GUARD(current);
|
1082
1086
|
Check_Type(current, T_HASH);
|
1083
|
-
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
|
1087
|
+
resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
|
1084
1088
|
|
1085
1089
|
return resultObj;
|
1086
1090
|
}
|
@@ -1097,6 +1101,39 @@ static VALUE rb_mysql_client_encoding(VALUE self) {
|
|
1097
1101
|
}
|
1098
1102
|
#endif
|
1099
1103
|
|
1104
|
+
/* call-seq:
|
1105
|
+
* client.automatic_close?
|
1106
|
+
*
|
1107
|
+
* @return [Boolean]
|
1108
|
+
*/
|
1109
|
+
static VALUE get_automatic_close(VALUE self) {
|
1110
|
+
GET_CLIENT(self);
|
1111
|
+
return wrapper->automatic_close ? Qtrue : Qfalse;
|
1112
|
+
}
|
1113
|
+
|
1114
|
+
/* call-seq:
|
1115
|
+
* client.automatic_close = false
|
1116
|
+
*
|
1117
|
+
* Set this to +false+ to leave the connection open after it is garbage
|
1118
|
+
* collected. To avoid "Aborted connection" errors on the server, explicitly
|
1119
|
+
* call +close+ when the connection is no longer needed.
|
1120
|
+
*
|
1121
|
+
* @see http://dev.mysql.com/doc/en/communication-errors.html
|
1122
|
+
*/
|
1123
|
+
static VALUE set_automatic_close(VALUE self, VALUE value) {
|
1124
|
+
GET_CLIENT(self);
|
1125
|
+
if (RTEST(value)) {
|
1126
|
+
wrapper->automatic_close = 1;
|
1127
|
+
} else {
|
1128
|
+
#ifndef _WIN32
|
1129
|
+
wrapper->automatic_close = 0;
|
1130
|
+
#else
|
1131
|
+
rb_warn("Connections are always closed by garbage collector on Windows");
|
1132
|
+
#endif
|
1133
|
+
}
|
1134
|
+
return value;
|
1135
|
+
}
|
1136
|
+
|
1100
1137
|
/* call-seq:
|
1101
1138
|
* client.reconnect = true
|
1102
1139
|
*
|
@@ -1149,7 +1186,6 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
|
|
1149
1186
|
static VALUE set_charset_name(VALUE self, VALUE value) {
|
1150
1187
|
char *charset_name;
|
1151
1188
|
#ifdef HAVE_RUBY_ENCODING_H
|
1152
|
-
size_t charset_name_len;
|
1153
1189
|
const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
|
1154
1190
|
rb_encoding *enc;
|
1155
1191
|
VALUE rb_enc;
|
@@ -1159,8 +1195,7 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
|
|
1159
1195
|
charset_name = RSTRING_PTR(value);
|
1160
1196
|
|
1161
1197
|
#ifdef HAVE_RUBY_ENCODING_H
|
1162
|
-
|
1163
|
-
mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len);
|
1198
|
+
mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
|
1164
1199
|
if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
|
1165
1200
|
VALUE inspect = rb_inspect(value);
|
1166
1201
|
rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
|
@@ -1183,11 +1218,11 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
|
|
1183
1218
|
GET_CLIENT(self);
|
1184
1219
|
|
1185
1220
|
mysql_ssl_set(wrapper->client,
|
1186
|
-
NIL_P(key)
|
1187
|
-
NIL_P(cert)
|
1188
|
-
NIL_P(ca)
|
1189
|
-
NIL_P(capath) ? NULL :
|
1190
|
-
NIL_P(cipher) ? NULL :
|
1221
|
+
NIL_P(key) ? NULL : StringValueCStr(key),
|
1222
|
+
NIL_P(cert) ? NULL : StringValueCStr(cert),
|
1223
|
+
NIL_P(ca) ? NULL : StringValueCStr(ca),
|
1224
|
+
NIL_P(capath) ? NULL : StringValueCStr(capath),
|
1225
|
+
NIL_P(cipher) ? NULL : StringValueCStr(cipher));
|
1191
1226
|
|
1192
1227
|
return self;
|
1193
1228
|
}
|
@@ -1213,14 +1248,26 @@ static VALUE initialize_ext(VALUE self) {
|
|
1213
1248
|
|
1214
1249
|
if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
|
1215
1250
|
/* TODO: warning - not enough memory? */
|
1216
|
-
|
1251
|
+
rb_raise_mysql2_error(wrapper);
|
1217
1252
|
}
|
1218
1253
|
|
1219
1254
|
wrapper->initialized = 1;
|
1220
1255
|
return self;
|
1221
1256
|
}
|
1222
1257
|
|
1258
|
+
/* call-seq: client.prepare # => Mysql2::Statement
|
1259
|
+
*
|
1260
|
+
* Create a new prepared statement.
|
1261
|
+
*/
|
1262
|
+
static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
|
1263
|
+
GET_CLIENT(self);
|
1264
|
+
REQUIRE_CONNECTED(wrapper);
|
1265
|
+
|
1266
|
+
return rb_mysql_stmt_new(self, sql);
|
1267
|
+
}
|
1268
|
+
|
1223
1269
|
void init_mysql2_client() {
|
1270
|
+
#ifdef _WIN32
|
1224
1271
|
/* verify the libmysql we're about to use was the version we were built against
|
1225
1272
|
https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
|
1226
1273
|
int i;
|
@@ -1235,15 +1282,14 @@ void init_mysql2_client() {
|
|
1235
1282
|
}
|
1236
1283
|
if (lib[i] != MYSQL_LINK_VERSION[i]) {
|
1237
1284
|
rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib);
|
1238
|
-
return;
|
1239
1285
|
}
|
1240
1286
|
}
|
1287
|
+
#endif
|
1241
1288
|
|
1242
1289
|
/* Initializing mysql library, so different threads could call Client.new */
|
1243
1290
|
/* without race condition in the library */
|
1244
1291
|
if (mysql_library_init(0, NULL, NULL) != 0) {
|
1245
1292
|
rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
|
1246
|
-
return;
|
1247
1293
|
}
|
1248
1294
|
|
1249
1295
|
#if 0
|
@@ -1254,26 +1300,29 @@ void init_mysql2_client() {
|
|
1254
1300
|
rb_define_alloc_func(cMysql2Client, allocate);
|
1255
1301
|
|
1256
1302
|
rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
|
1303
|
+
rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
1257
1304
|
|
1258
1305
|
rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
|
1259
|
-
rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
|
1260
1306
|
rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
|
1261
1307
|
rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
|
1262
|
-
rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
|
1263
1308
|
rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
|
1264
1309
|
rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
|
1265
1310
|
rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
|
1266
1311
|
rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
|
1267
1312
|
rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
|
1313
|
+
rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
|
1268
1314
|
rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
|
1269
1315
|
rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
|
1270
1316
|
rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
|
1271
1317
|
rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0);
|
1272
1318
|
rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
|
1273
1319
|
rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
|
1320
|
+
rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0);
|
1321
|
+
rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1);
|
1274
1322
|
rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1);
|
1275
1323
|
rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0);
|
1276
1324
|
rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0);
|
1325
|
+
rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0);
|
1277
1326
|
#ifdef HAVE_RUBY_ENCODING_H
|
1278
1327
|
rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
|
1279
1328
|
#endif
|
@@ -1290,19 +1339,21 @@ void init_mysql2_client() {
|
|
1290
1339
|
rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
|
1291
1340
|
rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
|
1292
1341
|
rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
|
1342
|
+
rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
|
1293
1343
|
|
1294
1344
|
sym_id = ID2SYM(rb_intern("id"));
|
1295
1345
|
sym_version = ID2SYM(rb_intern("version"));
|
1346
|
+
sym_header_version = ID2SYM(rb_intern("header_version"));
|
1296
1347
|
sym_async = ID2SYM(rb_intern("async"));
|
1297
1348
|
sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
|
1298
1349
|
sym_as = ID2SYM(rb_intern("as"));
|
1299
1350
|
sym_array = ID2SYM(rb_intern("array"));
|
1300
1351
|
sym_stream = ID2SYM(rb_intern("stream"));
|
1301
1352
|
|
1353
|
+
intern_brackets = rb_intern("[]");
|
1302
1354
|
intern_merge = rb_intern("merge");
|
1303
1355
|
intern_merge_bang = rb_intern("merge!");
|
1304
|
-
|
1305
|
-
intern_sql_state_eql = rb_intern("sql_state=");
|
1356
|
+
intern_new_with_args = rb_intern("new_with_args");
|
1306
1357
|
|
1307
1358
|
#ifdef CLIENT_LONG_PASSWORD
|
1308
1359
|
rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
|