mysql2 0.3.8 → 0.4.10

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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -220
  3. data/LICENSE +21 -0
  4. data/README.md +370 -79
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +1017 -305
  8. data/ext/mysql2/client.h +35 -11
  9. data/ext/mysql2/extconf.rb +222 -34
  10. data/ext/mysql2/infile.c +122 -0
  11. data/ext/mysql2/infile.h +1 -0
  12. data/ext/mysql2/mysql2_ext.c +1 -0
  13. data/ext/mysql2/mysql2_ext.h +12 -14
  14. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  15. data/ext/mysql2/mysql_enc_to_ruby.h +249 -0
  16. data/ext/mysql2/result.c +664 -166
  17. data/ext/mysql2/result.h +16 -6
  18. data/ext/mysql2/statement.c +595 -0
  19. data/ext/mysql2/statement.h +19 -0
  20. data/lib/mysql2/client.rb +118 -211
  21. data/lib/mysql2/console.rb +5 -0
  22. data/lib/mysql2/em.rb +23 -5
  23. data/lib/mysql2/error.rb +62 -6
  24. data/lib/mysql2/field.rb +3 -0
  25. data/lib/mysql2/statement.rb +17 -0
  26. data/lib/mysql2/version.rb +1 -1
  27. data/lib/mysql2.rb +66 -3
  28. data/spec/configuration.yml.example +11 -0
  29. data/spec/em/em_spec.rb +96 -10
  30. data/spec/my.cnf.example +9 -0
  31. data/spec/mysql2/client_spec.rb +779 -205
  32. data/spec/mysql2/error_spec.rb +58 -45
  33. data/spec/mysql2/result_spec.rb +316 -159
  34. data/spec/mysql2/statement_spec.rb +776 -0
  35. data/spec/spec_helper.rb +97 -56
  36. data/spec/ssl/ca-cert.pem +17 -0
  37. data/spec/ssl/ca-key.pem +27 -0
  38. data/spec/ssl/ca.cnf +22 -0
  39. data/spec/ssl/cert.cnf +22 -0
  40. data/spec/ssl/client-cert.pem +17 -0
  41. data/spec/ssl/client-key.pem +27 -0
  42. data/spec/ssl/client-req.pem +15 -0
  43. data/spec/ssl/gen_certs.sh +48 -0
  44. data/spec/ssl/pkcs8-client-key.pem +28 -0
  45. data/spec/ssl/pkcs8-server-key.pem +28 -0
  46. data/spec/ssl/server-cert.pem +17 -0
  47. data/spec/ssl/server-key.pem +27 -0
  48. data/spec/ssl/server-req.pem +15 -0
  49. data/spec/test_data +1 -0
  50. data/support/5072E1F5.asc +432 -0
  51. data/support/libmysql.def +219 -0
  52. data/support/mysql_enc_to_ruby.rb +81 -0
  53. data/support/ruby_enc_to_mysql.rb +61 -0
  54. metadata +77 -196
  55. data/.gitignore +0 -12
  56. data/.rspec +0 -3
  57. data/.rvmrc +0 -1
  58. data/.travis.yml +0 -7
  59. data/Gemfile +0 -3
  60. data/MIT-LICENSE +0 -20
  61. data/Rakefile +0 -5
  62. data/benchmark/active_record.rb +0 -51
  63. data/benchmark/active_record_threaded.rb +0 -42
  64. data/benchmark/allocations.rb +0 -33
  65. data/benchmark/escape.rb +0 -36
  66. data/benchmark/query_with_mysql_casting.rb +0 -80
  67. data/benchmark/query_without_mysql_casting.rb +0 -56
  68. data/benchmark/sequel.rb +0 -37
  69. data/benchmark/setup_db.rb +0 -119
  70. data/benchmark/threaded.rb +0 -44
  71. data/mysql2.gemspec +0 -29
  72. data/tasks/benchmarks.rake +0 -20
  73. data/tasks/compile.rake +0 -71
  74. data/tasks/rspec.rake +0 -16
  75. data/tasks/vendor_mysql.rake +0 -40
data/ext/mysql2/client.c CHANGED
@@ -1,32 +1,79 @@
1
1
  #include <mysql2_ext.h>
2
- #include <client.h>
2
+
3
+ #include <time.h>
3
4
  #include <errno.h>
4
5
  #ifndef _WIN32
6
+ #include <sys/types.h>
5
7
  #include <sys/socket.h>
6
8
  #endif
9
+ #ifndef _MSC_VER
10
+ #include <unistd.h>
11
+ #endif
12
+ #include <fcntl.h>
7
13
  #include "wait_for_single_fd.h"
8
14
 
15
+ #include "mysql_enc_name_to_ruby.h"
16
+
9
17
  VALUE cMysql2Client;
10
18
  extern VALUE mMysql2, cMysql2Error;
11
- static VALUE intern_encoding_from_charset;
12
- static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
13
- static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
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;
14
21
 
15
- #define REQUIRE_OPEN_DB(wrapper) \
16
- if(!wrapper->reconnect_enabled && wrapper->closed) { \
17
- rb_raise(cMysql2Error, "closed MySQL connection"); \
22
+ #ifndef HAVE_RB_HASH_DUP
23
+ VALUE rb_hash_dup(VALUE other) {
24
+ return rb_funcall(rb_cHash, intern_brackets, 1, other);
25
+ }
26
+ #endif
27
+
28
+ #define REQUIRE_INITIALIZED(wrapper) \
29
+ if (!wrapper->initialized) { \
30
+ rb_raise(cMysql2Error, "MySQL client is not initialized"); \
18
31
  }
19
32
 
20
- #define MARK_CONN_INACTIVE(conn) \
21
- wrapper->active = 0
33
+ #if defined(HAVE_MYSQL_NET_VIO) || defined(HAVE_ST_NET_VIO)
34
+ #define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1)
35
+ #elif defined(HAVE_MYSQL_NET_PVIO) || defined(HAVE_ST_NET_PVIO)
36
+ #define CONNECTED(wrapper) (wrapper->client->net.pvio != NULL && wrapper->client->net.fd != -1)
37
+ #endif
22
38
 
23
- #define GET_CLIENT(self) \
24
- mysql_client_wrapper *wrapper; \
25
- Data_Get_Struct(self, mysql_client_wrapper, wrapper)
39
+ #define REQUIRE_CONNECTED(wrapper) \
40
+ REQUIRE_INITIALIZED(wrapper) \
41
+ if (!CONNECTED(wrapper) && !wrapper->reconnect_enabled) { \
42
+ rb_raise(cMysql2Error, "MySQL client is not connected"); \
43
+ }
44
+
45
+ #define REQUIRE_NOT_CONNECTED(wrapper) \
46
+ REQUIRE_INITIALIZED(wrapper) \
47
+ if (CONNECTED(wrapper)) { \
48
+ rb_raise(cMysql2Error, "MySQL connection is already open"); \
49
+ }
50
+
51
+ /*
52
+ * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
53
+ * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
54
+ * linking against the server itself
55
+ */
56
+ #if defined(MARIADB_CLIENT_VERSION_STR)
57
+ #define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR
58
+ #elif defined(LIBMYSQL_VERSION)
59
+ #define MYSQL_LINK_VERSION LIBMYSQL_VERSION
60
+ #else
61
+ #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION
62
+ #endif
63
+
64
+ /*
65
+ * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10.
66
+ */
67
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
68
+ #define SSL_MODE_DISABLED 1
69
+ #define SSL_MODE_REQUIRED 3
70
+ #define HAVE_CONST_SSL_MODE_DISABLED
71
+ #define HAVE_CONST_SSL_MODE_REQUIRED
72
+ #endif
26
73
 
27
74
  /*
28
75
  * used to pass all arguments to mysql_real_connect while inside
29
- * rb_thread_blocking_region
76
+ * rb_thread_call_without_gvl
30
77
  */
31
78
  struct nogvl_connect_args {
32
79
  MYSQL *mysql;
@@ -41,14 +88,61 @@ struct nogvl_connect_args {
41
88
 
42
89
  /*
43
90
  * used to pass all arguments to mysql_send_query while inside
44
- * rb_thread_blocking_region
91
+ * rb_thread_call_without_gvl
45
92
  */
46
93
  struct nogvl_send_query_args {
47
94
  MYSQL *mysql;
48
95
  VALUE sql;
96
+ const char *sql_ptr;
97
+ long sql_len;
49
98
  mysql_client_wrapper *wrapper;
50
99
  };
51
100
 
101
+ /*
102
+ * used to pass all arguments to mysql_select_db while inside
103
+ * rb_thread_call_without_gvl
104
+ */
105
+ struct nogvl_select_db_args {
106
+ MYSQL *mysql;
107
+ char *db;
108
+ };
109
+
110
+ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
111
+ unsigned long version = mysql_get_client_version();
112
+
113
+ if (version < 50703) {
114
+ rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." );
115
+ return Qnil;
116
+ }
117
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
118
+ GET_CLIENT(self);
119
+ int val = NUM2INT( setting );
120
+ if (version >= 50703 && version < 50711) {
121
+ if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
122
+ bool b = ( val == SSL_MODE_REQUIRED );
123
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b );
124
+ return INT2NUM(result);
125
+ } else {
126
+ rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" );
127
+ return Qnil;
128
+ }
129
+ }
130
+ #endif
131
+ #ifdef FULL_SSL_MODE_SUPPORT
132
+ GET_CLIENT(self);
133
+ int val = NUM2INT( setting );
134
+
135
+ if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
136
+ rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
137
+ }
138
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val );
139
+
140
+ return INT2NUM(result);
141
+ #endif
142
+ #ifdef NO_SSL_MODE_SUPPORT
143
+ return Qnil;
144
+ #endif
145
+ }
52
146
  /*
53
147
  * non-blocking mysql_*() functions that we won't be wrapping since
54
148
  * they do not appear to hit the network nor issue any interruptible
@@ -75,90 +169,150 @@ static void rb_mysql_client_mark(void * wrapper) {
75
169
  mysql_client_wrapper * w = wrapper;
76
170
  if (w) {
77
171
  rb_gc_mark(w->encoding);
172
+ rb_gc_mark(w->active_thread);
78
173
  }
79
174
  }
80
175
 
81
176
  static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
82
177
  VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
83
178
  VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
84
- #ifdef HAVE_RUBY_ENCODING_H
85
- rb_encoding *default_internal_enc = rb_default_internal_encoding();
86
- rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
179
+ VALUE e;
87
180
 
88
- rb_enc_associate(rb_error_msg, conn_enc);
89
- rb_enc_associate(rb_sql_state, conn_enc);
90
- if (default_internal_enc) {
91
- rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
92
- rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
93
- }
181
+ #ifdef HAVE_RUBY_ENCODING_H
182
+ rb_enc_associate(rb_error_msg, rb_utf8_encoding());
183
+ rb_enc_associate(rb_sql_state, rb_usascii_encoding());
94
184
  #endif
95
185
 
96
- VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg);
97
- rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
98
- rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
186
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
187
+ rb_error_msg,
188
+ LONG2FIX(wrapper->server_version),
189
+ UINT2NUM(mysql_errno(wrapper->client)),
190
+ rb_sql_state);
99
191
  rb_exc_raise(e);
100
- return Qnil;
101
192
  }
102
193
 
103
- static VALUE nogvl_init(void *ptr) {
194
+ static void *nogvl_init(void *ptr) {
104
195
  MYSQL *client;
196
+ mysql_client_wrapper *wrapper = ptr;
105
197
 
106
198
  /* may initialize embedded server and read /etc/services off disk */
