mysql2 0.3.10 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ }