mysql2 0.3.8 → 0.4.10

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