mysql2 0.4.2 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
data/ext/mysql2/client.c CHANGED
@@ -15,24 +15,31 @@
15
15
  #include "mysql_enc_name_to_ruby.h"
16
16
 
17
17
  VALUE cMysql2Client;
18
- extern VALUE mMysql2, cMysql2Error;
18
+ extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError;
19
19
  static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
20
+ static VALUE sym_no_good_index_used, sym_no_index_used, sym_query_was_slow;
20
21
  static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
21
22
 
22
- #ifndef HAVE_RB_HASH_DUP
23
- VALUE rb_hash_dup(VALUE other) {
24
- return rb_funcall(rb_cHash, intern_brackets, 1, other);
25
- }
26
- #endif
27
-
28
23
  #define REQUIRE_INITIALIZED(wrapper) \
29
24
  if (!wrapper->initialized) { \
30
25
  rb_raise(cMysql2Error, "MySQL client is not initialized"); \
31
26
  }
32
27
 
28
+ #if defined(HAVE_MYSQL_NET_VIO) || defined(HAVE_ST_NET_VIO)
29
+ #define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1)
30
+ #elif defined(HAVE_MYSQL_NET_PVIO) || defined(HAVE_ST_NET_PVIO)
31
+ #define CONNECTED(wrapper) (wrapper->client->net.pvio != NULL && wrapper->client->net.fd != -1)
32
+ #endif
33
+
34
+ #define REQUIRE_CONNECTED(wrapper) \
35
+ REQUIRE_INITIALIZED(wrapper) \
36
+ if (!CONNECTED(wrapper) && !wrapper->reconnect_enabled) { \
37
+ rb_raise(cMysql2Error, "MySQL client is not connected"); \
38
+ }
39
+
33
40
  #define REQUIRE_NOT_CONNECTED(wrapper) \
34
41
  REQUIRE_INITIALIZED(wrapper) \
35
- if (wrapper->connected) { \
42
+ if (CONNECTED(wrapper)) { \
36
43
  rb_raise(cMysql2Error, "MySQL connection is already open"); \
37
44
  }
38
45
 
@@ -41,12 +48,24 @@ VALUE rb_hash_dup(VALUE other) {
41
48
  * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
42
49
  * linking against the server itself
43
50
  */
44
- #ifdef LIBMYSQL_VERSION
51
+ #if defined(MARIADB_CLIENT_VERSION_STR)
52
+ #define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR
53
+ #elif defined(LIBMYSQL_VERSION)
45
54
  #define MYSQL_LINK_VERSION LIBMYSQL_VERSION
46
55
  #else
47
56
  #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION
48
57
  #endif
49
58
 
59
+ /*
60
+ * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10.
61
+ */
62
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
63
+ #define SSL_MODE_DISABLED 1
64
+ #define SSL_MODE_REQUIRED 3
65
+ #define HAVE_CONST_SSL_MODE_DISABLED
66
+ #define HAVE_CONST_SSL_MODE_REQUIRED
67
+ #endif
68
+
50
69
  /*
51
70
  * used to pass all arguments to mysql_real_connect while inside
52
71
  * rb_thread_call_without_gvl
@@ -83,6 +102,42 @@ struct nogvl_select_db_args {
83
102
  char *db;
84
103
  };
85
104
 
105
+ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
106
+ unsigned long version = mysql_get_client_version();
107
+
108
+ if (version < 50703) {
109
+ rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." );
110
+ return Qnil;
111
+ }
112
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
113
+ GET_CLIENT(self);
114
+ int val = NUM2INT( setting );
115
+ if (version >= 50703 && version < 50711) {
116
+ if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
117
+ my_bool b = ( val == SSL_MODE_REQUIRED );
118
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b );
119
+ return INT2NUM(result);
120
+ } else {
121
+ rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" );
122
+ return Qnil;
123
+ }
124
+ }
125
+ #endif
126
+ #ifdef FULL_SSL_MODE_SUPPORT
127
+ GET_CLIENT(self);
128
+ int val = NUM2INT( setting );
129
+
130
+ if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
131
+ rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
132
+ }
133
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val );
134
+
135
+ return INT2NUM(result);
136
+ #endif
137
+ #ifdef NO_SSL_MODE_SUPPORT
138
+ return Qnil;
139
+ #endif
140
+ }
86
141
  /*
87
142
  * non-blocking mysql_*() functions that we won't be wrapping since
88
143
  * they do not appear to hit the network nor issue any interruptible
@@ -118,10 +173,8 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
118
173
  VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
119
174
  VALUE e;
120
175
 
121
- #ifdef HAVE_RUBY_ENCODING_H
122
176
  rb_enc_associate(rb_error_msg, rb_utf8_encoding());
123
177
  rb_enc_associate(rb_sql_state, rb_usascii_encoding());
124
- #endif
125
178
 
126
179
  e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
127
180
  rb_error_msg,
@@ -211,11 +264,10 @@ static VALUE invalidate_fd(int clientfd)
211
264
  static void *nogvl_close(void *ptr) {
212
265
  mysql_client_wrapper *wrapper = ptr;
213
266
 
214
- if (wrapper->client) {
267
+ if (!wrapper->closed) {
215
268
  mysql_close(wrapper->client);
216
- xfree(wrapper->client);
217
- wrapper->client = NULL;
218
- wrapper->connected = 0;
269
+ wrapper->closed = 1;
270
+ wrapper->reconnect_enabled = 0;
219
271
  wrapper->active_thread = Qnil;
220
272
  }
221
273
 
@@ -234,7 +286,7 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper)
234
286
 
235
287
  if (wrapper->refcount == 0) {
236
288
  #ifndef _WIN32
237
- if (wrapper->connected) {
289
+ if (CONNECTED(wrapper) && !wrapper->automatic_close) {
238
290
  /* The client is being garbage collected while connected. Prevent
239
291
  * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
240
292
  * the socket by invalidating it. invalidate_fd() will drop this
@@ -246,10 +298,12 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper)
246
298
  fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
247
299
  close(wrapper->client->net.fd);
248
300
  }
301
+ wrapper->client->net.fd = -1;
249
302
  }
250
303
  #endif
251
304
 
252
305
  nogvl_close(wrapper);
306
+ xfree(wrapper->client);
253
307
  xfree(wrapper);
254
308
  }
255
309
  }
@@ -259,13 +313,14 @@ static VALUE allocate(VALUE klass) {
259
313
  mysql_client_wrapper * wrapper;
260
314
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
261
315
  wrapper->encoding = Qnil;
262
- MARK_CONN_INACTIVE(self);
316
+ wrapper->active_thread = Qnil;
317
+ wrapper->automatic_close = 1;
263
318
  wrapper->server_version = 0;
264
319
  wrapper->reconnect_enabled = 0;
265
320
  wrapper->connect_timeout = 0;
266
- wrapper->connected = 0; /* means that a database connection is open */
267
321
  wrapper->initialized = 0; /* means that that the wrapper is initialized */
268
322
  wrapper->refcount = 1;
323
+ wrapper->closed = 0;
269
324
  wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
270
325
 
271
326
  return obj;
@@ -295,9 +350,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
295
350
  return str;
296
351
  } else {
297
352
  rb_str = rb_str_new((const char*)newStr, newLen);
298
- #ifdef HAVE_RUBY_ENCODING_H
299
353
  rb_enc_copy(rb_str, str);
300
- #endif
301
354
  xfree(newStr);
302
355
  return rb_str;
303
356
  }
@@ -324,14 +377,43 @@ static VALUE rb_mysql_info(VALUE self) {
324
377
  }
325
378
 
326
379
  rb_str = rb_str_new2(info);
327
- #ifdef HAVE_RUBY_ENCODING_H
328
380
  rb_enc_associate(rb_str, rb_utf8_encoding());
329
- #endif
330
381
 
331
382
  return rb_str;
332
383
  }
333
384
 
334
- static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
385
+ static VALUE rb_mysql_get_ssl_cipher(VALUE self)
386
+ {
387
+ const char *cipher;
388
+ VALUE rb_str;
389
+ GET_CLIENT(self);
390
+
391
+ cipher = mysql_get_ssl_cipher(wrapper->client);
392
+
393
+ if (cipher == NULL) {
394
+ return Qnil;
395
+ }
396
+
397
+ rb_str = rb_str_new2(cipher);
398
+ rb_enc_associate(rb_str, rb_utf8_encoding());
399
+
400
+ return rb_str;
401
+ }
402
+
403
+ #ifdef CLIENT_CONNECT_ATTRS
404
+ static int opt_connect_attr_add_i(VALUE key, VALUE value, VALUE arg)
405
+ {
406
+ mysql_client_wrapper *wrapper = (mysql_client_wrapper *)arg;
407
+ rb_encoding *enc = rb_to_encoding(wrapper->encoding);
408
+ key = rb_str_export_to_enc(key, enc);
409
+ value = rb_str_export_to_enc(value, enc);
410
+
411
+ mysql_options4(wrapper->client, MYSQL_OPT_CONNECT_ATTR_ADD, StringValueCStr(key), StringValueCStr(value));
412
+ return ST_CONTINUE;
413
+ }
414
+ #endif
415
+
416
+ static VALUE rb_mysql_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags, VALUE conn_attrs) {
335
417
  struct nogvl_connect_args args;
336
418
  time_t start_time, end_time, elapsed_time, connect_timeout;
337
419
  VALUE rv;
@@ -346,6 +428,11 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
346
428
  args.mysql = wrapper->client;
347
429
  args.client_flag = NUM2ULONG(flags);
348
430
 
431
+ #ifdef CLIENT_CONNECT_ATTRS
432
+ mysql_options(wrapper->client, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
433
+ rb_hash_foreach(conn_attrs, opt_connect_attr_add_i, (VALUE)wrapper);
434
+ #endif
435
+
349
436
  if (wrapper->connect_timeout)
350
437
  time(&start_time);
351
438
  rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
@@ -372,33 +459,41 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
372
459
  if (wrapper->connect_timeout)
373
460
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
374
461
  if (rv == Qfalse)
375
- return rb_raise_mysql2_error(wrapper);
462
+ rb_raise_mysql2_error(wrapper);
376
463
  }
377
464
 
378
465
  wrapper->server_version = mysql_get_server_version(wrapper->client);
379
- wrapper->connected = 1;
380
466
  return self;
381
467
  }
382
468
 
383
469
  /*
384
- * Terminate the connection; call this when the connection is no longer needed.
385
- * The garbage collector can close the connection, but doing so emits an
386
- * "Aborted connection" error on the server and increments the Aborted_clients
387
- * status variable.
470
+ * Immediately disconnect from the server; normally the garbage collector
471
+ * will disconnect automatically when a connection is no longer needed.
472
+ * Explicitly closing this will free up server resources sooner than waiting
473
+ * for the garbage collector.
388
474
  *
389
- * @see http://dev.mysql.com/doc/en/communication-errors.html
390
- * @return [void]
475
+ * @return [nil]
391
476
  */
392
477
  static VALUE rb_mysql_client_close(VALUE self) {
393
478
  GET_CLIENT(self);
394
479
 
395
- if (wrapper->connected) {
480
+ if (wrapper->client) {
396
481
  rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
397
482
  }
398
483
 
399
484
  return Qnil;
400
485
  }
401
486
 
487
+ /* call-seq:
488
+ * client.closed?
489
+ *
490
+ * @return [Boolean]
491
+ */
492
+ static VALUE rb_mysql_client_closed(VALUE self) {
493
+ GET_CLIENT(self);
494
+ return CONNECTED(wrapper) ? Qfalse : Qtrue;
495
+ }
496
+
402
497
  /*
403
498
  * mysql_send_query is unlikely to block since most queries are small
404
499
  * enough to fit in a socket buffer, but sometimes large UPDATE and
@@ -418,8 +513,8 @@ static VALUE do_send_query(void *args) {
418
513
  mysql_client_wrapper *wrapper = query_args->wrapper;
419
514
  if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
420
515
  /* an error occurred, we're not active anymore */
421
- MARK_CONN_INACTIVE(self);
422
- return rb_raise_mysql2_error(wrapper);
516
+ wrapper->active_thread = Qnil;
517
+ rb_raise_mysql2_error(wrapper);
423
518
  }
424
519
  return Qnil;
425
520
  }
@@ -448,7 +543,7 @@ static void *nogvl_do_result(void *ptr, char use_result) {
448
543
 
449
544
  /* once our result is stored off, this connection is
450
545
  ready for another command to be issued */
451
- MARK_CONN_INACTIVE(self);
546
+ wrapper->active_thread = Qnil;
452
547
 
453
548
  return result;
454
549
  }
@@ -480,8 +575,8 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
480
575
  REQUIRE_CONNECTED(wrapper);
481
576
  if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
482
577
  /* an error occurred, mark this connection inactive */
483
- MARK_CONN_INACTIVE(self);
484
- return rb_raise_mysql2_error(wrapper);
578
+ wrapper->active_thread = Qnil;
579
+ rb_raise_mysql2_error(wrapper);
485
580
  }
486
581
 
