mysql2 0.3.1 → 0.5.2

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