107
- client = mysql_init((MYSQL *)ptr);
108
- return client ? Qtrue : Qfalse;
199
+ client = mysql_init(wrapper->client);
200
+
201
+ if (client) mysql2_set_local_infile(client, wrapper);
202
+
203
+ return (void*)(client ? Qtrue : Qfalse);
109
204
  }
110
205
 
111
- static VALUE nogvl_connect(void *ptr) {
206
+ static void *nogvl_connect(void *ptr) {
112
207
  struct nogvl_connect_args *args = ptr;
113
208
  MYSQL *client;
114
209
 
115
- do {
116
- client = mysql_real_connect(args->mysql, args->host,
117
- args->user, args->passwd,
118
- args->db, args->port, args->unix_socket,
119
- args->client_flag);
120
- } while (! client && errno == EINTR && (errno = 0) == 0);
210
+ client = mysql_real_connect(args->mysql, args->host,
211
+ args->user, args->passwd,
212
+ args->db, args->port, args->unix_socket,
213
+ args->client_flag);
121
214
 
122
- return client ? Qtrue : Qfalse;
215
+ return (void *)(client ? Qtrue : Qfalse);
123
216
  }
124
217
 
125
- static VALUE nogvl_close(void *ptr) {
126
- mysql_client_wrapper *wrapper;
127
218
  #ifndef _WIN32
128
- int flags;
219
+ /*
220
+ * Redirect clientfd to /dev/null for mysql_close and SSL_close to write,
221
+ * shutdown, and close. The hack is needed to prevent shutdown() from breaking
222
+ * a socket that may be in use by the parent or other processes after fork.
223
+ *
224
+ * /dev/null is used to absorb writes; previously a dummy socket was used, but
225
+ * it could not abosrb writes and caused openssl to go into an infinite loop.
226
+ *
227
+ * Returns Qtrue or Qfalse (success or failure)
228
+ *
229
+ * Note: if this function is needed on Windows, use "nul" instead of "/dev/null"
230
+ */
231
+ static VALUE invalidate_fd(int clientfd)
232
+ {
233
+ #ifdef O_CLOEXEC
234
+ /* Atomically set CLOEXEC on the new FD in case another thread forks */
235
+ int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC);
236
+ #else
237
+ /* Well we don't have O_CLOEXEC, trigger the fallback code below */
238
+ int sockfd = -1;
129
239
  #endif
130
- wrapper = ptr;
131
- if (!wrapper->closed) {
132
- wrapper->closed = 1;
133
- wrapper->active = 0;
134
- /*
135
- * we'll send a QUIT message to the server, but that message is more of a
136
- * formality than a hard requirement since the socket is getting shutdown
137
- * anyways, so ensure the socket write does not block our interpreter
138
- *
139
- *
140
- * if the socket is dead we have no chance of blocking,
141
- * so ignore any potential fcntl errors since they don't matter
240
+
241
+ if (sockfd < 0) {
242
+ /* Either O_CLOEXEC wasn't defined at compile time, or it was defined at
243
+ * compile time, but isn't available at run-time. So we'll just be quick
244
+ * about setting FD_CLOEXEC now.
142
245
  */
143
- #ifndef _WIN32
144
- flags = fcntl(wrapper->client->net.fd, F_GETFL);
145
- if (flags > 0 && !(flags & O_NONBLOCK))
146
- fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
147
- #endif
246
+ int flags;
247
+ sockfd = open("/dev/null", O_RDWR);
248
+ flags = fcntl(sockfd, F_GETFD);
249
+ /* Do the flags dance in case there are more defined flags in the future */
250
+ if (flags != -1) {
251
+ flags |= FD_CLOEXEC;
252
+ fcntl(sockfd, F_SETFD, flags);
253
+ }
254
+ }
148
255
 
256
+ if (sockfd < 0) {
257
+ /* Cannot raise here, because one or both of the following may be true:
258
+ * a) we have no GVL (in C Ruby)
259
+ * b) are running as a GC finalizer
260
+ */
261
+ return Qfalse;
262
+ }
263
+
264
+ dup2(sockfd, clientfd);
265
+ close(sockfd);
266
+
267
+ return Qtrue;
268
+ }
269
+ #endif /* _WIN32 */
270
+
271
+ static void *nogvl_close(void *ptr) {
272
+ mysql_client_wrapper *wrapper = ptr;
273
+
274
+ if (!wrapper->closed) {
149
275
  mysql_close(wrapper->client);
150
- free(wrapper->client);
276
+ wrapper->closed = 1;
277
+ wrapper->reconnect_enabled = 0;
278
+ wrapper->active_thread = Qnil;
151
279
  }
152
280
 
153
- return Qnil;
281
+ return NULL;
154
282
  }
155
283
 
156
- static void rb_mysql_client_free(void * ptr) {
157
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
284
+ /* this is called during GC */
285
+ static void rb_mysql_client_free(void *ptr) {
286
+ mysql_client_wrapper *wrapper = ptr;
287
+ decr_mysql2_client(wrapper);
288
+ }
158
289
 
159
- nogvl_close(wrapper);
290
+ void decr_mysql2_client(mysql_client_wrapper *wrapper)
291
+ {
292
+ wrapper->refcount--;
160
293
 
161
- free(ptr);
294
+ if (wrapper->refcount == 0) {
295
+ #ifndef _WIN32
296
+ if (CONNECTED(wrapper) && !wrapper->automatic_close) {
297
+ /* The client is being garbage collected while connected. Prevent
298
+ * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
299
+ * the socket by invalidating it. invalidate_fd() will drop this
300
+ * process's reference to the socket only, while a QUIT or shutdown()
301
+ * would render the underlying connection unusable, interrupting other
302
+ * processes which share this object across a fork().
303
+ */
304
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
305
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
306
+ close(wrapper->client->net.fd);
307
+ }
308
+ wrapper->client->net.fd = -1;
309
+ }
310
+ #endif
311
+
312
+ nogvl_close(wrapper);
313
+ xfree(wrapper->client);
314
+ xfree(wrapper);
315
+ }
162
316
  }
163
317
 
164
318
  static VALUE allocate(VALUE klass) {
@@ -166,13 +320,26 @@ static VALUE allocate(VALUE klass) {
166
320
  mysql_client_wrapper * wrapper;
167
321
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
168
322
  wrapper->encoding = Qnil;
169
- wrapper->active = 0;
323
+ wrapper->active_thread = Qnil;
324
+ wrapper->automatic_close = 1;
325
+ wrapper->server_version = 0;
170
326
  wrapper->reconnect_enabled = 0;
171
- wrapper->closed = 1;
172
- wrapper->client = (MYSQL*)malloc(sizeof(MYSQL));
327
+ wrapper->connect_timeout = 0;
328
+ wrapper->initialized = 0; /* means that that the wrapper is initialized */
329
+ wrapper->refcount = 1;
330
+ wrapper->closed = 0;
331
+ wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
332
+
173
333
  return obj;
174
334
  }
175
335
 
336
+ /* call-seq:
337
+ * Mysql2::Client.escape(string)
338
+ *
339
+ * Escape +string+ so that it may be used in a SQL statement.
340
+ * Note that this escape method is not connection encoding aware.
341
+ * If you need encoding support use Mysql2::Client#escape instead.
342
+ */
176
343
  static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
177
344
  unsigned char *newStr;
178
345
  VALUE rb_str;
@@ -181,83 +348,168 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
181
348
  Check_Type(str, T_STRING);
182
349
 
183
350
  oldLen = RSTRING_LEN(str);
184
- newStr = malloc(oldLen*2+1);
351
+ newStr = xmalloc(oldLen*2+1);
185
352
 
186
- newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen);
353
+ newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen);
187
354
  if (newLen == oldLen) {
188
- // no need to return a new ruby string if nothing changed
189
- free(newStr);
355
+ /* no need to return a new ruby string if nothing changed */
356
+ xfree(newStr);
190
357
  return str;
191
358
  } else {
192
359
  rb_str = rb_str_new((const char*)newStr, newLen);
193
360
  #ifdef HAVE_RUBY_ENCODING_H
194
361
  rb_enc_copy(rb_str, str);
195
362
  #endif
196
- free(newStr);
363
+ xfree(newStr);
197
364
  return rb_str;
198
365
  }
