mysql2 0.4.2 → 0.5.5

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.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +231 -86
  3. data/ext/mysql2/client.c +527 -128
  4. data/ext/mysql2/client.h +11 -52
  5. data/ext/mysql2/extconf.rb +100 -21
  6. data/ext/mysql2/mysql2_ext.c +8 -2
  7. data/ext/mysql2/mysql2_ext.h +21 -8
  8. data/ext/mysql2/mysql_enc_name_to_ruby.h +60 -56
  9. data/ext/mysql2/mysql_enc_to_ruby.h +64 -3
  10. data/ext/mysql2/result.c +333 -109
  11. data/ext/mysql2/result.h +1 -0
  12. data/ext/mysql2/statement.c +247 -90
  13. data/ext/mysql2/statement.h +0 -2
  14. data/ext/mysql2/wait_for_single_fd.h +2 -1
  15. data/lib/mysql2/client.rb +71 -31
  16. data/lib/mysql2/em.rb +2 -4
  17. data/lib/mysql2/error.rb +52 -22
  18. data/lib/mysql2/result.rb +2 -0
  19. data/lib/mysql2/statement.rb +3 -11
  20. data/lib/mysql2/version.rb +1 -1
  21. data/lib/mysql2.rb +19 -15
  22. data/support/3A79BD29.asc +49 -0
  23. data/support/5072E1F5.asc +432 -0
  24. data/support/C74CD1D8.asc +104 -0
  25. data/support/mysql_enc_to_ruby.rb +8 -3
  26. data/support/ruby_enc_to_mysql.rb +7 -5
  27. metadata +19 -61
  28. data/examples/eventmachine.rb +0 -21
  29. data/examples/threaded.rb +0 -18
  30. data/spec/configuration.yml.example +0 -17
  31. data/spec/em/em_spec.rb +0 -135
  32. data/spec/my.cnf.example +0 -9
  33. data/spec/mysql2/client_spec.rb +0 -939
  34. data/spec/mysql2/error_spec.rb +0 -84
  35. data/spec/mysql2/result_spec.rb +0 -510
  36. data/spec/mysql2/statement_spec.rb +0 -684
  37. data/spec/rcov.opts +0 -3
  38. data/spec/spec_helper.rb +0 -94
  39. data/spec/ssl/ca-cert.pem +0 -17
  40. data/spec/ssl/ca-key.pem +0 -27
  41. data/spec/ssl/ca.cnf +0 -22
  42. data/spec/ssl/cert.cnf +0 -22
  43. data/spec/ssl/client-cert.pem +0 -17
  44. data/spec/ssl/client-key.pem +0 -27
  45. data/spec/ssl/client-req.pem +0 -15
  46. data/spec/ssl/gen_certs.sh +0 -48
  47. data/spec/ssl/pkcs8-client-key.pem +0 -28
  48. data/spec/ssl/pkcs8-server-key.pem +0 -28
  49. data/spec/ssl/server-cert.pem +0 -17
  50. data/spec/ssl/server-key.pem +0 -27
  51. data/spec/ssl/server-req.pem +0 -15
  52. data/spec/test_data +0 -1
data/ext/mysql2/client.c CHANGED
@@ -15,38 +15,73 @@
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 ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
21
-
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
20
+ static VALUE sym_no_good_index_used, sym_no_index_used, sym_query_was_slow;
21
+ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args,
22
+ intern_current_query_options, intern_read_timeout;
27
23
 
28
24
  #define REQUIRE_INITIALIZED(wrapper) \
29
25
  if (!wrapper->initialized) { \
30
26
  rb_raise(cMysql2Error, "MySQL client is not initialized"); \
31
27
  }
32
28
 
29
+ #if defined(HAVE_MYSQL_NET_VIO) || defined(HAVE_ST_NET_VIO)
30
+ #define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1)
31
+ #elif defined(HAVE_MYSQL_NET_PVIO) || defined(HAVE_ST_NET_PVIO)
32
+ #define CONNECTED(wrapper) (wrapper->client->net.pvio != NULL && wrapper->client->net.fd != -1)
33
+ #endif
34
+
35
+ #define REQUIRE_CONNECTED(wrapper) \
36
+ REQUIRE_INITIALIZED(wrapper) \
37
+ if (!CONNECTED(wrapper) && !wrapper->reconnect_enabled) { \
38
+ rb_raise(cMysql2Error, "MySQL client is not connected"); \
39
+ }
40
+
33
41
  #define REQUIRE_NOT_CONNECTED(wrapper) \
34
42
  REQUIRE_INITIALIZED(wrapper) \
