mysql2 0.4.2 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
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
  }