mysql2 0.3.20 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +132 -55
  4. data/examples/eventmachine.rb +1 -1
  5. data/examples/threaded.rb +4 -6
  6. data/ext/mysql2/client.c +323 -140
  7. data/ext/mysql2/client.h +13 -3
  8. data/ext/mysql2/extconf.rb +111 -44
  9. data/ext/mysql2/infile.c +2 -2
  10. data/ext/mysql2/mysql2_ext.c +1 -0
  11. data/ext/mysql2/mysql2_ext.h +5 -10
  12. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  13. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  14. data/ext/mysql2/result.c +491 -97
  15. data/ext/mysql2/result.h +12 -4
  16. data/ext/mysql2/statement.c +595 -0
  17. data/ext/mysql2/statement.h +19 -0
  18. data/lib/mysql2/client.rb +82 -27
  19. data/lib/mysql2/console.rb +1 -1
  20. data/lib/mysql2/em.rb +5 -6
  21. data/lib/mysql2/error.rb +17 -26
  22. data/lib/mysql2/field.rb +3 -0
  23. data/lib/mysql2/statement.rb +17 -0
  24. data/lib/mysql2/version.rb +1 -1
  25. data/lib/mysql2.rb +38 -18
  26. data/spec/configuration.yml.example +0 -6
  27. data/spec/em/em_spec.rb +22 -21
  28. data/spec/mysql2/client_spec.rb +498 -380
  29. data/spec/mysql2/error_spec.rb +38 -39
  30. data/spec/mysql2/result_spec.rb +260 -229
  31. data/spec/mysql2/statement_spec.rb +776 -0
  32. data/spec/spec_helper.rb +80 -59
  33. data/spec/ssl/ca-cert.pem +17 -0
  34. data/spec/ssl/ca-key.pem +27 -0
  35. data/spec/ssl/ca.cnf +22 -0
  36. data/spec/ssl/cert.cnf +22 -0
  37. data/spec/ssl/client-cert.pem +17 -0
  38. data/spec/ssl/client-key.pem +27 -0
  39. data/spec/ssl/client-req.pem +15 -0
  40. data/spec/ssl/gen_certs.sh +48 -0
  41. data/spec/ssl/pkcs8-client-key.pem +28 -0
  42. data/spec/ssl/pkcs8-server-key.pem +28 -0
  43. data/spec/ssl/server-cert.pem +17 -0
  44. data/spec/ssl/server-key.pem +27 -0
  45. data/spec/ssl/server-req.pem +15 -0
  46. data/support/5072E1F5.asc +432 -0
  47. data/support/mysql_enc_to_ruby.rb +7 -8
  48. data/support/ruby_enc_to_mysql.rb +1 -1
  49. metadata +50 -55
data/ext/mysql2/client.c CHANGED
@@ -17,11 +17,11 @@
17
17
  VALUE cMysql2Client;
18
18
  extern VALUE mMysql2, cMysql2Error;
19
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_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql;
20
+ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
21
21
 
22
22
  #ifndef HAVE_RB_HASH_DUP