35
- if (wrapper->connected) { \
43
+ if (CONNECTED(wrapper)) { \
36
44
  rb_raise(cMysql2Error, "MySQL connection is already open"); \
37
45
  }
38
46
 
39
47
  /*
40
- * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
48
+ * compatibility with mysql-connector-c, where LIBMYSQL_VERSION is the correct
41
49
  * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
42
50
  * linking against the server itself
43
51
  */
44
- #ifdef LIBMYSQL_VERSION
52
+ #if defined(MARIADB_CLIENT_VERSION_STR)
53
+ #define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR
54
+ #elif defined(LIBMYSQL_VERSION)
45
55
  #define MYSQL_LINK_VERSION LIBMYSQL_VERSION
46
56
  #else
47
57
  #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION
48
58
  #endif
49
59
 
60
+ /*
61
+ * mariadb-connector-c defines CLIENT_SESSION_TRACKING and SESSION_TRACK_TRANSACTION_TYPE
62
+ * while mysql-connector-c defines CLIENT_SESSION_TRACK and SESSION_TRACK_TRANSACTION_STATE
63
+ * This is a hack to take care of both clients.
64
+ */
65
+ #if defined(CLIENT_SESSION_TRACK)
66
+ #elif defined(CLIENT_SESSION_TRACKING)
67
+ #define CLIENT_SESSION_TRACK CLIENT_SESSION_TRACKING
68
+ #define SESSION_TRACK_TRANSACTION_STATE SESSION_TRACK_TRANSACTION_TYPE
69
+ #endif
70
+
71
+ /*
72
+ * compatibility with mysql-connector-c 6.1.x, MySQL 5.7.3 - 5.7.10 & with MariaDB 10.x and later.
73
+ */
74
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT
75
+ #define SSL_MODE_VERIFY_IDENTITY 5
76
+ #define HAVE_CONST_SSL_MODE_VERIFY_IDENTITY
77
+ #endif
78
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
79
+ #define SSL_MODE_DISABLED 1
80
+ #define SSL_MODE_REQUIRED 3
81
+ #define HAVE_CONST_SSL_MODE_DISABLED
82
+ #define HAVE_CONST_SSL_MODE_REQUIRED
83
+ #endif
84
+
50
85
  /*
51
86
  * used to pass all arguments to mysql_real_connect while inside
52
87
  * rb_thread_call_without_gvl
@@ -83,6 +118,82 @@ struct nogvl_select_db_args {
83
118
  char *db;
84
119
  };
85
120
 
121
+ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
122
+ unsigned long version = mysql_get_client_version();
123
+ const char *version_str = mysql_get_client_info();
124
+
125
+ /* Warn about versions that are known to be incomplete; these are pretty
126
+ * ancient, we want people to upgrade if they need SSL/TLS to work
127
+ *
128
+ * MySQL 5.x before 5.6.30 -- ssl_mode introduced but not fully working until 5.6.36)
129
+ * MySQL 5.7 before 5.7.3 -- ssl_mode introduced but not fully working until 5.7.11)
130
+ */
131
+ if ((version >= 50000 && version < 50630) || (version >= 50700 && version < 50703)) {
132
+ rb_warn("Your mysql client library version %s does not support setting ssl_mode; full support comes with 5.6.36+, 5.7.11+, 8.0+", version_str);
133
+ return Qnil;
134
+ }
135
+
136
+ /* For these versions, map from the options we're exposing to Ruby to the constant available:
137
+ * ssl_mode: :verify_identity to MYSQL_OPT_SSL_VERIFY_SERVER_CERT = 1
138
+ * ssl_mode: :required to MYSQL_OPT_SSL_ENFORCE = 1
139
+ * ssl_mode: :disabled to MYSQL_OPT_SSL_ENFORCE = 0
140
+ */
141
+ #if defined(HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT) || defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE)
142
+ GET_CLIENT(self);
143
+ int val = NUM2INT(setting);
144
+
145
+ /* Expected code path for MariaDB 10.x and MariaDB Connector/C 3.x
146
+ * Workaround code path for MySQL 5.7.3 - 5.7.10 and MySQL Connector/C 6.1.3 - 6.1.x
147
+ */
148
+ if (version >= 100000 // MariaDB (all versions numbered 10.x)
149
+ || (version >= 30000 && version < 40000) // MariaDB Connector/C (all versions numbered 3.x)
150
+ || (version >= 50703 && version < 50711) // Workaround for MySQL 5.7.3 - 5.7.10
151
+ || (version >= 60103 && version < 60200)) { // Workaround for MySQL Connector/C 6.1.3 - 6.1.x
152
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT
153
+ if (val == SSL_MODE_VERIFY_IDENTITY) {
154
+ my_bool b = 1;
155
+ int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &b);
156
+ return INT2NUM(result);
157
+ }
158
+ #endif
159
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
160
+ if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
161
+ my_bool b = (val == SSL_MODE_REQUIRED);
162
+ int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b);
163
+ return INT2NUM(result);
164
+ }
165
+ #endif
166
+ rb_warn("Your mysql client library version %s does not support ssl_mode %d", version_str, val);
167
+ return Qnil;
168
+ } else {
169
+ rb_warn("Your mysql client library version %s does not support ssl_mode as expected", version_str);
170
+ return Qnil;
171
+ }
172
+ #endif
173
+
174
+ /* For other versions -- known to be MySQL 5.6.36+, 5.7.11+, 8.0+
175
+ * pass the value of the argument to MYSQL_OPT_SSL_MODE -- note the code
176
+ * mapping from atoms / constants is in the MySQL::Client Ruby class
177
+ */
178
+ #ifdef FULL_SSL_MODE_SUPPORT
179
+ GET_CLIENT(self);
180
+ int val = NUM2INT(setting);
181
+
182
+ if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
183
+ rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
184
+ }
185
+ int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_MODE, &val);
186
+
187
+ return INT2NUM(result);
188
+ #endif
189
+
190
+ // Warn if we get this far
191
+ #ifdef NO_SSL_MODE_SUPPORT
192
+ rb_warn("Your mysql client library does not support setting ssl_mode");
193
+ return Qnil;
194
+ #endif
195
+ }
196
+
86
197
  /*
87
198
  * non-blocking mysql_*() functions that we won't be wrapping since
88
199
  * they do not appear to hit the network nor issue any interruptible
@@ -108,20 +219,54 @@ struct nogvl_select_db_args {
108
219
  static void rb_mysql_client_mark(void * wrapper) {
109
220
  mysql_client_wrapper * w = wrapper;
110
221
  if (w) {
111
- rb_gc_mark(w->encoding);
112
- rb_gc_mark(w->active_thread);
222
+ rb_gc_mark_movable(w->encoding);
223
+ rb_gc_mark_movable(w->active_fiber);
113
224
  }
114
225
  }
115
226
 
227
+ /* this is called during GC */
228
+ static void rb_mysql_client_free(void *ptr) {
229
+ mysql_client_wrapper *wrapper = ptr;
230
+ decr_mysql2_client(wrapper);
231
+ }
232
+
233
+ static size_t rb_mysql_client_memsize(const void * wrapper) {
234
+ const mysql_client_wrapper * w = wrapper;
235
+ return sizeof(*w);
236
+ }
237
+
238
+ static void rb_mysql_client_compact(void * wrapper) {
239
+ mysql_client_wrapper * w = wrapper;
240
+ if (w) {
241
+ rb_mysql2_gc_location(w->encoding);
242
+ rb_mysql2_gc_location(w->active_fiber);
243
+ }
244
+ }
245
+
246
+ const rb_data_type_t rb_mysql_client_type = {
247
+ "rb_mysql_client",
248
+ {
249
+ rb_mysql_client_mark,
250
+ rb_mysql_client_free,
251
+ rb_mysql_client_memsize,
252
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
253
+ rb_mysql_client_compact,
254
+ #endif
255
+ },
256
+ 0,
257
+ 0,
258
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
259
+ RUBY_TYPED_FREE_IMMEDIATELY,
260
+ #endif
261
+ };
262
+
116
263
  static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
117
264
  VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
118
- VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
265
+ VALUE rb_sql_state = rb_str_new2(mysql_sqlstate(wrapper->client));
119
266
  VALUE e;
120
267
 
121
- #ifdef HAVE_RUBY_ENCODING_H
122
268
  rb_enc_associate(rb_error_msg, rb_utf8_encoding());
123
269
  rb_enc_associate(rb_sql_state, rb_usascii_encoding());
124
- #endif
125
270
 
126
271
  e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
127
272
  rb_error_msg,
