mysql2 0.3.11-x86-mingw32 → 0.3.18-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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
data/ext/mysql2/client.c CHANGED
@@ -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
  }