487
582
  is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
@@ -493,18 +588,21 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
493
588
 
494
589
  if (result == NULL) {
495
590
  if (mysql_errno(wrapper->client) != 0) {
496
- MARK_CONN_INACTIVE(self);
591
+ wrapper->active_thread = Qnil;
497
592
  rb_raise_mysql2_error(wrapper);
498
593
  }
499
594
  /* no data and no error, so query was not a SELECT */
500
595
  return Qnil;
501
596
  }
502
597
 
598
+ // Duplicate the options hash and put the copy in the Result object
503
599
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
504
600
  (void)RB_GC_GUARD(current);
505
601
  Check_Type(current, T_HASH);
506
602
  resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
507
603
 
604
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
605
+
508
606
  return resultObj;
509
607
  }
510
608
 
@@ -517,15 +615,17 @@ struct async_query_args {
517
615
  static VALUE disconnect_and_raise(VALUE self, VALUE error) {
518
616
  GET_CLIENT(self);
519
617
 
520
- MARK_CONN_INACTIVE(self);
521
- wrapper->connected = 0;
618
+ wrapper->active_thread = Qnil;
522
619
 
523
620
  /* Invalidate the MySQL socket to prevent further communication.
524
621
  * The GC will come along later and call mysql_close to free it.
525
622
  */
526
- if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
527
- fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
528
- close(wrapper->client->net.fd);
623
+ if (CONNECTED(wrapper)) {
624
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
625
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
626
+ close(wrapper->client->net.fd);
627
+ }
628
+ wrapper->client->net.fd = -1;
529
629
  }
530
630
 
531
631
  rb_exc_raise(error);
@@ -560,7 +660,7 @@ static VALUE do_query(void *args) {
560
660
  retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp);
561
661
 
562
662
  if (retval == 0) {
563
- rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
663
+ rb_raise(cMysql2TimeoutError, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
564
664
  }
565
665
 
566
666
  if (retval < 0) {
@@ -574,26 +674,32 @@ static VALUE do_query(void *args) {
574
674
 
575
675
  return Qnil;
576
676
  }
577
- #else
578
- static VALUE finish_and_mark_inactive(void *args) {
579
- VALUE self = args;
580
- MYSQL_RES *result;
677
+ #endif
581
678
 
679
+ static VALUE disconnect_and_mark_inactive(VALUE self) {
582
680
  GET_CLIENT(self);
583
681
 
682
+ /* Check if execution terminated while result was still being read. */
584
683
  if (!NIL_P(wrapper->active_thread)) {
585
- /* if we got here, the result hasn't been read off the wire yet
586
- so lets do that and then throw it away because we have no way
587
- of getting it back up to the caller from here */
588
- result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
589
- mysql_free_result(result);
590
-
591
- MARK_CONN_INACTIVE(self);
684
+ if (CONNECTED(wrapper)) {
685
+ /* Invalidate the MySQL socket to prevent further communication. */
686
+ #ifndef _WIN32
687
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
688
+ rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n");
689
+ close(wrapper->client->net.fd);
690
+ }
691
+ #else
692
+ close(wrapper->client->net.fd);
693
+ #endif
694
+ wrapper->client->net.fd = -1;
695
+ }
696
+ /* Skip mysql client check performed before command execution. */
697
+ wrapper->client->status = MYSQL_STATUS_READY;
698
+ wrapper->active_thread = Qnil;
592
699
  }
593
700
 
594
701
  return Qnil;
595
702
  }
596
- #endif
597
703
 
598
704
  void rb_mysql_client_set_active_thread(VALUE self) {
599
705
  VALUE thread_current = rb_thread_current();
@@ -649,7 +755,7 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
649
755
  * Query the database with +sql+, with optional +options+. For the possible
650
756
  * options, see default_query_options on the Mysql2::Client class.
651
757
  */
652
- static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
758
+ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) {
653
759
  #ifndef _WIN32
654
760
  struct async_query_args async_args;
655
761
  #endif
@@ -664,12 +770,8 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
664
770
  rb_iv_set(self, "@current_query_options", current);
665
771
 
666
772
  Check_Type(sql, T_STRING);
667
- #ifdef HAVE_RUBY_ENCODING_H
668
773
  /* ensure the string is in the encoding the connection is expecting */
669
774
  args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
670
- #else
671
- args.sql = sql;
672
- #endif
673
775
  args.sql_ptr = RSTRING_PTR(args.sql);
674
776
  args.sql_len = RSTRING_LEN(args.sql);
675
777
  args.wrapper = wrapper;
@@ -687,13 +789,13 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
687
789
 
688
790
  rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
689
791
 
690
- return rb_mysql_client_async_result(self);
792
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
691
793
  }
692
794
  #else
693
795
  do_send_query(&args);
694
796
 
695
797
  /* this will just block until the result is ready */
696
- return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
798
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
697
799
  #endif
698
800
  }
699
801
 
@@ -706,20 +808,16 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
706
808
  unsigned char *newStr;
707
809
  VALUE rb_str;
708
810
  unsigned long newLen, oldLen;
709
- #ifdef HAVE_RUBY_ENCODING_H
710
811
  rb_encoding *default_internal_enc;
711
812
  rb_encoding *conn_enc;
712
- #endif
713
813
  GET_CLIENT(self);
714
814
 
715
815
  REQUIRE_CONNECTED(wrapper);
716
816
  Check_Type(str, T_STRING);
717
- #ifdef HAVE_RUBY_ENCODING_H
718
817
  default_internal_enc = rb_default_internal_encoding();
719
818
  conn_enc = rb_to_encoding(wrapper->encoding);
720
819
  /* ensure the string is in the encoding the connection is expecting */
721
820
  str = rb_str_export_to_enc(str, conn_enc);
722
- #endif
723
821
 
724
822
  oldLen = RSTRING_LEN(str);
725
823
  newStr = xmalloc(oldLen*2+1);
@@ -727,21 +825,17 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
727
825
  newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
728
826
  if (newLen == oldLen) {
729
827
  /* no need to return a new ruby string if nothing changed */
730
- #ifdef HAVE_RUBY_ENCODING_H
731
828
  if (default_internal_enc) {
732
829
  str = rb_str_export_to_enc(str, default_internal_enc);
733
830
  }
734
- #endif
735
831
  xfree(newStr);
736
832
  return str;
737
833
  } else {
738
834
  rb_str = rb_str_new((const char*)newStr, newLen);
739
- #ifdef HAVE_RUBY_ENCODING_H
740
835
  rb_enc_associate(rb_str, conn_enc);
741
836
  if (default_internal_enc) {
742
837
  rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
743
838
  }
744
- #endif
745
839
  xfree(newStr);
746
840
  return rb_str;
747
841
  }
@@ -787,10 +881,12 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
787
881
  retval = &boolval;
788
882
  break;
789
883
 
884
+ #ifdef MYSQL_SECURE_AUTH
790
885
  case MYSQL_SECURE_AUTH:
791
886
  boolval = (value == Qfalse ? 0 : 1);
792
887
  retval = &boolval;
793
888
  break;
889
+ #endif
794
890
 
795
891
  case MYSQL_READ_DEFAULT_FILE:
796
892
  charval = (const char *)StringValueCStr(value);
@@ -807,6 +903,13 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
807
903
  retval = charval;
808
904
  break;
809
905
 
906
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
907
+ case MYSQL_ENABLE_CLEARTEXT_PLUGIN:
908
+ boolval = (value == Qfalse ? 0 : 1);
909
+ retval = &boolval;
910
+ break;
911
+ #endif
912
+
810
913
  default:
811
914
  return Qfalse;
812
915
  }
@@ -843,10 +946,8 @@ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
843
946
  version = rb_str_new2(mysql_get_client_info());
844
947
  header_version = rb_str_new2(MYSQL_LINK_VERSION);
845
948
 
846
- #ifdef HAVE_RUBY_ENCODING_H
847
949
  rb_enc_associate(version, rb_usascii_encoding());
848
950
  rb_enc_associate(header_version, rb_usascii_encoding());
849
- #endif
850
951
 
851
952
  rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
852
953
  rb_hash_aset(version_info, sym_version, version);
@@ -862,27 +963,21 @@ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
862
963
  */
863
964
  static VALUE rb_mysql_client_server_info(VALUE self) {
864
965
  VALUE version, server_info;
865
- #ifdef HAVE_RUBY_ENCODING_H
866
966
  rb_encoding *default_internal_enc;
867
967
  rb_encoding *conn_enc;
868
- #endif
869
968
  GET_CLIENT(self);
870
969
 
871
970
  REQUIRE_CONNECTED(wrapper);
872
- #ifdef HAVE_RUBY_ENCODING_H
873
971
  default_internal_enc = rb_default_internal_encoding();
874
972
  conn_enc = rb_to_encoding(wrapper->encoding);
875
- #endif
876
973
 
877
974
  version = rb_hash_new();
878
975
  rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
879
976
  server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
880
- #ifdef HAVE_RUBY_ENCODING_H
881
977
  rb_enc_associate(server_info, conn_enc);
882
978
  if (default_internal_enc) {
883
979
  server_info = rb_str_export_to_enc(server_info, default_internal_enc);
884
980
  }
885
- #endif
886
981
  rb_hash_aset(version, sym_version, server_info);
887
982
  return version;
888
983
  }
@@ -996,13 +1091,30 @@ static void *nogvl_ping(void *ptr) {
996
1091
  static VALUE rb_mysql_client_ping(VALUE self) {
997
1092
  GET_CLIENT(self);
998
1093
 
999
- if (!wrapper->connected) {
1094
+ if (!CONNECTED(wrapper)) {
1000
1095
  return Qfalse;
1001
1096
  } else {
1002
1097
  return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
1003
1098
  }
1004
1099
  }
1005
1100
 
1101
+ /* call-seq:
1102
+ * client.set_server_option(value)
1103
+ *
1104
+ * Enables or disables an option for the connection.
1105
+ * Read https://dev.mysql.com/doc/refman/5.7/en/mysql-set-server-option.html
1106
+ * for more information.
1107
+ */
1108
+ static VALUE rb_mysql_client_set_server_option(VALUE self, VALUE value) {
1109
+ GET_CLIENT(self);
1110
+
1111
+ if (mysql_set_server_option(wrapper->client, NUM2INT(value)) == 0) {
1112
+ return Qtrue;
1113
+ } else {
1114
+ return Qfalse;
1115
+ }
1116
+ }
1117
+
1006
1118
  /* call-seq:
1007
1119
  * client.more_results?
1008
1120
  *
@@ -1011,10 +1123,10 @@ static VALUE rb_mysql_client_ping(VALUE self) {
1011
1123
  static VALUE rb_mysql_client_more_results(VALUE self)
1012
1124
  {
1013
1125
  GET_CLIENT(self);
1014
- if (mysql_more_results(wrapper->client) == 0)
1015
- return Qfalse;
1016
- else
1017
- return Qtrue;
1126
+ if (mysql_more_results(wrapper->client) == 0)
1127
+ return Qfalse;
1128
+ else
1129
+ return Qtrue;
1018
1130
  }
1019
1131
 
1020
1132
  /* call-seq:
@@ -1061,6 +1173,7 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1061
1173
  return Qnil;
1062
1174
  }
1063
1175
 
1176
+ // Duplicate the options hash and put the copy in the Result object
1064
1177
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1065
1178
  (void)RB_GC_GUARD(current);
1066
1179
  Check_Type(current, T_HASH);
@@ -1069,7 +1182,6 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1069
1182
  return resultObj;
1070
1183
  }
1071
1184
 
1072
- #ifdef HAVE_RUBY_ENCODING_H
1073
1185
  /* call-seq:
1074
1186
  * client.encoding
1075
1187
  *
@@ -1079,7 +1191,39 @@ static VALUE rb_mysql_client_encoding(VALUE self) {
1079
1191
  GET_CLIENT(self);
1080
1192
  return wrapper->encoding;
1081
1193
  }
1194
+
1195
+ /* call-seq:
1196
+ * client.automatic_close?
1197
+ *
1198
+ * @return [Boolean]
1199
+ */
1200
+ static VALUE get_automatic_close(VALUE self) {
1201
+ GET_CLIENT(self);
1202
+ return wrapper->automatic_close ? Qtrue : Qfalse;
1203
+ }
1204
+
1205
+ /* call-seq:
1206
+ * client.automatic_close = false
1207
+ *
1208
+ * Set this to +false+ to leave the connection open after it is garbage
1209
+ * collected. To avoid "Aborted connection" errors on the server, explicitly
1210
+ * call +close+ when the connection is no longer needed.
1211
+ *
1212
+ * @see http://dev.mysql.com/doc/en/communication-errors.html
1213
+ */
1214
+ static VALUE set_automatic_close(VALUE self, VALUE value) {
1215
+ GET_CLIENT(self);
1216
+ if (RTEST(value)) {
1217
+ wrapper->automatic_close = 1;
1218
+ } else {
1219
+ #ifndef _WIN32
1220
+ wrapper->automatic_close = 0;
1221
+ #else
1222
+ rb_warn("Connections are always closed by garbage collector on Windows");
1082
1223
  #endif
1224
+ }
1225
+ return value;
1226
+ }
1083
1227
 
1084
1228
  /* call-seq:
1085
1229
  * client.reconnect = true
@@ -1132,16 +1276,14 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
1132
1276
 
1133
1277
  static VALUE set_charset_name(VALUE self, VALUE value) {
1134
1278
  char *charset_name;
1135
- #ifdef HAVE_RUBY_ENCODING_H
1136
1279
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1137
1280
  rb_encoding *enc;
1138
1281
  VALUE rb_enc;
1139
- #endif
1140
1282
  GET_CLIENT(self);
1141
1283
 
1284
+ Check_Type(value, T_STRING);
1142
1285
  charset_name = RSTRING_PTR(value);
1143
1286
 
1144
- #ifdef HAVE_RUBY_ENCODING_H
1145
1287
  mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1146
1288
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
1147
1289
  VALUE inspect = rb_inspect(value);
@@ -1151,7 +1293,6 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
1151
1293
  rb_enc = rb_enc_from_encoding(enc);
1152
1294
  wrapper->encoding = rb_enc;
1153
1295
  }
1154
- #endif
1155
1296
 
1156
1297
  if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
1157
1298
  /* TODO: warning - unable to set charset */
@@ -1175,7 +1316,12 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
1175
1316
  }
1176
1317
 
1177
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
1178
1321
  return _mysql_client_options(self, MYSQL_SECURE_AUTH, value);
1322
+ #else
1323
+ return Qfalse;
1324
+ #endif
1179
1325
  }
1180
1326
 
1181
1327
  static VALUE set_read_default_file(VALUE self, VALUE value) {
@@ -1190,12 +1336,20 @@ static VALUE set_init_command(VALUE self, VALUE value) {
1190
1336
  return _mysql_client_options(self, MYSQL_INIT_COMMAND, value);
1191
1337
  }
1192
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
+
1193
1347
  static VALUE initialize_ext(VALUE self) {
1194
1348
  GET_CLIENT(self);
1195
1349
 
1196
1350
  if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
1197
1351
  /* TODO: warning - not enough memory? */
1198
- return rb_raise_mysql2_error(wrapper);
1352
+ rb_raise_mysql2_error(wrapper);
1199
1353
  }
1200
1354
 
1201
1355
  wrapper->initialized = 1;
@@ -1250,6 +1404,7 @@ void init_mysql2_client() {
1250
1404
  rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1251
1405
 
1252
1406
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
1407
+ rb_define_method(cMysql2Client, "closed?", rb_mysql_client_closed, 0);
1253
1408
  rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
1254
1409
  rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
1255
1410
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
@@ -1261,15 +1416,17 @@ void init_mysql2_client() {
1261
1416
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
1262
1417
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1263
1418
  rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
1419
+ rb_define_method(cMysql2Client, "set_server_option", rb_mysql_client_set_server_option, 1);
1264
1420
  rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0);
1265
1421
  rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
1266
1422
  rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
1423
+ rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0);
1424
+ rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1);
1267
1425
  rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1);