@@ -211,30 +356,23 @@ static VALUE invalidate_fd(int clientfd)
211
356
  static void *nogvl_close(void *ptr) {
212
357
  mysql_client_wrapper *wrapper = ptr;
213
358
 
214
- if (wrapper->client) {
359
+ if (wrapper->initialized && !wrapper->closed) {
215
360
  mysql_close(wrapper->client);
216
- xfree(wrapper->client);
217
- wrapper->client = NULL;
218
- wrapper->connected = 0;
219
- wrapper->active_thread = Qnil;
361
+ wrapper->closed = 1;
362
+ wrapper->reconnect_enabled = 0;
363
+ wrapper->active_fiber = Qnil;
220
364
  }
221
365
 
222
366
  return NULL;
223
367
  }
224
368
 
225
- /* this is called during GC */
226
- static void rb_mysql_client_free(void *ptr) {
227
- mysql_client_wrapper *wrapper = ptr;
228
- decr_mysql2_client(wrapper);
229
- }
230
-
231
369
  void decr_mysql2_client(mysql_client_wrapper *wrapper)
232
370
  {
233
371
  wrapper->refcount--;
234
372
 
235
373
  if (wrapper->refcount == 0) {
236
374
  #ifndef _WIN32
237
- if (wrapper->connected) {
375
+ if (CONNECTED(wrapper) && !wrapper->automatic_close) {
238
376
  /* The client is being garbage collected while connected. Prevent
239
377
  * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
240
378
  * the socket by invalidating it. invalidate_fd() will drop this
@@ -246,10 +384,12 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper)
246
384
  fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
247
385
  close(wrapper->client->net.fd);
248
386
  }
387
+ wrapper->client->net.fd = -1;
249
388
  }
250
389
  #endif
251
390
 
252
391
  nogvl_close(wrapper);
392
+ xfree(wrapper->client);
253
393
  xfree(wrapper);
254
394
  }
255
395
  }
@@ -257,14 +397,19 @@ void decr_mysql2_client(mysql_client_wrapper *wrapper)
257
397
  static VALUE allocate(VALUE klass) {
258
398
  VALUE obj;
259
399
  mysql_client_wrapper * wrapper;
400
+ #ifdef NEW_TYPEDDATA_WRAPPER
401
+ obj = TypedData_Make_Struct(klass, mysql_client_wrapper, &rb_mysql_client_type, wrapper);
402
+ #else
260
403
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
404
+ #endif
261
405
  wrapper->encoding = Qnil;
262
- MARK_CONN_INACTIVE(self);
406
+ wrapper->active_fiber = Qnil;
407
+ wrapper->automatic_close = 1;
263
408
  wrapper->server_version = 0;
264
409
  wrapper->reconnect_enabled = 0;
265
410
  wrapper->connect_timeout = 0;
266
- wrapper->connected = 0; /* means that a database connection is open */
267
- wrapper->initialized = 0; /* means that that the wrapper is initialized */
411
+ wrapper->initialized = 0; /* will be set true after calling mysql_init */
412
+ wrapper->closed = 1; /* will be set false after calling mysql_real_connect */
268
413
  wrapper->refcount = 1;
269
414
  wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
270
415
 
@@ -295,9 +440,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
295
440
  return str;
296
441
  } else {
297
442
  rb_str = rb_str_new((const char*)newStr, newLen);
298
- #ifdef HAVE_RUBY_ENCODING_H
299
443
  rb_enc_copy(rb_str, str);
300
- #endif
301
444
  xfree(newStr);
302
445
  return rb_str;
303
446
  }
@@ -324,14 +467,43 @@ static VALUE rb_mysql_info(VALUE self) {
324
467
  }
325
468
 
326
469
  rb_str = rb_str_new2(info);
327
- #ifdef HAVE_RUBY_ENCODING_H
328
470
  rb_enc_associate(rb_str, rb_utf8_encoding());
329
- #endif
330
471
 
331
472
  return rb_str;
332
473
  }
333
474
 
334
- static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
475
+ static VALUE rb_mysql_get_ssl_cipher(VALUE self)
476
+ {
477
+ const char *cipher;
478
+ VALUE rb_str;
479
+ GET_CLIENT(self);
480
+
481
+ cipher = mysql_get_ssl_cipher(wrapper->client);
482
+
483
+ if (cipher == NULL) {
484
+ return Qnil;
485
+ }
486
+
487
+ rb_str = rb_str_new2(cipher);
488
+ rb_enc_associate(rb_str, rb_utf8_encoding());
489
+
490
+ return rb_str;
491
+ }
492
+
493
+ #ifdef CLIENT_CONNECT_ATTRS
494
+ static int opt_connect_attr_add_i(VALUE key, VALUE value, VALUE arg)
495
+ {
496
+ mysql_client_wrapper *wrapper = (mysql_client_wrapper *)arg;
497
+ rb_encoding *enc = rb_to_encoding(wrapper->encoding);
498
+ key = rb_str_export_to_enc(key, enc);
499
+ value = rb_str_export_to_enc(value, enc);
500
+
501
+ mysql_options4(wrapper->client, MYSQL_OPT_CONNECT_ATTR_ADD, StringValueCStr(key), StringValueCStr(value));
502
+ return ST_CONTINUE;
503
+ }
504
+ #endif
505
+
506
+ 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
507
  struct nogvl_connect_args args;
336
508
  time_t start_time, end_time, elapsed_time, connect_timeout;
337
509
  VALUE rv;
@@ -346,6 +518,11 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
346
518
  args.mysql = wrapper->client;
347
519
  args.client_flag = NUM2ULONG(flags);
348
520
 
521
+ #ifdef CLIENT_CONNECT_ATTRS
522
+ mysql_options(wrapper->client, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
523
+ rb_hash_foreach(conn_attrs, opt_connect_attr_add_i, (VALUE)wrapper);
524
+ #endif
525
+
349
526
  if (wrapper->connect_timeout)
350
527
  time(&start_time);
351
528
  rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
@@ -372,33 +549,42 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
372
549
  if (wrapper->connect_timeout)
373
550
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
374
551
  if (rv == Qfalse)
375
- return rb_raise_mysql2_error(wrapper);
552
+ rb_raise_mysql2_error(wrapper);
376
553
  }
377
554
 
555
+ wrapper->closed = 0;
378
556
  wrapper->server_version = mysql_get_server_version(wrapper->client);
379
- wrapper->connected = 1;
380
557
  return self;
381
558
  }
382
559
 
383
560
  /*
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.
561
+ * Immediately disconnect from the server; normally the garbage collector
562
+ * will disconnect automatically when a connection is no longer needed.
563
+ * Explicitly closing this will free up server resources sooner than waiting
564
+ * for the garbage collector.
388
565
  *
389
- * @see http://dev.mysql.com/doc/en/communication-errors.html
390
- * @return [void]
566
+ * @return [nil]
391
567
  */
392
568
  static VALUE rb_mysql_client_close(VALUE self) {
393
569
  GET_CLIENT(self);
394
570
 
395
- if (wrapper->connected) {
571
+ if (wrapper->client) {
396
572
  rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
397
573
  }
398
574
 
399
575
  return Qnil;
400
576
  }
401
577
 
578
+ /* call-seq:
579
+ * client.closed?
580
+ *
581
+ * @return [Boolean]
582
+ */
583
+ static VALUE rb_mysql_client_closed(VALUE self) {
584
+ GET_CLIENT(self);
585
+ return CONNECTED(wrapper) ? Qfalse : Qtrue;
586
+ }
587
+
402
588
  /*
403
589
  * mysql_send_query is unlikely to block since most queries are small
404
590
  * enough to fit in a socket buffer, but sometimes large UPDATE and
@@ -413,13 +599,13 @@ static void *nogvl_send_query(void *ptr) {
413
599
  return (void*)(rv == 0 ? Qtrue : Qfalse);
414
600
  }
415
601
 
416
- static VALUE do_send_query(void *args) {
417
- struct nogvl_send_query_args *query_args = args;
602
+ static VALUE do_send_query(VALUE args) {
603
+ struct nogvl_send_query_args *query_args = (void *)args;
418
604
  mysql_client_wrapper *wrapper = query_args->wrapper;
419
- if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
605
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, query_args, RUBY_UBF_IO, 0) == Qfalse) {
420
606
  /* an error occurred, we're not active anymore */
421
- MARK_CONN_INACTIVE(self);
422
- return rb_raise_mysql2_error(wrapper);
607
+ wrapper->active_fiber = Qnil;
608
+ rb_raise_mysql2_error(wrapper);
423
609
  }
424
610
  return Qnil;
425
611
  }
@@ -448,7 +634,7 @@ static void *nogvl_do_result(void *ptr, char use_result) {
448
634
 
449
635
  /* once our result is stored off, this connection is
450
636
  ready for another command to be issued */
451
- MARK_CONN_INACTIVE(self);
637
+ wrapper->active_fiber = Qnil;
452
638
 
453
639
  return result;
454
640
  }
@@ -474,17 +660,17 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
474
660
  GET_CLIENT(self);
475
661
 
476
662
  /* if we're not waiting on a result, do nothing */
477
- if (NIL_P(wrapper->active_thread))
663
+ if (NIL_P(wrapper->active_fiber))
478
664
  return Qnil;
479
665
 
480
666
  REQUIRE_CONNECTED(wrapper);
481
667
  if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
482
668
  /* an error occurred, mark this connection inactive */
483
- MARK_CONN_INACTIVE(self);
484
- return rb_raise_mysql2_error(wrapper);
669
+ wrapper->active_fiber = Qnil;
670
+ rb_raise_mysql2_error(wrapper);
485
671
  }