23
- static VALUE rb_hash_dup(VALUE other) {
24
- return rb_funcall(rb_cHash, rb_intern("[]"), 1, other);
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,36 +30,47 @@ static VALUE rb_hash_dup(VALUE other) {
30
30
  rb_raise(cMysql2Error, "MySQL client is not initialized"); \
31
31
  }
32
32
 
33
+ #if defined(HAVE_MYSQL_NET_VIO) || defined(HAVE_ST_NET_VIO)
34
+ #define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1)
35
+ #elif defined(HAVE_MYSQL_NET_PVIO) || defined(HAVE_ST_NET_PVIO)
36
+ #define CONNECTED(wrapper) (wrapper->client->net.pvio != NULL && wrapper->client->net.fd != -1)
37
+ #endif
38
+
33
39
  #define REQUIRE_CONNECTED(wrapper) \
34
40
  REQUIRE_INITIALIZED(wrapper) \
35
- if (!wrapper->connected && !wrapper->reconnect_enabled) { \
36
- rb_raise(cMysql2Error, "closed MySQL connection"); \
41
+ if (!CONNECTED(wrapper) && !wrapper->reconnect_enabled) { \
42
+ rb_raise(cMysql2Error, "MySQL client is not connected"); \
37
43
  }
38
44
 
39
45
  #define REQUIRE_NOT_CONNECTED(wrapper) \
40
46
  REQUIRE_INITIALIZED(wrapper) \
41
- if (wrapper->connected) { \
47
+ if (CONNECTED(wrapper)) { \
42
48
  rb_raise(cMysql2Error, "MySQL connection is already open"); \
43
49
  }
44
50
 
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
51
  /*
53
52
  * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
54
53
  * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
55
54
  * linking against the server itself
56
55
  */
57
- #ifdef LIBMYSQL_VERSION
56
+ #if defined(MARIADB_CLIENT_VERSION_STR)
57
+ #define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR
58
+ #elif defined(LIBMYSQL_VERSION)
58
59
  #define MYSQL_LINK_VERSION LIBMYSQL_VERSION
59
60
  #else
60
61
  #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION
61
62
  #endif
62
63
 
64
+ /*
65
+ * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10.
66
+ */
67
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
68
+ #define SSL_MODE_DISABLED 1
69
+ #define SSL_MODE_REQUIRED 3
70
+ #define HAVE_CONST_SSL_MODE_DISABLED
71
+ #define HAVE_CONST_SSL_MODE_REQUIRED
72
+ #endif
73
+
63
74
  /*
64
75
  * used to pass all arguments to mysql_real_connect while inside
65
76
  * rb_thread_call_without_gvl
@@ -96,6 +107,42 @@ struct nogvl_select_db_args {
96
107
  char *db;
97
108
  };
98
109
 
110
+ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
111
+ unsigned long version = mysql_get_client_version();
112
+
113
+ if (version < 50703) {
114
+ rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." );
115
+ return Qnil;
116
+ }
117
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
118
+ GET_CLIENT(self);
119
+ int val = NUM2INT( setting );
120
+ if (version >= 50703 && version < 50711) {
121
+ if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
122
+ bool b = ( val == SSL_MODE_REQUIRED );
123
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b );
124
+ return INT2NUM(result);
125
+ } else {
126
+ rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" );
127
+ return Qnil;
128
+ }
129
+ }
130
+ #endif
131
+ #ifdef FULL_SSL_MODE_SUPPORT
132
+ GET_CLIENT(self);
133
+ int val = NUM2INT( setting );
134
+
135
+ if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
136
+ rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
137
+ }
138
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val );
139
+
140
+ return INT2NUM(result);
141
+ #endif
142
+ #ifdef NO_SSL_MODE_SUPPORT
143
+ return Qnil;
144
+ #endif
145
+ }
99
146
  /*
100
147
  * non-blocking mysql_*() functions that we won't be wrapping since
101
148
  * they do not appear to hit the network nor issue any interruptible
@@ -136,16 +183,17 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
136
183
  rb_enc_associate(rb_sql_state, rb_usascii_encoding());
137
184
  #endif
138
185
 
139
- e = rb_funcall(cMysql2Error, rb_intern("new"), 2, rb_error_msg, LONG2FIX(wrapper->server_version));
140
- rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
141
- rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
186
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
187
+ rb_error_msg,
188
+ LONG2FIX(wrapper->server_version),
189
+ UINT2NUM(mysql_errno(wrapper->client)),
190
+ rb_sql_state);
142
191
  rb_exc_raise(e);
143
- return Qnil;
144
192
  }
145
193
 
146
194
  static void *nogvl_init(void *ptr) {
147
195
  MYSQL *client;
148
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
196
+ mysql_client_wrapper *wrapper = ptr;
149
197
 
150
198
  /* may initialize embedded server and read /etc/services off disk */
151
199
  client = mysql_init(wrapper->client);
@@ -221,41 +269,46 @@ static VALUE invalidate_fd(int clientfd)
221
269
  #endif /* _WIN32 */
222
270
 
223
271
  static void *nogvl_close(void *ptr) {
224
- mysql_client_wrapper *wrapper;
225
- wrapper = ptr;
226
- if (wrapper->connected) {
227
- wrapper->active_thread = Qnil;
228
- wrapper->connected = 0;
229
- #ifndef _WIN32
230
- /* Invalidate the socket before calling mysql_close(). This prevents
231
- * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
232
- * the socket. The difference is that invalidate_fd will drop this
233
- * process's reference to the socket only, while a QUIT or shutdown()
234
- * would render the underlying connection unusable, interrupting other
235
- * processes which share this object across a fork().
236
- */
237
- if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
238
- fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, leaking some memory\n");
239
- close(wrapper->client->net.fd);
240
- return NULL;
241
- }
242
- #endif
272
+ mysql_client_wrapper *wrapper = ptr;
243
273
 
244
- mysql_close(wrapper->client); /* only used to free memory at this point */
274
+ if (!wrapper->closed) {
275
+ mysql_close(wrapper->client);
276
+ wrapper->closed = 1;
277
+ wrapper->reconnect_enabled = 0;
278
+ wrapper->active_thread = Qnil;
245
279
  }
246
280
 
247
281
  return NULL;
248
282
  }
249
283
 
284
+ /* this is called during GC */
250
285
  static void rb_mysql_client_free(void *ptr) {
251
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
286
+ mysql_client_wrapper *wrapper = ptr;
252
287
  decr_mysql2_client(wrapper);
253
288
  }
254
289
 
255
290
  void decr_mysql2_client(mysql_client_wrapper *wrapper)
256
291
  {
257
292
  wrapper->refcount--;
293
+
258
294
  if (wrapper->refcount == 0) {
295
+ #ifndef _WIN32
296
+ if (CONNECTED(wrapper) && !wrapper->automatic_close) {
297
+ /* The client is being garbage collected while connected. Prevent
298
+ * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
299
+ * the socket by invalidating it. invalidate_fd() will drop this
300
+ * process's reference to the socket only, while a QUIT or shutdown()
301
+ * would render the underlying connection unusable, interrupting other
302
+ * processes which share this object across a fork().
303
+ */
304
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
305
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
306
+ close(wrapper->client->net.fd);
307
+ }
308
+ wrapper->client->net.fd = -1;
309
+ }
310
+ #endif
311
+
259
312
  nogvl_close(wrapper);
260
313
  xfree(wrapper->client);
261
314
  xfree(wrapper);
@@ -268,13 +321,15 @@ static VALUE allocate(VALUE klass) {
268
321
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
269
322
  wrapper->encoding = Qnil;
270
323
  wrapper->active_thread = Qnil;
324
+ wrapper->automatic_close = 1;
271
325
  wrapper->server_version = 0;
272
326
  wrapper->reconnect_enabled = 0;
273
327
  wrapper->connect_timeout = 0;
274
- wrapper->connected = 0; /* means that a database connection is open */
275
328
  wrapper->initialized = 0; /* means that that the wrapper is initialized */
276
329
  wrapper->refcount = 1;
330
+ wrapper->closed = 0;
277
331
  wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
332
+
278
333
  return obj;
279
334
  }
280
335
 
@@ -338,10 +393,29 @@ static VALUE rb_mysql_info(VALUE self) {
338
393
  return rb_str;
339
394
  }
340
395
 
396
+ static VALUE rb_mysql_get_ssl_cipher(VALUE self)
397
+ {
398
+ const char *cipher;
399
+ VALUE rb_str;
400
+ GET_CLIENT(self);
401
+
402
+ cipher = mysql_get_ssl_cipher(wrapper->client);
403
+
404
+ if (cipher == NULL) {
405
+ return Qnil;
406
+ }
407
+
408
+ rb_str = rb_str_new2(cipher);
409
+ #ifdef HAVE_RUBY_ENCODING_H
410
+ rb_enc_associate(rb_str, rb_utf8_encoding());
411
+ #endif
412
+
413
+ return rb_str;
414
+ }
415
+
341
416
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
342
417
  struct nogvl_connect_args args;
343
- time_t start_time, end_time;
344
- unsigned int elapsed_time, connect_timeout;
418
+ time_t start_time, end_time, elapsed_time, connect_timeout;
345
419
  VALUE rv;
346
420
  GET_CLIENT(self);
347
421
 
@@ -368,7 +442,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
368
442
  /* avoid an early timeout due to time truncating milliseconds off the start time */
369
443
  if (elapsed_time > 0)
370
444
  elapsed_time--;
371
- if (elapsed_time >= wrapper->connect_timeout)
445
+ if (elapsed_time >= (time_t)wrapper->connect_timeout)
372
446
  break;
373
447
  connect_timeout = wrapper->connect_timeout - elapsed_time;
374
448
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
@@ -380,30 +454,41 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
380
454
  if (wrapper->connect_timeout)
381
455
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
382
456
  if (rv == Qfalse)
383
- return rb_raise_mysql2_error(wrapper);
457
+ rb_raise_mysql2_error(wrapper);
384
458
  }
385
459
 
386
460
  wrapper->server_version = mysql_get_server_version(wrapper->client);
387
- wrapper->connected = 1;
388
461
  return self;
389
462
  }
390
463
 
391
464
  /*
392
- * Immediately disconnect from the server, normally the garbage collector
465
+ * Immediately disconnect from the server; normally the garbage collector
393
466
  * will disconnect automatically when a connection is no longer needed.
394
467
  * Explicitly closing this will free up server resources sooner than waiting
395
468
  * for the garbage collector.
469
+ *
470
+ * @return [nil]
396
471
  */
397
472
  static VALUE rb_mysql_client_close(VALUE self) {
398
473
  GET_CLIENT(self);
399
474
 
400
- if (wrapper->connected) {
475
+ if (wrapper->client) {
401
476
  rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
402
477
  }
403
478
 
404
479
  return Qnil;
405
480
  }
406
481
 
482
+ /* call-seq:
483
+ * client.closed?
484
+ *
485
+ * @return [Boolean]
486
+ */
487
+ static VALUE rb_mysql_client_closed(VALUE self) {
488
+ GET_CLIENT(self);
489
+ return CONNECTED(wrapper) ? Qfalse : Qtrue;
490
+ }
491
+
407
492
  /*
408
493
  * mysql_send_query is unlikely to block since most queries are small
409
494
  * enough to fit in a socket buffer, but sometimes large UPDATE and
@@ -423,8 +508,8 @@ static VALUE do_send_query(void *args) {
423
508
  mysql_client_wrapper *wrapper = query_args->wrapper;
424
509
  if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
425
510
  /* an error occurred, we're not active anymore */
426
- MARK_CONN_INACTIVE(self);
427
- return rb_raise_mysql2_error(wrapper);
511
+ wrapper->active_thread = Qnil;
512
+ rb_raise_mysql2_error(wrapper);
428
513
  }
429
514
  return Qnil;
430
515
  }
@@ -436,16 +521,15 @@ static VALUE do_send_query(void *args) {
436
521
  */
437
522
  static void *nogvl_read_query_result(void *ptr) {
438
523
  MYSQL * client = ptr;
439
- my_bool res = mysql_read_query_result(client);
524
+ bool res = mysql_read_query_result(client);
440
525
 
441
526
  return (void *)(res == 0 ? Qtrue : Qfalse);
442
527
  }
443
528
 
444
529
  static void *nogvl_do_result(void *ptr, char use_result) {
445
- mysql_client_wrapper *wrapper;
530
+ mysql_client_wrapper *wrapper = ptr;
446
531
  MYSQL_RES *result;
447
532
 
448
- wrapper = (mysql_client_wrapper *)ptr;
449
533
  if (use_result) {
450
534
  result = mysql_use_result(wrapper->client);
451
535
  } else {
@@ -486,8 +570,8 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
486
570
  REQUIRE_CONNECTED(wrapper);
487
571
  if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
488
572
  /* an error occurred, mark this connection inactive */
489
- MARK_CONN_INACTIVE(self);
490
- return rb_raise_mysql2_error(wrapper);
573
+ wrapper->active_thread = Qnil;
574
+ rb_raise_mysql2_error(wrapper);
491
575
  }
492
576
 
493
577
  is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
@@ -499,7 +583,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
499
583
 
500
584
  if (result == NULL) {
501
585
  if (mysql_errno(wrapper->client) != 0) {
502
- MARK_CONN_INACTIVE(self);
586
+ wrapper->active_thread = Qnil;
503
587
  rb_raise_mysql2_error(wrapper);
504
588
  }
505
589
  /* no data and no error, so query was not a SELECT */
@@ -507,9 +591,9 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
507
591
  }
508
592
 
509
593
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
510
- RB_GC_GUARD(current);
594
+ (void)RB_GC_GUARD(current);
511
595
  Check_Type(current, T_HASH);
512
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
596
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
513
597
 
514
598
  return resultObj;
515
599
  }
@@ -524,30 +608,29 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
524
608
  GET_CLIENT(self);
525
609
 
526
610
  wrapper->active_thread = Qnil;
527
- wrapper->connected = 0;
528
611
 
529
612
  /* Invalidate the MySQL socket to prevent further communication.
530
613
  * The GC will come along later and call mysql_close to free it.
531
614
  */
532
- if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
533
- fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
534
- close(wrapper->client->net.fd);
615
+ if (CONNECTED(wrapper)) {
616
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
617
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
618
+ close(wrapper->client->net.fd);
619
+ }
620
+ wrapper->client->net.fd = -1;
535
621
  }
536
622
 
537
623
  rb_exc_raise(error);
538
-
539
- return Qnil;
540
624
  }