199
366
  }
200
367
 
368
+ static VALUE rb_mysql_client_warning_count(VALUE self) {
369
+ unsigned int warning_count;
370
+ GET_CLIENT(self);
371
+
372
+ warning_count = mysql_warning_count(wrapper->client);
373
+
374
+ return UINT2NUM(warning_count);
375
+ }
376
+
377
+ static VALUE rb_mysql_info(VALUE self) {
378
+ const char *info;
379
+ VALUE rb_str;
380
+ GET_CLIENT(self);
381
+
382
+ info = mysql_info(wrapper->client);
383
+
384
+ if (info == NULL) {
385
+ return Qnil;
386
+ }
387
+
388
+ rb_str = rb_str_new2(info);
389
+ #ifdef HAVE_RUBY_ENCODING_H
390
+ rb_enc_associate(rb_str, rb_utf8_encoding());
391
+ #endif
392
+
393
+ return rb_str;
394
+ }
395
+
396
+ static VALUE rb_mysql_get_ssl_cipher(VALUE self)
397
+ {
398
+ const char *cipher;
399
+ VALUE rb_str;
400
+ GET_CLIENT(self);
401
+
402
+ cipher = mysql_get_ssl_cipher(wrapper->client);
403
+
404
+ if (cipher == NULL) {
405
+ return Qnil;
406
+ }
407
+
408
+ rb_str = rb_str_new2(cipher);
409
+ #ifdef HAVE_RUBY_ENCODING_H
410
+ rb_enc_associate(rb_str, rb_utf8_encoding());
411
+ #endif
412
+
413
+ return rb_str;
414
+ }
415
+
201
416
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
202
417
  struct nogvl_connect_args args;
418
+ time_t start_time, end_time, elapsed_time, connect_timeout;
419
+ VALUE rv;
203
420
  GET_CLIENT(self);
204
421
 
205
- args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
206
- args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
207
- args.port = NIL_P(port) ? 3306 : NUM2INT(port);
208
- args.user = NIL_P(user) ? NULL : StringValuePtr(user);
209
- args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
210
- args.db = NIL_P(database) ? NULL : StringValuePtr(database);
211
- args.mysql = wrapper->client;
422
+ args.host = NIL_P(host) ? NULL : StringValueCStr(host);
423
+ args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket);
424
+ args.port = NIL_P(port) ? 0 : NUM2INT(port);
425
+ args.user = NIL_P(user) ? NULL : StringValueCStr(user);
426
+ args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass);
427
+ args.db = NIL_P(database) ? NULL : StringValueCStr(database);
428
+ args.mysql = wrapper->client;
212
429
  args.client_flag = NUM2ULONG(flags);
213
430
 
214
- if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
215
- // unable to connect
216
- return rb_raise_mysql2_error(wrapper);
431
+ if (wrapper->connect_timeout)
432
+ time(&start_time);
433
+ rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
434
+ if (rv == Qfalse) {
435
+ while (rv == Qfalse && errno == EINTR) {
436
+ if (wrapper->connect_timeout) {
437
+ time(&end_time);
438
+ /* avoid long connect timeout from system time changes */
439
+ if (end_time < start_time)
440
+ start_time = end_time;
441
+ elapsed_time = end_time - start_time;
442
+ /* avoid an early timeout due to time truncating milliseconds off the start time */
443
+ if (elapsed_time > 0)
444
+ elapsed_time--;
445
+ if (elapsed_time >= (time_t)wrapper->connect_timeout)
446
+ break;
447
+ connect_timeout = wrapper->connect_timeout - elapsed_time;
448
+ mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
449
+ }
450
+ errno = 0;
451
+ rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
452
+ }
453
+ /* restore the connect timeout for reconnecting */
454
+ if (wrapper->connect_timeout)
455
+ mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
456
+ if (rv == Qfalse)
457
+ rb_raise_mysql2_error(wrapper);
217
458
  }
218
459
 
460
+ wrapper->server_version = mysql_get_server_version(wrapper->client);
219
461
  return self;
220
462
  }
221
463
 
222
464
  /*
223
- * Immediately disconnect from the server, normally the garbage collector
465
+ * Immediately disconnect from the server; normally the garbage collector
224
466
  * will disconnect automatically when a connection is no longer needed.
225
467
  * Explicitly closing this will free up server resources sooner than waiting
226
468
  * for the garbage collector.
469
+ *
470
+ * @return [nil]
227
471
  */