486
672
 
487
- is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
673
+ is_streaming = rb_hash_aref(rb_ivar_get(self, intern_current_query_options), sym_stream);
488
674
  if (is_streaming == Qtrue) {
489
675
  result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0);
490
676
  } else {
@@ -493,18 +679,21 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
493
679
 
494
680
  if (result == NULL) {
495
681
  if (mysql_errno(wrapper->client) != 0) {
496
- MARK_CONN_INACTIVE(self);
682
+ wrapper->active_fiber = Qnil;
497
683
  rb_raise_mysql2_error(wrapper);
498
684
  }
499
685
  /* no data and no error, so query was not a SELECT */
500
686
  return Qnil;
501
687
  }
502
688
 
503
- current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
689
+ // Duplicate the options hash and put the copy in the Result object
690
+ current = rb_hash_dup(rb_ivar_get(self, intern_current_query_options));
504
691
  (void)RB_GC_GUARD(current);
505
692
  Check_Type(current, T_HASH);
506
693
  resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
507
694
 
695
+ rb_mysql_set_server_query_flags(wrapper->client, resultObj);
696
+
508
697
  return resultObj;
509
698
  }
510
699
 
@@ -517,29 +706,31 @@ struct async_query_args {
517
706
  static VALUE disconnect_and_raise(VALUE self, VALUE error) {
518
707
  GET_CLIENT(self);
519
708
 
520
- MARK_CONN_INACTIVE(self);
521
- wrapper->connected = 0;
709
+ wrapper->active_fiber = Qnil;
522
710
 
523
711
  /* Invalidate the MySQL socket to prevent further communication.
524
712
  * The GC will come along later and call mysql_close to free it.
525
713
  */
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);
714
+ if (CONNECTED(wrapper)) {
715
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
716
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
717
+ close(wrapper->client->net.fd);
718
+ }
719
+ wrapper->client->net.fd = -1;
529
720
  }
530
721
 
531
722
  rb_exc_raise(error);
532
723
  }
533
724
 