1268
1426
  rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0);
1269
1427
  rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0);
1270
- #ifdef HAVE_RUBY_ENCODING_H
1428
+ rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0);
1271
1429
  rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
1272
- #endif
1273
1430
 
1274
1431
  rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
1275
1432
  rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1);
@@ -1281,9 +1438,11 @@ void init_mysql2_client() {
1281
1438
  rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1);
1282
1439
  rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1);
1283
1440
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
1441
+ rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1);
1442
+ rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1);
1284
1443
  rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
1285
- rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
1286
- rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
1444
+ rb_define_private_method(cMysql2Client, "connect", rb_mysql_connect, 8);
1445
+ rb_define_private_method(cMysql2Client, "_query", rb_mysql_query, 2);
1287
1446
 
1288
1447
  sym_id = ID2SYM(rb_intern("id"));
1289
1448
  sym_version = ID2SYM(rb_intern("version"));
@@ -1294,6 +1453,10 @@ void init_mysql2_client() {
1294
1453
  sym_array = ID2SYM(rb_intern("array"));
1295
1454
  sym_stream = ID2SYM(rb_intern("stream"));
1296
1455
 
1456
+ sym_no_good_index_used = ID2SYM(rb_intern("no_good_index_used"));
1457
+ sym_no_index_used = ID2SYM(rb_intern("no_index_used"));
1458
+ sym_query_was_slow = ID2SYM(rb_intern("query_was_slow"));
1459
+
1297
1460
  intern_brackets = rb_intern("[]");
1298
1461
  intern_merge = rb_intern("merge");
1299
1462
  intern_merge_bang = rb_intern("merge!");
@@ -1302,6 +1465,10 @@ void init_mysql2_client() {
1302
1465
  #ifdef CLIENT_LONG_PASSWORD
1303
1466
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
1304
1467
  LONG2NUM(CLIENT_LONG_PASSWORD));
1468
+ #else
1469
+ /* HACK because MariaDB 10.2 no longer defines this constant,
1470
+ * but we're using it in our default connection flags. */
1471
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), INT2NUM(0));
1305
1472
  #endif
