mysql2 0.3.10 → 0.5.3

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