534
- static VALUE do_query(void *args) {
535
- struct async_query_args *async_args = args;
725
+ static VALUE do_query(VALUE args) {
726
+ struct async_query_args *async_args = (void *)args;
536
727
  struct timeval tv;
537
728
  struct timeval *tvp;
538
729
  long int sec;
539
730
  int retval;
540
731
  VALUE read_timeout;
541
732
 
542
- read_timeout = rb_iv_get(async_args->self, "@read_timeout");
733
+ read_timeout = rb_ivar_get(async_args->self, intern_read_timeout);
543
734
 
544
735
  tvp = NULL;
545
736
  if (!NIL_P(read_timeout)) {
@@ -560,7 +751,7 @@ static VALUE do_query(void *args) {
560
751
  retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp);
561
752
 
562
753
  if (retval == 0) {
563
- rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
754
+ rb_raise(cMysql2TimeoutError, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
564
755
  }
565
756
 
566
757
  if (retval < 0) {
@@ -574,39 +765,45 @@ static VALUE do_query(void *args) {
574
765
 
575
766
  return Qnil;
576
767
  }
577
- #else
578
- static VALUE finish_and_mark_inactive(void *args) {
579
- VALUE self = args;
580
- MYSQL_RES *result;
768
+ #endif
581
769
 
770
+ static VALUE disconnect_and_mark_inactive(VALUE self) {
582
771
  GET_CLIENT(self);
583
772
 
584
- 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);
773
+ /* Check if execution terminated while result was still being read. */
774
+ if (!NIL_P(wrapper->active_fiber)) {
775
+ if (CONNECTED(wrapper)) {
776
+ /* Invalidate the MySQL socket to prevent further communication. */
777
+ #ifndef _WIN32
778
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
779
+ rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n");
780
+ close(wrapper->client->net.fd);
781
+ }
782
+ #else
783
+ close(wrapper->client->net.fd);
784
+ #endif
785
+ wrapper->client->net.fd = -1;
786
+ }
787
+ /* Skip mysql client check performed before command execution. */
788
+ wrapper->client->status = MYSQL_STATUS_READY;
789
+ wrapper->active_fiber = Qnil;
592
790
  }
593
791
 
594
792
  return Qnil;
595
793
  }
596
- #endif
597
794
 
598
- void rb_mysql_client_set_active_thread(VALUE self) {
599
- VALUE thread_current = rb_thread_current();
795
+ static void rb_mysql_client_set_active_fiber(VALUE self) {
796
+ VALUE fiber_current = rb_fiber_current();
600
797
  GET_CLIENT(self);
601
798
 
602
799
  // see if this connection is still waiting on a result from a previous query
603
- if (NIL_P(wrapper->active_thread)) {
800
+ if (NIL_P(wrapper->active_fiber)) {
604
801
  // mark this connection active
605
- wrapper->active_thread = thread_current;
606
- } else if (wrapper->active_thread == thread_current) {
802
+ wrapper->active_fiber = fiber_current;
803
+ } else if (wrapper->active_fiber == fiber_current) {
607
804
  rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
608
805
  } else {
609
- VALUE inspect = rb_inspect(wrapper->active_thread);
806
+ VALUE inspect = rb_inspect(wrapper->active_fiber);
610
807
  const char *thr = StringValueCStr(inspect);
611
808
 
612
809
  rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
@@ -649,7 +846,7 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
649
846
  * Query the database with +sql+, with optional +options+. For the possible
650
847
  * options, see default_query_options on the Mysql2::Client class.
651
848
  */
652
- static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
849
+ static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) {
653
850
  #ifndef _WIN32
654
851
  struct async_query_args async_args;
655
852
  #endif
@@ -661,23 +858,20 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
661
858
 
662
859
  (void)RB_GC_GUARD(current);
663
860
  Check_Type(current, T_HASH);
664
- rb_iv_set(self, "@current_query_options", current);
861
+ rb_ivar_set(self, intern_current_query_options, current);
665
862
 
666
863
  Check_Type(sql, T_STRING);
667
- #ifdef HAVE_RUBY_ENCODING_H
668
864
  /* ensure the string is in the encoding the connection is expecting */
669
865
  args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
670
- #else
671
- args.sql = sql;
672
- #endif
673
866
  args.sql_ptr = RSTRING_PTR(args.sql);
674
867
  args.sql_len = RSTRING_LEN(args.sql);
675
868
  args.wrapper = wrapper;
676
869
 
677
- rb_mysql_client_set_active_thread(self);
870
+ rb_mysql_client_set_active_fiber(self);
678
871
 
679
872
  #ifndef _WIN32
680
873
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
874
+ (void)RB_GC_GUARD(sql);
681
875
 
682
876
  if (rb_hash_aref(current, sym_async) == Qtrue) {
683
877
  return Qnil;
@@ -687,13 +881,14 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
687
881
 
688
882
  rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
689
883
 
690
- return rb_mysql_client_async_result(self);
884
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
691
885
  }
692
886
  #else
693
- do_send_query(&args);
887
+ do_send_query((VALUE)&args);
888
+ (void)RB_GC_GUARD(sql);
694
889
 
695
890
  /* this will just block until the result is ready */
696
- return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
891
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
697
892
  #endif
698
893
  }
699
894
 
@@ -706,20 +901,16 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
706
901
  unsigned char *newStr;
707
902
  VALUE rb_str;
708
903
  unsigned long newLen, oldLen;
709
- #ifdef HAVE_RUBY_ENCODING_H
710
904
  rb_encoding *default_internal_enc;
711
905
  rb_encoding *conn_enc;
712
- #endif
713
906
  GET_CLIENT(self);
714
907
 
715
908
  REQUIRE_CONNECTED(wrapper);
716
909
  Check_Type(str, T_STRING);
717
- #ifdef HAVE_RUBY_ENCODING_H
718
910
  default_internal_enc = rb_default_internal_encoding();
719
911
  conn_enc = rb_to_encoding(wrapper->encoding);
720
912
  /* ensure the string is in the encoding the connection is expecting */
721
913
  str = rb_str_export_to_enc(str, conn_enc);
722
- #endif
723
914
 
724
915
  oldLen = RSTRING_LEN(str);
725
916
  newStr = xmalloc(oldLen*2+1);
@@ -727,21 +918,17 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
727
918
  newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
728
919
  if (newLen == oldLen) {
729
920
  /* no need to return a new ruby string if nothing changed */
730
- #ifdef HAVE_RUBY_ENCODING_H
731
921
  if (default_internal_enc) {
732
922
  str = rb_str_export_to_enc(str, default_internal_enc);
733
923
  }
734
- #endif
735
924
  xfree(newStr);
736
925
  return str;
737
926
  } else {
738
927
  rb_str = rb_str_new((const char*)newStr, newLen);
739
- #ifdef HAVE_RUBY_ENCODING_H
740
928
  rb_enc_associate(rb_str, conn_enc);
741
929
  if (default_internal_enc) {
742
930
  rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
743
931
  }
744
- #endif
745
932
  xfree(newStr);
746
933
  return rb_str;
747
934
  }
@@ -787,10 +974,12 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
787
974
  retval = &boolval;
788
975
  break;
789
976
 
977
+ #ifdef MYSQL_SECURE_AUTH
790
978
  case MYSQL_SECURE_AUTH:
791
979
  boolval = (value == Qfalse ? 0 : 1);
792
980
  retval = &boolval;
793
981
  break;
982
+ #endif
794
983
 
795
984
  case MYSQL_READ_DEFAULT_FILE:
796
985
  charval = (const char *)StringValueCStr(value);
@@ -807,6 +996,20 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
807
996
  retval = charval;
808
997
  break;
809
998
 
999
+ #ifdef HAVE_MYSQL_DEFAULT_AUTH
1000
+ case MYSQL_DEFAULT_AUTH:
1001
+ charval = (const char *)StringValueCStr(value);
1002
+ retval = charval;
1003
+ break;
1004
+ #endif
1005
+
1006
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
1007
+ case MYSQL_ENABLE_CLEARTEXT_PLUGIN:
1008
+ boolval = (value == Qfalse ? 0 : 1);
1009
+ retval = &boolval;
1010
+ break;
1011
+ #endif
1012
+
810
1013
  default:
811
1014
  return Qfalse;
812
1015
  }
@@ -843,10 +1046,8 @@ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
843
1046
  version = rb_str_new2(mysql_get_client_info());
844
1047
  header_version = rb_str_new2(MYSQL_LINK_VERSION);
845
1048
 
846
- #ifdef HAVE_RUBY_ENCODING_H
847
1049
  rb_enc_associate(version, rb_usascii_encoding());
848
1050
  rb_enc_associate(header_version, rb_usascii_encoding());
849
- #endif
850
1051
 
851
1052
  rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
852
1053
  rb_hash_aset(version_info, sym_version, version);
@@ -862,27 +1063,21 @@ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
862
1063
  */
863
1064
  static VALUE rb_mysql_client_server_info(VALUE self) {
864
1065
  VALUE version, server_info;
865
- #ifdef HAVE_RUBY_ENCODING_H
866
1066
  rb_encoding *default_internal_enc;
867
1067
  rb_encoding *conn_enc;
868
- #endif
869
1068
  GET_CLIENT(self);
870
1069
 
871
1070
  REQUIRE_CONNECTED(wrapper);
872
- #ifdef HAVE_RUBY_ENCODING_H
873
1071
  default_internal_enc = rb_default_internal_encoding();
874
1072
  conn_enc = rb_to_encoding(wrapper->encoding);
875
- #endif
876
1073
 
877
1074
  version = rb_hash_new();
878
1075
  rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
879
1076
  server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
880
- #ifdef HAVE_RUBY_ENCODING_H
881
1077
  rb_enc_associate(server_info, conn_enc);
882
1078
  if (default_internal_enc) {
883
1079
  server_info = rb_str_export_to_enc(server_info, default_internal_enc);
884
1080
  }
885
- #endif
886
1081
  rb_hash_aset(version, sym_version, server_info);
887
1082
  return version;
888
1083
  }
@@ -916,6 +1111,36 @@ static VALUE rb_mysql_client_last_id(VALUE self) {
916
1111
  return ULL2NUM(mysql_insert_id(wrapper->client));
917
1112
  }
918
1113
 
1114
+ /* call-seq:
1115
+ * client.session_track
1116
+ *
1117
+ * Returns information about changes to the session state on the server.
1118
+ */
1119
+ static VALUE rb_mysql_client_session_track(VALUE self, VALUE type) {
1120
+ #ifdef CLIENT_SESSION_TRACK
1121
+ const char *data;
1122
+ size_t length;
1123
+ my_ulonglong retVal;
1124
+ GET_CLIENT(self);
1125
+
1126
+ REQUIRE_CONNECTED(wrapper);
1127
+ retVal = mysql_session_track_get_first(wrapper->client, NUM2INT(type), &data, &length);
1128
+ if (retVal != 0) {
1129
+ return Qnil;
1130
+ }
1131
+ VALUE rbAry = rb_ary_new();
1132
+ VALUE rbFirst = rb_str_new(data, length);
1133
+ rb_ary_push(rbAry, rbFirst);
1134
+ while(mysql_session_track_get_next(wrapper->client, NUM2INT(type), &data, &length) == 0) {
1135
+ VALUE rbNext = rb_str_new(data, length);
1136
+ rb_ary_push(rbAry, rbNext);
1137
+ }
1138
+ return rbAry;
1139
+ #else
1140
+ return Qnil;
1141
+ #endif
1142
+ }
1143
+
919
1144
  /* call-seq:
920
1145
  * client.affected_rows
921
1146
  *
@@ -996,13 +1221,30 @@ static void *nogvl_ping(void *ptr) {
996
1221
  static VALUE rb_mysql_client_ping(VALUE self) {
997
1222
  GET_CLIENT(self);
998
1223
 
999
- if (!wrapper->connected) {
1224
+ if (!CONNECTED(wrapper)) {
1000
1225
  return Qfalse;
1001
1226
  } else {
1002
1227
  return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
1003
1228
  }
1004
1229
  }
1005
1230
 
1231
+ /* call-seq:
1232
+ * client.set_server_option(value)
1233
+ *
1234
+ * Enables or disables an option for the connection.
1235
+ * Read https://dev.mysql.com/doc/refman/5.7/en/mysql-set-server-option.html
1236
+ * for more information.
1237
+ */
1238
+ static VALUE rb_mysql_client_set_server_option(VALUE self, VALUE value) {
1239
+ GET_CLIENT(self);
1240
+
1241
+ if (mysql_set_server_option(wrapper->client, NUM2INT(value)) == 0) {
1242
+ return Qtrue;
1243
+ } else {
1244
+ return Qfalse;
1245
+ }
1246
+ }
1247
+
1006
1248
  /* call-seq:
1007
1249
  * client.more_results?
1008
1250
  *
@@ -1011,10 +1253,10 @@ static VALUE rb_mysql_client_ping(VALUE self) {
1011
1253
  static VALUE rb_mysql_client_more_results(VALUE self)
1012
1254
  {
1013
1255
  GET_CLIENT(self);
1014
- if (mysql_more_results(wrapper->client) == 0)
1015
- return Qfalse;
1016
- else
1017
- return Qtrue;
1256
+ if (mysql_more_results(wrapper->client) == 0)
1257
+ return Qfalse;
1258
+ else
1259
+ return Qtrue;
1018
1260
  }
1019
1261
 
1020
1262
  /* call-seq:
@@ -1061,7 +1303,8 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1061
1303
  return Qnil;
1062
1304
  }
1063
1305
 
1064
- current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1306
+ // Duplicate the options hash and put the copy in the Result object
1307
+ current = rb_hash_dup(rb_ivar_get(self, intern_current_query_options));
1065
1308
  (void)RB_GC_GUARD(current);
1066
1309
  Check_Type(current, T_HASH);
1067
1310
  resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
@@ -1069,7 +1312,6 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1069
1312
  return resultObj;
1070
1313
  }
1071
1314
 
1072
- #ifdef HAVE_RUBY_ENCODING_H
1073
1315
  /* call-seq:
1074
1316
  * client.encoding
1075
1317
  *
@@ -1079,7 +1321,39 @@ static VALUE rb_mysql_client_encoding(VALUE self) {
1079
1321
  GET_CLIENT(self);
1080
1322
  return wrapper->encoding;
1081
1323
  }
1324
+
1325
+ /* call-seq:
1326
+ * client.automatic_close?
1327
+ *
1328
+ * @return [Boolean]
1329
+ */
1330
+ static VALUE get_automatic_close(VALUE self) {
1331
+ GET_CLIENT(self);
1332
+ return wrapper->automatic_close ? Qtrue : Qfalse;
1333
+ }
1334
+
1335
+ /* call-seq:
1336
+ * client.automatic_close = false
1337
+ *
1338
+ * Set this to +false+ to leave the connection open after it is garbage
1339
+ * collected. To avoid "Aborted connection" errors on the server, explicitly
1340
+ * call +close+ when the connection is no longer needed.
1341
+ *
1342
+ * @see http://dev.mysql.com/doc/en/communication-errors.html
1343
+ */
1344
+ static VALUE set_automatic_close(VALUE self, VALUE value) {
1345
+ GET_CLIENT(self);
1346
+ if (RTEST(value)) {
1347
+ wrapper->automatic_close = 1;
1348
+ } else {
1349
+ #ifndef _WIN32
1350
+ wrapper->automatic_close = 0;
1351
+ #else
1352
+ rb_warn("Connections are always closed by garbage collector on Windows");
1082
1353
  #endif
1354
+ }
1355
+ return value;
1356
+ }
1083
1357
 
1084
1358
  /* call-seq:
1085
1359
  * client.reconnect = true
@@ -1116,7 +1390,7 @@ static VALUE set_read_timeout(VALUE self, VALUE value) {
1116
1390
  /* Set the instance variable here even though _mysql_client_options
1117
1391
  might not succeed, because the timeout is used in other ways
1118
1392
  elsewhere */
1119
- rb_iv_set(self, "@read_timeout", value);
1393
+ rb_ivar_set(self, intern_read_timeout, value);
1120
1394
  return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value);
1121
1395
  }
1122
1396
 
@@ -1132,16 +1406,14 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
1132
1406
 
1133
1407
  static VALUE set_charset_name(VALUE self, VALUE value) {
1134
1408
  char *charset_name;
1135
- #ifdef HAVE_RUBY_ENCODING_H
1136
1409
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1137
1410
  rb_encoding *enc;
1138
1411
  VALUE rb_enc;
1139
- #endif
1140
1412
  GET_CLIENT(self);
1141
1413
 
1414
+ Check_Type(value, T_STRING);
1142
1415
  charset_name = RSTRING_PTR(value);
1143
1416
 
1144
- #ifdef HAVE_RUBY_ENCODING_H
1145
1417
  mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1146
1418
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
1147
1419
  VALUE inspect = rb_inspect(value);
@@ -1151,7 +1423,6 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
1151
1423
  rb_enc = rb_enc_from_encoding(enc);
1152
1424
  wrapper->encoding = rb_enc;
1153
1425
  }
1154
- #endif
1155
1426
 
1156
1427
  if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
1157
1428
  /* TODO: warning - unable to set charset */
@@ -1175,7 +1446,12 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
1175
1446
  }
1176
1447
 
1177
1448
  static VALUE set_secure_auth(VALUE self, VALUE value) {
1449
+ /* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */
1450
+ #ifdef MYSQL_SECURE_AUTH
1178
1451
  return _mysql_client_options(self, MYSQL_SECURE_AUTH, value);
1452
+ #else
1453
+ return Qfalse;
1454
+ #endif
1179
1455
  }
1180
1456
 
1181
1457
  static VALUE set_read_default_file(VALUE self, VALUE value) {
@@ -1190,12 +1466,28 @@ static VALUE set_init_command(VALUE self, VALUE value) {
1190
1466
  return _mysql_client_options(self, MYSQL_INIT_COMMAND, value);
1191
1467
  }
1192
1468
 
1469
+ static VALUE set_default_auth(VALUE self, VALUE value) {
1470
+ #ifdef HAVE_MYSQL_DEFAULT_AUTH
1471
+ return _mysql_client_options(self, MYSQL_DEFAULT_AUTH, value);
1472
+ #else
1473
+ rb_raise(cMysql2Error, "pluggable authentication is not available, you may need a newer MySQL client library");
1474
+ #endif
1475
+ }
1476
+
1477
+ static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) {
1478
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
1479
+ return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value);
1480
+ #else
1481
+ rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library");
1482
+ #endif
1483
+ }
1484
+
1193
1485
  static VALUE initialize_ext(VALUE self) {
1194
1486
  GET_CLIENT(self);
1195
1487
 
1196
1488
  if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
1197
1489
  /* TODO: warning - not enough memory? */
1198
- return rb_raise_mysql2_error(wrapper);
1490
+ rb_raise_mysql2_error(wrapper);
1199
1491
  }
1200
1492
 
1201
1493
  wrapper->initialized = 1;
@@ -1243,6 +1535,7 @@ void init_mysql2_client() {
1243
1535
  mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant.
1244
1536
  #endif
1245
1537
  cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
1538
+ rb_global_variable(&cMysql2Client);
1246
1539
 
1247
1540
  rb_define_alloc_func(cMysql2Client, allocate);
1248
1541
 
@@ -1250,6 +1543,7 @@ void init_mysql2_client() {
1250
1543
  rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1251
1544
 
1252
1545
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
1546
+ rb_define_method(cMysql2Client, "closed?", rb_mysql_client_closed, 0);
1253
1547
  rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
1254
1548
  rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
1255
1549
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
@@ -1261,15 +1555,18 @@ void init_mysql2_client() {
1261
1555
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
1262
1556
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1263
1557
  rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
1558
+ rb_define_method(cMysql2Client, "set_server_option", rb_mysql_client_set_server_option, 1);
1264
1559
  rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0);
1265
1560
  rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
1266
1561
  rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
1562
+ rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0);
1563
+ rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1);
1267
1564
  rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1);
1268
1565
  rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0);