228
472
  static VALUE rb_mysql_client_close(VALUE self) {
229
473
  GET_CLIENT(self);
230
474
 
231
- if (!wrapper->closed) {
232
- rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
475
+ if (wrapper->client) {
476
+ rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
233
477
  }
234
478
 
235
479
  return Qnil;
236
480
  }
237
481
 
482
+ /* call-seq:
483
+ * client.closed?
484
+ *
485
+ * @return [Boolean]
486
+ */
487
+ static VALUE rb_mysql_client_closed(VALUE self) {
488
+ GET_CLIENT(self);
489
+ return CONNECTED(wrapper) ? Qfalse : Qtrue;
490
+ }
491
+
238
492
  /*
239
493
  * mysql_send_query is unlikely to block since most queries are small
240
494
  * enough to fit in a socket buffer, but sometimes large UPDATE and
241
495
  * INSERTs will cause the process to block
242
496
  */
243
- static VALUE nogvl_send_query(void *ptr) {
497
+ static void *nogvl_send_query(void *ptr) {
244
498
  struct nogvl_send_query_args *args = ptr;
245
499
  int rv;
246
- const char *sql = StringValuePtr(args->sql);
247
- long sql_len = RSTRING_LEN(args->sql);
248
500
 
249
- rv = mysql_send_query(args->mysql, sql, sql_len);
501
+ rv = mysql_send_query(args->mysql, args->sql_ptr, args->sql_len);
250
502
 
251
- return rv == 0 ? Qtrue : Qfalse;
503
+ return (void*)(rv == 0 ? Qtrue : Qfalse);
252
504
  }
253
505
 
254
506
  static VALUE do_send_query(void *args) {
255
507
  struct nogvl_send_query_args *query_args = args;
256
508
  mysql_client_wrapper *wrapper = query_args->wrapper;
257
- if (rb_thread_blocking_region(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
258
- // an error occurred, we're not active anymore
259
- MARK_CONN_INACTIVE(self);
260
- return rb_raise_mysql2_error(wrapper);
509
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
510
+ /* an error occurred, we're not active anymore */
511
+ wrapper->active_thread = Qnil;
512
+ rb_raise_mysql2_error(wrapper);
261
513
  }
262
514
  return Qnil;
263
515
  }
@@ -267,64 +519,82 @@ static VALUE do_send_query(void *args) {
267
519
  * response can overflow the socket buffers and cause us to eventually
268
520
  * block while calling mysql_read_query_result
269
521
  */
270
- static VALUE nogvl_read_query_result(void *ptr) {
522
+ static void *nogvl_read_query_result(void *ptr) {
271
523
  MYSQL * client = ptr;
272
- my_bool res = mysql_read_query_result(client);
524
+ bool res = mysql_read_query_result(client);
273
525
 
274
- return res == 0 ? Qtrue : Qfalse;
526
+ return (void *)(res == 0 ? Qtrue : Qfalse);
275
527
  }
276
528
 
277
- /* mysql_store_result may (unlikely) read rows off the socket */
278
- static VALUE nogvl_store_result(void *ptr) {
279
- mysql_client_wrapper *wrapper;
529
+ static void *nogvl_do_result(void *ptr, char use_result) {
530
+ mysql_client_wrapper *wrapper = ptr;
280
531
  MYSQL_RES *result;
281
532
 
282
- wrapper = (mysql_client_wrapper *)ptr;
283
- result = mysql_store_result(wrapper->client);
533
+ if (use_result) {
534
+ result = mysql_use_result(wrapper->client);
535
+ } else {
536
+ result = mysql_store_result(wrapper->client);
537
+ }
538
+
539
+ /* once our result is stored off, this connection is
540
+ ready for another command to be issued */
541
+ wrapper->active_thread = Qnil;
284
542
 
285
- // once our result is stored off, this connection is
286
- // ready for another command to be issued
287
- wrapper->active = 0;
543
+ return result;
544
+ }
545
+
546
+ /* mysql_store_result may (unlikely) read rows off the socket */
547
+ static void *nogvl_store_result(void *ptr) {
548
+ return nogvl_do_result(ptr, 0);
549
+ }
288
550
 
289
- return (VALUE)result;
551
+ static void *nogvl_use_result(void *ptr) {
552
+ return nogvl_do_result(ptr, 1);
290
553
  }
291
554
 
555
+ /* call-seq:
556
+ * client.async_result
557
+ *
558
+ * Returns the result for the last async issued query.
559
+ */
292
560
  static VALUE rb_mysql_client_async_result(VALUE self) {
293
561
  MYSQL_RES * result;
294
562
  VALUE resultObj;
295
- #ifdef HAVE_RUBY_ENCODING_H
296
- mysql2_result_wrapper * result_wrapper;
297
- #endif
563
+ VALUE current, is_streaming;
298
564
  GET_CLIENT(self);
299
565
 
300
- // if we're not waiting on a result, do nothing
301
- if (!wrapper->active)
566
+ /* if we're not waiting on a result, do nothing */
567
+ if (NIL_P(wrapper->active_thread))
302
568
  return Qnil;
303
569
 
304
- REQUIRE_OPEN_DB(wrapper);
305
- if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
306
- // an error occurred, mark this connection inactive
307
- MARK_CONN_INACTIVE(self);
308
- return rb_raise_mysql2_error(wrapper);
570
+ REQUIRE_CONNECTED(wrapper);
571
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
572
+ /* an error occurred, mark this connection inactive */
573
+ wrapper->active_thread = Qnil;
574
+ rb_raise_mysql2_error(wrapper);
309
575
  }
310
576
 
311
- result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
577
+ is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
578
+ if (is_streaming == Qtrue) {
579
+ result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_use_result, wrapper, RUBY_UBF_IO, 0);
580
+ } else {
581
+ result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
582
+ }
312
583
 
313
584
  if (result == NULL) {
314
- if (mysql_field_count(wrapper->client) != 0) {
585
+ if (mysql_errno(wrapper->client) != 0) {
586
+ wrapper->active_thread = Qnil;
315
587
  rb_raise_mysql2_error(wrapper);
316
588
  }
589
+ /* no data and no error, so query was not a SELECT */
317
590
  return Qnil;
318
591
  }
319
592
 
320
- resultObj = rb_mysql_result_to_obj(result);
321
- // pass-through query options for result construction later
322
- rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
593
+ current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
594
+ (void)RB_GC_GUARD(current);
595
+ Check_Type(current, T_HASH);
596
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
323
597
 
324
- #ifdef HAVE_RUBY_ENCODING_H
325
- GetMysql2Result(resultObj, result_wrapper);
326
- result_wrapper->encoding = wrapper->encoding;
327
- #endif
328
598
  return resultObj;
329
599
  }
330
600
 
@@ -337,27 +607,30 @@ struct async_query_args {
337
607
  static VALUE disconnect_and_raise(VALUE self, VALUE error) {
338
608
  GET_CLIENT(self);
339
609
 
340
- wrapper->closed = 1;
341
- wrapper->active = 0;
610
+ wrapper->active_thread = Qnil;
342
611
 
343
- // manually close the socket for read/write
344
- // this feels dirty, but is there another way?
345
- shutdown(wrapper->client->net.fd, 2);
612
+ /* Invalidate the MySQL socket to prevent further communication.
613
+ * The GC will come along later and call mysql_close to free it.
614
+ */
615
+ if (CONNECTED(wrapper)) {
616
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
617
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
618
+ close(wrapper->client->net.fd);
619
+ }
620
+ wrapper->client->net.fd = -1;
621
+ }
346
622
 
347
623
  rb_exc_raise(error);
348
-
349
- return Qnil;
350
624
  }
351
625
 
352
626
  static VALUE do_query(void *args) {
353
- struct async_query_args *async_args;
627
+ struct async_query_args *async_args = args;
354
628
  struct timeval tv;
355
- struct timeval* tvp;
629
+ struct timeval *tvp;
356
630
  long int sec;
357
631
  int retval;
358
632
  VALUE read_timeout;
359
633
 
360
- async_args = (struct async_query_args *)args;
361
634
  read_timeout = rb_iv_get(async_args->self, "@read_timeout");
362
635
 
363
636
  tvp = NULL;
@@ -365,8 +638,8 @@ static VALUE do_query(void *args) {
365
638
  Check_Type(read_timeout, T_FIXNUM);
366
639
  tvp = &tv;
367
640
  sec = FIX2INT(read_timeout);
368
- // TODO: support partial seconds?
369
- // also, this check is here for sanity, we also check up in Ruby
641
+ /* TODO: support partial seconds?
642
+ also, this check is here for sanity, we also check up in Ruby */
370
643
  if (sec >= 0) {
371
644
  tvp->tv_sec = sec;
372
645
  } else {
@@ -393,92 +666,140 @@ static VALUE do_query(void *args) {
393
666
 
394
667
  return Qnil;
395
668
  }
669
+ #endif
670
+
671
+ static VALUE disconnect_and_mark_inactive(VALUE self) {
672
+ GET_CLIENT(self);
673
+
674
+ /* Check if execution terminated while result was still being read. */
675
+ if (!NIL_P(wrapper->active_thread)) {
676
+ if (CONNECTED(wrapper)) {
677
+ /* Invalidate the MySQL socket to prevent further communication. */
678
+ #ifndef _WIN32
679
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
680
+ rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n");
681
+ close(wrapper->client->net.fd);
682
+ }
396
683
  #else
397
- static VALUE finish_and_mark_inactive(void *args) {
398
- VALUE self;
399
- MYSQL_RES *result;
684
+ close(wrapper->client->net.fd);
685
+ #endif
686
+ wrapper->client->net.fd = -1;
687
+ }
688
+ /* Skip mysql client check performed before command execution. */
689
+ wrapper->client->status = MYSQL_STATUS_READY;
690
+ wrapper->active_thread = Qnil;
691
+ }
400
692
 
401
- self = (VALUE)args;
693
+ return Qnil;
694
+ }
402
695
 
696
+ void rb_mysql_client_set_active_thread(VALUE self) {
697
+ VALUE thread_current = rb_thread_current();
403
698
  GET_CLIENT(self);
404
699
 
405
- if (wrapper->active) {
406
- // if we got here, the result hasn't been read off the wire yet
407
- // so lets do that and then throw it away because we have no way
408
- // of getting it back up to the caller from here
409
- result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
410
- mysql_free_result(result);
700
+ // see if this connection is still waiting on a result from a previous query
701
+ if (NIL_P(wrapper->active_thread)) {
702
+ // mark this connection active
703
+ wrapper->active_thread = thread_current;
704
+ } else if (wrapper->active_thread == thread_current) {
705
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
706
+ } else {
707
+ VALUE inspect = rb_inspect(wrapper->active_thread);
708
+ const char *thr = StringValueCStr(inspect);
411
709
 
412
- wrapper->active = 0;
710
+ rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
711
+ }
712
+ }
713
+
714
+ /* call-seq:
715
+ * client.abandon_results!
716
+ *
717
+ * When using MULTI_STATEMENTS support, calling this will throw
718
+ * away any unprocessed results as fast as it can in order to
719
+ * put the connection back into a state where queries can be issued
720
+ * again.
721
+ */
722
+ static VALUE rb_mysql_client_abandon_results(VALUE self) {
723
+ MYSQL_RES *result;
724
+ int ret;
725
+
726
+ GET_CLIENT(self);
727
+
728
+ while (mysql_more_results(wrapper->client) == 1) {
729
+ ret = mysql_next_result(wrapper->client);
730
+ if (ret > 0) {
731
+ rb_raise_mysql2_error(wrapper);
732
+ }
733
+
734
+ result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
735
+
736
+ if (result != NULL) {
737
+ mysql_free_result(result);
738
+ }
413
739
  }
414
740
 
415
741
  return Qnil;
416
742
  }
417
- #endif
418
743
 
419
- static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
744
+ /* call-seq:
745
+ * client.query(sql, options = {})
746
+ *
747
+ * Query the database with +sql+, with optional +options+. For the possible
748
+ * options, see default_query_options on the Mysql2::Client class.
749
+ */
750
+ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
420
751
  #ifndef _WIN32
421
752
  struct async_query_args async_args;
422
753
  #endif
423
754
  struct nogvl_send_query_args args;
424
- int async = 0;
425
- VALUE opts, defaults;
426
- #ifdef HAVE_RUBY_ENCODING_H
427
- rb_encoding *conn_enc;
428
- #endif
429
755
  GET_CLIENT(self);
430
756
 
431
- REQUIRE_OPEN_DB(wrapper);
757
+ REQUIRE_CONNECTED(wrapper);
432
758
  args.mysql = wrapper->client;
433
759
 
760
+ (void)RB_GC_GUARD(current);
761
+ Check_Type(current, T_HASH);
762
+ rb_iv_set(self, "@current_query_options", current);
434
763
 
435
- defaults = rb_iv_get(self, "@query_options");
436
- if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
437
- opts = rb_funcall(defaults, intern_merge, 1, opts);
438
- rb_iv_set(self, "@query_options", opts);
439
-
440
- if (rb_hash_aref(opts, sym_async) == Qtrue) {
441
- async = 1;
442
- }
443
- } else {
444
- opts = defaults;
445
- }
446
-
447
- Check_Type(args.sql, T_STRING);
764
+ Check_Type(sql, T_STRING);
448
765
  #ifdef HAVE_RUBY_ENCODING_H
449
- conn_enc = rb_to_encoding(wrapper->encoding);
450
- // ensure the string is in the encoding the connection is expecting
451
- args.sql = rb_str_export_to_enc(args.sql, conn_enc);
766
+ /* ensure the string is in the encoding the connection is expecting */
767
+ args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
768
+ #else
769
+ args.sql = sql;
452
770
  #endif
771
+ args.sql_ptr = RSTRING_PTR(args.sql);
772
+ args.sql_len = RSTRING_LEN(args.sql);
773
+ args.wrapper = wrapper;
453
774
 
454
- // see if this connection is still waiting on a result from a previous query
455
- if (wrapper->active == 0) {
456
- // mark this connection active
457
- wrapper->active = 1;
458
- } else {
459
- rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
460
- }
775
+ rb_mysql_client_set_active_thread(self);
461
776
 
462
- args.wrapper = wrapper;
777
+ #ifndef _WIN32
463
778
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
464
779
 
465
- #ifndef _WIN32
466
- if (!async) {
780
+ if (rb_hash_aref(current, sym_async) == Qtrue) {
781
+ return Qnil;
782
+ } else {
467
783
  async_args.fd = wrapper->client->net.fd;
468
784
  async_args.self = self;
469
785
 
470
786
  rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
471
787
 
472
- return rb_mysql_client_async_result(self);
473
- } else {
474
- return Qnil;
788
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
475
789
  }
476
790
  #else
477
- // this will just block until the result is ready
478
- return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
791
+ do_send_query(&args);
792
+
793
+ /* this will just block until the result is ready */
794
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
479
795
  #endif
480
796
  }
481
797
 
798
+ /* call-seq:
799
+ * client.escape(string)
800
+ *
801
+ * Escape +string+ so that it may be used in a SQL statement.
802
+ */
482
803
  static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
483
804
  unsigned char *newStr;
484
805
  VALUE rb_str;
@@ -489,22 +810,27 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
489
810
  #endif
490
811
  GET_CLIENT(self);
491
812
 
492
- REQUIRE_OPEN_DB(wrapper);
813
+ REQUIRE_CONNECTED(wrapper);
493
814
  Check_Type(str, T_STRING);
494
815
  #ifdef HAVE_RUBY_ENCODING_H
495
816
  default_internal_enc = rb_default_internal_encoding();
496
817
  conn_enc = rb_to_encoding(wrapper->encoding);
497
- // ensure the string is in the encoding the connection is expecting
818
+ /* ensure the string is in the encoding the connection is expecting */
498
819
  str = rb_str_export_to_enc(str, conn_enc);
499
820
  #endif
500
821
 
501
822
  oldLen = RSTRING_LEN(str);
502
- newStr = malloc(oldLen*2+1);
823
+ newStr = xmalloc(oldLen*2+1);
503
824
 
504
- newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
825
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
505
826
  if (newLen == oldLen) {
506
- // no need to return a new ruby string if nothing changed
507
- free(newStr);
827
+ /* no need to return a new ruby string if nothing changed */
828
+ #ifdef HAVE_RUBY_ENCODING_H
829
+ if (default_internal_enc) {
830
+ str = rb_str_export_to_enc(str, default_internal_enc);
831
+ }
832
+ #endif
833
+ xfree(newStr);
508
834
  return str;
509
835
  } else {
510
836
  rb_str = rb_str_new((const char*)newStr, newLen);
@@ -514,37 +840,133 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
514
840
  rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
515
841
  }
516
842
  #endif
517
- free(newStr);
843
+ xfree(newStr);
518
844
  return rb_str;
519
845
  }
520
846
  }
521
847
 
522
- static VALUE rb_mysql_client_info(VALUE self) {
523
- VALUE version, client_info;
524
- #ifdef HAVE_RUBY_ENCODING_H
525
- rb_encoding *default_internal_enc;
526
- rb_encoding *conn_enc;
527
- #endif
848
+ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
849
+ int result;
850
+ const void *retval = NULL;
851
+ unsigned int intval = 0;
852
+ const char * charval = NULL;
853
+ bool boolval;
854
+
528
855
  GET_CLIENT(self);
529
- version = rb_hash_new();
530
856
 
531
- #ifdef HAVE_RUBY_ENCODING_H
532
- default_internal_enc = rb_default_internal_encoding();
533
- conn_enc = rb_to_encoding(wrapper->encoding);
857
+ REQUIRE_NOT_CONNECTED(wrapper);
858
+
859
+ if (NIL_P(value))
860
+ return Qfalse;
861
+
862
+ switch(opt) {
863
+ case MYSQL_OPT_CONNECT_TIMEOUT:
864
+ intval = NUM2UINT(value);
865
+ retval = &intval;
866
+ break;
867
+
868
+ case MYSQL_OPT_READ_TIMEOUT:
869
+ intval = NUM2UINT(value);
870
+ retval = &intval;
871
+ break;
872
+
873
+ case MYSQL_OPT_WRITE_TIMEOUT:
874
+ intval = NUM2UINT(value);
875
+ retval = &intval;
876
+ break;
877
+
878
+ case MYSQL_OPT_LOCAL_INFILE:
879
+ intval = (value == Qfalse ? 0 : 1);
880
+ retval = &intval;
881
+ break;
882
+
883
+ case MYSQL_OPT_RECONNECT:
884
+ boolval = (value == Qfalse ? 0 : 1);
885
+ retval = &boolval;
886
+ break;
887
+
888
+ #ifdef MYSQL_SECURE_AUTH
889
+ case MYSQL_SECURE_AUTH:
890
+ boolval = (value == Qfalse ? 0 : 1);
891
+ retval = &boolval;
892
+ break;
534
893
  #endif
535
894
 
536
- rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
537
- client_info = rb_str_new2(mysql_get_client_info());
538
- #ifdef HAVE_RUBY_ENCODING_H
539
- rb_enc_associate(client_info, conn_enc);
540
- if (default_internal_enc) {
541
- client_info = rb_str_export_to_enc(client_info, default_internal_enc);
895
+ case MYSQL_READ_DEFAULT_FILE:
896
+ charval = (const char *)StringValueCStr(value);
897
+ retval = charval;
898
+ break;
899
+
900
+ case MYSQL_READ_DEFAULT_GROUP:
901
+ charval = (const char *)StringValueCStr(value);
902
+ retval = charval;
903
+ break;
904
+
905
+ case MYSQL_INIT_COMMAND:
906
+ charval = (const char *)StringValueCStr(value);
907
+ retval = charval;
908
+ break;
909
+
910
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
911
+ case MYSQL_ENABLE_CLEARTEXT_PLUGIN:
912
+ boolval = (value == Qfalse ? 0 : 1);
913
+ retval = &boolval;
914
+ break;
915
+ #endif
916
+
917
+ default:
918
+ return Qfalse;
919
+ }
920
+
921
+ result = mysql_options(wrapper->client, opt, retval);
922
+
923
+ /* Zero means success */
924
+ if (result != 0) {
925
+ rb_warn("%s\n", mysql_error(wrapper->client));
926
+ } else {
927
+ /* Special case for options that are stored in the wrapper struct */
928
+ switch (opt) {
929
+ case MYSQL_OPT_RECONNECT:
930
+ wrapper->reconnect_enabled = boolval;
931
+ break;
932
+ case MYSQL_OPT_CONNECT_TIMEOUT:
933
+ wrapper->connect_timeout = intval;
934
+ break;
935
+ }
542
936
  }
937
+
938
+ return (result == 0) ? Qtrue : Qfalse;
939
+ }
940
+
941
+ /* call-seq:
942
+ * client.info
943
+ *
944
+ * Returns a string that represents the client library version.
945
+ */
946
+ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
947
+ VALUE version_info, version, header_version;
948
+ version_info = rb_hash_new();
949
+
950
+ version = rb_str_new2(mysql_get_client_info());
951
+ header_version = rb_str_new2(MYSQL_LINK_VERSION);
952
+
953
+ #ifdef HAVE_RUBY_ENCODING_H
954
+ rb_enc_associate(version, rb_usascii_encoding());
955
+ rb_enc_associate(header_version, rb_usascii_encoding());
543
956
  #endif
544
- rb_hash_aset(version, sym_version, client_info);
545
- return version;
957
+
958
+ rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
959
+ rb_hash_aset(version_info, sym_version, version);
960
+ rb_hash_aset(version_info, sym_header_version, header_version);
961
+
962
+ return version_info;
546
963
  }
547
964
 
965
+ /* call-seq:
966
+ * client.server_info
967
+ *
968
+ * Returns a string that represents the server version number
969
+ */
548
970
  static VALUE rb_mysql_client_server_info(VALUE self) {
549
971
  VALUE version, server_info;
550
972
  #ifdef HAVE_RUBY_ENCODING_H
@@ -553,7 +975,7 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
553
975
  #endif
554
976
  GET_CLIENT(self);
555
977
 
556
- REQUIRE_OPEN_DB(wrapper);
978
+ REQUIRE_CONNECTED(wrapper);
557
979
  #ifdef HAVE_RUBY_ENCODING_H
558
980
  default_internal_enc = rb_default_internal_encoding();
559
981
  conn_enc = rb_to_encoding(wrapper->encoding);
@@ -572,28 +994,46 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
572
994
  return version;
573
995
  }
574
996
 
997
+ /* call-seq:
998
+ * client.socket
999
+ *
1000
+ * Return the file descriptor number for this client.
1001
+ */
1002
+ #ifndef _WIN32
575
1003
  static VALUE rb_mysql_client_socket(VALUE self) {
576
1004
  GET_CLIENT(self);
577
- #ifndef _WIN32
578
- REQUIRE_OPEN_DB(wrapper);
579
- int fd_set_fd = wrapper->client->net.fd;
580
- return INT2NUM(fd_set_fd);
1005
+ REQUIRE_CONNECTED(wrapper);
1006
+ return INT2NUM(wrapper->client->net.fd);
1007
+ }
581
1008
  #else
1009
+ static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
582
1010
  rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
583
- #endif
584
1011
  }
1012
+ #endif
585
1013
 
1014
+ /* call-seq:
1015
+ * client.last_id
1016
+ *
1017
+ * Returns the value generated for an AUTO_INCREMENT column by the previous INSERT or UPDATE
1018
+ * statement.
1019
+ */
586
1020
  static VALUE rb_mysql_client_last_id(VALUE self) {
587
1021
  GET_CLIENT(self);
588
- REQUIRE_OPEN_DB(wrapper);
1022
+ REQUIRE_CONNECTED(wrapper);
589
1023
  return ULL2NUM(mysql_insert_id(wrapper->client));
590
1024
  }
591
1025
 
1026
+ /* call-seq:
1027
+ * client.affected_rows
1028
+ *
1029
+ * returns the number of rows changed, deleted, or inserted by the last statement
1030
+ * if it was an UPDATE, DELETE, or INSERT.
1031
+ */
592
1032
  static VALUE rb_mysql_client_affected_rows(VALUE self) {
593
1033
  my_ulonglong retVal;
594
1034
  GET_CLIENT(self);
595
1035
 
596
- REQUIRE_OPEN_DB(wrapper);
1036
+ REQUIRE_CONNECTED(wrapper);
597
1037
  retVal = mysql_affected_rows(wrapper->client);
598
1038
  if (retVal == (my_ulonglong)-1) {
599
1039
  rb_raise_mysql2_error(wrapper);
@@ -601,93 +1041,259 @@ static VALUE rb_mysql_client_affected_rows(VALUE self) {
601
1041
  return ULL2NUM(retVal);
602
1042
  }
603
1043
 
1044
+ /* call-seq:
1045
+ * client.thread_id
1046
+ *
1047
+ * Returns the thread ID of the current connection.
1048
+ */
604
1049
  static VALUE rb_mysql_client_thread_id(VALUE self) {
605
1050
  unsigned long retVal;
606
1051
  GET_CLIENT(self);
607
1052
 
608
- REQUIRE_OPEN_DB(wrapper);
1053
+ REQUIRE_CONNECTED(wrapper);
609
1054
  retVal = mysql_thread_id(wrapper->client);
610
1055
  return ULL2NUM(retVal);
611
1056
  }
612
1057
 
613
- static VALUE nogvl_ping(void *ptr) {
1058
+ static void *nogvl_select_db(void *ptr) {
1059
+ struct nogvl_select_db_args *args = ptr;
1060
+
1061
+ if (mysql_select_db(args->mysql, args->db) == 0)
1062
+ return (void *)Qtrue;
1063
+ else
1064
+ return (void *)Qfalse;
1065
+ }
1066
+
1067
+ /* call-seq:
1068
+ * client.select_db(name)
1069
+ *
1070
+ * Causes the database specified by +name+ to become the default (current)
1071
+ * database on the connection specified by mysql.
1072
+ */
1073
+ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db)
1074
+ {
1075
+ struct nogvl_select_db_args args;
1076
+
1077
+ GET_CLIENT(self);
1078
+ REQUIRE_CONNECTED(wrapper);
1079
+
1080
+ args.mysql = wrapper->client;
1081
+ args.db = StringValueCStr(db);
1082
+
1083
+ if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse)
1084
+ rb_raise_mysql2_error(wrapper);
1085
+
1086
+ return db;
1087
+ }
1088
+
1089
+ static void *nogvl_ping(void *ptr) {
614
1090
  MYSQL *client = ptr;
615
1091
 
616
- return mysql_ping(client) == 0 ? Qtrue : Qfalse;
1092
+ return (void *)(mysql_ping(client) == 0 ? Qtrue : Qfalse);
617
1093
  }
618
1094
 
1095
+ /* call-seq:
1096
+ * client.ping
1097
+ *
1098
+ * Checks whether the connection to the server is working. If the connection
1099
+ * has gone down and auto-reconnect is enabled an attempt to reconnect is made.
1100
+ * If the connection is down and auto-reconnect is disabled, ping returns an
1101
+ * error.
1102
+ */
619
1103
  static VALUE rb_mysql_client_ping(VALUE self) {
620
1104
  GET_CLIENT(self);
621
1105
 
622
- if (wrapper->closed) {
1106
+ if (!CONNECTED(wrapper)) {
623
1107
  return Qfalse;
624
1108
  } else {
625
- return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
1109
+ return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
1110
+ }
1111
+ }
1112
+
1113
+ /* call-seq:
1114
+ * client.more_results?
1115
+ *
1116
+ * Returns true or false if there are more results to process.
1117
+ */
1118
+ static VALUE rb_mysql_client_more_results(VALUE self)
1119
+ {
1120
+ GET_CLIENT(self);
1121
+ if (mysql_more_results(wrapper->client) == 0)
1122
+ return Qfalse;
1123
+ else
1124
+ return Qtrue;
1125
+ }
1126
+
1127
+ /* call-seq:
1128
+ * client.next_result
1129
+ *
1130
+ * Fetch the next result set from the server.
1131
+ * Returns nothing.
1132
+ */
1133
+ static VALUE rb_mysql_client_next_result(VALUE self)
1134
+ {
1135
+ int ret;
1136
+ GET_CLIENT(self);
1137
+ ret = mysql_next_result(wrapper->client);
1138
+ if (ret > 0) {
1139
+ rb_raise_mysql2_error(wrapper);
1140
+ return Qfalse;
1141
+ } else if (ret == 0) {
1142
+ return Qtrue;
1143
+ } else {
1144
+ return Qfalse;
1145
+ }
1146
+ }
1147
+
1148
+ /* call-seq:
1149
+ * client.store_result
1150
+ *
1151
+ * Return the next result object from a query which
1152
+ * yielded multiple result sets.
1153
+ */
1154
+ static VALUE rb_mysql_client_store_result(VALUE self)
1155
+ {
1156
+ MYSQL_RES * result;
1157
+ VALUE resultObj;
1158
+ VALUE current;
1159
+ GET_CLIENT(self);
1160
+
1161
+ result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
1162
+
1163
+ if (result == NULL) {
1164
+ if (mysql_errno(wrapper->client) != 0) {
1165
+ rb_raise_mysql2_error(wrapper);
1166
+ }
1167
+ /* no data and no error, so query was not a SELECT */
1168
+ return Qnil;
626
1169
  }
1170
+
1171
+ current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1172
+ (void)RB_GC_GUARD(current);
1173
+ Check_Type(current, T_HASH);
1174
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
1175
+
1176
+ return resultObj;
627
1177
  }
628
1178
 
629
1179
  #ifdef HAVE_RUBY_ENCODING_H
1180
+ /* call-seq:
1181
+ * client.encoding
1182
+ *
1183
+ * Returns the encoding set on the client.
1184
+ */
630
1185
  static VALUE rb_mysql_client_encoding(VALUE self) {
631
1186
  GET_CLIENT(self);
632
1187
  return wrapper->encoding;
633
1188
  }
634
1189
  #endif
635
1190
 
636
- static VALUE set_reconnect(VALUE self, VALUE value) {
637
- my_bool reconnect;
1191
+ /* call-seq:
1192
+ * client.automatic_close?
1193
+ *
1194
+ * @return [Boolean]
1195
+ */
1196
+ static VALUE get_automatic_close(VALUE self) {
638
1197
  GET_CLIENT(self);
1198
+ return wrapper->automatic_close ? Qtrue : Qfalse;
1199
+ }
639
1200
 
640
- if(!NIL_P(value)) {
641
- reconnect = value == Qfalse ? 0 : 1;
642
-
643
- wrapper->reconnect_enabled = reconnect;
644
- /* set default reconnect behavior */
645
- if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
646
- /* TODO: warning - unable to set reconnect behavior */
647
- rb_warn("%s\n", mysql_error(wrapper->client));
648
- }
1201
+ /* call-seq:
1202
+ * client.automatic_close = false
1203
+ *
1204
+ * Set this to +false+ to leave the connection open after it is garbage
1205
+ * collected. To avoid "Aborted connection" errors on the server, explicitly
1206
+ * call +close+ when the connection is no longer needed.
1207
+ *
1208
+ * @see http://dev.mysql.com/doc/en/communication-errors.html
1209
+ */
1210
+ static VALUE set_automatic_close(VALUE self, VALUE value) {
1211
+ GET_CLIENT(self);
1212
+ if (RTEST(value)) {
1213
+ wrapper->automatic_close = 1;
1214
+ } else {
1215
+ #ifndef _WIN32
1216
+ wrapper->automatic_close = 0;
1217
+ #else
1218
+ rb_warn("Connections are always closed by garbage collector on Windows");
1219
+ #endif
649
1220
  }
650
1221
  return value;
651
1222
  }
652
1223
 
1224
+ /* call-seq:
1225
+ * client.reconnect = true
1226
+ *
1227
+ * Enable or disable the automatic reconnect behavior of libmysql.
1228
+ * Read http://dev.mysql.com/doc/refman/5.5/en/auto-reconnect.html
1229
+ * for more information.
1230
+ */
1231
+ static VALUE set_reconnect(VALUE self, VALUE value) {
1232
+ return _mysql_client_options(self, MYSQL_OPT_RECONNECT, value);
1233
+ }
1234
+
1235
+ static VALUE set_local_infile(VALUE self, VALUE value) {
1236
+ return _mysql_client_options(self, MYSQL_OPT_LOCAL_INFILE, value);
1237
+ }
1238
+
653
1239
  static VALUE set_connect_timeout(VALUE self, VALUE value) {
654
- unsigned int connect_timeout = 0;
655
- GET_CLIENT(self);
1240
+ long int sec;
1241
+ Check_Type(value, T_FIXNUM);
1242
+ sec = FIX2INT(value);
1243
+ if (sec < 0) {
1244
+ rb_raise(cMysql2Error, "connect_timeout must be a positive integer, you passed %ld", sec);
1245
+ }
1246
+ return _mysql_client_options(self, MYSQL_OPT_CONNECT_TIMEOUT, value);
1247
+ }
656
1248
 
657
- if(!NIL_P(value)) {
658
- connect_timeout = NUM2INT(value);
659
- if(0 == connect_timeout) return value;
1249
+ static VALUE set_read_timeout(VALUE self, VALUE value) {
1250
+ long int sec;
1251
+ Check_Type(value, T_FIXNUM);
1252
+ sec = FIX2INT(value);
1253
+ if (sec < 0) {
1254
+ rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
1255
+ }
1256
+ /* Set the instance variable here even though _mysql_client_options
1257
+ might not succeed, because the timeout is used in other ways
1258
+ elsewhere */
1259
+ rb_iv_set(self, "@read_timeout", value);
1260
+ return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value);
1261
+ }
660
1262
 
661
- /* set default connection timeout behavior */
662
- if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
663
- /* TODO: warning - unable to set connection timeout */
664
- rb_warn("%s\n", mysql_error(wrapper->client));
665
- }
1263
+ static VALUE set_write_timeout(VALUE self, VALUE value) {
1264
+ long int sec;
1265
+ Check_Type(value, T_FIXNUM);
1266
+ sec = FIX2INT(value);
1267
+ if (sec < 0) {
1268
+ rb_raise(cMysql2Error, "write_timeout must be a positive integer, you passed %ld", sec);
666
1269
  }
667
- return value;
1270
+ return _mysql_client_options(self, MYSQL_OPT_WRITE_TIMEOUT, value);
668
1271
  }
669
1272
 
670
1273
  static VALUE set_charset_name(VALUE self, VALUE value) {
671
- char * charset_name;
1274
+ char *charset_name;
672
1275
  #ifdef HAVE_RUBY_ENCODING_H
673
- VALUE new_encoding;
1276
+ const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1277
+ rb_encoding *enc;
1278
+ VALUE rb_enc;
674
1279
  #endif
675
1280
  GET_CLIENT(self);
676
1281
 
1282
+ Check_Type(value, T_STRING);
1283
+ charset_name = RSTRING_PTR(value);
1284
+
677
1285
  #ifdef HAVE_RUBY_ENCODING_H
678
- new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
679
- if (new_encoding == Qnil) {
1286
+ mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1287
+ if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
680
1288
  VALUE inspect = rb_inspect(value);
681
1289
  rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
682
1290
  } else {
683
- if (wrapper->encoding == Qnil) {
684
- wrapper->encoding = new_encoding;
685
- }
1291
+ enc = rb_enc_find(mysql2rb->rb_name);
1292
+ rb_enc = rb_enc_from_encoding(enc);
1293
+ wrapper->encoding = rb_enc;
686
1294
  }
687
1295
  #endif
688
1296
 
689
- charset_name = StringValuePtr(value);
690
-
691
1297
  if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
692
1298
  /* TODO: warning - unable to set charset */
693
1299
  rb_warn("%s\n", mysql_error(wrapper->client));
@@ -699,192 +1305,298 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
699
1305
  static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
700
1306
  GET_CLIENT(self);
701
1307
 
702
- if(!NIL_P(ca) || !NIL_P(key)) {
703
- mysql_ssl_set(wrapper->client,
704
- NIL_P(key) ? NULL : StringValuePtr(key),
705
- NIL_P(cert) ? NULL : StringValuePtr(cert),
706
- NIL_P(ca) ? NULL : StringValuePtr(ca),
707
- NIL_P(capath) ? NULL : StringValuePtr(capath),
708
- NIL_P(cipher) ? NULL : StringValuePtr(cipher));
709
- }
1308
+ mysql_ssl_set(wrapper->client,
1309
+ NIL_P(key) ? NULL : StringValueCStr(key),
1310
+ NIL_P(cert) ? NULL : StringValueCStr(cert),
1311
+ NIL_P(ca) ? NULL : StringValueCStr(ca),
1312
+ NIL_P(capath) ? NULL : StringValueCStr(capath),
1313
+ NIL_P(cipher) ? NULL : StringValueCStr(cipher));
710
1314
 
711
1315
  return self;
712
1316
  }
713
1317
 
714
- static VALUE init_connection(VALUE self) {
1318
+ static VALUE set_secure_auth(VALUE self, VALUE value) {
1319
+ /* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */
1320
+ #ifdef MYSQL_SECURE_AUTH
1321
+ return _mysql_client_options(self, MYSQL_SECURE_AUTH, value);
1322
+ #else
1323
+ return Qfalse;
1324
+ #endif
1325
+ }
1326
+
1327
+ static VALUE set_read_default_file(VALUE self, VALUE value) {
1328
+ return _mysql_client_options(self, MYSQL_READ_DEFAULT_FILE, value);
1329
+ }
1330
+
1331
+ static VALUE set_read_default_group(VALUE self, VALUE value) {
1332
+ return _mysql_client_options(self, MYSQL_READ_DEFAULT_GROUP, value);
1333
+ }
1334
+
1335
+ static VALUE set_init_command(VALUE self, VALUE value) {
1336
+ return _mysql_client_options(self, MYSQL_INIT_COMMAND, value);
1337
+ }
1338
+
1339
+ static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) {
1340
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
1341
+ return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value);
1342
+ #else
1343
+ rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library");
1344
+ #endif
1345
+ }
1346
+
1347
+ static VALUE initialize_ext(VALUE self) {
715
1348
  GET_CLIENT(self);
716
1349
 
717
- if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
1350
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
718
1351
  /* TODO: warning - not enough memory? */
719
- return rb_raise_mysql2_error(wrapper);
1352
+ rb_raise_mysql2_error(wrapper);
720
1353
  }
721
1354
 
722
- wrapper->closed = 0;
1355
+ wrapper->initialized = 1;
723
1356
  return self;
724
1357
  }
725
1358
 
1359
+ /* call-seq: client.prepare # => Mysql2::Statement
1360
+ *
1361
+ * Create a new prepared statement.
1362
+ */
1363
+ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
1364
+ GET_CLIENT(self);
1365
+ REQUIRE_CONNECTED(wrapper);
1366
+
1367
+ return rb_mysql_stmt_new(self, sql);
1368
+ }
1369
+
726
1370
  void init_mysql2_client() {
727
- // verify the libmysql we're about to use was the version we were built against
728
- // https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99
1371
+ #ifdef _WIN32
1372
+ /* verify the libmysql we're about to use was the version we were built against
1373
+ https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
729
1374
  int i;
730
1375
  int dots = 0;
731
1376
  const char *lib = mysql_get_client_info();
732
- for (i = 0; lib[i] != 0 && MYSQL_SERVER_VERSION[i] != 0; i++) {
1377
+
1378
+ for (i = 0; lib[i] != 0 && MYSQL_LINK_VERSION[i] != 0; i++) {
733
1379
  if (lib[i] == '.') {
734
1380
  dots++;
735
- // we only compare MAJOR and MINOR
1381
+ /* we only compare MAJOR and MINOR */
736
1382
  if (dots == 2) break;
737
1383
  }
738
- if (lib[i] != MYSQL_SERVER_VERSION[i]) {
739
- rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_SERVER_VERSION, lib);
740
- return;
1384
+ if (lib[i] != MYSQL_LINK_VERSION[i]) {
1385
+ rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib);
741
1386
  }
742
1387
  }
1388
+ #endif
1389
+
1390
+ /* Initializing mysql library, so different threads could call Client.new */
1391
+ /* without race condition in the library */
1392
+ if (mysql_library_init(0, NULL, NULL) != 0) {
1393
+ rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
1394
+ }
743
1395
 
1396
+ #if 0
1397
+ mMysql2 = rb_define_module("Mysql2"); Teach RDoc about Mysql2 constant.
1398
+ #endif
744
1399
  cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
745
1400
 
746
1401
  rb_define_alloc_func(cMysql2Client, allocate);
747
1402
 
748
1403
  rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
1404
+ rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
749
1405
 
750
1406
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
751
- rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
1407
+ rb_define_method(cMysql2Client, "closed?", rb_mysql_client_closed, 0);
1408
+ rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
752
1409
  rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
753
- rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
754
1410
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
755
1411
  rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
756
1412
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
757
1413
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
758
1414
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
1415
+ rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
759
1416
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
760
1417
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1418
+ rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
1419
+ rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0);
1420
+ rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
1421
+ rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
1422
+ rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0);
1423
+ rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1);
1424
+ rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1);
1425
+ rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0);
1426
+ rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0);
1427
+ rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0);
761
1428
  #ifdef HAVE_RUBY_ENCODING_H
