mysql2 0.3.18 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 791bc0352c6464ca1765498b0babcedfad3c284e
4
- data.tar.gz: 08b44b761494ec18a0c9903fcb67bfe61718b77b
3
+ metadata.gz: a78b4022bc64300ea98eb6609cd7257900bc3245
4
+ data.tar.gz: e9be4bdd59bb65e1a05631e53c70359476bf2a3d
5
5
  SHA512:
6
- metadata.gz: 5554897f395a4fcd71e26612ef895da5d3e1d528ef29604380974950f7216783bf7636197d04228c39b471a51863f75c56e9140e05bb79410e1582c64842d0dc
7
- data.tar.gz: afad060a7a899d69b75ca2c44aa69a07c743984fc6897cce825c6d9ec3269bbb3733671243afe7fb33a615e61e4aa8c7b9e7863fa18289578ab29abc8409e2e8
6
+ metadata.gz: e6cd0601f7dbb44e17969a1d818049d4e3d330acce4a491f6079db61a0a8f368b855889984d8e1ab089c8beca5667e2339db7ba50f7e50d4afe92e4783dee044
7
+ data.tar.gz: 2d012a8b59ea34983f18871ba16176ede502b7bc9938481466112aec34a076cfeebcc7da1d0627cd854279ffacbbf38af8d9c5e56f02175371aa713553c2d45f
data/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ Changes are maintained under [Releases](https://github.com/brianmario/mysql2/releases)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Brian Lopez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -9,12 +9,14 @@ This one is not.
9
9
 
10
10
  It also forces the use of UTF-8 [or binary] for the connection [and all strings in 1.9, unless Encoding.default_internal is set then it'll convert from UTF-8 to that encoding] and uses encoding-aware MySQL API calls where it can.
11
11
 
12
- The API consists of two classes:
12
+ The API consists of three classes:
13
13
 
14
14
  `Mysql2::Client` - your connection to the database.
15
15
 
16
16
  `Mysql2::Result` - returned from issuing a #query on the connection. It includes Enumerable.
17
17
 
18
+ `Mysql2::Statement` - returned from issuing a #prepare on the connection. Execute the statement to get a Result.
19
+
18
20
  ## Installing
19
21
  ### General Instructions
20
22
  ``` sh
@@ -153,6 +155,20 @@ results.each(:as => :array) do |row|
153
155
  end
154
156
  ```
155
157
 
158
+ Prepared statements are supported, as well. In a prepared statement, use a `?`
159
+ in place of each value and then execute the statement to retrieve a result set.
160
+ Pass your arguments to the execute method in the same number and order as the
161
+ question marks in the statement.
162
+
163
+ ``` ruby
164
+ statement = @client.prepare("SELECT * FROM users WHERE login_count = ?")
165
+ result1 = statement.execute(1)
166
+ result2 = statement.execute(2)
167
+
168
+ statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?")
169
+ result = statement.execute(1, "CA")
170
+ ```
171
+
156
172
  ## Connection options
157
173
 
158
174
  You may set the following connection options in Mysql2::Client.new(...):
@@ -186,7 +202,8 @@ Setting any of the following options will enable an SSL connection, but only if
186
202
  your MySQL client library and server have been compiled with SSL support.
187
203
  MySQL client library defaults will be used for any parameters that are left out
188
204
  or set to nil. Relative paths are allowed, and may be required by managed
189
- hosting providers such as Heroku.
205
+ hosting providers such as Heroku. Set `:sslverify => true` to require that the
206
+ server presents a valid certificate.
190
207
 
191
208
  ``` ruby
192
209
  Mysql2::Client.new(
@@ -195,7 +212,8 @@ Mysql2::Client.new(
195
212
  :sslcert => '/path/to/client-cert.pem',
196
213
  :sslca => '/path/to/ca-cert.pem',
197
214
  :sslcapath => '/path/to/cacerts',
198
- :sslcipher => 'DHE-RSA-AES256-SHA'
215
+ :sslcipher => 'DHE-RSA-AES256-SHA',
216
+ :sslverify => true,
199
217
  )
200
218
  ```
201
219
 
@@ -437,13 +455,13 @@ As for field values themselves, I'm workin on it - but expect that soon.
437
455
 
438
456
  This gem is tested with the following Ruby versions on Linux and Mac OS X:
439
457
 
440
- * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.x, 2.2.x (ongoing patch releases)
458
+ * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x
441
459
  * Ruby Enterprise Edition (based on MRI 1.8.7)
442
460
  * Rubinius 2.x
443
461
 
444
462
  This gem is tested with the following MySQL and MariaDB versions:
445
463
 
446
- * MySQL 5.0, 5.1, 5.5, 5.6, 5.7
464
+ * MySQL 5.5, 5.7
447
465
  * MySQL Connector/C 6.0 and 6.1 (primarily on Windows)
448
466
  * MariaDB 5.5, 10.0
449
467
 
@@ -536,4 +554,8 @@ though.
536
554
  * Yury Korolev (http://github.com/yury) - for TONS of help testing the Active Record adapter
537
555
  * Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness
538
556
  * Mike Perham (http://github.com/mperham) - Async Active Record adapter (uses Fibers and EventMachine)
539
- * Aaron Stone (http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support.
557
+ * Aaron Stone (http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support
558
+ * Kouhei Ueno (https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012
559
+ * John Cant (http://github.com/johncant) - polishing and updating Prepared Statements support
560
+ * Justin Case (http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged
561
+ * Tamir Duberstein (http://github.com/tamird) - for help with timeouts and all around updates and cleanups
data/examples/threaded.rb CHANGED
@@ -4,17 +4,15 @@ $LOAD_PATH.unshift 'lib'
4
4
  require 'mysql2'
5
5
  require 'timeout'
6
6
 
7
- threads = []
8
7
  # Should never exceed worst case 3.5 secs across all 20 threads
9
8
  Timeout.timeout(3.5) do
10
- 20.times do
11
- threads << Thread.new do
9
+ 20.times.map do
10
+ Thread.new do
12
11
  overhead = rand(3)
13
12
  puts ">> thread #{Thread.current.object_id} query, #{overhead} sec overhead"
14
13
  # 3 second overhead per query
15
14
  Mysql2::Client.new(:host => "localhost", :username => "root").query("SELECT sleep(#{overhead}) as result")
16
15
  puts "<< thread #{Thread.current.object_id} result, #{overhead} sec overhead"
17
16
  end
18
- end
19
- threads.each{|t| t.join }
20
- end
17
+ end.each(&:join)
18
+ end
data/ext/mysql2/client.c CHANGED
@@ -16,12 +16,13 @@
16
16
 
17
17
  VALUE cMysql2Client;
18
18
  extern VALUE mMysql2, cMysql2Error;
19
- static VALUE sym_id, sym_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
19
+ static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
20
20
  static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql;
21
+ static ID intern_brackets, intern_new;
21
22
 
22
23
  #ifndef HAVE_RB_HASH_DUP
23
- static VALUE rb_hash_dup(VALUE other) {
24
- return rb_funcall(rb_cHash, rb_intern("[]"), 1, other);
24
+ VALUE rb_hash_dup(VALUE other) {
25
+ return rb_funcall(rb_cHash, intern_brackets, 1, other);
25
26
  }
26
27
  #endif
27
28
 
@@ -30,25 +31,12 @@ static VALUE rb_hash_dup(VALUE other) {
30
31
  rb_raise(cMysql2Error, "MySQL client is not initialized"); \
31
32
  }
32
33
 
33
- #define REQUIRE_CONNECTED(wrapper) \
34
- REQUIRE_INITIALIZED(wrapper) \
35
- if (!wrapper->connected && !wrapper->reconnect_enabled) { \
36
- rb_raise(cMysql2Error, "closed MySQL connection"); \
37
- }
38
-
39
34
  #define REQUIRE_NOT_CONNECTED(wrapper) \
40
35
  REQUIRE_INITIALIZED(wrapper) \
41
36
  if (wrapper->connected) { \
42
37
  rb_raise(cMysql2Error, "MySQL connection is already open"); \
43
38
  }
44
39
 
45
- #define MARK_CONN_INACTIVE(conn) \
46
- wrapper->active_thread = Qnil;
47
-
48
- #define GET_CLIENT(self) \
49
- mysql_client_wrapper *wrapper; \
50
- Data_Get_Struct(self, mysql_client_wrapper, wrapper)
51
-
52
40
  /*
53
41
  * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
54
42
  * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
@@ -136,7 +124,7 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
136
124
  rb_enc_associate(rb_sql_state, rb_usascii_encoding());
137
125
  #endif
138
126
 
139
- e = rb_funcall(cMysql2Error, rb_intern("new"), 2, rb_error_msg, LONG2FIX(wrapper->server_version));
127
+ e = rb_funcall(cMysql2Error, intern_new, 2, rb_error_msg, LONG2FIX(wrapper->server_version));
140
128
  rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
141
129
  rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
142
130
  rb_exc_raise(e);
@@ -182,23 +170,31 @@ static void *nogvl_connect(void *ptr) {
182
170
  */
183
171
  static VALUE invalidate_fd(int clientfd)
184
172
  {
185
- #ifdef SOCK_CLOEXEC
173
+ #ifdef O_CLOEXEC
186
174
  /* Atomically set CLOEXEC on the new FD in case another thread forks */
187
175
  int sockfd = open("/dev/null", O_RDWR | O_CLOEXEC);
188
- if (sockfd < 0) {
189
- /* Maybe SOCK_CLOEXEC is defined but not available on this kernel */
190
- int sockfd = open("/dev/null", O_RDWR);
191
- fcntl(sockfd, F_SETFD, FD_CLOEXEC);
192
- }
193
176
  #else
194
- /* Well we don't have SOCK_CLOEXEC, so just set FD_CLOEXEC quickly */
195
- int sockfd = open("/dev/null", O_RDWR);
196
- fcntl(sockfd, F_SETFD, FD_CLOEXEC);
177
+ /* Well we don't have O_CLOEXEC, trigger the fallback code below */
178
+ int sockfd = -1;
197
179
  #endif
198
180
 
199
181
  if (sockfd < 0) {
200
- /*
201
- * Cannot raise here, because one or both of the following may be true:
182
+ /* Either O_CLOEXEC wasn't defined at compile time, or it was defined at
183
+ * compile time, but isn't available at run-time. So we'll just be quick
184
+ * about setting FD_CLOEXEC now.
185
+ */
186
+ int flags;
187
+ sockfd = open("/dev/null", O_RDWR);
188
+ flags = fcntl(sockfd, F_GETFD);
189
+ /* Do the flags dance in case there are more defined flags in the future */
190
+ if (flags != -1) {
191
+ flags |= FD_CLOEXEC;
192
+ fcntl(sockfd, F_SETFD, flags);
193
+ }
194
+ }
195
+
196
+ if (sockfd < 0) {
197
+ /* Cannot raise here, because one or both of the following may be true:
202
198
  * a) we have no GVL (in C Ruby)
203
199
  * b) are running as a GC finalizer
204
200
  */
@@ -247,6 +243,7 @@ static void rb_mysql_client_free(void *ptr) {
247
243
  void decr_mysql2_client(mysql_client_wrapper *wrapper)
248
244
  {
249
245
  wrapper->refcount--;
246
+
250
247
  if (wrapper->refcount == 0) {
251
248
  nogvl_close(wrapper);
252
249
  xfree(wrapper->client);
@@ -267,6 +264,7 @@ static VALUE allocate(VALUE klass) {
267
264
  wrapper->initialized = 0; /* means that that the wrapper is initialized */
268
265
  wrapper->refcount = 1;
269
266
  wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
267
+
270
268
  return obj;
271
269
  }
272
270
 
@@ -287,7 +285,7 @@ static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
287
285
  oldLen = RSTRING_LEN(str);
288
286
  newStr = xmalloc(oldLen*2+1);
289
287
 
290
- newLen = mysql_escape_string((char *)newStr, StringValuePtr(str), oldLen);
288
+ newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen);
291
289
  if (newLen == oldLen) {
292
290
  /* no need to return a new ruby string if nothing changed */
293
291
  xfree(newStr);
@@ -332,18 +330,17 @@ static VALUE rb_mysql_info(VALUE self) {
332
330
 
333
331
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
334
332
  struct nogvl_connect_args args;
335
- time_t start_time, end_time;
336
- unsigned int elapsed_time, connect_timeout;
333
+ time_t start_time, end_time, elapsed_time, connect_timeout;
337
334
  VALUE rv;
338
335
  GET_CLIENT(self);
339
336
 
340
- args.host = NIL_P(host) ? NULL : StringValuePtr(host);
341
- args.unix_socket = NIL_P(socket) ? NULL : StringValuePtr(socket);
342
- args.port = NIL_P(port) ? 0 : NUM2INT(port);
343
- args.user = NIL_P(user) ? NULL : StringValuePtr(user);
344
- args.passwd = NIL_P(pass) ? NULL : StringValuePtr(pass);
345
- args.db = NIL_P(database) ? NULL : StringValuePtr(database);
346
- args.mysql = wrapper->client;
337
+ args.host = NIL_P(host) ? NULL : StringValueCStr(host);
338
+ args.unix_socket = NIL_P(socket) ? NULL : StringValueCStr(socket);
339
+ args.port = NIL_P(port) ? 0 : NUM2INT(port);
340
+ args.user = NIL_P(user) ? NULL : StringValueCStr(user);
341
+ args.passwd = NIL_P(pass) ? NULL : StringValueCStr(pass);
342
+ args.db = NIL_P(database) ? NULL : StringValueCStr(database);
343
+ args.mysql = wrapper->client;
347
344
  args.client_flag = NUM2ULONG(flags);
348
345
 
349
346
  if (wrapper->connect_timeout)
@@ -360,7 +357,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
360
357
  /* avoid an early timeout due to time truncating milliseconds off the start time */
361
358
  if (elapsed_time > 0)
362
359
  elapsed_time--;
363
- if (elapsed_time >= wrapper->connect_timeout)
360
+ if (elapsed_time >= (time_t)wrapper->connect_timeout)
364
361
  break;
365
362
  connect_timeout = wrapper->connect_timeout - elapsed_time;
366
363
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
@@ -415,7 +412,7 @@ static VALUE do_send_query(void *args) {
415
412
  mysql_client_wrapper *wrapper = query_args->wrapper;
416
413
  if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
417
414
  /* an error occurred, we're not active anymore */
418
- MARK_CONN_INACTIVE(self);
415
+ wrapper->active_thread = Qnil;
419
416
  return rb_raise_mysql2_error(wrapper);
420
417
  }
421
418
  return Qnil;
@@ -499,9 +496,9 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
499
496
  }
500
497
 
501
498
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
502
- RB_GC_GUARD(current);
499
+ (void)RB_GC_GUARD(current);
503
500
  Check_Type(current, T_HASH);
504
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
501
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
505
502
 
506
503
  return resultObj;
507
504
  }
@@ -598,6 +595,25 @@ static VALUE finish_and_mark_inactive(void *args) {
598
595
  }
599
596
  #endif
600
597
 
598
+ void rb_mysql_client_set_active_thread(VALUE self) {
599
+ VALUE thread_current = rb_thread_current();
600
+ GET_CLIENT(self);
601
+
602
+ // see if this connection is still waiting on a result from a previous query
603
+ if (NIL_P(wrapper->active_thread)) {
604
+ // mark this connection active
605
+ wrapper->active_thread = thread_current;
606
+ } else if (wrapper->active_thread == thread_current) {
607
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
608
+ } else {
609
+ VALUE inspect = rb_inspect(wrapper->active_thread);
610
+ const char *thr = StringValueCStr(inspect);
611
+
612
+ rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
613
+ (void)RB_GC_GUARD(inspect);
614
+ }
615
+ }
616
+
601
617
  /* call-seq:
602
618
  * client.abandon_results!
603
619
  *
@@ -634,72 +650,45 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
634
650
  * Query the database with +sql+, with optional +options+. For the possible
635
651
  * options, see @@default_query_options on the Mysql2::Client class.
636
652
  */
637
- static VALUE rb_mysql_client_query(int argc, VALUE * argv, VALUE self) {
653
+ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
638
654
  #ifndef _WIN32
639
655
  struct async_query_args async_args;
640
656
  #endif
641
657
  struct nogvl_send_query_args args;
642
- int async = 0;
643
- VALUE opts, current;
644
- VALUE thread_current = rb_thread_current();
645
- #ifdef HAVE_RUBY_ENCODING_H
646
- rb_encoding *conn_enc;
647
- #endif
648
658
  GET_CLIENT(self);
649
659
 
650
660
  REQUIRE_CONNECTED(wrapper);
651
661
  args.mysql = wrapper->client;
652
662
 
653
- current = rb_hash_dup(rb_iv_get(self, "@query_options"));
654
- RB_GC_GUARD(current);
663
+ (void)RB_GC_GUARD(current);
655
664
  Check_Type(current, T_HASH);
656
665
  rb_iv_set(self, "@current_query_options", current);
657
666
 
658
- if (rb_scan_args(argc, argv, "11", &args.sql, &opts) == 2) {
659
- rb_funcall(current, intern_merge_bang, 1, opts);
660
-
661
- if (rb_hash_aref(current, sym_async) == Qtrue) {
662
- async = 1;
663
- }
664
- }
665
-
666
- Check_Type(args.sql, T_STRING);
667
+ Check_Type(sql, T_STRING);
667
668
  #ifdef HAVE_RUBY_ENCODING_H
668
- conn_enc = rb_to_encoding(wrapper->encoding);
669
669
  /* ensure the string is in the encoding the connection is expecting */
670
- args.sql = rb_str_export_to_enc(args.sql, conn_enc);
670
+ args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
671
+ #else
672
+ args.sql = sql;
671
673
  #endif
672
- args.sql_ptr = StringValuePtr(args.sql);
674
+ args.sql_ptr = RSTRING_PTR(args.sql);
673
675
  args.sql_len = RSTRING_LEN(args.sql);
674
-
675
- /* see if this connection is still waiting on a result from a previous query */
676
- if (NIL_P(wrapper->active_thread)) {
677
- /* mark this connection active */
678
- wrapper->active_thread = thread_current;
679
- } else if (wrapper->active_thread == thread_current) {
680
- rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
681
- } else {
682
- VALUE inspect = rb_inspect(wrapper->active_thread);
683
- const char *thr = StringValueCStr(inspect);
684
-
685
- rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
686
- RB_GC_GUARD(inspect);
687
- }
688
-
689
676
  args.wrapper = wrapper;
690
677
 
678
+ rb_mysql_client_set_active_thread(self);
679
+
691
680
  #ifndef _WIN32
692
681
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
693
682
 
694
- if (!async) {
683
+ if (rb_hash_aref(current, sym_async) == Qtrue) {
684
+ return Qnil;
685
+ } else {
695
686
  async_args.fd = wrapper->client->net.fd;
696
687
  async_args.self = self;
697
688
 
698
689
  rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
699
690
 
700
691
  return rb_mysql_client_async_result(self);
701
- } else {
702
- return Qnil;
703
692
  }
704
693
  #else
705
694
  do_send_query(&args);
@@ -736,9 +725,14 @@ static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
736
725
  oldLen = RSTRING_LEN(str);
737
726
  newStr = xmalloc(oldLen*2+1);
738
727
 
739
- newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, StringValuePtr(str), oldLen);
728
+ newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
740
729
  if (newLen == oldLen) {
741
730
  /* no need to return a new ruby string if nothing changed */
731
+ #ifdef HAVE_RUBY_ENCODING_H
732
+ if (default_internal_enc) {
733
+ str = rb_str_export_to_enc(str, default_internal_enc);
734
+ }
735
+ #endif
742
736
  xfree(newStr);
743
737
  return str;
744
738
  } else {
@@ -800,17 +794,17 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
800
794
  break;
801
795
 
802
796
  case MYSQL_READ_DEFAULT_FILE:
803
- charval = (const char *)StringValuePtr(value);
797
+ charval = (const char *)StringValueCStr(value);
804
798
  retval = charval;
805
799
  break;
806
800
 
807
801
  case MYSQL_READ_DEFAULT_GROUP:
808
- charval = (const char *)StringValuePtr(value);
802
+ charval = (const char *)StringValueCStr(value);
809
803
  retval = charval;
810
804
  break;
811
805
 
812
806
  case MYSQL_INIT_COMMAND:
813
- charval = (const char *)StringValuePtr(value);
807
+ charval = (const char *)StringValueCStr(value);
814
808
  retval = charval;
815
809
  break;
816
810
 
@@ -843,30 +837,23 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
843
837
  *
844
838
  * Returns a string that represents the client library version.
845
839
  */
846
- static VALUE rb_mysql_client_info(VALUE self) {
847
- VALUE version, client_info;
848
- #ifdef HAVE_RUBY_ENCODING_H
849
- rb_encoding *default_internal_enc;
850
- rb_encoding *conn_enc;
851
- GET_CLIENT(self);
852
- #endif
853
- version = rb_hash_new();
840
+ static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
841
+ VALUE version_info, version, header_version;
842
+ version_info = rb_hash_new();
854
843
 
855
- #ifdef HAVE_RUBY_ENCODING_H
856
- default_internal_enc = rb_default_internal_encoding();
857
- conn_enc = rb_to_encoding(wrapper->encoding);
858
- #endif
844
+ version = rb_str_new2(mysql_get_client_info());
845
+ header_version = rb_str_new2(MYSQL_LINK_VERSION);
859
846
 
860
- rb_hash_aset(version, sym_id, LONG2NUM(mysql_get_client_version()));
861
- client_info = rb_str_new2(mysql_get_client_info());
862
847
  #ifdef HAVE_RUBY_ENCODING_H
863
- rb_enc_associate(client_info, conn_enc);
864
- if (default_internal_enc) {
865
- client_info = rb_str_export_to_enc(client_info, default_internal_enc);
866
- }
848
+ rb_enc_associate(version, rb_usascii_encoding());
849
+ rb_enc_associate(header_version, rb_usascii_encoding());
867
850
  #endif
868
- rb_hash_aset(version, sym_version, client_info);
869
- return version;
851
+
852
+ rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
853
+ rb_hash_aset(version_info, sym_version, version);
854
+ rb_hash_aset(version_info, sym_header_version, header_version);
855
+
856
+ return version_info;
870
857
  }
871
858
 
872
859
  /* call-seq:
@@ -906,19 +893,17 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
906
893
  *
907
894
  * Return the file descriptor number for this client.
908
895
  */
896
+ #ifndef _WIN32
909
897
  static VALUE rb_mysql_client_socket(VALUE self) {
910
898
  GET_CLIENT(self);
911
- #ifndef _WIN32
912
- {
913
- int fd_set_fd;
914
- REQUIRE_CONNECTED(wrapper);
915
- fd_set_fd = wrapper->client->net.fd;
916
- return INT2NUM(fd_set_fd);
917
- }
899
+ REQUIRE_CONNECTED(wrapper);
900
+ return INT2NUM(wrapper->client->net.fd);
901
+ }
918
902
  #else
903
+ static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
919
904
  rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
920
- #endif
921
905
  }
906
+ #endif
922
907
 
923
908
  /* call-seq:
924
909
  * client.last_id
@@ -987,7 +972,7 @@ static VALUE rb_mysql_client_select_db(VALUE self, VALUE db)
987
972
  REQUIRE_CONNECTED(wrapper);
988
973
 
989
974
  args.mysql = wrapper->client;
990
- args.db = StringValuePtr(db);
975
+ args.db = StringValueCStr(db);
991
976
 
992
977
  if (rb_thread_call_without_gvl(nogvl_select_db, &args, RUBY_UBF_IO, 0) == Qfalse)
993
978
  rb_raise_mysql2_error(wrapper);
@@ -1078,9 +1063,9 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1078
1063
  }
1079
1064
 
1080
1065
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1081
- RB_GC_GUARD(current);
1066
+ (void)RB_GC_GUARD(current);
1082
1067
  Check_Type(current, T_HASH);
1083
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
1068
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
1084
1069
 
1085
1070
  return resultObj;
1086
1071
  }
@@ -1149,7 +1134,6 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
1149
1134
  static VALUE set_charset_name(VALUE self, VALUE value) {
1150
1135
  char *charset_name;
1151
1136
  #ifdef HAVE_RUBY_ENCODING_H
1152
- size_t charset_name_len;
1153
1137
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1154
1138
  rb_encoding *enc;
1155
1139
  VALUE rb_enc;
@@ -1159,8 +1143,7 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
1159
1143
  charset_name = RSTRING_PTR(value);
1160
1144
 
1161
1145
  #ifdef HAVE_RUBY_ENCODING_H
1162
- charset_name_len = RSTRING_LEN(value);
1163
- mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len);
1146
+ mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1164
1147
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
1165
1148
  VALUE inspect = rb_inspect(value);
1166
1149
  rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
@@ -1183,11 +1166,11 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
1183
1166
  GET_CLIENT(self);
1184
1167
 
1185
1168
  mysql_ssl_set(wrapper->client,
1186
- NIL_P(key) ? NULL : StringValuePtr(key),
1187
- NIL_P(cert) ? NULL : StringValuePtr(cert),
1188
- NIL_P(ca) ? NULL : StringValuePtr(ca),
1189
- NIL_P(capath) ? NULL : StringValuePtr(capath),
1190
- NIL_P(cipher) ? NULL : StringValuePtr(cipher));
1169
+ NIL_P(key) ? NULL : StringValueCStr(key),
1170
+ NIL_P(cert) ? NULL : StringValueCStr(cert),
1171
+ NIL_P(ca) ? NULL : StringValueCStr(ca),
1172
+ NIL_P(capath) ? NULL : StringValueCStr(capath),
1173
+ NIL_P(cipher) ? NULL : StringValueCStr(cipher));
1191
1174
 
1192
1175
  return self;
1193
1176
  }
@@ -1220,6 +1203,17 @@ static VALUE initialize_ext(VALUE self) {
1220
1203
  return self;
1221
1204
  }
1222
1205
 
1206
+ /* call-seq: client.prepare # => Mysql2::Statement
1207
+ *
1208
+ * Create a new prepared statement.
1209
+ */
1210
+ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
1211
+ GET_CLIENT(self);
1212
+ REQUIRE_CONNECTED(wrapper);
1213
+
1214
+ return rb_mysql_stmt_new(self, sql);
1215
+ }
1216
+
1223
1217
  void init_mysql2_client() {
1224
1218
  /* verify the libmysql we're about to use was the version we were built against
1225
1219
  https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
@@ -1254,17 +1248,17 @@ void init_mysql2_client() {
1254
1248
  rb_define_alloc_func(cMysql2Client, allocate);
1255
1249
 
1256
1250
  rb_define_singleton_method(cMysql2Client, "escape", rb_mysql_client_escape, 1);
1251
+ rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1257
1252
 
1258
1253
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
1259
- rb_define_method(cMysql2Client, "query", rb_mysql_client_query, -1);
1260
1254
  rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
1261
1255
  rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
1262
- rb_define_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1263
1256
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
1264
1257
  rb_define_method(cMysql2Client, "socket", rb_mysql_client_socket, 0);
1265
1258
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
1266
1259
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
1267
1260
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
1261
+ rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
1268
1262
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
1269
1263
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1270
1264
  rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
@@ -1290,15 +1284,19 @@ void init_mysql2_client() {
1290
1284
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
1291
1285
  rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
1292
1286
  rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
1287
+ rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
1293
1288
 
1294
1289
  sym_id = ID2SYM(rb_intern("id"));
1295
1290
  sym_version = ID2SYM(rb_intern("version"));
1291
+ sym_header_version = ID2SYM(rb_intern("header_version"));
1296
1292
  sym_async = ID2SYM(rb_intern("async"));
1297
1293
  sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
1298
1294
  sym_as = ID2SYM(rb_intern("as"));
1299
1295
  sym_array = ID2SYM(rb_intern("array"));
1300
1296
  sym_stream = ID2SYM(rb_intern("stream"));
1301
1297
 
1298
+ intern_brackets = rb_intern("[]");
1299
+ intern_new = rb_intern("new");
1302
1300
  intern_merge = rb_intern("merge");
1303
1301
  intern_merge_bang = rb_intern("merge!");
1304
1302
  intern_error_number_eql = rb_intern("error_number=");
data/ext/mysql2/client.h CHANGED
@@ -50,7 +50,28 @@ typedef struct {
50
50
  MYSQL *client;
51
51
  } mysql_client_wrapper;
52
52
 
53
+ #define REQUIRE_CONNECTED(wrapper) \
54
+ REQUIRE_INITIALIZED(wrapper) \
55
+ if (!wrapper->connected && !wrapper->reconnect_enabled) { \
56
+ rb_raise(cMysql2Error, "closed MySQL connection"); \
57
+ }
58
+
59
+ void rb_mysql_client_set_active_thread(VALUE self);
60
+
61
+ #define MARK_CONN_INACTIVE(conn) do {\
62
+ GET_CLIENT(conn); \
63
+ wrapper->active_thread = Qnil; \
64
+ } while(0)
65
+
66
+ #define GET_CLIENT(self) \
67
+ mysql_client_wrapper *wrapper; \
68
+ Data_Get_Struct(self, mysql_client_wrapper, wrapper);
69
+
53
70
  void init_mysql2_client();
54
71
  void decr_mysql2_client(mysql_client_wrapper *wrapper);
55
72
 
56
73
  #endif
74
+
75
+ #ifndef HAVE_RB_HASH_DUP
76
+ VALUE rb_hash_dup(VALUE other);
77
+ #endif