541
625
 
542
626
  static VALUE do_query(void *args) {
543
- struct async_query_args *async_args;
627
+ struct async_query_args *async_args = args;
544
628
  struct timeval tv;
545
- struct timeval* tvp;
629
+ struct timeval *tvp;
546
630
  long int sec;
547
631
  int retval;
548
632
  VALUE read_timeout;
549
633
 
550
- async_args = (struct async_query_args *)args;
551
634
  read_timeout = rb_iv_get(async_args->self, "@read_timeout");
552
635
 
553
636
  tvp = NULL;
@@ -583,28 +666,50 @@ static VALUE do_query(void *args) {
583
666
 
584
667
  return Qnil;
585
668
  }
586
- #else
587
- static VALUE finish_and_mark_inactive(void *args) {
588
- VALUE self;
589
- MYSQL_RES *result;
590
-
591
- self = (VALUE)args;
669
+ #endif
592
670
 
671
+ static VALUE disconnect_and_mark_inactive(VALUE self) {
593
672
  GET_CLIENT(self);
594
673
 
674
+ /* Check if execution terminated while result was still being read. */
595
675
  if (!NIL_P(wrapper->active_thread)) {
596
- /* if we got here, the result hasn't been read off the wire yet
597
- so lets do that and then throw it away because we have no way
598
- of getting it back up to the caller from here */
599
- result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
600
- mysql_free_result(result);
601
-
676
+ if (CONNECTED(wrapper)) {
677
+ /* Invalidate the MySQL socket to prevent further communication. */
678
+ #ifndef _WIN32
679
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
680
+ rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n");
681
+ close(wrapper->client->net.fd);
682
+ }
683
+ #else
684
+ close(wrapper->client->net.fd);
685
+ #endif
686
+ wrapper->client->net.fd = -1;
687
+ }
688
+ /* Skip mysql client check performed before command execution. */
689
+ wrapper->client->status = MYSQL_STATUS_READY;
602
690
  wrapper->active_thread = Qnil;
603
691
  }
604
692
 
605
693
  return Qnil;
606
694
  }
607
- #endif
695
+
696
+ void rb_mysql_client_set_active_thread(VALUE self) {
697
+ VALUE thread_current = rb_thread_current();
698
+ GET_CLIENT(self);
699
+
700
+ // see if this connection is still waiting on a result from a previous query
701
+ if (NIL_P(wrapper->active_thread)) {
702
+ // mark this connection active
703
+ wrapper->active_thread = thread_current;
704
+ } else if (wrapper->active_thread == thread_current) {
705
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
706
+ } else {
707
+ VALUE inspect = rb_inspect(wrapper->active_thread);
708
+ const char *thr = StringValueCStr(inspect);
709
+
710
+ rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
711
+ }
712
+ }
608
713
 
609
714
  /* call-seq:
610
715
  * client.abandon_results!
@@ -640,80 +745,53 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
640
745
  * client.query(sql, options = {})
641
746
  *
642
747
  * Query the database with +sql+, with optional +options+. For the possible
643
- * options, see @@default_query_options on the Mysql2::Client class.
748
+ * options, see default_query_options on the Mysql2::Client class.
644
749
  */
645
- static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
750
+ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
646
751
  #ifndef _WIN32
647
752
  struct async_query_args async_args;
648
753
  #endif
649
754
  struct nogvl_send_query_args args;
650
- int async = 0;
651
- VALUE opts, current;
652
- VALUE thread_current = rb_thread_current();
653
- #ifdef HAVE_RUBY_ENCODING_H
654
- rb_encoding *conn_enc;
655
- #endif
656
755
  GET_CLIENT(self);
657
756
 
658
757
  REQUIRE_CONNECTED(wrapper);
659
758
  args.mysql = wrapper->client;
660
759
 
661
- current = rb_hash_dup(rb_iv_get(self, "@query_options"));
662
- RB_GC_GUARD(current);
760
+ (void)RB_GC_GUARD(current);
663
761
  Check_Type(current, T_HASH);
664
762
  rb_iv_set(self, "@current_query_options", current);
665
763
 
666
- if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
667
- rb_funcall(current, intern_merge_bang, 1, opts);
668
-
669
- if (rb_hash_aref(current, sym_async) == Qtrue) {
670
- async = 1;
671
- }
672
- }
673
-
674
- Check_Type(args.sql, T_STRING);
764
+ Check_Type(sql, T_STRING);
675
765
  #ifdef HAVE_RUBY_ENCODING_H
676
- conn_enc = rb_to_encoding(wrapper->encoding);
677
766
  /* ensure the string is in the encoding the connection is expecting */
678
- args.sql = rb_str_export_to_enc(args.sql, conn_enc);
767
+ args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
768
+ #else
769
+ args.sql = sql;
679
770
  #endif
680
771
  args.sql_ptr = RSTRING_PTR(args.sql);
681
772
  args.sql_len = RSTRING_LEN(args.sql);
682
-
683
- /* see if this connection is still waiting on a result from a previous query */
684
- if (NIL_P(wrapper->active_thread)) {
685
- /* mark this connection active */
686
- wrapper->active_thread = thread_current;
687
- } else if (wrapper->active_thread == thread_current) {
688
- rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
689
- } else {
690
- VALUE inspect = rb_inspect(wrapper->active_thread);
691
- const char *thr = StringValueCStr(inspect);
692
-
693
- rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
694
- RB_GC_GUARD(inspect);
695
- }
696
-
697
773
  args.wrapper = wrapper;
698
774
 
775
+ rb_mysql_client_set_active_thread(self);
776
+
699
777
  #ifndef _WIN32
700
778
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
701
779
 
702
- if (!async) {
780
+ if (rb_hash_aref(current, sym_async) == Qtrue) {
781
+ return Qnil;
782
+ } else {
703
783
  async_args.fd = wrapper->client->net.fd;
704
784
  async_args.self = self;
705
785
 
706
786
  rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
707
787
 
708
- return rb_mysql_client_async_result(self);
709
- } else {
710
- return Qnil;
788
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
711
789
  }
712
790
  #else
713
791
  do_send_query(&args);
714
792
 
715
793
  /* this will just block until the result is ready */
716
- return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
794
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
717
795
  #endif
718
796
  }
