mysql2-sp 0.3.10

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 (46) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +3 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +230 -0
  6. data/Gemfile +3 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +350 -0
  9. data/Rakefile +5 -0
  10. data/benchmark/active_record.rb +51 -0
  11. data/benchmark/active_record_threaded.rb +42 -0
  12. data/benchmark/allocations.rb +33 -0
  13. data/benchmark/escape.rb +36 -0
  14. data/benchmark/query_with_mysql_casting.rb +80 -0
  15. data/benchmark/query_without_mysql_casting.rb +56 -0
  16. data/benchmark/sequel.rb +37 -0
  17. data/benchmark/setup_db.rb +119 -0
  18. data/benchmark/threaded.rb +44 -0
  19. data/examples/eventmachine.rb +21 -0
  20. data/examples/threaded.rb +20 -0
  21. data/ext/mysql2/client.c +955 -0
  22. data/ext/mysql2/client.h +42 -0
  23. data/ext/mysql2/extconf.rb +73 -0
  24. data/ext/mysql2/mysql2_ext.c +12 -0
  25. data/ext/mysql2/mysql2_ext.h +42 -0
  26. data/ext/mysql2/result.c +568 -0
  27. data/ext/mysql2/result.h +20 -0
  28. data/ext/mysql2/wait_for_single_fd.h +36 -0
  29. data/lib/mysql2.rb +21 -0
  30. data/lib/mysql2/client.rb +242 -0
  31. data/lib/mysql2/em.rb +37 -0
  32. data/lib/mysql2/error.rb +15 -0
  33. data/lib/mysql2/result.rb +5 -0
  34. data/lib/mysql2/version.rb +3 -0
  35. data/mysql2.gemspec +29 -0
  36. data/spec/em/em_spec.rb +50 -0
  37. data/spec/mysql2/client_spec.rb +491 -0
  38. data/spec/mysql2/error_spec.rb +69 -0
  39. data/spec/mysql2/result_spec.rb +388 -0
  40. data/spec/rcov.opts +3 -0
  41. data/spec/spec_helper.rb +67 -0
  42. data/tasks/benchmarks.rake +20 -0
  43. data/tasks/compile.rake +71 -0
  44. data/tasks/rspec.rake +16 -0
  45. data/tasks/vendor_mysql.rake +40 -0
  46. metadata +198 -0
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'benchmark'
6
+ require 'active_record'
7
+
8
+ mysql2_opts = {
9
+ :adapter => 'mysql2',
10
+ :database => 'test',
11
+ :pool => 25
12
+ }
13
+ ActiveRecord::Base.establish_connection(mysql2_opts)
14
+ x = Benchmark.realtime do
15
+ threads = []
16
+ 25.times do
17
+ threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
18
+ end
19
+ threads.each {|t| t.join }
20
+ end
21
+ puts x
22
+
23
+ mysql2_opts = {
24
+ :adapter => 'mysql',
25
+ :database => 'test',
26
+ :pool => 25
27
+ }
28
+ ActiveRecord::Base.establish_connection(mysql2_opts)
29
+ x = Benchmark.realtime do
30
+ threads = []
31
+ 25.times do
32
+ threads << Thread.new { ActiveRecord::Base.connection.execute("select sleep(1)") }
33
+ end
34
+ threads.each {|t| t.join }
35
+ end
36
+ puts x
37
+
38
+ # these results are similar on 1.8.7, 1.9.2 and rbx-head
39
+ #
40
+ # $ bundle exec ruby benchmarks/threaded.rb
41
+ # 1.0774750709533691
42
+ #
43
+ # and using the mysql gem
44
+ # 25.099437952041626
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH.unshift 'lib'
4
+
5
+ require 'rubygems'
6
+ require 'eventmachine'
7
+ require 'mysql2/em'
8
+
9
+ EM.run do
10
+ client1 = Mysql2::EM::Client.new
11
+ defer1 = client1.query "SELECT sleep(3) as first_query"
12
+ defer1.callback do |result|
13
+ puts "Result: #{result.to_a.inspect}"
14
+ end
15
+
16
+ client2 = Mysql2::EM::Client.new
17
+ defer2 = client2.query "SELECT sleep(1) second_query"
18
+ defer2.callback do |result|
19
+ puts "Result: #{result.to_a.inspect}"
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH.unshift 'lib'
4
+ require 'mysql2'
5
+ require 'timeout'
6
+
7
+ threads = []
8
+ # Should never exceed worst case 3.5 secs across all 20 threads
9
+ Timeout.timeout(3.5) do
10
+ 20.times do
11
+ threads << Thread.new do
12
+ overhead = rand(3)
13
+ puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead"
14
+ # 3 second overhead per query
15
+ Mysql2::Client.new(:host => "localhost", :username => "root").query("SELECT sleep(#{overhead}) as result")
16
+ puts "<< thread #{Thread.current.object_id} result, #{overhead} sec overhead"
17
+ end
18
+ end
19
+ threads.each{|t| t.join }
20
+ end
@@ -0,0 +1,955 @@
1
+ #include <mysql2_ext.h>
2
+ #include <client.h>
3
+ #include <errno.h>
4
+ #ifndef _WIN32
5
+ #include <sys/socket.h>
6
+ #endif
7
+ #include "wait_for_single_fd.h"
8
+
9
+ 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;
14
+
15
+ #define REQUIRE_OPEN_DB(wrapper) \
16
+ if(!wrapper->reconnect_enabled && wrapper->closed) { \
17
+ rb_raise(cMysql2Error, "closed MySQL connection"); \
18
+ }
19
+
20
+ #define MARK_CONN_INACTIVE(conn) \
21
+ wrapper->active = 0
22
+
23
+ #define GET_CLIENT(self) \
24
+ mysql_client_wrapper *wrapper; \
25
+ Data_Get_Struct(self, mysql_client_wrapper, wrapper)
26
+
27
+ /*
28
+ * used to pass all arguments to mysql_real_connect while inside
29
+ * rb_thread_blocking_region
30
+ */
31
+ struct nogvl_connect_args {
32
+ MYSQL *mysql;
33
+ const char *host;
34
+ const char *user;
35
+ const char *passwd;
36
+ const char *db;
37
+ unsigned int port;
38
+ const char *unix_socket;
39
+ unsigned long client_flag;
40
+ };
41
+
42
+ /*
43
+ * used to pass all arguments to mysql_send_query while inside
44
+ * rb_thread_blocking_region
45
+ */
46
+ struct nogvl_send_query_args {
47
+ MYSQL *mysql;
48
+ VALUE sql;
49
+ mysql_client_wrapper *wrapper;
50
+ };
51
+
52
+ /*
53
+ * non-blocking mysql_*() functions that we won't be wrapping since
54
+ * they do not appear to hit the network nor issue any interruptible
55
+ * or blocking system calls.
56
+ *
57
+ * - mysql_affected_rows()
58
+ * - mysql_error()
59
+ * - mysql_fetch_fields()
60
+ * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths
61
+ * - mysql_field_count()
62
+ * - mysql_get_client_info()
63
+ * - mysql_get_client_version()
64
+ * - mysql_get_server_info()
65
+ * - mysql_get_server_version()
66
+ * - mysql_insert_id()
67
+ * - mysql_num_fields()
68
+ * - mysql_num_rows()
69
+ * - mysql_options()
70
+ * - mysql_real_escape_string()
71
+ * - mysql_ssl_set()
72
+ */
73
+
74
+ static void rb_mysql_client_mark(void * wrapper) {
75
+ mysql_client_wrapper * w = wrapper;
76
+ if (w) {
77
+ rb_gc_mark(w->encoding);
78
+ }
79
+ }
80
+
81
+ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
82
+ VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
83
+ 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);
87
+
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
95
+
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);
99
+ rb_exc_raise(e);
100
+ return Qnil;
101
+ }
102
+
103
+ static VALUE nogvl_init(void *ptr) {
104
+ MYSQL *client;
105
+
106
+ /* may initialize embedded server and read /etc/services off disk */
107
+ client = mysql_init((MYSQL *)ptr);
108
+ return client ? Qtrue : Qfalse;
109
+ }
110
+
111
+ static VALUE nogvl_connect(void *ptr) {
112
+ struct nogvl_connect_args *args = ptr;
113
+ MYSQL *client;
114
+
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);
121
+
122
+ return client ? Qtrue : Qfalse;
123
+ }
124
+
125
+ static VALUE nogvl_close(void *ptr) {
126
+ mysql_client_wrapper *wrapper;
127
+ #ifndef _WIN32
128
+ int flags;
129
+ #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
142
+ */
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
148
+
149
+ mysql_close(wrapper->client);
150
+ free(wrapper->client);
151
+ }
152
+
153
+ return Qnil;
154
+ }
155
+
156
+ static void rb_mysql_client_free(void * ptr) {
157
+ mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
158
+
159
+ nogvl_close(wrapper);
160
+
161
+ free(ptr);
162
+ }
163
+
164
+ static VALUE allocate(VALUE klass) {
165
+ VALUE obj;
166
+ mysql_client_wrapper * wrapper;
167
+ obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
168
+ wrapper->encoding = Qnil;
169
+ wrapper->active = 0;
170
+ wrapper->reconnect_enabled = 0;
171
+ wrapper->closed = 1;
172
+ wrapper->client = (MYSQL*)malloc(sizeof(MYSQL));
173
+ return obj;
174
+ }
175
+
176
+ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
177
+ unsigned char *newStr;
178
+ VALUE rb_str;
179
+ unsigned long newLen, oldLen;
180
+
181
+ Check_Type(str, T_STRING);
182
+
183
+ oldLen = RSTRING_LEN(str);
184
+ newStr = malloc(oldLen*2+1);
185
+
186
+ newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen);
187
+ if (newLen == oldLen) {
188
+ // no need to return a new ruby string if nothing changed
189
+ free(newStr);
190
+ return str;
191
+ } else {
192
+ rb_str = rb_str_new((const char*)newStr, newLen);
193
+ #ifdef HAVE_RUBY_ENCODING_H
194
+ rb_enc_copy(rb_str, str);
195
+ #endif
196
+ free(newStr);
197
+ return rb_str;
198
+ }
199
+ }
200
+
201
+ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
202
+ struct nogvl_connect_args args;
203
+ GET_CLIENT(self);
204
+
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;
212
+ args.client_flag = NUM2ULONG(flags);
213
+
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);
217
+ }
218
+
219
+ return self;
220
+ }
221
+
222
+ /*
223
+ * Immediately disconnect from the server, normally the garbage collector
224
+ * will disconnect automatically when a connection is no longer needed.
225
+ * Explicitly closing this will free up server resources sooner than waiting
226
+ * for the garbage collector.
227
+ */
228
+ static VALUE rb_mysql_client_close(VALUE self) {
229
+ GET_CLIENT(self);
230
+
231
+ if (!wrapper->closed) {
232
+ rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
233
+ }
234
+
235
+ return Qnil;
236
+ }
237
+
238
+ /*
239
+ * mysql_send_query is unlikely to block since most queries are small
240
+ * enough to fit in a socket buffer, but sometimes large UPDATE and
241
+ * INSERTs will cause the process to block
242
+ */
243
+ static VALUE nogvl_send_query(void *ptr) {
244
+ struct nogvl_send_query_args *args = ptr;
245
+ int rv;
246
+ const char *sql = StringValuePtr(args->sql);
247
+ long sql_len = RSTRING_LEN(args->sql);
248
+
249
+ rv = mysql_send_query(args->mysql, sql, sql_len);
250
+
251
+ return rv == 0 ? Qtrue : Qfalse;
252
+ }
253
+
254
+ static VALUE do_send_query(void *args) {
255
+ struct nogvl_send_query_args *query_args = args;
256
+ 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);
261
+ }
262
+ return Qnil;
263
+ }
264
+
265
+ /*
266
+ * even though we did rb_thread_select before calling this, a large
267
+ * response can overflow the socket buffers and cause us to eventually
268
+ * block while calling mysql_read_query_result
269
+ */
270
+ static VALUE nogvl_read_query_result(void *ptr) {
271
+ MYSQL * client = ptr;
272
+ my_bool res = mysql_read_query_result(client);
273
+
274
+ return res == 0 ? Qtrue : Qfalse;
275
+ }
276
+
277
+ /* mysql_store_result may (unlikely) read rows off the socket */
278
+ static VALUE nogvl_store_result(void *ptr) {
279
+ mysql_client_wrapper *wrapper;
280
+ MYSQL_RES *result;
281
+
282
+ wrapper = (mysql_client_wrapper *)ptr;
283
+ result = mysql_store_result(wrapper->client);
284
+
285
+ // once our result is stored off, this connection is
286
+ // ready for another command to be issued
287
+ wrapper->active = 0;
288
+
289
+ return (VALUE)result;
290
+ }
291
+
292
+ static VALUE rb_mysql_client_async_result(VALUE self) {
293
+ MYSQL_RES * result;
294
+ VALUE resultObj;
295
+ #ifdef HAVE_RUBY_ENCODING_H
296
+ mysql2_result_wrapper * result_wrapper;
297
+ #endif
298
+ GET_CLIENT(self);
299
+
300
+ // if we're not waiting on a result, do nothing
301
+ if (!wrapper->active)
302
+ return Qnil;
303
+
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);
309
+ }
310
+
311
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
312
+
313
+ if (result == NULL) {
314
+ if (mysql_errno(wrapper->client) != 0) {
315
+ rb_raise_mysql2_error(wrapper);
316
+ }
317
+ // no data and no error, so query was not a SELECT
318
+ return Qnil;
319
+ }
320
+
321
+ resultObj = rb_mysql_result_to_obj(result);
322
+ // pass-through query options for result construction later
323
+ rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
324
+
325
+ #ifdef HAVE_RUBY_ENCODING_H
326
+ GetMysql2Result(resultObj, result_wrapper);
327
+ result_wrapper->encoding = wrapper->encoding;
328
+ #endif
329
+ return resultObj;
330
+ }
331
+
332
+ #ifndef _WIN32
333
+ struct async_query_args {
334
+ int fd;
335
+ VALUE self;
336
+ };
337
+
338
+ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
339
+ GET_CLIENT(self);
340
+
341
+ wrapper->closed = 1;
342
+ wrapper->active = 0;
343
+
344
+ // manually close the socket for read/write
345
+ // this feels dirty, but is there another way?
346
+ shutdown(wrapper->client->net.fd, 2);
347
+
348
+ rb_exc_raise(error);
349
+
350
+ return Qnil;
351
+ }
352
+
353
+ static VALUE do_query(void *args) {
354
+ struct async_query_args *async_args;
355
+ struct timeval tv;
356
+ struct timeval* tvp;
357
+ long int sec;
358
+ int retval;
359
+ VALUE read_timeout;
360
+
361
+ async_args = (struct async_query_args *)args;
362
+ read_timeout = rb_iv_get(async_args->self, "@read_timeout");
363
+
364
+ tvp = NULL;
365
+ if (!NIL_P(read_timeout)) {
366
+ Check_Type(read_timeout, T_FIXNUM);
367
+ tvp = &tv;
368
+ sec = FIX2INT(read_timeout);
369
+ // TODO: support partial seconds?
370
+ // also, this check is here for sanity, we also check up in Ruby
371
+ if (sec >= 0) {
372
+ tvp->tv_sec = sec;
373
+ } else {
374
+ rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
375
+ }
376
+ tvp->tv_usec = 0;
377
+ }
378
+
379
+ for(;;) {
380
+ retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp);
381
+
382
+ if (retval == 0) {
383
+ rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
384
+ }
385
+
386
+ if (retval < 0) {
387
+ rb_sys_fail(0);
388
+ }
389
+
390
+ if (retval > 0) {
391
+ break;
392
+ }
393
+ }
394
+
395
+ return Qnil;
396
+ }
397
+ #else
398
+ static VALUE finish_and_mark_inactive(void *args) {
399
+ VALUE self;
400
+ MYSQL_RES *result;
401
+
402
+ self = (VALUE)args;
403
+
404
+ GET_CLIENT(self);
405
+
406
+ if (wrapper->active) {
407
+ // if we got here, the result hasn't been read off the wire yet
408
+ // so lets do that and then throw it away because we have no way
409
+ // of getting it back up to the caller from here
410
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
411
+ mysql_free_result(result);
412
+
413
+ wrapper->active = 0;
414
+ }
415
+
416
+ return Qnil;
417
+ }
418
+ #endif
419
+
420
+ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
421
+ #ifndef _WIN32
422
+ struct async_query_args async_args;
423
+ #endif
424
+ struct nogvl_send_query_args args;
425
+ int async = 0;
426
+ VALUE opts, defaults;
427
+ #ifdef HAVE_RUBY_ENCODING_H
428
+ rb_encoding *conn_enc;
429
+ #endif
430
+ GET_CLIENT(self);
431
+
432
+ REQUIRE_OPEN_DB(wrapper);
433
+ args.mysql = wrapper->client;
434
+
435
+
436
+ defaults = rb_iv_get(self, "@query_options");
437
+ if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
438
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
439
+ rb_iv_set(self, "@query_options", opts);
440
+
441
+ if (rb_hash_aref(opts, sym_async) == Qtrue) {
442
+ async = 1;
443
+ }
444
+ } else {
445
+ opts = defaults;
446
+ }
447
+
448
+ Check_Type(args.sql, T_STRING);
449
+ #ifdef HAVE_RUBY_ENCODING_H
450
+ conn_enc = rb_to_encoding(wrapper->encoding);
451
+ // ensure the string is in the encoding the connection is expecting
452
+ args.sql = rb_str_export_to_enc(args.sql, conn_enc);
453
+ #endif
454
+
455
+ // see if this connection is still waiting on a result from a previous query
456
+ if (wrapper->active == 0) {
457
+ // mark this connection active
458
+ wrapper->active = 1;
459
+ } else {
460
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
461
+ }
462
+
463
+ args.wrapper = wrapper;
464
+
465
+ #ifndef _WIN32
466
+ rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
467
+
468
+ if (!async) {
469
+ async_args.fd = wrapper->client->net.fd;
470
+ async_args.self = self;
471
+
472
+ rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
473
+
474
+ return rb_mysql_client_async_result(self);
475
+ } else {
476
+ return Qnil;
477
+ }
478
+ #else
479
+ do_send_query(&args);
480
+
481
+ // this will just block until the result is ready
482
+ return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
483
+ #endif
484
+ }
485
+
486
+ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
487
+ unsigned char *newStr;
488
+ VALUE rb_str;
489
+ unsigned long newLen, oldLen;
490
+ #ifdef HAVE_RUBY_ENCODING_H
491
+ rb_encoding *default_internal_enc;
492
+ rb_encoding *conn_enc;
493
+ #endif
494
+ GET_CLIENT(self);
495
+
496
+ REQUIRE_OPEN_DB(wrapper);
497
+ Check_Type(str, T_STRING);
498
+ #ifdef HAVE_RUBY_ENCODING_H
499
+ default_internal_enc = rb_default_internal_encoding();
500
+ conn_enc = rb_to_encoding(wrapper->encoding);
501
+ // ensure the string is in the encoding the connection is expecting
502
+ str = rb_str_export_to_enc(str, conn_enc);
503
+ #endif
504
+
505
+ oldLen = RSTRING_LEN(str);
506
+ newStr = malloc(oldLen*2+1);
507
+
508
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
509
+ if (newLen == oldLen) {
510
+ // no need to return a new ruby string if nothing changed
511
+ free(newStr);
512
+ return str;
513
+ } else {
514
+ rb_str = rb_str_new((const char*)newStr, newLen);
515
+ #ifdef HAVE_RUBY_ENCODING_H
516
+ rb_enc_associate(rb_str, conn_enc);
517
+ if (default_internal_enc) {
518
+ rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
519
+ }
520
+ #endif
521
+ free(newStr);
522
+ return rb_str;
523
+ }
524
+ }
525
+
526
+ static VALUE rb_mysql_client_info(VALUE self) {
527
+ VALUE version, client_info;
528
+ #ifdef HAVE_RUBY_ENCODING_H
529
+ rb_encoding *default_internal_enc;
530
+ rb_encoding *conn_enc;
531
+ #endif
532
+ GET_CLIENT(self);
533
+ version = rb_hash_new();
534
+
535
+ #ifdef HAVE_RUBY_ENCODING_H
536
+ default_internal_enc = rb_default_internal_encoding();
537
+ conn_enc = rb_to_encoding(wrapper->encoding);
538
+ #endif
539
+
540
+ rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
541
+ client_info = rb_str_new2(mysql_get_client_info());
542
+ #ifdef HAVE_RUBY_ENCODING_H
543
+ rb_enc_associate(client_info, conn_enc);
544
+ if (default_internal_enc) {
545
+ client_info = rb_str_export_to_enc(client_info, default_internal_enc);
546
+ }
547
+ #endif
548
+ rb_hash_aset(version, sym_version, client_info);
549
+ return version;
550
+ }
551
+
552
+ static VALUE rb_mysql_client_server_info(VALUE self) {
553
+ VALUE version, server_info;
554
+ #ifdef HAVE_RUBY_ENCODING_H
555
+ rb_encoding *default_internal_enc;
556
+ rb_encoding *conn_enc;
557
+ #endif
558
+ GET_CLIENT(self);
559
+
560
+ REQUIRE_OPEN_DB(wrapper);
561
+ #ifdef HAVE_RUBY_ENCODING_H
562
+ default_internal_enc = rb_default_internal_encoding();
563
+ conn_enc = rb_to_encoding(wrapper->encoding);
564
+ #endif
565
+
566
+ version = rb_hash_new();
567
+ rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
568
+ server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
569
+ #ifdef HAVE_RUBY_ENCODING_H
570
+ rb_enc_associate(server_info, conn_enc);
571
+ if (default_internal_enc) {
572
+ server_info = rb_str_export_to_enc(server_info, default_internal_enc);
573
+ }
574
+ #endif
575
+ rb_hash_aset(version, sym_version, server_info);
576
+ return version;
577
+ }
578
+
579
+ static VALUE rb_mysql_client_socket(VALUE self) {
580
+ GET_CLIENT(self);
581
+ #ifndef _WIN32
582
+ REQUIRE_OPEN_DB(wrapper);
583
+ int fd_set_fd = wrapper->client->net.fd;
584
+ return INT2NUM(fd_set_fd);
585
+ #else
586
+ rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
587
+ #endif
588
+ }
589
+
590
+ static VALUE rb_mysql_client_last_id(VALUE self) {
591
+ GET_CLIENT(self);
592
+ REQUIRE_OPEN_DB(wrapper);
593
+ return ULL2NUM(mysql_insert_id(wrapper->client));
594
+ }
595
+
596
+ static VALUE rb_mysql_client_affected_rows(VALUE self) {
597
+ my_ulonglong retVal;
598
+ GET_CLIENT(self);
599
+
600
+ REQUIRE_OPEN_DB(wrapper);
601
+ retVal = mysql_affected_rows(wrapper->client);
602
+ if (retVal == (my_ulonglong)-1) {
603
+ rb_raise_mysql2_error(wrapper);
604
+ }
605
+ return ULL2NUM(retVal);
606
+ }
607
+
608
+ static VALUE rb_mysql_client_thread_id(VALUE self) {
609
+ unsigned long retVal;
610
+ GET_CLIENT(self);
611
+
612
+ REQUIRE_OPEN_DB(wrapper);
613
+ retVal = mysql_thread_id(wrapper->client);
614
+ return ULL2NUM(retVal);
615
+ }
616
+
617
+ static VALUE nogvl_ping(void *ptr) {
618
+ MYSQL *client = ptr;
619
+
620
+ return mysql_ping(client) == 0 ? Qtrue : Qfalse;
621
+ }
622
+
623
+ static VALUE rb_mysql_client_ping(VALUE self) {
624
+ GET_CLIENT(self);
625
+
626
+ if (wrapper->closed) {
627
+ return Qfalse;
628
+ } else {
629
+ return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
630
+ }
631
+ }
632
+
633
+ static VALUE rb_mysql_client_more_results(VALUE self)
634
+ {
635
+ GET_CLIENT(self);
636
+ if (mysql_more_results(wrapper->client) == 0)
637
+ return Qfalse;
638
+ else
639
+ return Qtrue;
640
+ }
641
+
642
+ static VALUE rb_mysql_client_next_result(VALUE self)
643
+ {
644
+ GET_CLIENT(self);
645
+ int ret;
646
+ ret = mysql_next_result(wrapper->client);
647
+ if (ret == 0)
648
+ return Qtrue;
649
+ else
650
+ return Qfalse;
651
+ }
652
+
653
+
654
+ static VALUE rb_mysql_client_store_result(VALUE self)
655
+ {
656
+ MYSQL_RES * result;
657
+ VALUE resultObj;
658
+ #ifdef HAVE_RUBY_ENCODING_H
659
+ mysql2_result_wrapper * result_wrapper;
660
+ #endif
661
+
662
+
663
+ GET_CLIENT(self);
664
+ // MYSQL_RES* res = mysql_store_result(wrapper->client);
665
+ // if (res == NULL)
666
+ // mysql_raise(wrapper->client);
667
+ // return mysqlres2obj(res);
668
+
669
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
670
+
671
+ if (result == NULL) {
672
+ if (mysql_errno(wrapper->client) != 0) {
673
+ rb_raise_mysql2_error(wrapper);
674
+ }
675
+ // no data and no error, so query was not a SELECT
676
+ return Qnil;
677
+ }
678
+
679
+ resultObj = rb_mysql_result_to_obj(result);
680
+ // pass-through query options for result construction later
681
+ rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
682
+
683
+ #ifdef HAVE_RUBY_ENCODING_H
684
+ GetMysql2Result(resultObj, result_wrapper);
685
+ result_wrapper->encoding = wrapper->encoding;
686
+ #endif
687
+ return resultObj;
688
+
689
+ }
690
+
691
+ #ifdef HAVE_RUBY_ENCODING_H
692
+ static VALUE rb_mysql_client_encoding(VALUE self) {
693
+ GET_CLIENT(self);
694
+ return wrapper->encoding;
695
+ }
696
+ #endif
697
+
698
+ static VALUE set_reconnect(VALUE self, VALUE value) {
699
+ my_bool reconnect;
700
+ GET_CLIENT(self);
701
+
702
+ if(!NIL_P(value)) {
703
+ reconnect = value == Qfalse ? 0 : 1;
704
+
705
+ wrapper->reconnect_enabled = reconnect;
706
+ /* set default reconnect behavior */
707
+ if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
708
+ /* TODO: warning - unable to set reconnect behavior */
709
+ rb_warn("%s\n", mysql_error(wrapper->client));
710
+ }
711
+ }
712
+ return value;
713
+ }
714
+
715
+ static VALUE set_connect_timeout(VALUE self, VALUE value) {
716
+ unsigned int connect_timeout = 0;
717
+ GET_CLIENT(self);
718
+
719
+ if(!NIL_P(value)) {
720
+ connect_timeout = NUM2INT(value);
721
+ if(0 == connect_timeout) return value;
722
+
723
+ /* set default connection timeout behavior */
724
+ if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
725
+ /* TODO: warning - unable to set connection timeout */
726
+ rb_warn("%s\n", mysql_error(wrapper->client));
727
+ }
728
+ }
729
+ return value;
730
+ }
731
+
732
+ static VALUE set_charset_name(VALUE self, VALUE value) {
733
+ char * charset_name;
734
+ #ifdef HAVE_RUBY_ENCODING_H
735
+ VALUE new_encoding;
736
+ #endif
737
+ GET_CLIENT(self);
738
+
739
+ #ifdef HAVE_RUBY_ENCODING_H
740
+ new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
741
+ if (new_encoding == Qnil) {
742
+ VALUE inspect = rb_inspect(value);
743
+ rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
744
+ } else {
745
+ if (wrapper->encoding == Qnil) {
746
+ wrapper->encoding = new_encoding;
747
+ }
748
+ }
749
+ #endif
750
+
751
+ charset_name = StringValuePtr(value);
752
+
753
+ if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
754
+ /* TODO: warning - unable to set charset */
755
+ rb_warn("%s\n", mysql_error(wrapper->client));
756
+ }
757
+
758
+ return value;
759
+ }
760
+
761
+ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
762
+ GET_CLIENT(self);
763
+
764
+ if(!NIL_P(ca) || !NIL_P(key)) {
765
+ mysql_ssl_set(wrapper->client,
766
+ NIL_P(key) ? NULL : StringValuePtr(key),
767
+ NIL_P(cert) ? NULL : StringValuePtr(cert),
768
+ NIL_P(ca) ? NULL : StringValuePtr(ca),
769
+ NIL_P(capath) ? NULL : StringValuePtr(capath),
770
+ NIL_P(cipher) ? NULL : StringValuePtr(cipher));
771
+ }
772
+
773
+ return self;
774
+ }
775
+
776
+ static VALUE init_connection(VALUE self) {
777
+ GET_CLIENT(self);
778
+
779
+ if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
780
+ /* TODO: warning - not enough memory? */
781
+ return rb_raise_mysql2_error(wrapper);
782
+ }
783
+
784
+ wrapper->closed = 0;
785
+ return self;
786
+ }
787
+
788
+ void init_mysql2_client() {
789
+ // verify the libmysql we're about to use was the version we were built against
790
+ // https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99
791
+ int i;
792
+ int dots = 0;
793
+ const char *lib = mysql_get_client_info();
794
+ for (i = 0; lib[i] != 0 && MYSQL_SERVER_VERSION[i] != 0; i++) {
795
+ if (lib[i] == '.') {
796
+ dots++;
797
+ // we only compare MAJOR and MINOR
798
+ if (dots == 2) break;
799
+ }
800
+ if (lib[i] != MYSQL_SERVER_VERSION[i]) {
801
+ 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);
802
+ return;
803
+ }
804
+ }
805
+
806
+ cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
807
+
808
+ rb_define_alloc_func(cMysql2Client, allocate);
809
+
810
+ rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
811
+
812
+ rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
813
+ rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
814
+ rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
815
+ rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
816
+ rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
817
+ rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
818
+ rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
819
+ rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
820
+ rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
821
+ rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
822
+ rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
823
+ rb_define_method(cMysql2Client, "more_results", rb_mysql_client_more_results, 0);
824
+ rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
825
+ rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
826
+ #ifdef HAVE_RUBY_ENCODING_H
827
+ rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
828
+ #endif
829
+
830
+ rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
831
+ rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
832
+ rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
833
+ rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
834
+ rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0);
835
+ rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
836
+
837
+ intern_encoding_from_charset = rb_intern("encoding_from_charset");
838
+
839
+ sym_id = ID2SYM(rb_intern("id"));
840
+ sym_version = ID2SYM(rb_intern("version"));
841
+ sym_async = ID2SYM(rb_intern("async"));
842
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
843
+ sym_as = ID2SYM(rb_intern("as"));
844
+ sym_array = ID2SYM(rb_intern("array"));
845
+
846
+ intern_merge = rb_intern("merge");
847
+ intern_error_number_eql = rb_intern("error_number=");
848
+ intern_sql_state_eql = rb_intern("sql_state=");
849
+
850
+ #ifdef CLIENT_LONG_PASSWORD
851
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
852
+ INT2NUM(CLIENT_LONG_PASSWORD));
853
+ #endif
854
+
855
+ #ifdef CLIENT_FOUND_ROWS
856
+ rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"),
857
+ INT2NUM(CLIENT_FOUND_ROWS));
858
+ #endif
859
+
860
+ #ifdef CLIENT_LONG_FLAG
861
+ rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"),
862
+ INT2NUM(CLIENT_LONG_FLAG));
863
+ #endif
864
+
865
+ #ifdef CLIENT_CONNECT_WITH_DB
866
+ rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"),
867
+ INT2NUM(CLIENT_CONNECT_WITH_DB));
868
+ #endif
869
+
870
+ #ifdef CLIENT_NO_SCHEMA
871
+ rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"),
872
+ INT2NUM(CLIENT_NO_SCHEMA));
873
+ #endif
874
+
875
+ #ifdef CLIENT_COMPRESS
876
+ rb_const_set(cMysql2Client, rb_intern("COMPRESS"), INT2NUM(CLIENT_COMPRESS));
877
+ #endif
878
+
879
+ #ifdef CLIENT_ODBC
880
+ rb_const_set(cMysql2Client, rb_intern("ODBC"), INT2NUM(CLIENT_ODBC));
881
+ #endif
882
+
883
+ #ifdef CLIENT_LOCAL_FILES
884
+ rb_const_set(cMysql2Client, rb_intern("LOCAL_FILES"),
885
+ INT2NUM(CLIENT_LOCAL_FILES));
886
+ #endif
887
+
888
+ #ifdef CLIENT_IGNORE_SPACE
889
+ rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"),
890
+ INT2NUM(CLIENT_IGNORE_SPACE));
891
+ #endif
892
+
893
+ #ifdef CLIENT_PROTOCOL_41
894
+ rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"),
895
+ INT2NUM(CLIENT_PROTOCOL_41));
896
+ #endif
897
+
898
+ #ifdef CLIENT_INTERACTIVE
899
+ rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"),
900
+ INT2NUM(CLIENT_INTERACTIVE));
901
+ #endif
902
+
903
+ #ifdef CLIENT_SSL
904
+ rb_const_set(cMysql2Client, rb_intern("SSL"), INT2NUM(CLIENT_SSL));
905
+ #endif
906
+
907
+ #ifdef CLIENT_IGNORE_SIGPIPE
908
+ rb_const_set(cMysql2Client, rb_intern("IGNORE_SIGPIPE"),
909
+ INT2NUM(CLIENT_IGNORE_SIGPIPE));
910
+ #endif
911
+
912
+ #ifdef CLIENT_TRANSACTIONS
913
+ rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"),
914
+ INT2NUM(CLIENT_TRANSACTIONS));
915
+ #endif
916
+
917
+ #ifdef CLIENT_RESERVED
918
+ rb_const_set(cMysql2Client, rb_intern("RESERVED"), INT2NUM(CLIENT_RESERVED));
919
+ #endif
920
+
921
+ #ifdef CLIENT_SECURE_CONNECTION
922
+ rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"),
923
+ INT2NUM(CLIENT_SECURE_CONNECTION));
924
+ #endif
925
+
926
+ #ifdef CLIENT_MULTI_STATEMENTS
927
+ rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
928
+ INT2NUM(CLIENT_MULTI_STATEMENTS));
929
+ #endif
930
+
931
+ #ifdef CLIENT_PS_MULTI_RESULTS
932
+ rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"),
933
+ INT2NUM(CLIENT_PS_MULTI_RESULTS));
934
+ #endif
935
+
936
+ #ifdef CLIENT_SSL_VERIFY_SERVER_CERT
937
+ rb_const_set(cMysql2Client, rb_intern("SSL_VERIFY_SERVER_CERT"),
938
+ INT2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
939
+ #endif
940
+
941
+ #ifdef CLIENT_REMEMBER_OPTIONS
942
+ rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"),
943
+ INT2NUM(CLIENT_REMEMBER_OPTIONS));
944
+ #endif
945
+
946
+ #ifdef CLIENT_ALL_FLAGS
947
+ rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"),
948
+ INT2NUM(CLIENT_ALL_FLAGS));
949
+ #endif
950
+
951
+ #ifdef CLIENT_BASIC_FLAGS
952
+ rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
953
+ INT2NUM(CLIENT_BASIC_FLAGS));
954
+ #endif
955
+ }