solaris-mysql2 0.3.11

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 +244 -0
  6. data/Gemfile +3 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +334 -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 +901 -0
  22. data/ext/mysql2/client.h +42 -0
  23. data/ext/mysql2/extconf.rb +74 -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 +566 -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 +264 -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/solaris-mysql2.gemspec +29 -0
  36. data/spec/em/em_spec.rb +50 -0
  37. data/spec/mysql2/client_spec.rb +465 -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,901 @@
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
+ const char *sql_ptr;
50
+ long sql_len;
51
+ mysql_client_wrapper *wrapper;
52
+ };
53
+
54
+ /*
55
+ * non-blocking mysql_*() functions that we won't be wrapping since
56
+ * they do not appear to hit the network nor issue any interruptible
57
+ * or blocking system calls.
58
+ *
59
+ * - mysql_affected_rows()
60
+ * - mysql_error()
61
+ * - mysql_fetch_fields()
62
+ * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths
63
+ * - mysql_field_count()
64
+ * - mysql_get_client_info()
65
+ * - mysql_get_client_version()
66
+ * - mysql_get_server_info()
67
+ * - mysql_get_server_version()
68
+ * - mysql_insert_id()
69
+ * - mysql_num_fields()
70
+ * - mysql_num_rows()
71
+ * - mysql_options()
72
+ * - mysql_real_escape_string()
73
+ * - mysql_ssl_set()
74
+ */
75
+
76
+ static void rb_mysql_client_mark(void * wrapper) {
77
+ mysql_client_wrapper * w = wrapper;
78
+ if (w) {
79
+ rb_gc_mark(w->encoding);
80
+ }
81
+ }
82
+
83
+ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
84
+ VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
85
+ VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
86
+ #ifdef HAVE_RUBY_ENCODING_H
87
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
88
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
89
+
90
+ rb_enc_associate(rb_error_msg, conn_enc);
91
+ rb_enc_associate(rb_sql_state, conn_enc);
92
+ if (default_internal_enc) {
93
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
94
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
95
+ }
96
+ #endif
97
+
98
+ VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg);
99
+ rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
100
+ rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
101
+ rb_exc_raise(e);
102
+ return Qnil;
103
+ }
104
+
105
+ static VALUE nogvl_init(void *ptr) {
106
+ MYSQL *client;
107
+
108
+ /* may initialize embedded server and read /etc/services off disk */
109
+ client = mysql_init((MYSQL *)ptr);
110
+ return client ? Qtrue : Qfalse;
111
+ }
112
+
113
+ static VALUE nogvl_connect(void *ptr) {
114
+ struct nogvl_connect_args *args = ptr;
115
+ MYSQL *client;
116
+
117
+ client = mysql_real_connect(args->mysql, args->host,
118
+ args->user, args->passwd,
119
+ args->db, args->port, args->unix_socket,
120
+ args->client_flag);
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
+ xfree(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
+ xfree(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*)xmalloc(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 = xmalloc(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
+ xfree(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
+ xfree(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
+ VALUE rv;
204
+ GET_CLIENT(self);
205
+
206
+ args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
207
+ args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
208
+ args.port = NIL_P(port) ? 3306 : NUM2INT(port);
209
+ args.user = NIL_P(user) ? NULL : StringValuePtr(user);
210
+ args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
211
+ args.db = NIL_P(database) ? NULL : StringValuePtr(database);
212
+ args.mysql = wrapper->client;
213
+ args.client_flag = NUM2ULONG(flags);
214
+
215
+ rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0);
216
+ if (rv == Qfalse) {
217
+ while (rv == Qfalse && errno == EINTR) {
218
+ errno = 0;
219
+ rv = rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0);
220
+ }
221
+ if (rv == Qfalse)
222
+ return rb_raise_mysql2_error(wrapper);
223
+ }
224
+
225
+ return self;
226
+ }
227
+
228
+ /*
229
+ * Immediately disconnect from the server, normally the garbage collector
230
+ * will disconnect automatically when a connection is no longer needed.
231
+ * Explicitly closing this will free up server resources sooner than waiting
232
+ * for the garbage collector.
233
+ */
234
+ static VALUE rb_mysql_client_close(VALUE self) {
235
+ GET_CLIENT(self);
236
+
237
+ if (!wrapper->closed) {
238
+ rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
239
+ }
240
+
241
+ return Qnil;
242
+ }
243
+
244
+ /*
245
+ * mysql_send_query is unlikely to block since most queries are small
246
+ * enough to fit in a socket buffer, but sometimes large UPDATE and
247
+ * INSERTs will cause the process to block
248
+ */
249
+ static VALUE nogvl_send_query(void *ptr) {
250
+ struct nogvl_send_query_args *args = ptr;
251
+ int rv;
252
+
253
+ rv = mysql_send_query(args->mysql, args->sql_ptr, args->sql_len);
254
+
255
+ return rv == 0 ? Qtrue : Qfalse;
256
+ }
257
+
258
+ static VALUE do_send_query(void *args) {
259
+ struct nogvl_send_query_args *query_args = args;
260
+ mysql_client_wrapper *wrapper = query_args->wrapper;
261
+ if (rb_thread_blocking_region(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
262
+ // an error occurred, we're not active anymore
263
+ MARK_CONN_INACTIVE(self);
264
+ return rb_raise_mysql2_error(wrapper);
265
+ }
266
+ return Qnil;
267
+ }
268
+
269
+ /*
270
+ * even though we did rb_thread_select before calling this, a large
271
+ * response can overflow the socket buffers and cause us to eventually
272
+ * block while calling mysql_read_query_result
273
+ */
274
+ static VALUE nogvl_read_query_result(void *ptr) {
275
+ MYSQL * client = ptr;
276
+ my_bool res = mysql_read_query_result(client);
277
+
278
+ return res == 0 ? Qtrue : Qfalse;
279
+ }
280
+
281
+ /* mysql_store_result may (unlikely) read rows off the socket */
282
+ static VALUE nogvl_store_result(void *ptr) {
283
+ mysql_client_wrapper *wrapper;
284
+ MYSQL_RES *result;
285
+
286
+ wrapper = (mysql_client_wrapper *)ptr;
287
+ result = mysql_store_result(wrapper->client);
288
+
289
+ // once our result is stored off, this connection is
290
+ // ready for another command to be issued
291
+ wrapper->active = 0;
292
+
293
+ return (VALUE)result;
294
+ }
295
+
296
+ static VALUE rb_mysql_client_async_result(VALUE self) {
297
+ MYSQL_RES * result;
298
+ VALUE resultObj;
299
+ #ifdef HAVE_RUBY_ENCODING_H
300
+ mysql2_result_wrapper * result_wrapper;
301
+ #endif
302
+ GET_CLIENT(self);
303
+
304
+ // if we're not waiting on a result, do nothing
305
+ if (!wrapper->active)
306
+ return Qnil;
307
+
308
+ REQUIRE_OPEN_DB(wrapper);
309
+ if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
310
+ // an error occurred, mark this connection inactive
311
+ MARK_CONN_INACTIVE(self);
312
+ return rb_raise_mysql2_error(wrapper);
313
+ }
314
+
315
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
316
+
317
+ if (result == NULL) {
318
+ if (mysql_errno(wrapper->client) != 0) {
319
+ MARK_CONN_INACTIVE(self);
320
+ rb_raise_mysql2_error(wrapper);
321
+ }
322
+ // no data and no error, so query was not a SELECT
323
+ return Qnil;
324
+ }
325
+
326
+ resultObj = rb_mysql_result_to_obj(result);
327
+ // pass-through query options for result construction later
328
+ rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
329
+
330
+ #ifdef HAVE_RUBY_ENCODING_H
331
+ GetMysql2Result(resultObj, result_wrapper);
332
+ result_wrapper->encoding = wrapper->encoding;
333
+ #endif
334
+ return resultObj;
335
+ }
336
+
337
+ #ifndef _WIN32
338
+ struct async_query_args {
339
+ int fd;
340
+ VALUE self;
341
+ };
342
+
343
+ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
344
+ GET_CLIENT(self);
345
+
346
+ wrapper->closed = 1;
347
+ wrapper->active = 0;
348
+
349
+ // manually close the socket for read/write
350
+ // this feels dirty, but is there another way?
351
+ shutdown(wrapper->client->net.fd, 2);
352
+
353
+ rb_exc_raise(error);
354
+
355
+ return Qnil;
356
+ }
357
+
358
+ static VALUE do_query(void *args) {
359
+ struct async_query_args *async_args;
360
+ struct timeval tv;
361
+ struct timeval* tvp;
362
+ long int sec;
363
+ int retval;
364
+ VALUE read_timeout;
365
+
366
+ async_args = (struct async_query_args *)args;
367
+ read_timeout = rb_iv_get(async_args->self, "@read_timeout");
368
+
369
+ tvp = NULL;
370
+ if (!NIL_P(read_timeout)) {
371
+ Check_Type(read_timeout, T_FIXNUM);
372
+ tvp = &tv;
373
+ sec = FIX2INT(read_timeout);
374
+ // TODO: support partial seconds?
375
+ // also, this check is here for sanity, we also check up in Ruby
376
+ if (sec >= 0) {
377
+ tvp->tv_sec = sec;
378
+ } else {
379
+ rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
380
+ }
381
+ tvp->tv_usec = 0;
382
+ }
383
+
384
+ for(;;) {
385
+ retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp);
386
+
387
+ if (retval == 0) {
388
+ rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
389
+ }
390
+
391
+ if (retval < 0) {
392
+ rb_sys_fail(0);
393
+ }
394
+
395
+ if (retval > 0) {
396
+ break;
397
+ }
398
+ }
399
+
400
+ return Qnil;
401
+ }
402
+ #else
403
+ static VALUE finish_and_mark_inactive(void *args) {
404
+ VALUE self;
405
+ MYSQL_RES *result;
406
+
407
+ self = (VALUE)args;
408
+
409
+ GET_CLIENT(self);
410
+
411
+ if (wrapper->active) {
412
+ // if we got here, the result hasn't been read off the wire yet
413
+ // so lets do that and then throw it away because we have no way
414
+ // of getting it back up to the caller from here
415
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
416
+ mysql_free_result(result);
417
+
418
+ wrapper->active = 0;
419
+ }
420
+
421
+ return Qnil;
422
+ }
423
+ #endif
424
+
425
+ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
426
+ #ifndef _WIN32
427
+ struct async_query_args async_args;
428
+ #endif
429
+ struct nogvl_send_query_args args;
430
+ int async = 0;
431
+ VALUE opts, defaults;
432
+ #ifdef HAVE_RUBY_ENCODING_H
433
+ rb_encoding *conn_enc;
434
+ #endif
435
+ GET_CLIENT(self);
436
+
437
+ REQUIRE_OPEN_DB(wrapper);
438
+ args.mysql = wrapper->client;
439
+
440
+
441
+ defaults = rb_iv_get(self, "@query_options");
442
+ if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
443
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
444
+ rb_iv_set(self, "@query_options", opts);
445
+
446
+ if (rb_hash_aref(opts, sym_async) == Qtrue) {
447
+ async = 1;
448
+ }
449
+ } else {
450
+ opts = defaults;
451
+ }
452
+
453
+ Check_Type(args.sql, T_STRING);
454
+ #ifdef HAVE_RUBY_ENCODING_H
455
+ conn_enc = rb_to_encoding(wrapper->encoding);
456
+ // ensure the string is in the encoding the connection is expecting
457
+ args.sql = rb_str_export_to_enc(args.sql, conn_enc);
458
+ #endif
459
+ args.sql_ptr = StringValuePtr(args.sql);
460
+ args.sql_len = RSTRING_LEN(args.sql);
461
+
462
+ // see if this connection is still waiting on a result from a previous query
463
+ if (wrapper->active == 0) {
464
+ // mark this connection active
465
+ wrapper->active = 1;
466
+ } else {
467
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
468
+ }
469
+
470
+ args.wrapper = wrapper;
471
+
472
+ #ifndef _WIN32
473
+ rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
474
+
475
+ if (!async) {
476
+ async_args.fd = wrapper->client->net.fd;
477
+ async_args.self = self;
478
+
479
+ rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
480
+
481
+ return rb_mysql_client_async_result(self);
482
+ } else {
483
+ return Qnil;
484
+ }
485
+ #else
486
+ do_send_query(&args);
487
+
488
+ // this will just block until the result is ready
489
+ return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
490
+ #endif
491
+ }
492
+
493
+ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
494
+ unsigned char *newStr;
495
+ VALUE rb_str;
496
+ unsigned long newLen, oldLen;
497
+ #ifdef HAVE_RUBY_ENCODING_H
498
+ rb_encoding *default_internal_enc;
499
+ rb_encoding *conn_enc;
500
+ #endif
501
+ GET_CLIENT(self);
502
+
503
+ REQUIRE_OPEN_DB(wrapper);
504
+ Check_Type(str, T_STRING);
505
+ #ifdef HAVE_RUBY_ENCODING_H
506
+ default_internal_enc = rb_default_internal_encoding();
507
+ conn_enc = rb_to_encoding(wrapper->encoding);
508
+ // ensure the string is in the encoding the connection is expecting
509
+ str = rb_str_export_to_enc(str, conn_enc);
510
+ #endif
511
+
512
+ oldLen = RSTRING_LEN(str);
513
+ newStr = xmalloc(oldLen*2+1);
514
+
515
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
516
+ if (newLen == oldLen) {
517
+ // no need to return a new ruby string if nothing changed
518
+ xfree(newStr);
519
+ return str;
520
+ } else {
521
+ rb_str = rb_str_new((const char*)newStr, newLen);
522
+ #ifdef HAVE_RUBY_ENCODING_H
523
+ rb_enc_associate(rb_str, conn_enc);
524
+ if (default_internal_enc) {
525
+ rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
526
+ }
527
+ #endif
528
+ xfree(newStr);
529
+ return rb_str;
530
+ }
531
+ }
532
+
533
+ static VALUE rb_mysql_client_info(VALUE self) {
534
+ VALUE version, client_info;
535
+ #ifdef HAVE_RUBY_ENCODING_H
536
+ rb_encoding *default_internal_enc;
537
+ rb_encoding *conn_enc;
538
+ #endif
539
+ GET_CLIENT(self);
540
+ version = rb_hash_new();
541
+
542
+ #ifdef HAVE_RUBY_ENCODING_H
543
+ default_internal_enc = rb_default_internal_encoding();
544
+ conn_enc = rb_to_encoding(wrapper->encoding);
545
+ #endif
546
+
547
+ rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
548
+ client_info = rb_str_new2(mysql_get_client_info());
549
+ #ifdef HAVE_RUBY_ENCODING_H
550
+ rb_enc_associate(client_info, conn_enc);
551
+ if (default_internal_enc) {
552
+ client_info = rb_str_export_to_enc(client_info, default_internal_enc);
553
+ }
554
+ #endif
555
+ rb_hash_aset(version, sym_version, client_info);
556
+ return version;
557
+ }
558
+
559
+ static VALUE rb_mysql_client_server_info(VALUE self) {
560
+ VALUE version, server_info;
561
+ #ifdef HAVE_RUBY_ENCODING_H
562
+ rb_encoding *default_internal_enc;
563
+ rb_encoding *conn_enc;
564
+ #endif
565
+ GET_CLIENT(self);
566
+
567
+ REQUIRE_OPEN_DB(wrapper);
568
+ #ifdef HAVE_RUBY_ENCODING_H
569
+ default_internal_enc = rb_default_internal_encoding();
570
+ conn_enc = rb_to_encoding(wrapper->encoding);
571
+ #endif
572
+
573
+ version = rb_hash_new();
574
+ rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
575
+ server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
576
+ #ifdef HAVE_RUBY_ENCODING_H
577
+ rb_enc_associate(server_info, conn_enc);
578
+ if (default_internal_enc) {
579
+ server_info = rb_str_export_to_enc(server_info, default_internal_enc);
580
+ }
581
+ #endif
582
+ rb_hash_aset(version, sym_version, server_info);
583
+ return version;
584
+ }
585
+
586
+ static VALUE rb_mysql_client_socket(VALUE self) {
587
+ GET_CLIENT(self);
588
+ #ifndef _WIN32
589
+ REQUIRE_OPEN_DB(wrapper);
590
+ int fd_set_fd = wrapper->client->net.fd;
591
+ return INT2NUM(fd_set_fd);
592
+ #else
593
+ rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
594
+ #endif
595
+ }
596
+
597
+ static VALUE rb_mysql_client_last_id(VALUE self) {
598
+ GET_CLIENT(self);
599
+ REQUIRE_OPEN_DB(wrapper);
600
+ return ULL2NUM(mysql_insert_id(wrapper->client));
601
+ }
602
+
603
+ static VALUE rb_mysql_client_affected_rows(VALUE self) {
604
+ my_ulonglong retVal;
605
+ GET_CLIENT(self);
606
+
607
+ REQUIRE_OPEN_DB(wrapper);
608
+ retVal = mysql_affected_rows(wrapper->client);
609
+ if (retVal == (my_ulonglong)-1) {
610
+ rb_raise_mysql2_error(wrapper);
611
+ }
612
+ return ULL2NUM(retVal);
613
+ }
614
+
615
+ static VALUE rb_mysql_client_thread_id(VALUE self) {
616
+ unsigned long retVal;
617
+ GET_CLIENT(self);
618
+
619
+ REQUIRE_OPEN_DB(wrapper);
620
+ retVal = mysql_thread_id(wrapper->client);
621
+ return ULL2NUM(retVal);
622
+ }
623
+
624
+ static VALUE nogvl_ping(void *ptr) {
625
+ MYSQL *client = ptr;
626
+
627
+ return mysql_ping(client) == 0 ? Qtrue : Qfalse;
628
+ }
629
+
630
+ static VALUE rb_mysql_client_ping(VALUE self) {
631
+ GET_CLIENT(self);
632
+
633
+ if (wrapper->closed) {
634
+ return Qfalse;
635
+ } else {
636
+ return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
637
+ }
638
+ }
639
+
640
+ #ifdef HAVE_RUBY_ENCODING_H
641
+ static VALUE rb_mysql_client_encoding(VALUE self) {
642
+ GET_CLIENT(self);
643
+ return wrapper->encoding;
644
+ }
645
+ #endif
646
+
647
+ static VALUE set_reconnect(VALUE self, VALUE value) {
648
+ my_bool reconnect;
649
+ GET_CLIENT(self);
650
+
651
+ if(!NIL_P(value)) {
652
+ reconnect = value == Qfalse ? 0 : 1;
653
+
654
+ wrapper->reconnect_enabled = reconnect;
655
+ /* set default reconnect behavior */
656
+ if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
657
+ /* TODO: warning - unable to set reconnect behavior */
658
+ rb_warn("%s\n", mysql_error(wrapper->client));
659
+ }
660
+ }
661
+ return value;
662
+ }
663
+
664
+ static VALUE set_connect_timeout(VALUE self, VALUE value) {
665
+ unsigned int connect_timeout = 0;
666
+ GET_CLIENT(self);
667
+
668
+ if(!NIL_P(value)) {
669
+ connect_timeout = NUM2INT(value);
670
+ if(0 == connect_timeout) return value;
671
+
672
+ /* set default connection timeout behavior */
673
+ if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
674
+ /* TODO: warning - unable to set connection timeout */
675
+ rb_warn("%s\n", mysql_error(wrapper->client));
676
+ }
677
+ }
678
+ return value;
679
+ }
680
+
681
+ static VALUE set_charset_name(VALUE self, VALUE value) {
682
+ char * charset_name;
683
+ #ifdef HAVE_RUBY_ENCODING_H
684
+ VALUE new_encoding;
685
+ #endif
686
+ GET_CLIENT(self);
687
+
688
+ #ifdef HAVE_RUBY_ENCODING_H
689
+ new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
690
+ if (new_encoding == Qnil) {
691
+ VALUE inspect = rb_inspect(value);
692
+ rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
693
+ } else {
694
+ if (wrapper->encoding == Qnil) {
695
+ wrapper->encoding = new_encoding;
696
+ }
697
+ }
698
+ #endif
699
+
700
+ charset_name = StringValuePtr(value);
701
+
702
+ if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
703
+ /* TODO: warning - unable to set charset */
704
+ rb_warn("%s\n", mysql_error(wrapper->client));
705
+ }
706
+
707
+ return value;
708
+ }
709
+
710
+ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
711
+ GET_CLIENT(self);
712
+
713
+ if(!NIL_P(ca) || !NIL_P(key)) {
714
+ mysql_ssl_set(wrapper->client,
715
+ NIL_P(key) ? NULL : StringValuePtr(key),
716
+ NIL_P(cert) ? NULL : StringValuePtr(cert),
717
+ NIL_P(ca) ? NULL : StringValuePtr(ca),
718
+ NIL_P(capath) ? NULL : StringValuePtr(capath),
719
+ NIL_P(cipher) ? NULL : StringValuePtr(cipher));
720
+ }
721
+
722
+ return self;
723
+ }
724
+
725
+ static VALUE init_connection(VALUE self) {
726
+ GET_CLIENT(self);
727
+
728
+ if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
729
+ /* TODO: warning - not enough memory? */
730
+ return rb_raise_mysql2_error(wrapper);
731
+ }
732
+
733
+ wrapper->closed = 0;
734
+ return self;
735
+ }
736
+
737
+ void init_mysql2_client() {
738
+ // verify the libmysql we're about to use was the version we were built against
739
+ // https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99
740
+ int i;
741
+ int dots = 0;
742
+ const char *lib = mysql_get_client_info();
743
+ for (i = 0; lib[i] != 0 && MYSQL_SERVER_VERSION[i] != 0; i++) {
744
+ if (lib[i] == '.') {
745
+ dots++;
746
+ // we only compare MAJOR and MINOR
747
+ if (dots == 2) break;
748
+ }
749
+ if (lib[i] != MYSQL_SERVER_VERSION[i]) {
750
+ 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);
751
+ return;
752
+ }
753
+ }
754
+
755
+ cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
756
+
757
+ rb_define_alloc_func(cMysql2Client, allocate);
758
+
759
+ rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
760
+
761
+ rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
762
+ rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
763
+ rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
764
+ rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
765
+ rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
766
+ rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
767
+ rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
768
+ rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
769
+ rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
770
+ rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
771
+ rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
772
+ #ifdef HAVE_RUBY_ENCODING_H
773
+ rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
774
+ #endif
775
+
776
+ rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
777
+ rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
778
+ rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
779
+ rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
780
+ rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0);
781
+ rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
782
+
783
+ intern_encoding_from_charset = rb_intern("encoding_from_charset");
784
+
785
+ sym_id = ID2SYM(rb_intern("id"));
786
+ sym_version = ID2SYM(rb_intern("version"));
787
+ sym_async = ID2SYM(rb_intern("async"));
788
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
789
+ sym_as = ID2SYM(rb_intern("as"));
790
+ sym_array = ID2SYM(rb_intern("array"));
791
+
792
+ intern_merge = rb_intern("merge");
793
+ intern_error_number_eql = rb_intern("error_number=");
794
+ intern_sql_state_eql = rb_intern("sql_state=");
795
+
796
+ #ifdef CLIENT_LONG_PASSWORD
797
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
798
+ INT2NUM(CLIENT_LONG_PASSWORD));
799
+ #endif
800
+
801
+ #ifdef CLIENT_FOUND_ROWS
802
+ rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"),
803
+ INT2NUM(CLIENT_FOUND_ROWS));
804
+ #endif
805
+
806
+ #ifdef CLIENT_LONG_FLAG
807
+ rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"),
808
+ INT2NUM(CLIENT_LONG_FLAG));
809
+ #endif
810
+
811
+ #ifdef CLIENT_CONNECT_WITH_DB
812
+ rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"),
813
+ INT2NUM(CLIENT_CONNECT_WITH_DB));
814
+ #endif
815
+
816
+ #ifdef CLIENT_NO_SCHEMA
817
+ rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"),
818
+ INT2NUM(CLIENT_NO_SCHEMA));
819
+ #endif
820
+
821
+ #ifdef CLIENT_COMPRESS
822
+ rb_const_set(cMysql2Client, rb_intern("COMPRESS"), INT2NUM(CLIENT_COMPRESS));
823
+ #endif
824
+
825
+ #ifdef CLIENT_ODBC
826
+ rb_const_set(cMysql2Client, rb_intern("ODBC"), INT2NUM(CLIENT_ODBC));
827
+ #endif
828
+
829
+ #ifdef CLIENT_LOCAL_FILES
830
+ rb_const_set(cMysql2Client, rb_intern("LOCAL_FILES"),
831
+ INT2NUM(CLIENT_LOCAL_FILES));
832
+ #endif
833
+
834
+ #ifdef CLIENT_IGNORE_SPACE
835
+ rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"),
836
+ INT2NUM(CLIENT_IGNORE_SPACE));
837
+ #endif
838
+
839
+ #ifdef CLIENT_PROTOCOL_41
840
+ rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"),
841
+ INT2NUM(CLIENT_PROTOCOL_41));
842
+ #endif
843
+
844
+ #ifdef CLIENT_INTERACTIVE
845
+ rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"),
846
+ INT2NUM(CLIENT_INTERACTIVE));
847
+ #endif
848
+
849
+ #ifdef CLIENT_SSL
850
+ rb_const_set(cMysql2Client, rb_intern("SSL"), INT2NUM(CLIENT_SSL));
851
+ #endif
852
+
853
+ #ifdef CLIENT_IGNORE_SIGPIPE
854
+ rb_const_set(cMysql2Client, rb_intern("IGNORE_SIGPIPE"),
855
+ INT2NUM(CLIENT_IGNORE_SIGPIPE));
856
+ #endif
857
+
858
+ #ifdef CLIENT_TRANSACTIONS
859
+ rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"),
860
+ INT2NUM(CLIENT_TRANSACTIONS));
861
+ #endif
862
+
863
+ #ifdef CLIENT_RESERVED
864
+ rb_const_set(cMysql2Client, rb_intern("RESERVED"), INT2NUM(CLIENT_RESERVED));
865
+ #endif
866
+
867
+ #ifdef CLIENT_SECURE_CONNECTION
868
+ rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"),
869
+ INT2NUM(CLIENT_SECURE_CONNECTION));
870
+ #endif
871
+
872
+ #ifdef CLIENT_MULTI_STATEMENTS
873
+ rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
874
+ INT2NUM(CLIENT_MULTI_STATEMENTS));
875
+ #endif
876
+
877
+ #ifdef CLIENT_PS_MULTI_RESULTS
878
+ rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"),
879
+ INT2NUM(CLIENT_PS_MULTI_RESULTS));
880
+ #endif
881
+
882
+ #ifdef CLIENT_SSL_VERIFY_SERVER_CERT
883
+ rb_const_set(cMysql2Client, rb_intern("SSL_VERIFY_SERVER_CERT"),
884
+ INT2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
885
+ #endif
886
+
887
+ #ifdef CLIENT_REMEMBER_OPTIONS
888
+ rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"),
889
+ INT2NUM(CLIENT_REMEMBER_OPTIONS));
890
+ #endif
891
+
892
+ #ifdef CLIENT_ALL_FLAGS
893
+ rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"),
894
+ INT2NUM(CLIENT_ALL_FLAGS));
895
+ #endif
896
+
897
+ #ifdef CLIENT_BASIC_FLAGS
898
+ rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
899
+ INT2NUM(CLIENT_BASIC_FLAGS));
900
+ #endif
901
+ }