719
797
 
@@ -772,7 +850,7 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
772
850
  const void *retval = NULL;
773
851
  unsigned int intval = 0;
774
852
  const char * charval = NULL;
775
- my_bool boolval;
853
+ bool boolval;
776
854
 
777
855
  GET_CLIENT(self);
778
856
 
@@ -807,10 +885,12 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
807
885
  retval = &boolval;
808
886
  break;
809
887
 
888
+ #ifdef MYSQL_SECURE_AUTH
810
889
  case MYSQL_SECURE_AUTH:
811
890
  boolval = (value == Qfalse ? 0 : 1);
812
891
  retval = &boolval;
813
892
  break;
893
+ #endif
814
894
 
815
895
  case MYSQL_READ_DEFAULT_FILE:
816
896
  charval = (const char *)StringValueCStr(value);
@@ -827,6 +907,13 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
827
907
  retval = charval;
828
908
  break;
829
909
 
910
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
911
+ case MYSQL_ENABLE_CLEARTEXT_PLUGIN:
912
+ boolval = (value == Qfalse ? 0 : 1);
913
+ retval = &boolval;
914
+ break;
915
+ #endif
916
+
830
917
  default:
831
918
  return Qfalse;
832
919
  }
@@ -912,15 +999,17 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
912
999
  *
