solaris-mysql2 0.3.11

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 +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
+ }