rdp-mysql2 0.2.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/CHANGELOG.md +142 -0
  5. data/Gemfile +3 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.rdoc +261 -0
  8. data/Rakefile +5 -0
  9. data/benchmark/active_record.rb +51 -0
  10. data/benchmark/active_record_threaded.rb +42 -0
  11. data/benchmark/allocations.rb +33 -0
  12. data/benchmark/escape.rb +36 -0
  13. data/benchmark/query_with_mysql_casting.rb +80 -0
  14. data/benchmark/query_without_mysql_casting.rb +47 -0
  15. data/benchmark/sequel.rb +37 -0
  16. data/benchmark/setup_db.rb +119 -0
  17. data/benchmark/threaded.rb +44 -0
  18. data/examples/eventmachine.rb +21 -0
  19. data/examples/threaded.rb +20 -0
  20. data/ext/mysql2/client.c +839 -0
  21. data/ext/mysql2/client.h +41 -0
  22. data/ext/mysql2/extconf.rb +72 -0
  23. data/ext/mysql2/mysql2_ext.c +12 -0
  24. data/ext/mysql2/mysql2_ext.h +42 -0
  25. data/ext/mysql2/result.c +488 -0
  26. data/ext/mysql2/result.h +20 -0
  27. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +64 -0
  28. data/lib/active_record/connection_adapters/mysql2_adapter.rb +654 -0
  29. data/lib/active_record/fiber_patches.rb +104 -0
  30. data/lib/arel/engines/sql/compilers/mysql2_compiler.rb +11 -0
  31. data/lib/mysql2.rb +16 -0
  32. data/lib/mysql2/client.rb +240 -0
  33. data/lib/mysql2/em.rb +37 -0
  34. data/lib/mysql2/em_fiber.rb +31 -0
  35. data/lib/mysql2/error.rb +15 -0
  36. data/lib/mysql2/result.rb +5 -0
  37. data/lib/mysql2/version.rb +3 -0
  38. data/mysql2.gemspec +32 -0
  39. data/spec/em/em_fiber_spec.rb +22 -0
  40. data/spec/em/em_spec.rb +49 -0
  41. data/spec/mysql2/client_spec.rb +430 -0
  42. data/spec/mysql2/error_spec.rb +69 -0
  43. data/spec/mysql2/result_spec.rb +333 -0
  44. data/spec/rcov.opts +3 -0
  45. data/spec/spec_helper.rb +66 -0
  46. data/tasks/benchmarks.rake +20 -0
  47. data/tasks/compile.rake +71 -0
  48. data/tasks/rspec.rake +16 -0
  49. data/tasks/vendor_mysql.rake +40 -0
  50. metadata +236 -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,839 @@