913
1000
  * Return the file descriptor number for this client.
914
1001
  */
915
- static VALUE rb_mysql_client_socket(VALUE self) {
916
1002
  #ifndef _WIN32
1003
+ static VALUE rb_mysql_client_socket(VALUE self) {
917
1004
  GET_CLIENT(self);
918
1005
  REQUIRE_CONNECTED(wrapper);
919
1006
  return INT2NUM(wrapper->client->net.fd);
1007
+ }
920
1008
  #else
1009
+ static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
921
1010
  rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
922
- #endif
923
1011
  }
1012
+ #endif
924
1013
 
925
1014
  /* call-seq:
926
1015
  * client.last_id
@@ -1014,7 +1103,7 @@ static void *nogvl_ping(void *ptr) {
1014
1103
  static VALUE rb_mysql_client_ping(VALUE self) {
1015
1104
  GET_CLIENT(self);
1016
1105
 
1017
- if (!wrapper->connected) {
1106
+ if (!CONNECTED(wrapper)) {
1018
1107
  return Qfalse;
1019
1108
  } else {
1020
1109
  return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
@@ -1029,10 +1118,10 @@ static VALUE rb_mysql_client_ping(VALUE self) {
1029
1118
  static VALUE rb_mysql_client_more_results(VALUE self)
1030
1119
  {
1031
1120
  GET_CLIENT(self);
1032
- if (mysql_more_results(wrapper->client) == 0)
1033
- return Qfalse;
1034
- else
1035
- return Qtrue;
1121
+ if (mysql_more_results(wrapper->client) == 0)
1122
+ return Qfalse;
1123
+ else
1124
+ return Qtrue;
1036
1125
  }
1037
1126
 
1038
1127
  /* call-seq:
@@ -1080,9 +1169,9 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1080
1169
  }
1081
1170
 
1082
1171
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1083
- RB_GC_GUARD(current);
1172
+ (void)RB_GC_GUARD(current);
1084
1173
  Check_Type(current, T_HASH);
1085
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
1174
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
1086
1175
 
1087
1176
  return resultObj;
1088
1177
  }
@@ -1099,6 +1188,39 @@ static VALUE rb_mysql_client_encoding(VALUE self) {
1099
1188
  }
1100
1189
  #endif
1101
1190
 
1191
+ /* call-seq:
1192
+ * client.automatic_close?
1193
+ *
1194
+ * @return [Boolean]
1195
+ */
1196
+ static VALUE get_automatic_close(VALUE self) {
1197
+ GET_CLIENT(self);
1198
+ return wrapper->automatic_close ? Qtrue : Qfalse;
1199
+ }
1200
+
1201
+ /* call-seq:
1202
+ * client.automatic_close = false
1203
+ *
1204
+ * Set this to +false+ to leave the connection open after it is garbage
1205
+ * collected. To avoid "Aborted connection" errors on the server, explicitly
1206
+ * call +close+ when the connection is no longer needed.
1207
+ *
1208
+ * @see http://dev.mysql.com/doc/en/communication-errors.html
1209
+ */
1210
+ static VALUE set_automatic_close(VALUE self, VALUE value) {
1211
+ GET_CLIENT(self);
1212
+ if (RTEST(value)) {
1213
+ wrapper->automatic_close = 1;
1214
+ } else {
1215
+ #ifndef _WIN32
1216
+ wrapper->automatic_close = 0;
1217
+ #else
1218
+ rb_warn("Connections are always closed by garbage collector on Windows");
1219
+ #endif
1220
+ }
1221
+ return value;
1222
+ }
1223
+
1102
1224
  /* call-seq:
1103
1225
  * client.reconnect = true
1104
1226
  *
@@ -1151,18 +1273,17 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
1151
1273
  static VALUE set_charset_name(VALUE self, VALUE value) {
1152
1274
  char *charset_name;
1153
1275
  #ifdef HAVE_RUBY_ENCODING_H
1154
- size_t charset_name_len;
1155
1276
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1156
1277
  rb_encoding *enc;
1157
1278
  VALUE rb_enc;
1158
1279
  #endif
1159
1280
  GET_CLIENT(self);
1160
1281
 
1282
+ Check_Type(value, T_STRING);
1161
1283
  charset_name = RSTRING_PTR(value);
1162
1284
 
1163
1285
  #ifdef HAVE_RUBY_ENCODING_H
1164
- charset_name_len = RSTRING_LEN(value);
1165
- mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len);
1286
+ mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1166
1287
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
1167
1288
  VALUE inspect = rb_inspect(value);
1168
1289
  rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
@@ -1195,7 +1316,12 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
1195
1316
  }
1196
1317
 
1197
1318
  static VALUE set_secure_auth(VALUE self, VALUE value) {
1319
+ /* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */
1320
+ #ifdef MYSQL_SECURE_AUTH
1198
1321
  return _mysql_client_options(self, MYSQL_SECURE_AUTH, value);