1269
1566
  rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0);
1270
- #ifdef HAVE_RUBY_ENCODING_H
1567
+ rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0);
1271
1568
  rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
1272
- #endif
1569
+ rb_define_method(cMysql2Client, "session_track", rb_mysql_client_session_track, 1);
1273
1570
 
1274
1571
  rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
1275
1572
  rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1);
@@ -1280,10 +1577,13 @@ void init_mysql2_client() {
1280
1577
  rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1);
1281
1578
  rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1);
1282
1579
  rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1);
1580
+ rb_define_private_method(cMysql2Client, "default_auth=", set_default_auth, 1);
1283
1581
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
1582
+ rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1);
1583
+ rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1);
1284
1584
  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);
1585
+ rb_define_private_method(cMysql2Client, "connect", rb_mysql_connect, 8);
1586
+ rb_define_private_method(cMysql2Client, "_query", rb_mysql_query, 2);
1287
1587
 
1288
1588
  sym_id = ID2SYM(rb_intern("id"));
1289
1589
  sym_version = ID2SYM(rb_intern("version"));
@@ -1294,14 +1594,24 @@ void init_mysql2_client() {
1294
1594
  sym_array = ID2SYM(rb_intern("array"));
1295
1595
  sym_stream = ID2SYM(rb_intern("stream"));
1296
1596
 
1597
+ sym_no_good_index_used = ID2SYM(rb_intern("no_good_index_used"));
1598
+ sym_no_index_used = ID2SYM(rb_intern("no_index_used"));
1599
+ sym_query_was_slow = ID2SYM(rb_intern("query_was_slow"));
1600
+
1297
1601
  intern_brackets = rb_intern("[]");
1298
1602
  intern_merge = rb_intern("merge");
1299
1603
  intern_merge_bang = rb_intern("merge!");
1300
1604
  intern_new_with_args = rb_intern("new_with_args");
1605
+ intern_current_query_options = rb_intern("@current_query_options");
1606
+ intern_read_timeout = rb_intern("@read_timeout");
1301
1607
 
1302
1608
  #ifdef CLIENT_LONG_PASSWORD
1303
1609
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
1304
1610
  LONG2NUM(CLIENT_LONG_PASSWORD));