1
+ #include <mysql2_ext.h>
2
+ #include <client.h>
3
+ #include <errno.h>
4
+
5
+ VALUE cMysql2Client;
6
+ extern VALUE mMysql2, cMysql2Error;
7
+ static VALUE intern_encoding_from_charset;
8
+ static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array;
9
+ static ID intern_merge, intern_error_number_eql, intern_sql_state_eql;
10
+
11
+ #define REQUIRE_OPEN_DB(wrapper) \
12
+ if(wrapper->closed) { \
13
+ rb_raise(cMysql2Error, "closed MySQL connection"); \
14
+ }
15
+
16
+ #define MARK_CONN_INACTIVE(conn) \
17
+ wrapper->active = 0
18
+
19
+ #define GET_CLIENT(self) \
20
+ mysql_client_wrapper *wrapper; \
21
+ Data_Get_Struct(self, mysql_client_wrapper, wrapper)
22
+
23
+ /*
24
+ * used to pass all arguments to mysql_real_connect while inside
25
+ * rb_thread_blocking_region
26
+ */
27
+ struct nogvl_connect_args {
28
+ MYSQL *mysql;
29
+ const char *host;
30
+ const char *user;
31
+ const char *passwd;
32
+ const char *db;
33
+ unsigned int port;
34
+ const char *unix_socket;
35
+ unsigned long client_flag;
36
+ };
37
+
38
+ /*
39
+ * used to pass all arguments to mysql_send_query while inside
40
+ * rb_thread_blocking_region
41
+ */
42
+ struct nogvl_send_query_args {
43
+ MYSQL *mysql;
44
+ VALUE sql;
45
+ };
46
+
47
+ /*
48
+ * non-blocking mysql_*() functions that we won't be wrapping since
49
+ * they do not appear to hit the network nor issue any interruptible
50
+ * or blocking system calls.
51
+ *
52
+ * - mysql_affected_rows()
53
+ * - mysql_error()
54
+ * - mysql_fetch_fields()
55
+ * - mysql_fetch_lengths() - calls cli_fetch_lengths or emb_fetch_lengths
56
+ * - mysql_field_count()
57
+ * - mysql_get_client_info()
58
+ * - mysql_get_client_version()
59
+ * - mysql_get_server_info()
60
+ * - mysql_get_server_version()
61
+ * - mysql_insert_id()
62
+ * - mysql_num_fields()
63
+ * - mysql_num_rows()
64
+ * - mysql_options()
65
+ * - mysql_real_escape_string()
66
+ * - mysql_ssl_set()
67
+ */
68
+
69
+ static void rb_mysql_client_mark(void * wrapper) {
70
+ mysql_client_wrapper * w = wrapper;
71
+ if (w) {
72
+ rb_gc_mark(w->encoding);
73
+ }
74
+ }
75
+
76
+ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
77
+ VALUE rb_error_msg = rb_str_new2(mysql_error(wrapper->client));
78
+ VALUE rb_sql_state = rb_tainted_str_new2(mysql_sqlstate(wrapper->client));
79
+ #ifdef HAVE_RUBY_ENCODING_H
80
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
81
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
82
+
83
+ rb_enc_associate(rb_error_msg, conn_enc);
84
+ rb_enc_associate(rb_sql_state, conn_enc);
85
+ if (default_internal_enc) {
86
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
87
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
88
+ }
89
+ #endif
90
+
91
+ VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg);
92
+ rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
93
+ rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
94
+ rb_exc_raise(e);
95
+ return Qnil;
96
+ }
97
+
98
+ static VALUE nogvl_init(void *ptr) {
99
+ MYSQL *client;
100
+
101
+ /* may initialize embedded server and read /etc/services off disk */
102
+ client = mysql_init((MYSQL *)ptr);
103
+ return client ? Qtrue : Qfalse;
104
+ }
105
+
106
+ static VALUE nogvl_connect(void *ptr) {
107
+ struct nogvl_connect_args *args = ptr;
108
+ MYSQL *client;
109
+
110
+ do {
111
+ client = mysql_real_connect(args->mysql, args->host,
112
+ args->user, args->passwd,
113
+ args->db, args->port, args->unix_socket,
114
+ args->client_flag);
115
+ } while (! client && errno == EINTR && (errno = 0) == 0);
116
+
117
+ return client ? Qtrue : Qfalse;
118
+ }
119
+
120
+ static VALUE nogvl_close(void *ptr) {
121
+ mysql_client_wrapper *wrapper;
122
+ #ifndef _WIN32
123
+ int flags;
124
+ #endif
125
+ wrapper = ptr;
126
+ if (!wrapper->closed) {
127
+ wrapper->closed = 1;
128
+
129
+ /*
130
+ * we'll send a QUIT message to the server, but that message is more of a
131
+ * formality than a hard requirement since the socket is getting shutdown
132
+ * anyways, so ensure the socket write does not block our interpreter
133
+ *
134
+ *
135
+ * if the socket is dead we have no chance of blocking,
136
+ * so ignore any potential fcntl errors since they don't matter
137
+ */
138
+ #ifndef _WIN32
139
+ flags = fcntl(wrapper->client->net.fd, F_GETFL);
140
+ if (flags > 0 && !(flags & O_NONBLOCK))
141
+ fcntl(wrapper->client->net.fd, F_SETFL, flags | O_NONBLOCK);
142
+ #else
143
+ u_long iMode;
144
+ iMode = 1;
145
+ ioctlsocket(wrapper->client->net.fd, FIONBIO, &iMode);
146
+ #endif
147
+
148
+ mysql_close(wrapper->client);
149
+ xfree(wrapper->client);
150
+ }
151
+
152
+ return Qnil;
153
+ }
154
+
155
+ static void rb_mysql_client_free(void * ptr) {
156
+ mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
157
+
158
+ nogvl_close(wrapper);
159
+
160
+ xfree(ptr);
161
+ }
162
+
163
+ static VALUE allocate(VALUE klass) {
164
+ VALUE obj;
165
+ mysql_client_wrapper * wrapper;
166
+ obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
167
+ wrapper->encoding = Qnil;
168
+ wrapper->active = 0;
169
+ wrapper->closed = 1;
170
+ wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
171
+ return obj;
172
+ }
173
+
174
+ static VALUE rb_mysql_client_escape(VALUE klass, VALUE str) {
175
+ unsigned char *newStr;
176
+ VALUE rb_str;
177
+ unsigned long newLen, oldLen;
178
+
179
+ Check_Type(str, T_STRING);
180
+
181
+ oldLen = RSTRING_LEN(str);
182
+ newStr = xmalloc(oldLen*2+1);
183
+
184
+ newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen);
185
+ if (newLen == oldLen) {
186
+ // no need to return a new ruby string if nothing changed
187
+ xfree(newStr);
188
+ return str;
189
+ } else {
190
+ rb_str = rb_str_new((const char*)newStr, newLen);
191
+ #ifdef HAVE_RUBY_ENCODING_H
192
+ rb_enc_copy(rb_str, str);
193
+ #endif
194
+ xfree(newStr);
195
+ return rb_str;
196
+ }
197
+ }
198
+
199
+ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
200
+ struct nogvl_connect_args args;
201
+ GET_CLIENT(self);
202
+
203
+ args.host = NIL_P(host) ? "localhost" : StringValuePtr(host);
204
+ args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
205
+ args.port = NIL_P(port) ? 3306 : NUM2INT(port);
206
+ args.user = NIL_P(user) ? NULL : StringValuePtr(user);
207
+ args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
208
+ args.db = NIL_P(database) ? NULL : StringValuePtr(database);
209
+ args.mysql = wrapper->client;
210
+ args.client_flag = NUM2ULONG(flags);
211
+
212
+ if (rb_thread_blocking_region(nogvl_connect, &args, RUBY_UBF_IO, 0) == Qfalse) {
213
+ // unable to connect
214
+ return rb_raise_mysql2_error(wrapper);
215
+ }
216
+
217
+ return self;
218
+ }
219
+
220
+ /*
221
+ * Immediately disconnect from the server, normally the garbage collector
222
+ * will disconnect automatically when a connection is no longer needed.
223
+ * Explicitly closing this will free up server resources sooner than waiting
224
+ * for the garbage collector.
225
+ */
226
+ static VALUE rb_mysql_client_close(VALUE self) {
227
+ GET_CLIENT(self);
228
+
229
+ if (!wrapper->closed) {
230
+ rb_thread_blocking_region(nogvl_close, wrapper, RUBY_UBF_IO, 0);
231
+ }
232
+
233
+ return Qnil;
234
+ }
235
+
236
+ /*
237
+ * mysql_send_query is unlikely to block since most queries are small
238
+ * enough to fit in a socket buffer, but sometimes large UPDATE and
239
+ * INSERTs will cause the process to block
240
+ */
241
+ static VALUE nogvl_send_query(void *ptr) {
242
+ struct nogvl_send_query_args *args = ptr;
243
+ int rv;
244
+ const char *sql = StringValuePtr(args->sql);
245
+ long sql_len = RSTRING_LEN(args->sql);
246
+
247
+ rv = mysql_send_query(args->mysql, sql, sql_len);
248
+
249
+ return rv == 0 ? Qtrue : Qfalse;
250
+ }
251
+
252
+ /*
253
+ * even though we did rb_thread_select before calling this, a large
254
+ * response can overflow the socket buffers and cause us to eventually
255
+ * block while calling mysql_read_query_result
256
+ */
257
+ static VALUE nogvl_read_query_result(void *ptr) {
258
+ MYSQL * client = ptr;
259
+ my_bool res = mysql_read_query_result(client);
260
+
261
+ return res == 0 ? Qtrue : Qfalse;
262
+ }
263
+
264
+ /* mysql_store_result may (unlikely) read rows off the socket */
265
+ static VALUE nogvl_store_result(void *ptr) {
266
+ MYSQL * client = ptr;
267
+ return (VALUE)mysql_store_result(client);
268
+ }
269
+
270
+ static VALUE rb_mysql_client_async_result(VALUE self) {
271
+ MYSQL_RES * result;
272
+ VALUE resultObj;
273
+ #ifdef HAVE_RUBY_ENCODING_H
274
+ mysql2_result_wrapper * result_wrapper;
275
+ #endif
276
+ GET_CLIENT(self);
277
+
278
+ REQUIRE_OPEN_DB(wrapper);
279
+ if (rb_thread_blocking_region(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
280
+ // an error occurred, mark this connection inactive
281
+ MARK_CONN_INACTIVE(self);
282
+ return rb_raise_mysql2_error(wrapper);
283
+ }
284
+
285
+ result = (MYSQL_RES *)rb_thread_blocking_region(nogvl_store_result, wrapper->client, RUBY_UBF_IO, 0);
286
+
287
+ // we have our result, mark this connection inactive
288
+ MARK_CONN_INACTIVE(self);
289
+
290
+ if (result == NULL) {
291
+ if (mysql_field_count(wrapper->client) != 0) {
292
+ rb_raise_mysql2_error(wrapper);
293
+ }
294
+ return Qnil;
295
+ }
296
+
297
+ resultObj = rb_mysql_result_to_obj(result);
298
+ // pass-through query options for result construction later
299
+ rb_iv_set(resultObj, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), rb_intern("dup"), 0));
300
+
301
+ #ifdef HAVE_RUBY_ENCODING_H
302
+ GetMysql2Result(resultObj, result_wrapper);
303
+ result_wrapper->encoding = wrapper->encoding;
304
+ #endif
305
+ return resultObj;
306
+ }
307
+
308
+ static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
309
+ struct nogvl_send_query_args args;
310
+ fd_set fdset;
311
+ int fd, retval;
312
+ int async = 0;
313
+ VALUE opts, defaults, read_timeout;
314
+ #ifdef HAVE_RUBY_ENCODING_H
315
+ rb_encoding *conn_enc;
316
+ #endif
317
+ struct timeval tv;
318
+ struct timeval* tvp;
319
+ long int sec;
320
+ VALUE result;
321
+ GET_CLIENT(self);
322
+
323
+ REQUIRE_OPEN_DB(wrapper);
324
+ args.mysql = wrapper->client;
325
+
326
+ // see if this connection is still waiting on a result from a previous query
327
+ if (wrapper->active == 0) {
328
+ // mark this connection active
329
+ wrapper->active = 1;
330
+ } else {
331
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
332
+ }
333
+
334
+ defaults = rb_iv_get(self, "@query_options");
335
+ if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
336
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
337
+ rb_iv_set(self, "@query_options", opts);
338
+
339
+ if (rb_hash_aref(opts, sym_async) == Qtrue) {
340
+ async = 1;
341
+ }
342
+ } else {
343
+ opts = defaults;
344
+ }
345
+
346
+ Check_Type(args.sql, T_STRING);
347
+ #ifdef HAVE_RUBY_ENCODING_H
348
+ conn_enc = rb_to_encoding(wrapper->encoding);
349
+ // ensure the string is in the encoding the connection is expecting
350
+ args.sql = rb_str_export_to_enc(args.sql, conn_enc);
351
+ #endif
352
+
353
+ if (rb_thread_blocking_region(nogvl_send_query, &args, RUBY_UBF_IO, 0) == Qfalse) {
354
+ // an error occurred, we're not active anymore
355
+ MARK_CONN_INACTIVE(self);
356
+ return rb_raise_mysql2_error(wrapper);
357
+ }
358
+
359
+ read_timeout = rb_iv_get(self, "@read_timeout");
360
+
361
+ tvp = NULL;
362
+ if (!NIL_P(read_timeout)) {
363
+ Check_Type(read_timeout, T_FIXNUM);
364
+ tvp = &tv;
365
+ sec = FIX2INT(read_timeout);
366
+ // TODO: support partial seconds?
367
+ // also, this check is here for sanity, we also check up in Ruby
368
+ if (sec >= 0) {
369
+ tvp->tv_sec = sec;
370
+ } else {
371
+ rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
372
+ }
373
+ tvp->tv_usec = 0;
374
+ }
375
+
376
+ if (!async) {
377
+ // the below code is largely from do_mysql
378
+ // http://github.com/datamapper/do
379
+ fd = wrapper->client->net.fd;
380
+ for(;;) {
381
+ int fd_set_fd = fd;
382
+
383
+ #if defined(_WIN32) && !defined(HAVE_RB_THREAD_BLOCKING_REGION) // don't need this in 1.9
384
+ WSAPROTOCOL_INFO wsa_pi;
385
+ // dupicate the SOCKET from libmysql
386
+ int r = WSADuplicateSocket(fd, GetCurrentProcessId(), &wsa_pi);
387
+ SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
388
+ // create the CRT fd so ruby can get back to the SOCKET
389
+ fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY);
390
+ #endif
391
+
392
+ FD_ZERO(&fdset);
393
+ FD_SET(fd_set_fd, &fdset);
394
+
395
+ retval = rb_thread_select(fd_set_fd + 1, &fdset, NULL, NULL, tvp);
396
+
397
+ #if defined(_WIN32) && !defined(HAVE_RB_THREAD_BLOCKING_REGION)
398
+ // cleanup the CRT fd
399
+ _close(fd_set_fd);
400
+ // cleanup the duplicated SOCKET
401
+ closesocket(s);
402
+ #endif
403
+
404
+ if (retval == 0) {
405
+ rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
406
+ }
407
+
408
+ if (retval < 0) {
409
+ rb_sys_fail(0);
410
+ }
411
+
412
+ if (retval > 0) {
413
+ break;
414
+ }
415
+ }
416
+
417
+ result = rb_mysql_client_async_result(self);
418
+
419
+ return result;
420
+ } else {
421
+ return Qnil;
422
+ }
423
+ }
424
+
425
+ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
426
+ unsigned char *newStr;
427
+ VALUE rb_str;
428
+ unsigned long newLen, oldLen;
429
+ #ifdef HAVE_RUBY_ENCODING_H
430
+ rb_encoding *default_internal_enc;
431
+ rb_encoding *conn_enc;
432
+ #endif
433
+ GET_CLIENT(self);
434
+
435
+ REQUIRE_OPEN_DB(wrapper);
436
+ Check_Type(str, T_STRING);
437
+ #ifdef HAVE_RUBY_ENCODING_H
438
+ default_internal_enc = rb_default_internal_encoding();
439
+ conn_enc = rb_to_encoding(wrapper->encoding);
440
+ // ensure the string is in the encoding the connection is expecting
441
+ str = rb_str_export_to_enc(str, conn_enc);
442
+ #endif
443
+
444
+ oldLen = RSTRING_LEN(str);
445
+ newStr = xmalloc(oldLen*2+1);
446
+
447
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
448
+ if (newLen == oldLen) {
449
+ // no need to return a new ruby string if nothing changed
450
+ xfree(newStr);
451
+ return str;
452
+ } else {
453
+ rb_str = rb_str_new((const char*)newStr, newLen);
454
+ #ifdef HAVE_RUBY_ENCODING_H
455
+ rb_enc_associate(rb_str, conn_enc);
456
+ if (default_internal_enc) {
457
+ rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
458
+ }
459
+ #endif
460
+ xfree(newStr);
461
+ return rb_str;
462
+ }
463
+ }
464
+
465
+ static VALUE rb_mysql_client_info(VALUE self) {
466
+ VALUE version, client_info;
467
+ #ifdef HAVE_RUBY_ENCODING_H
468
+ rb_encoding *default_internal_enc;
469
+ rb_encoding *conn_enc;
470
+ #endif
471
+ GET_CLIENT(self);
472
+ version = rb_hash_new();
473
+
474
+ #ifdef HAVE_RUBY_ENCODING_H
475
+ default_internal_enc = rb_default_internal_encoding();
476
+ conn_enc = rb_to_encoding(wrapper->encoding);
477
+ #endif
478
+
479
+ rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
480
+ client_info = rb_str_new2(mysql_get_client_info());
481
+ #ifdef HAVE_RUBY_ENCODING_H
482
+ rb_enc_associate(client_info, conn_enc);
483
+ if (default_internal_enc) {
484
+ client_info = rb_str_export_to_enc(client_info, default_internal_enc);
485
+ }
486
+ #endif
487
+ rb_hash_aset(version, sym_version, client_info);
488
+ return version;
489
+ }
490
+
491
+ static VALUE rb_mysql_client_server_info(VALUE self) {
492
+ VALUE version, server_info;
493
+ #ifdef HAVE_RUBY_ENCODING_H
494
+ rb_encoding *default_internal_enc;
495
+ rb_encoding *conn_enc;
496
+ #endif
497
+ GET_CLIENT(self);
498
+
499
+ REQUIRE_OPEN_DB(wrapper);
500
+ #ifdef HAVE_RUBY_ENCODING_H
501
+ default_internal_enc = rb_default_internal_encoding();
502
+ conn_enc = rb_to_encoding(wrapper->encoding);
503
+ #endif
504
+
505
+ version = rb_hash_new();
506
+ rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
507
+ server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
508
+ #ifdef HAVE_RUBY_ENCODING_H
509
+ rb_enc_associate(server_info, conn_enc);
510
+ if (default_internal_enc) {
511
+ server_info = rb_str_export_to_enc(server_info, default_internal_enc);
512
+ }
513
+ #endif
514
+ rb_hash_aset(version, sym_version, server_info);
515
+ return version;
516
+ }
517
+
518
+ static VALUE rb_mysql_client_socket(VALUE self) {
519
+ GET_CLIENT(self);
520
+ REQUIRE_OPEN_DB(wrapper);
521
+ int fd_set_fd = wrapper->client->net.fd;
522
+ #ifdef _WIN32
523
+ WSAPROTOCOL_INFO wsa_pi;
524
+ // dupicate the SOCKET from libmysql
525
+ int r = WSADuplicateSocket(wrapper->client->net.fd, GetCurrentProcessId(), &wsa_pi);
526
+ SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
527
+ // create the CRT fd so ruby can get back to the SOCKET
528
+ fd_set_fd = _open_osfhandle(s, O_RDWR|O_BINARY);
529
+ return INT2NUM(fd_set_fd);
530
+ #else
531
+ return INT2NUM(fd_set_fd);
532
+ #endif
533
+ }
534
+
535
+ static VALUE rb_mysql_client_last_id(VALUE self) {
536
+ GET_CLIENT(self);
537
+ REQUIRE_OPEN_DB(wrapper);
538
+ return ULL2NUM(mysql_insert_id(wrapper->client));
539
+ }
540
+
541
+ static VALUE rb_mysql_client_affected_rows(VALUE self) {
542
+ my_ulonglong retVal;
543
+ GET_CLIENT(self);
544
+
545
+ REQUIRE_OPEN_DB(wrapper);
546
+ retVal = mysql_affected_rows(wrapper->client);
547
+ if (retVal == (my_ulonglong)-1) {
548
+ rb_raise_mysql2_error(wrapper);
549
+ }
550
+ return ULL2NUM(retVal);
551
+ }
552
+
553
+ static VALUE rb_mysql_client_thread_id(VALUE self) {
554
+ unsigned long retVal;
555
+ GET_CLIENT(self);
556
+
557
+ REQUIRE_OPEN_DB(wrapper);
558
+ retVal = mysql_thread_id(wrapper->client);
559
+ return ULL2NUM(retVal);
560
+ }
561
+
562
+ static VALUE nogvl_ping(void *ptr)
563
+ {
564
+ MYSQL *client = ptr;
565
+
566
+ return mysql_ping(client) == 0 ? Qtrue : Qfalse;
567
+ }
568
+
569
+ static VALUE rb_mysql_client_ping(VALUE self) {
570
+ GET_CLIENT(self);
571
+
572
+ if (wrapper->closed) {
573
+ return Qfalse;
574
+ } else {
575
+ return rb_thread_blocking_region(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
576
+ }
577
+ }
578
+
579
+ #ifdef HAVE_RUBY_ENCODING_H
580
+ static VALUE rb_mysql_client_encoding(VALUE self) {
581
+ GET_CLIENT(self);
582
+ return wrapper->encoding;
583
+ }
584
+ #endif
585
+
586
+ static VALUE set_reconnect(VALUE self, VALUE value) {
587
+ my_bool reconnect;
588
+ GET_CLIENT(self);
589
+
590
+ if(!NIL_P(value)) {
591
+ reconnect = value == Qfalse ? 0 : 1;
592
+
593
+ /* set default reconnect behavior */
594
+ if (mysql_options(wrapper->client, MYSQL_OPT_RECONNECT, &reconnect)) {
595
+ /* TODO: warning - unable to set reconnect behavior */
596
+ rb_warn("%s\n", mysql_error(wrapper->client));
597
+ }
598
+ }
599
+ return value;
600
+ }
601
+
602
+ static VALUE set_connect_timeout(VALUE self, VALUE value) {
603
+ unsigned int connect_timeout = 0;
604
+ GET_CLIENT(self);
605
+
606
+ if(!NIL_P(value)) {
607
+ connect_timeout = NUM2INT(value);
608
+ if(0 == connect_timeout) return value;
609
+
610
+ /* set default connection timeout behavior */
611
+ if (mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout)) {
612
+ /* TODO: warning - unable to set connection timeout */
613
+ rb_warn("%s\n", mysql_error(wrapper->client));
614
+ }
615
+ }
616
+ return value;
617
+ }
618
+
619
+ static VALUE set_charset_name(VALUE self, VALUE value) {
620
+ char * charset_name;
621
+ #ifdef HAVE_RUBY_ENCODING_H
622
+ VALUE new_encoding;
623
+ #endif
624
+ GET_CLIENT(self);
625
+
626
+ #ifdef HAVE_RUBY_ENCODING_H
627
+ new_encoding = rb_funcall(cMysql2Client, intern_encoding_from_charset, 1, value);
628
+ if (new_encoding == Qnil) {
629
+ VALUE inspect = rb_inspect(value);
630
+ rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
631
+ } else {
632
+ if (wrapper->encoding == Qnil) {
633
+ wrapper->encoding = new_encoding;
634
+ }
635
+ }
636
+ #endif
637
+
638
+ charset_name = StringValuePtr(value);
639
+
640
+ if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
641
+ /* TODO: warning - unable to set charset */
642
+ rb_warn("%s\n", mysql_error(wrapper->client));
643
+ }
644
+
645
+ return value;
646
+ }
647
+
648
+ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
649
+ GET_CLIENT(self);
650
+
651
+ if(!NIL_P(ca) || !NIL_P(key)) {
652
+ mysql_ssl_set(wrapper->client,
653
+ NIL_P(key) ? NULL : StringValuePtr(key),
654
+ NIL_P(cert) ? NULL : StringValuePtr(cert),
655
+ NIL_P(ca) ? NULL : StringValuePtr(ca),
656
+ NIL_P(capath) ? NULL : StringValuePtr(capath),
657
+ NIL_P(cipher) ? NULL : StringValuePtr(cipher));
658
+ }
659
+
660
+ return self;
661
+ }
662
+
663
+ static VALUE init_connection(VALUE self) {
664
+ GET_CLIENT(self);
665
+
666
+ if (rb_thread_blocking_region(nogvl_init, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
667
+ /* TODO: warning - not enough memory? */
668
+ return rb_raise_mysql2_error(wrapper);
669
+ }
670
+
671
+ wrapper->closed = 0;
672
+ return self;
673
+ }
674
+
675
+ void init_mysql2_client() {
676
+ // verify the libmysql we're about to use was the version we were built against
677
+ // https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99
678
+ int i;
679
+ int dots = 0;
680
+ const char *lib = mysql_get_client_info();
681
+ for (i = 0; lib[i] != 0 && MYSQL_SERVER_VERSION[i] != 0; i++) {
682
+ if (lib[i] == '.') {
683
+ dots++;
684
+ // we only compare MAJOR and MINOR
685
+ if (dots == 2) break;
686
+ }
687
+ if (lib[i] != MYSQL_SERVER_VERSION[i]) {
688
+ rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_SERVER_VERSION, lib);
689
+ return;
690
+ }
691
+ }
692
+
693
+ cMysql2Client = rb_define_class_under(mMysql2, "Client", rb_cObject);
694
+
695
+ rb_define_alloc_func(cMysql2Client, allocate);
696
+
697
+ rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
698
+
699
+ rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
700
+ rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
701
+ rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
702
+ rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
703
+ rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
704
+ rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
705
+ rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
706
+ rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
707
+ rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
708
+ rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
709
+ rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
710
+ #ifdef HAVE_RUBY_ENCODING_H
711
+ rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
712
+ #endif
713
+
714
+ rb_define_private_method(cMysql2Client, "reconnect=", set_reconnect, 1);
715
+ rb_define_private_method(cMysql2Client, "connect_timeout=", set_connect_timeout, 1);
716
+ rb_define_private_method(cMysql2Client, "charset_name=", set_charset_name, 1);
717
+ rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
718
+ rb_define_private_method(cMysql2Client, "init_connection", init_connection, 0);
719
+ rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
720
+
721
+ intern_encoding_from_charset = rb_intern("encoding_from_charset");
722
+
723
+ sym_id = ID2SYM(rb_intern("id"));
724
+ sym_version = ID2SYM(rb_intern("version"));
725
+ sym_async = ID2SYM(rb_intern("async"));
726
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
727
+ sym_as = ID2SYM(rb_intern("as"));
728
+ sym_array = ID2SYM(rb_intern("array"));
729
+
730
+ intern_merge = rb_intern("merge");
731
+ intern_error_number_eql = rb_intern("error_number=");
732
+ intern_sql_state_eql = rb_intern("sql_state=");
733
+
734
+ #ifdef CLIENT_LONG_PASSWORD
735
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
736
+ INT2NUM(CLIENT_LONG_PASSWORD));
737
+ #endif
738
+
739
+ #ifdef CLIENT_FOUND_ROWS
740
+ rb_const_set(cMysql2Client, rb_intern("FOUND_ROWS"),
741
+ INT2NUM(CLIENT_FOUND_ROWS));
742
+ #endif
743
+
744
+ #ifdef CLIENT_LONG_FLAG
745
+ rb_const_set(cMysql2Client, rb_intern("LONG_FLAG"),
746
+ INT2NUM(CLIENT_LONG_FLAG));
747
+ #endif
748
+
749
+ #ifdef CLIENT_CONNECT_WITH_DB
750
+ rb_const_set(cMysql2Client, rb_intern("CONNECT_WITH_DB"),
751
+ INT2NUM(CLIENT_CONNECT_WITH_DB));
752
+ #endif
753
+
754
+ #ifdef CLIENT_NO_SCHEMA
755
+ rb_const_set(cMysql2Client, rb_intern("NO_SCHEMA"),
756
+ INT2NUM(CLIENT_NO_SCHEMA));
757
+ #endif
758
+
759
+ #ifdef CLIENT_COMPRESS
760
+ rb_const_set(cMysql2Client, rb_intern("COMPRESS"), INT2NUM(CLIENT_COMPRESS));
761
+ #endif
762
+
763
+ #ifdef CLIENT_ODBC
764
+ rb_const_set(cMysql2Client, rb_intern("ODBC"), INT2NUM(CLIENT_ODBC));
765
+ #endif
766
+
767
+ #ifdef CLIENT_LOCAL_FILES
768
+ rb_const_set(cMysql2Client, rb_intern("LOCAL_FILES"),
769
+ INT2NUM(CLIENT_LOCAL_FILES));
770
+ #endif
771
+
772
+ #ifdef CLIENT_IGNORE_SPACE
773
+ rb_const_set(cMysql2Client, rb_intern("IGNORE_SPACE"),
774
+ INT2NUM(CLIENT_IGNORE_SPACE));
775
+ #endif
776
+
777
+ #ifdef CLIENT_PROTOCOL_41
778
+ rb_const_set(cMysql2Client, rb_intern("PROTOCOL_41"),
779
+ INT2NUM(CLIENT_PROTOCOL_41));
780
+ #endif
781
+
782
+ #ifdef CLIENT_INTERACTIVE
783
+ rb_const_set(cMysql2Client, rb_intern("INTERACTIVE"),
784
+ INT2NUM(CLIENT_INTERACTIVE));
785
+ #endif
786
+
787
+ #ifdef CLIENT_SSL
788
+ rb_const_set(cMysql2Client, rb_intern("SSL"), INT2NUM(CLIENT_SSL));
789
+ #endif
790
+
791
+ #ifdef CLIENT_IGNORE_SIGPIPE
792
+ rb_const_set(cMysql2Client, rb_intern("IGNORE_SIGPIPE"),
793
+ INT2NUM(CLIENT_IGNORE_SIGPIPE));
794
+ #endif
795
+
796
+ #ifdef CLIENT_TRANSACTIONS
797
+ rb_const_set(cMysql2Client, rb_intern("TRANSACTIONS"),
798
+ INT2NUM(CLIENT_TRANSACTIONS));
799
+ #endif
800
+
801
+ #ifdef CLIENT_RESERVED
802
+ rb_const_set(cMysql2Client, rb_intern("RESERVED"), INT2NUM(CLIENT_RESERVED));
803
+ #endif
804
+
805
+ #ifdef CLIENT_SECURE_CONNECTION
806
+ rb_const_set(cMysql2Client, rb_intern("SECURE_CONNECTION"),
807
+ INT2NUM(CLIENT_SECURE_CONNECTION));
808
+ #endif
809
+
810
+ #ifdef CLIENT_MULTI_STATEMENTS
811
+ rb_const_set(cMysql2Client, rb_intern("MULTI_STATEMENTS"),
812
+ INT2NUM(CLIENT_MULTI_STATEMENTS));
813
+ #endif
814
+
815
+ #ifdef CLIENT_PS_MULTI_RESULTS
816
+ rb_const_set(cMysql2Client, rb_intern("PS_MULTI_RESULTS"),
817
+ INT2NUM(CLIENT_PS_MULTI_RESULTS));
818
+ #endif
819
+
820
+ #ifdef CLIENT_SSL_VERIFY_SERVER_CERT
821
+ rb_const_set(cMysql2Client, rb_intern("SSL_VERIFY_SERVER_CERT"),
822
+ INT2NUM(CLIENT_SSL_VERIFY_SERVER_CERT));
823
+ #endif
824
+
825
+ #ifdef CLIENT_REMEMBER_OPTIONS
826
+ rb_const_set(cMysql2Client, rb_intern("REMEMBER_OPTIONS"),
827
+ INT2NUM(CLIENT_REMEMBER_OPTIONS));
828
+ #endif
829
+
830
+ #ifdef CLIENT_ALL_FLAGS
831
+ rb_const_set(cMysql2Client, rb_intern("ALL_FLAGS"),
832
+ INT2NUM(CLIENT_ALL_FLAGS));
833
+ #endif
834
+
835
+ #ifdef CLIENT_BASIC_FLAGS
836
+ rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
837
+ INT2NUM(CLIENT_BASIC_FLAGS));
838
+ #endif
839
+ }