762
1429
  rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
763
1430
  #endif
764
1431
 
765
- rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
766
1432
  rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
1433
+ rb_define_private_method(cMysql2Client, "read_timeout=", set_read_timeout, 1);
1434
+ rb_define_private_method(cMysql2Client, "write_timeout=", set_write_timeout, 1);
1435
+ rb_define_private_method(cMysql2Client, "local_infile=", set_local_infile, 1);
767
1436
  rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
1437
+ rb_define_private_method(cMysql2Client, "secure_auth=", set_secure_auth, 1);
1438
+ rb_define_private_method(cMysql2Client, "default_file=", set_read_default_file, 1);
1439
+ rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1);
1440
+ rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1);
768
1441
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
769
- rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0);
1442
+ rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1);
1443
+ rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1);
1444
+ rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
770
1445
  rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
771
-
772
- intern_encoding_from_charset = rb_intern("encoding_from_charset");
1446
+ rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
773
1447
 
774
1448
  sym_id = ID2SYM(rb_intern("id"));
775
1449
  sym_version = ID2SYM(rb_intern("version"));
1450
+ sym_header_version = ID2SYM(rb_intern("header_version"));
776
1451
  sym_async = ID2SYM(rb_intern("async"));
777
1452
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
778
1453
  sym_as = ID2SYM(rb_intern("as"));