1322
+ #else
1323
+ return Qfalse;
1324
+ #endif
1199
1325
  }
1200
1326
 
1201
1327
  static VALUE set_read_default_file(VALUE self, VALUE value) {
@@ -1210,19 +1336,39 @@ static VALUE set_init_command(VALUE self, VALUE value) {
1210
1336
  return _mysql_client_options(self, MYSQL_INIT_COMMAND, value);
1211
1337
  }
1212
1338
 
1339
+ static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) {
1340
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
1341
+ return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value);
1342
+ #else
1343
+ rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library");
1344
+ #endif
1345
+ }
1346
+
1213
1347
  static VALUE initialize_ext(VALUE self) {
1214
1348
  GET_CLIENT(self);
1215
1349
 
1216
1350
  if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
1217
1351
  /* TODO: warning - not enough memory? */
1218
- return rb_raise_mysql2_error(wrapper);
1352
+ rb_raise_mysql2_error(wrapper);
1219
1353
  }
1220
1354
 
1221
1355
  wrapper->initialized = 1;
1222
1356
  return self;
1223
1357
  }
1224
1358
 
1359
+ /* call-seq: client.prepare # => Mysql2::Statement
1360
+ *
1361
+ * Create a new prepared statement.
1362
+ */
1363
+ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
1364
+ GET_CLIENT(self);
1365
+ REQUIRE_CONNECTED(wrapper);
1366
+
1367
+ return rb_mysql_stmt_new(self, sql);
1368
+ }
1369
+
1225
1370
  void init_mysql2_client() {
1371
+ #ifdef _WIN32
1226
1372
  /* verify the libmysql we're about to use was the version we were built against
1227
1373
  https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
1228
1374
  int i;
@@ -1237,15 +1383,14 @@ void init_mysql2_client() {
1237
1383
  }
1238
1384
  if (lib[i] != MYSQL_LINK_VERSION[i]) {
1239
1385
  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);
1240
- return;
1241
1386
  }
1242
1387
  }
1388
+ #endif
1243
1389
 
1244
1390
  /* Initializing mysql library, so different threads could call Client.new */
1245
1391
  /* without race condition in the library */
1246
1392
  if (mysql_library_init(0, NULL, NULL) != 0) {
1247
1393
  rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
1248
- return;
1249
1394
  }
1250
1395
 
1251
1396
  #if 0
@@ -1259,7 +1404,7 @@ void init_mysql2_client() {
1259
1404
  rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1260
1405
 
1261
1406
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
1262
- rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
1407
+ rb_define_method(cMysql2Client, "closed?", rb_mysql_client_closed, 0);
1263
1408
  rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
1264
1409
  rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
1265
1410
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
@@ -1267,15 +1412,19 @@ void init_mysql2_client() {
1267
1412
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
1268
1413
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
1269
1414
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
1415
+ rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
1270
1416
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
1271
1417
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1272
1418
  rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
1273
1419
  rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0);
1274
1420
  rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
1275
1421
  rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
1422
+ rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0);
1423
+ rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1);
1276
1424
  rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1);
