mysql2-sp 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
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
+ }