1611
+ #else
1612
+ /* HACK because MariaDB 10.2 no longer defines this constant,
1613
+ * but we're using it in our default connection flags. */
1614
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), INT2NUM(0));
1305
1615
  #endif
1306
1616
 
1307
1617
  #ifdef CLIENT_FOUND_ROWS
@@ -1379,6 +1689,16 @@ void init_mysql2_client() {
1379
1689
  rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0));
1380
1690
  #endif
1381
1691
 
1692
+ #ifdef HAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_ON
1693
+ rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_ON"),
1694
+ LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_ON));
1695
+ #endif
1696
+
1697
+ #ifdef HAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_OFF
1698
+ rb_const_set(cMysql2Client, rb_intern("OPTION_MULTI_STATEMENTS_OFF"),
1699
+ LONG2NUM(MYSQL_OPTION_MULTI_STATEMENTS_OFF));
1700
+ #endif
1701
+
1382
1702
  #ifdef CLIENT_MULTI_STATEMENTS
1383
1703
  rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
1384
1704
  LONG2NUM(CLIENT_MULTI_STATEMENTS));
@@ -1408,4 +1728,83 @@ void init_mysql2_client() {
1408
1728
  rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
1409
1729
  LONG2NUM(CLIENT_BASIC_FLAGS));