1277
1425
  rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0);
1278
1426
  rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0);
1427
+ rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0);
1279
1428
  #ifdef HAVE_RUBY_ENCODING_H
1280
1429
  rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
1281
1430
  #endif
@@ -1290,8 +1439,11 @@ void init_mysql2_client() {
1290
1439
  rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1);
1291
1440
  rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1);
1292
1441
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
1442
+ rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1);
1443
+ rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1);
1293
1444
  rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
1294
1445
  rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
1446
+ rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
1295
1447
 
1296
1448
  sym_id = ID2SYM(rb_intern("id"));
1297
1449
  sym_version = ID2SYM(rb_intern("version"));
@@ -1302,14 +1454,18 @@ void init_mysql2_client() {
1302
1454
  sym_array = ID2SYM(rb_intern("array"));
1303
1455
  sym_stream = ID2SYM(rb_intern("stream"));
1304
1456
 
1457
+ intern_brackets = rb_intern("[]");
1305
1458
  intern_merge = rb_intern("merge");
1306
1459
  intern_merge_bang = rb_intern("merge!");
1307
- intern_error_number_eql = rb_intern("error_number=");
1308
- intern_sql_state_eql = rb_intern("sql_state=");
1460
+ intern_new_with_args = rb_intern("new_with_args");
1309
1461
 
1310
1462
  #ifdef CLIENT_LONG_PASSWORD
1311
1463
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
1312
1464
  LONG2NUM(CLIENT_LONG_PASSWORD));
1465
+ #else
1466
+ /* HACK because MariaDB 10.2 no longer defines this constant,
1467
+ * but we're using it in our default connection flags. */
1468
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), INT2NUM(0));
1313
1469
  #endif
1314
1470
 
1315
1471
  #ifdef CLIENT_FOUND_ROWS
@@ -1416,4 +1572,31 @@ void init_mysql2_client() {
1416
1572
  rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
1417
1573
  LONG2NUM(CLIENT_BASIC_FLAGS));
1418
1574
  #endif
1575
+
1576
+ #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above
1577
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1578
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED));
1579
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1580
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA));
1581
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY));
1582
+ #elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10
1583
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1584
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1585
+ #endif
1586
+
1587
+ #ifndef HAVE_CONST_SSL_MODE_DISABLED
1588
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0));
1589
+ #endif
1590
+ #ifndef HAVE_CONST_SSL_MODE_PREFERRED
1591
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0));
1592
+ #endif
1593
+ #ifndef HAVE_CONST_SSL_MODE_REQUIRED
1594
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0));
1595
+ #endif
1596
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_CA
1597
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0));
1598
+ #endif
1599
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY
1600
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0));
1601
+ #endif
1419
1602
  }