779
1454
  sym_array = ID2SYM(rb_intern("array"));
1455
+ sym_stream = ID2SYM(rb_intern("stream"));
780
1456
 
1457
+ intern_brackets = rb_intern("[]");
781
1458
  intern_merge = rb_intern("merge");
782
- intern_error_number_eql = rb_intern("error_number=");
783
- intern_sql_state_eql = rb_intern("sql_state=");
1459
+ intern_merge_bang = rb_intern("merge!");
1460
+ intern_new_with_args = rb_intern("new_with_args");
784
1461
 
785
1462
  #ifdef CLIENT_LONG_PASSWORD
786
1463
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
787
- INT2NUM(CLIENT_LONG_PASSWORD));
1464
+ LONG2NUM(CLIENT_LONG_PASSWORD));
1465
+ #else
1466
+ /* HACK because MariaDB 10.2 no longer defines this constant,
1467
+ * but we're using it in our default connection flags. */
1468
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), INT2NUM(0));
788
1469
  #endif
789
1470
 
790
1471
  #ifdef CLIENT_FOUND_ROWS
791
1472
  rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"),
792
- INT2NUM(CLIENT_FOUND_ROWS));
1473
+ LONG2NUM(CLIENT_FOUND_ROWS));
793
1474
  #endif
794
1475
 