1306
1473
 
1307
1474
  #ifdef CLIENT_FOUND_ROWS
@@ -1379,6 +1546,16 @@ void init_mysql2_client() {
1379
1546
  rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0));
1380
1547
  #endif
1381
1548
 
1549
+ #ifdef HAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_ON
1550
+ rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_ON"),
1551
+ LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_ON));
1552
+ #endif
1553
+
1554
+ #ifdef HAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_OFF
1555
+ rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_OFF"),
1556
+ LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_OFF));
1557
+ #endif
1558
+
1382
1559
  #ifdef CLIENT_MULTI_STATEMENTS
1383
1560
  rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
1384
1561
  LONG2NUM(CLIENT_MULTI_STATEMENTS));
@@ -1408,4 +1585,67 @@ void init_mysql2_client() {
1408
1585
  rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
1409
1586
  LONG2NUM(CLIENT_BASIC_FLAGS));
1410
1587
  #endif
1588
+
1589
+ #ifdef CLIENT_CONNECT_ATTRS
1590
+ rb_const_set(cMysql2Client, rb_intern("CONNECT_ATTRS"),
1591
+ LONG2NUM(CLIENT_CONNECT_ATTRS));
1592
+ #else
1593
+ /* HACK because MySQL 5.5 and earlier don't define this constant,
1594
+ * but we're using it in our default connection flags. */
1595
+ rb_const_set(cMysql2Client, rb_intern("CONNECT_ATTRS"),
1596
+ INT2NUM(0));
1597
+ #endif
1598
+
1599
+ #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above
1600
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1601
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED));
1602
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1603
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA));
1604
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY));
1605
+ #elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10
1606
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1607
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1608
+ #endif
1609
+
1610
+ #ifndef HAVE_CONST_SSL_MODE_DISABLED
1611
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0));
1612
+ #endif
1613
+ #ifndef HAVE_CONST_SSL_MODE_PREFERRED
1614
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0));
1615
+ #endif
1616
+ #ifndef HAVE_CONST_SSL_MODE_REQUIRED
1617
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0));
1618
+ #endif
1619
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_CA
1620
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0));
1621
+ #endif
1622
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY
1623
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0));
1624
+ #endif
1625
+ }
1626
+
1627
+ #define flag_to_bool(f) ((client->server_status & f) ? Qtrue : Qfalse)
1628
+
1629
+ void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result) {
1630
+ VALUE server_flags = rb_hash_new();
1631
+
1632
+ #ifdef HAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED
1633
+ rb_hash_aset(server_flags, sym_no_good_index_used, flag_to_bool(SERVER_QUERY_NO_GOOD_INDEX_USED));
1634
+ #else
1635
+ rb_hash_aset(server_flags, sym_no_good_index_used, Qnil);
1636
+ #endif
1637
+
1638
+ #ifdef HAVE_CONST_SERVER_QUERY_NO_INDEX_USED
1639
+ rb_hash_aset(server_flags, sym_no_index_used, flag_to_bool(SERVER_QUERY_NO_INDEX_USED));
1640
+ #else
1641
+ rb_hash_aset(server_flags, sym_no_index_used, Qnil);
1642
+ #endif
1643
+
1644
+ #ifdef HAVE_CONST_SERVER_QUERY_WAS_SLOW
1645
+ rb_hash_aset(server_flags, sym_query_was_slow, flag_to_bool(SERVER_QUERY_WAS_SLOW));
1646
+ #else
1647
+ rb_hash_aset(server_flags, sym_query_was_slow, Qnil);
1648
+ #endif
1649
+
1650
+ rb_iv_set(result, "@server_flags", server_flags);
1411
1651
  }