1410
1730
  #endif
1731
+
1732
+ #ifdef CLIENT_CONNECT_ATTRS
1733
+ rb_const_set(cMysql2Client, rb_intern("CONNECT_ATTRS"),
1734
+ LONG2NUM(CLIENT_CONNECT_ATTRS));
1735
+ #else
1736
+ /* HACK because MySQL 5.5 and earlier don't define this constant,
1737
+ * but we're using it in our default connection flags. */
1738
+ rb_const_set(cMysql2Client, rb_intern("CONNECT_ATTRS"),
1739
+ INT2NUM(0));
1740
+ #endif
1741
+
1742
+ #ifdef CLIENT_SESSION_TRACK
1743
+ rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK"), INT2NUM(CLIENT_SESSION_TRACK));
1744
+ /* From mysql_com.h -- but stable from at least 5.7.4 through 8.0.20 */
1745
+ rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_SYSTEM_VARIABLES"), INT2NUM(SESSION_TRACK_SYSTEM_VARIABLES));
1746
+ rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_SCHEMA"), INT2NUM(SESSION_TRACK_SCHEMA));
1747
+ rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_STATE_CHANGE"), INT2NUM(SESSION_TRACK_STATE_CHANGE));
1748
+ rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_GTIDS"), INT2NUM(SESSION_TRACK_GTIDS));
1749
+ rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_TRANSACTION_CHARACTERISTICS"), INT2NUM(SESSION_TRACK_TRANSACTION_CHARACTERISTICS));
1750
+ rb_const_set(cMysql2Client, rb_intern("SESSION_TRACK_TRANSACTION_STATE"), INT2NUM(SESSION_TRACK_TRANSACTION_STATE));
1751
+ #endif
1752
+
1753
+ #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.6.36 and MySQL 5.7.11 and above
1754
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1755
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED));
1756
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1757
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA));
1758
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY));
1759
+ #else
1760
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT // MySQL 5.7.3 - 5.7.10 & MariaDB 10.x and later
1761
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY));
1762
+ #endif
1763
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE // MySQL 5.7.3 - 5.7.10 & MariaDB 10.x and later
1764
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1765
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1766
+ #endif
1767
+ #endif
1768
+
1769
+ #ifndef HAVE_CONST_SSL_MODE_DISABLED
1770
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0));
1771
+ #endif
1772
+ #ifndef HAVE_CONST_SSL_MODE_PREFERRED
1773
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0));
1774
+ #endif
1775
+ #ifndef HAVE_CONST_SSL_MODE_REQUIRED
1776
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0));
1777
+ #endif
1778
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_CA
1779
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0));
1780
+ #endif
1781
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY
1782
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0));
1783
+ #endif
1784
+ }
1785
+
1786
+ #define flag_to_bool(f) ((client->server_status & f) ? Qtrue : Qfalse)
1787
+
1788
+ void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result) {
1789
+ VALUE server_flags = rb_hash_new();
1790
+
1791
+ #ifdef HAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED
1792
+ rb_hash_aset(server_flags, sym_no_good_index_used, flag_to_bool(SERVER_QUERY_NO_GOOD_INDEX_USED));
1793
+ #else
1794
+ rb_hash_aset(server_flags, sym_no_good_index_used, Qnil);
1795
+ #endif
1796
+
1797
+ #ifdef HAVE_CONST_SERVER_QUERY_NO_INDEX_USED
1798
+ rb_hash_aset(server_flags, sym_no_index_used, flag_to_bool(SERVER_QUERY_NO_INDEX_USED));
1799
+ #else
1800
+ rb_hash_aset(server_flags, sym_no_index_used, Qnil);
1801
+ #endif
1802
+
1803
+ #ifdef HAVE_CONST_SERVER_QUERY_WAS_SLOW
1804
+ rb_hash_aset(server_flags, sym_query_was_slow, flag_to_bool(SERVER_QUERY_WAS_SLOW));
1805
+ #else
1806
+ rb_hash_aset(server_flags, sym_query_was_slow, Qnil);
1807
+ #endif
1808
+
1809
+ rb_iv_set(result, "@server_flags", server_flags);
1411
1810
  }