795
1476
  #ifdef CLIENT_LONG_FLAG
796
1477
  rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"),
797
- INT2NUM(CLIENT_LONG_FLAG));
1478
+ LONG2NUM(CLIENT_LONG_FLAG));
798
1479
  #endif
799
1480
 
800
1481
  #ifdef CLIENT_CONNECT_WITH_DB
801
1482
  rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"),
802
- INT2NUM(CLIENT_CONNECT_WITH_DB));
1483
+ LONG2NUM(CLIENT_CONNECT_WITH_DB));
803
1484
  #endif
804
1485
 
805
1486
  #ifdef CLIENT_NO_SCHEMA
806
1487
  rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"),
807
- INT2NUM(CLIENT_NO_SCHEMA));
1488
+ LONG2NUM(CLIENT_NO_SCHEMA));
808
1489
  #endif
809
1490
 
810
1491
  #ifdef CLIENT_COMPRESS
811
- rb_const_set(cMysql2Client, rb_intern("COMPRESS"), INT2NUM(CLIENT_COMPRESS));
1492
+ rb_const_set(cMysql2Client, rb_intern("COMPRESS"), LONG2NUM(CLIENT_COMPRESS));
812
1493
  #endif
813
1494
 
814
1495
  #ifdef CLIENT_ODBC
