mysql2 0.3.11-x86-mswin32-60 → 0.3.18-x86-mswin32-60

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