rdp-mysql2 0.2.7.1

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