815
- rb_const_set(cMysql2Client, rb_intern("ODBC"), INT2NUM(CLIENT_ODBC));
1496
+ rb_const_set(cMysql2Client, rb_intern("ODBC"), LONG2NUM(CLIENT_ODBC));
816
1497
  #endif
817
1498
 
818
1499
  #ifdef CLIENT_LOCAL_FILES
819
1500
  rb_const_set(cMysql2Client, rb_intern("LOCAL_FILES"),
820
- INT2NUM(CLIENT_LOCAL_FILES));
1501
+ LONG2NUM(CLIENT_LOCAL_FILES));
821
1502
  #endif
822
1503
 
823
1504
  #ifdef CLIENT_IGNORE_SPACE
824
1505
  rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"),
825
- INT2NUM(CLIENT_IGNORE_SPACE));
1506
+ LONG2NUM(CLIENT_IGNORE_SPACE));
826
1507
  #endif
827
1508
 
828
1509
  #ifdef CLIENT_PROTOCOL_41
829
1510
  rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"),
830
- INT2NUM(CLIENT_PROTOCOL_41));
1511
+ LONG2NUM(CLIENT_PROTOCOL_41));
831
1512
  #endif
832
1513
 
833
1514
  #ifdef CLIENT_INTERACTIVE
834
1515
  rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"),
835
- INT2NUM(CLIENT_INTERACTIVE));
1516
+ LONG2NUM(CLIENT_INTERACTIVE));
836
1517
  #endif
837
1518
 
838
1519
  #ifdef CLIENT_SSL
839
- rb_const_set(cMysql2Client, rb_intern("SSL"), INT2NUM(CLIENT_SSL));
1520
+ rb_const_set(cMysql2Client, rb_intern("SSL"), LONG2NUM(CLIENT_SSL));
840
1521
  #endif
841
1522
 
842
1523
  #ifdef CLIENT_IGNORE_SIGPIPE
843
1524
  rb_const_set(cMysql2Client, rb_intern("IGNORE_SIGPIPE"),
844
- INT2NUM(CLIENT_IGNORE_SIGPIPE));
1525
+ LONG2NUM(CLIENT_IGNORE_SIGPIPE));
845
1526
  #endif
846
1527
 
847
1528
  #ifdef CLIENT_TRANSACTIONS
848
1529
  rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"),
849
- INT2NUM(CLIENT_TRANSACTIONS));
1530
+ LONG2NUM(CLIENT_TRANSACTIONS));
850
1531
  #endif
851
1532
 
852
1533
  #ifdef CLIENT_RESERVED
853
- rb_const_set(cMysql2Client, rb_intern("RESERVED"), INT2NUM(CLIENT_RESERVED));
1534
+ rb_const_set(cMysql2Client, rb_intern("RESERVED"), LONG2NUM(CLIENT_RESERVED));
854
1535
  #endif
855
1536
 
856
1537
  #ifdef CLIENT_SECURE_CONNECTION
857
1538
  rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"),
858
- INT2NUM(CLIENT_SECURE_CONNECTION));
1539
+ LONG2NUM(CLIENT_SECURE_CONNECTION));
1540
+ #else
1541
+ /* HACK because MySQL5.7 no longer defines this constant,
1542
+ * but we're using it in our default connection flags. */
1543
+ rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"), LONG2NUM(0));
859
1544
  #endif
860
1545
 
861
1546
  #ifdef CLIENT_MULTI_STATEMENTS
862
1547
  rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
863
- INT2NUM(CLIENT_MULTI_STATEMENTS));
1548
+ LONG2NUM(CLIENT_MULTI_STATEMENTS));
864
1549
  #endif
865
1550
 
866
1551
  #ifdef CLIENT_PS_MULTI_RESULTS
867
1552
  rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"),
868
- INT2NUM(CLIENT_PS_MULTI_RESULTS));
1553
+ LONG2NUM(CLIENT_PS_MULTI_RESULTS));
869
1554
  #endif
870
1555
 
871
1556
  #ifdef CLIENT_SSL_VERIFY_SERVER_CERT
872
1557
  rb_const_set(cMysql2Client, rb_intern("SSL_VERIFY_SERVER_CERT"),
873
- INT2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
1558
+ LONG2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
874
1559
  #endif
875
1560
 
876
1561
  #ifdef CLIENT_REMEMBER_OPTIONS
877
1562
  rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"),
878
- INT2NUM(CLIENT_REMEMBER_OPTIONS));
1563
+ LONG2NUM(CLIENT_REMEMBER_OPTIONS));
879
1564
  #endif
880
1565
 
881
1566
  #ifdef CLIENT_ALL_FLAGS
882
1567
  rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"),
883
- INT2NUM(CLIENT_ALL_FLAGS));
1568
+ LONG2NUM(CLIENT_ALL_FLAGS));
884
1569
  #endif
885
1570
 
886
1571
  #ifdef CLIENT_BASIC_FLAGS
887
1572
  rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
888
- INT2NUM(CLIENT_BASIC_FLAGS));
1573
+ LONG2NUM(CLIENT_BASIC_FLAGS));
1574
+ #endif
1575
+
1576
+ #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above
1577
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1578
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED));
1579
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1580
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA));
1581
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY));
1582
+ #elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10
1583
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1584
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1585
+ #endif
1586
+
1587
+ #ifndef HAVE_CONST_SSL_MODE_DISABLED
1588
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0));
1589
+ #endif
1590
+ #ifndef HAVE_CONST_SSL_MODE_PREFERRED
1591
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0));
1592
+ #endif
1593
+ #ifndef HAVE_CONST_SSL_MODE_REQUIRED
1594
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0));
1595
+ #endif
1596
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_CA
1597
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0));
1598
+ #endif
1599
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY
1600
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0));
889
1601
  #endif
890
1602
  }