mysql2 0.3.21 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +63 -12
  4. data/examples/eventmachine.rb +1 -1
  5. data/examples/threaded.rb +4 -6
  6. data/ext/mysql2/client.c +100 -95
  7. data/ext/mysql2/client.h +21 -1
  8. data/ext/mysql2/extconf.rb +86 -43
  9. data/ext/mysql2/infile.c +2 -2
  10. data/ext/mysql2/mysql2_ext.c +1 -0
  11. data/ext/mysql2/mysql2_ext.h +5 -6
  12. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  13. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  14. data/ext/mysql2/result.c +469 -99
  15. data/ext/mysql2/result.h +11 -4
  16. data/ext/mysql2/statement.c +494 -0
  17. data/ext/mysql2/statement.h +19 -0
  18. data/lib/mysql2/client.rb +52 -22
  19. data/lib/mysql2/console.rb +1 -1
  20. data/lib/mysql2/em.rb +5 -6
  21. data/lib/mysql2/error.rb +17 -26
  22. data/lib/mysql2/field.rb +3 -0
  23. data/lib/mysql2/statement.rb +17 -0
  24. data/lib/mysql2/version.rb +1 -1
  25. data/lib/mysql2.rb +37 -35
  26. data/spec/em/em_spec.rb +21 -21
  27. data/spec/mysql2/client_spec.rb +340 -302
  28. data/spec/mysql2/error_spec.rb +37 -36
  29. data/spec/mysql2/result_spec.rb +200 -209
  30. data/spec/mysql2/statement_spec.rb +684 -0
  31. data/spec/spec_helper.rb +7 -0
  32. data/spec/ssl/ca-cert.pem +17 -0
  33. data/spec/ssl/ca-key.pem +27 -0
  34. data/spec/ssl/ca.cnf +22 -0
  35. data/spec/ssl/cert.cnf +22 -0
  36. data/spec/ssl/client-cert.pem +17 -0
  37. data/spec/ssl/client-key.pem +27 -0
  38. data/spec/ssl/client-req.pem +15 -0
  39. data/spec/ssl/gen_certs.sh +48 -0
  40. data/spec/ssl/pkcs8-client-key.pem +28 -0
  41. data/spec/ssl/pkcs8-server-key.pem +28 -0
  42. data/spec/ssl/server-cert.pem +17 -0
  43. data/spec/ssl/server-key.pem +27 -0
  44. data/spec/ssl/server-req.pem +15 -0
  45. data/support/mysql_enc_to_ruby.rb +7 -8
  46. data/support/ruby_enc_to_mysql.rb +1 -1
  47. metadata +41 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e3404975106474ef92ef68d239b4c69901a380e
4
- data.tar.gz: 77464b6c2940d1b8be408918678f4a6abb47c95f
3
+ metadata.gz: bfe50d56c4b02379320402f6c9a50e1c004855b1
4
+ data.tar.gz: 495a7a40b1a1fc006e8f3816b0b215c8a82f4585
5
5
  SHA512:
6
- metadata.gz: e4e545f707900e4338c9965d068d81260b63cf3d7501926b5d2bce98415a1f589d88a541b256dc81f2e8519869103894303357f44dbc14b132fb3cfeca0d0f1d
7
- data.tar.gz: 0e3d0943c65f739827fd394e471fa30e04a9c0a9499ed896e944f1b513de4c4c066b0c8e168d4bf66ebbc6905b0df9ffe7fe904094ff49ebff29abdb5c6c6ffc
6
+ metadata.gz: 076e0ae48e790418cc2272bf14f3f93859b751452eeaaefd43537a22266bf5cab6d443645b06d2e3bcaab3be2768e65bc5b699bb5e100e10e5a35911d4e96c33
7
+ data.tar.gz: b921cb3b96ef7428db4870ca82719a52b7921d24fed356ba6765ff1890a98ae8c0c1351e8c158fa098d8652aebf4b958ec1cb3eeb46ff2e8c2b9e58d69dde1d7
data/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ Changes are maintained under [Releases](https://github.com/brianmario/mysql2/releases)
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
@@ -56,6 +58,20 @@ This may be needed if you deploy to a system where these libraries
56
58
  are located somewhere different than on your build system.
57
59
  This overrides any rpath calculated by default or by the options above.
58
60
 
61
+ * `--with-sanitize[=address,cfi,integer,memory,thread,undefined]` -
62
+ Enable sanitizers for Clang / GCC. If no argument is given, try to enable
63
+ all sanitizers or fail if none are available. If a command-separated list of
64
+ specific sanitizers is given, configure will fail unless they all are available.
65
+ Note that the some sanitizers may incur a performance penalty, and the Address
66
+ Sanitizer may require a runtime library.
67
+ To see line numbers in backtraces, declare these environment variables
68
+ (adjust the llvm-symbolizer path as needed for your system):
69
+
70
+ ``` sh
71
+ export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4
72
+ export ASAN_OPTIONS=symbolize=1
73
+ ```
74
+
59
75
  ### Linux and other Unixes
60
76
 
61
77
  You may need to install a package such as `libmysqlclient-dev` or `mysql-devel`;
@@ -153,6 +169,20 @@ results.each(:as => :array) do |row|
153
169
  end
154
170
  ```
155
171
 
172
+ Prepared statements are supported, as well. In a prepared statement, use a `?`
173
+ in place of each value and then execute the statement to retrieve a result set.
174
+ Pass your arguments to the execute method in the same number and order as the
175
+ question marks in the statement.
176
+
177
+ ``` ruby
178
+ statement = @client.prepare("SELECT * FROM users WHERE login_count = ?")
179
+ result1 = statement.execute(1)
180
+ result2 = statement.execute(2)
181
+
182
+ statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?")
183
+ result = statement.execute(1, "CA")
184
+ ```
185
+
156
186
  ## Connection options
157
187
 
158
188
  You may set the following connection options in Mysql2::Client.new(...):
@@ -186,7 +216,8 @@ Setting any of the following options will enable an SSL connection, but only if
186
216
  your MySQL client library and server have been compiled with SSL support.
187
217
  MySQL client library defaults will be used for any parameters that are left out
188
218
  or set to nil. Relative paths are allowed, and may be required by managed
189
- hosting providers such as Heroku.
219
+ hosting providers such as Heroku. Set `:sslverify => true` to require that the
220
+ server presents a valid certificate.
190
221
 
191
222
  ``` ruby
192
223
  Mysql2::Client.new(
@@ -195,7 +226,8 @@ Mysql2::Client.new(
195
226
  :sslcert => '/path/to/client-cert.pem',
196
227
  :sslca => '/path/to/ca-cert.pem',
197
228
  :sslcapath => '/path/to/cacerts',
198
- :sslcipher => 'DHE-RSA-AES256-SHA'
229
+ :sslcipher => 'DHE-RSA-AES256-SHA',
230
+ :sslverify => true,
199
231
  )
200
232
  ```
201
233
 
@@ -240,15 +272,26 @@ Yields:
240
272
  next_result: Unknown column 'A' in 'field list' (Mysql2::Error)
241
273
  ```
242
274
 
243
- See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Record.
244
-
245
275
  ### Secure auth
246
276
 
247
277
  Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this).
248
278
  When secure_auth is enabled, the server will refuse a connection if the account password is stored in old pre-MySQL 4.1 format.
249
279
  The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password.
250
- To bypass this restriction in the client, pass the option :secure_auth => false to Mysql2::Client.new().
251
- If using ActiveRecord, your database.yml might look something like this:
280
+ To bypass this restriction in the client, pass the option `:secure_auth => false` to Mysql2::Client.new().
281
+
282
+ ### Flags option parsing
283
+
284
+ The `:flags` parameter accepts an integer, a string, or an array. The integer
285
+ form allows the client to assemble flags from constants defined under
286
+ `Mysql2::Client` such as `Mysql2::Client::FOUND_ROWS`. Use a bitwise `|` (OR)
287
+ to specify several flags.
288
+
289
+ The string form will be split on whitespace and parsed as with the array form:
290
+ Plain flags are added to the default flags, while flags prefixed with `-`
291
+ (minus) are removed from the default flags.
292
+
293
+ This allows easier use with ActiveRecord's database.yml, avoiding the need for magic flag numbers.
294
+ For example, to disable protocol compression, and enable multiple statements and result sets:
252
295
 
253
296
  ``` yaml
254
297
  development:
@@ -259,13 +302,17 @@ development:
259
302
  password: my_password
260
303
  host: 127.0.0.1
261
304
  port: 3306
305
+ flags:
306
+ - -COMPRESS
307
+ - FOUND_ROWS
308
+ - MULTI_STATEMENTS
262
309
  secure_auth: false
263
310
  ```
264
311
 
265
312
  ### Reading a MySQL config file
266
313
 
267
314
  You may read configuration options from a MySQL configuration file by passing
268
- the `:default_file` and `:default_group` paramters. For example:
315
+ the `:default_file` and `:default_group` parameters. For example:
269
316
 
270
317
  ``` ruby
271
318
  Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client')
@@ -273,7 +320,7 @@ Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client')
273
320
 
274
321
  ### Initial command on connect and reconnect
275
322
 
276
- If you specify the init_command option, the SQL string you provide will be executed after the connection is established.
323
+ If you specify the `:init_command` option, the SQL string you provide will be executed after the connection is established.
277
324
  If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect.
278
325
  It is useful if you want to provide session options which survive reconnection.
279
326
 
@@ -437,13 +484,13 @@ As for field values themselves, I'm workin on it - but expect that soon.
437
484
 
438
485
  This gem is tested with the following Ruby versions on Linux and Mac OS X:
439
486
 
440
- * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.x, 2.2.x (ongoing patch releases)
487
+ * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x
441
488
  * Ruby Enterprise Edition (based on MRI 1.8.7)
442
489
  * Rubinius 2.x
443
490
 
444
491
  This gem is tested with the following MySQL and MariaDB versions:
445
492
 
446
- * MySQL 5.0, 5.1, 5.5, 5.6, 5.7
493
+ * MySQL 5.5, 5.7
447
494
  * MySQL Connector/C 6.0 and 6.1 (primarily on Windows)
448
495
  * MariaDB 5.5, 10.0
449
496
 
@@ -536,4 +583,8 @@ though.
536
583
  * Yury Korolev (http://github.com/yury) - for TONS of help testing the Active Record adapter
537
584
  * Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness
538
585
  * 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.
586
+ * Aaron Stone (http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support
587
+ * Kouhei Ueno (https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012
588
+ * John Cant (http://github.com/johncant) - polishing and updating Prepared Statements support
589
+ * Justin Case (http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged
590
+ * Tamir Duberstein (http://github.com/tamird) - for help with timeouts and all around updates and cleanups
@@ -18,4 +18,4 @@ EM.run do
18
18
  defer2.callback do |result|
19
19
  puts "Result: #{result.to_a.inspect}"
20
20
  end
21
- end
21
+ end
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
@@ -17,11 +17,11 @@
17
17
  VALUE cMysql2Client;
18
18
  extern VALUE mMysql2, cMysql2Error;
19
19
  static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
20
- static ID intern_merge, intern_merge_bang, intern_error_number_eql, intern_sql_state_eql;
20
+ static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
21
21
 
22
22
  #ifndef HAVE_RB_HASH_DUP
23
- static VALUE rb_hash_dup(VALUE other) {
24
- return rb_funcall(rb_cHash, rb_intern("[]"), 1, other);
23
+ VALUE rb_hash_dup(VALUE other) {
24
+ return rb_funcall(rb_cHash, intern_brackets, 1, other);
25
25
  }
26
26
  #endif
27
27
 
@@ -30,25 +30,12 @@ static VALUE rb_hash_dup(VALUE other) {
30
30
  rb_raise(cMysql2Error, "MySQL client is not initialized"); \
31
31
  }
32
32
 
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
33
  #define REQUIRE_NOT_CONNECTED(wrapper) \
40
34
  REQUIRE_INITIALIZED(wrapper) \
41
35
  if (wrapper->connected) { \
42
36
  rb_raise(cMysql2Error, "MySQL connection is already open"); \
43
37
  }
44
38
 
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
39
  /*
53
40
  * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
54
41
  * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
@@ -136,16 +123,17 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
136
123
  rb_enc_associate(rb_sql_state, rb_usascii_encoding());
137
124
  #endif
138
125
 
139
- e = rb_funcall(cMysql2Error, rb_intern("new"), 2, rb_error_msg, LONG2FIX(wrapper->server_version));
140
- rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_errno(wrapper->client)));
141
- rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
126
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
127
+ rb_error_msg,
128
+ LONG2FIX(wrapper->server_version),
129
+ UINT2NUM(mysql_errno(wrapper->client)),
130
+ rb_sql_state);
142
131
  rb_exc_raise(e);
143
- return Qnil;
144
132
  }
145
133
 
146
134
  static void *nogvl_init(void *ptr) {
147
135
  MYSQL *client;
148
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
136
+ mysql_client_wrapper *wrapper = ptr;
149
137
 
150
138
  /* may initialize embedded server and read /etc/services off disk */
151
139
  client = mysql_init(wrapper->client);
@@ -221,43 +209,47 @@ static VALUE invalidate_fd(int clientfd)
221
209
  #endif /* _WIN32 */
222
210
 
223
211
  static void *nogvl_close(void *ptr) {
224
- mysql_client_wrapper *wrapper;
225
- wrapper = ptr;
226
- if (wrapper->connected) {
227
- wrapper->active_thread = Qnil;
228
- wrapper->connected = 0;
229
- #ifndef _WIN32
230
- /* Invalidate the socket before calling mysql_close(). This prevents
231
- * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
232
- * the socket. The difference is that invalidate_fd will drop this
233
- * process's reference to the socket only, while a QUIT or shutdown()
234
- * would render the underlying connection unusable, interrupting other
235
- * processes which share this object across a fork().
236
- */
237
- if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
238
- fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, leaking some memory\n");
239
- close(wrapper->client->net.fd);
240
- return NULL;
241
- }
242
- #endif
212
+ mysql_client_wrapper *wrapper = ptr;
243
213
 
244
- mysql_close(wrapper->client); /* only used to free memory at this point */
214
+ if (wrapper->client) {
215
+ mysql_close(wrapper->client);
216
+ xfree(wrapper->client);
217
+ wrapper->client = NULL;
218
+ wrapper->connected = 0;
219
+ wrapper->active_thread = Qnil;
245
220
  }
246
221
 
247
222
  return NULL;
248
223
  }
249
224
 
225
+ /* this is called during GC */
250
226
  static void rb_mysql_client_free(void *ptr) {
251
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
227
+ mysql_client_wrapper *wrapper = ptr;
252
228
  decr_mysql2_client(wrapper);
253
229
  }
254
230
 
255
231
  void decr_mysql2_client(mysql_client_wrapper *wrapper)
256
232
  {
257
233
  wrapper->refcount--;
234
+
258
235
  if (wrapper->refcount == 0) {
236
+ #ifndef _WIN32
237
+ if (wrapper->connected) {
238
+ /* The client is being garbage collected while connected. Prevent
239
+ * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
240
+ * the socket by invalidating it. invalidate_fd() will drop this
241
+ * process's reference to the socket only, while a QUIT or shutdown()
242
+ * would render the underlying connection unusable, interrupting other
243
+ * processes which share this object across a fork().
244
+ */
245
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
246
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
247
+ close(wrapper->client->net.fd);
248
+ }
249
+ }
250
+ #endif
251
+
259
252
  nogvl_close(wrapper);
260
- xfree(wrapper->client);
261
253
  xfree(wrapper);
262
254
  }
263
255
  }
@@ -267,7 +259,7 @@ static VALUE allocate(VALUE klass) {
267
259
  mysql_client_wrapper * wrapper;
268
260
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
269
261
  wrapper->encoding = Qnil;
270
- wrapper->active_thread = Qnil;
262
+ MARK_CONN_INACTIVE(self);
271
263
  wrapper->server_version = 0;
272
264
  wrapper->reconnect_enabled = 0;
273
265
  wrapper->connect_timeout = 0;
@@ -275,6 +267,7 @@ static VALUE allocate(VALUE klass) {
275
267
  wrapper->initialized = 0; /* means that that the wrapper is initialized */
276
268
  wrapper->refcount = 1;
277
269
  wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
270
+
278
271
  return obj;
279
272
  }
280
273
 
@@ -340,8 +333,7 @@ static VALUE rb_mysql_info(VALUE self) {
340
333
 
341
334
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
342
335
  struct nogvl_connect_args args;
343
- time_t start_time, end_time;
344
- unsigned int elapsed_time, connect_timeout;
336
+ time_t start_time, end_time, elapsed_time, connect_timeout;
345
337
  VALUE rv;
346
338
  GET_CLIENT(self);
347
339
 
@@ -368,7 +360,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
368
360
  /* avoid an early timeout due to time truncating milliseconds off the start time */
369
361
  if (elapsed_time > 0)
370
362
  elapsed_time--;
371
- if (elapsed_time >= wrapper->connect_timeout)
363
+ if (elapsed_time >= (time_t)wrapper->connect_timeout)
372
364
  break;
373
365
  connect_timeout = wrapper->connect_timeout - elapsed_time;
374
366
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
@@ -389,10 +381,13 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
389
381
  }
390
382
 
391
383
  /*
392
- * Immediately disconnect from the server, normally the garbage collector
393
- * will disconnect automatically when a connection is no longer needed.
394
- * Explicitly closing this will free up server resources sooner than waiting
395
- * for the garbage collector.
384
+ * Terminate the connection; call this when the connection is no longer needed.
385
+ * The garbage collector can close the connection, but doing so emits an
386
+ * "Aborted connection" error on the server and increments the Aborted_clients
387
+ * status variable.
388
+ *
389
+ * @see http://dev.mysql.com/doc/en/communication-errors.html
390
+ * @return [void]
396
391
  */
397
392
  static VALUE rb_mysql_client_close(VALUE self) {
398
393
  GET_CLIENT(self);
@@ -442,10 +437,9 @@ static void *nogvl_read_query_result(void *ptr) {
442
437
  }
443
438
 
444
439
  static void *nogvl_do_result(void *ptr, char use_result) {
445
- mysql_client_wrapper *wrapper;
440
+ mysql_client_wrapper *wrapper = ptr;
446
441
  MYSQL_RES *result;
447
442
 
448
- wrapper = (mysql_client_wrapper *)ptr;
449
443
  if (use_result) {
450
444
  result = mysql_use_result(wrapper->client);
451
445
  } else {
@@ -454,7 +448,7 @@ static void *nogvl_do_result(void *ptr, char use_result) {
454
448
 
455
449
  /* once our result is stored off, this connection is
456
450
  ready for another command to be issued */
457
- wrapper->active_thread = Qnil;
451
+ MARK_CONN_INACTIVE(self);
458
452
 
459
453
  return result;
460
454
  }
@@ -507,9 +501,9 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
507
501
  }
508
502
 
509
503
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
510
- RB_GC_GUARD(current);
504
+ (void)RB_GC_GUARD(current);
511
505
  Check_Type(current, T_HASH);
512
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
506
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
513
507
 
514
508
  return resultObj;
515
509
  }
@@ -523,7 +517,7 @@ struct async_query_args {
523
517
  static VALUE disconnect_and_raise(VALUE self, VALUE error) {
524
518
  GET_CLIENT(self);
525
519
 
526
- wrapper->active_thread = Qnil;
520
+ MARK_CONN_INACTIVE(self);
527
521
  wrapper->connected = 0;
528
522
 
529
523
  /* Invalidate the MySQL socket to prevent further communication.
@@ -535,19 +529,16 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
535
529
  }
536
530
 
537
531
  rb_exc_raise(error);
538
-
539
- return Qnil;
540
532
  }
541
533
 
542
534
  static VALUE do_query(void *args) {
543
- struct async_query_args *async_args;
535
+ struct async_query_args *async_args = args;
544
536
  struct timeval tv;
545
- struct timeval* tvp;
537
+ struct timeval *tvp;
546
538
  long int sec;
547
539
  int retval;
548
540
  VALUE read_timeout;
549
541
 
550
- async_args = (struct async_query_args *)args;
551
542
  read_timeout = rb_iv_get(async_args->self, "@read_timeout");
552
543
 
553
544
  tvp = NULL;
@@ -585,11 +576,9 @@ static VALUE do_query(void *args) {
585
576
  }
586
577
  #else
587
578
  static VALUE finish_and_mark_inactive(void *args) {
588
- VALUE self;
579
+ VALUE self = args;
589
580
  MYSQL_RES *result;
590
581
 
591
- self = (VALUE)args;
592
-
593
582
  GET_CLIENT(self);
594
583
 
595
584
  if (!NIL_P(wrapper->active_thread)) {
@@ -599,13 +588,31 @@ static VALUE finish_and_mark_inactive(void *args) {
599
588
  result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
600
589
  mysql_free_result(result);
601
590
 
602
- wrapper->active_thread = Qnil;
591
+ MARK_CONN_INACTIVE(self);
603
592
  }
604
593
 
605
594
  return Qnil;
606
595
  }
607
596
  #endif
608
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
+ }
614
+ }
615
+
609
616
  /* call-seq:
610
617
  * client.abandon_results!
611
618
  *
@@ -640,20 +647,19 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
640
647
  * client.query(sql, options = {})
641
648
  *
642
649
  * Query the database with +sql+, with optional +options+. For the possible
643
- * options, see @@default_query_options on the Mysql2::Client class.
650
+ * options, see default_query_options on the Mysql2::Client class.
644
651
  */
645
652
  static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
646
653
  #ifndef _WIN32
647
654
  struct async_query_args async_args;
648
655
  #endif
649
656
  struct nogvl_send_query_args args;
650
- VALUE thread_current = rb_thread_current();
651
657
  GET_CLIENT(self);
652
658
 
653
659
  REQUIRE_CONNECTED(wrapper);
654
660
  args.mysql = wrapper->client;
655
661
 
656
- RB_GC_GUARD(current);
662
+ (void)RB_GC_GUARD(current);
657
663
  Check_Type(current, T_HASH);
658
664
  rb_iv_set(self, "@current_query_options", current);
659
665
 
@@ -666,23 +672,10 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
666
672
  #endif
667
673
  args.sql_ptr = RSTRING_PTR(args.sql);
668
674
  args.sql_len = RSTRING_LEN(args.sql);
669
-
670
- /* see if this connection is still waiting on a result from a previous query */
671
- if (NIL_P(wrapper->active_thread)) {
672
- /* mark this connection active */
673
- wrapper->active_thread = thread_current;
674
- } else if (wrapper->active_thread == thread_current) {
675
- rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
676
- } else {
677
- VALUE inspect = rb_inspect(wrapper->active_thread);
678
- const char *thr = StringValueCStr(inspect);
679
-
680
- rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
681
- RB_GC_GUARD(inspect);
682
- }
683
-
684
675
  args.wrapper = wrapper;
685
676
 
677
+ rb_mysql_client_set_active_thread(self);
678
+
686
679
  #ifndef _WIN32
687
680
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
688
681
 
@@ -899,15 +892,17 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
899
892
  *
900
893
  * Return the file descriptor number for this client.
901
894
  */
902
- static VALUE rb_mysql_client_socket(VALUE self) {
903
895
  #ifndef _WIN32
896
+ static VALUE rb_mysql_client_socket(VALUE self) {
904
897
  GET_CLIENT(self);
905
898
  REQUIRE_CONNECTED(wrapper);
906
899
  return INT2NUM(wrapper->client->net.fd);
900
+ }
907
901
  #else
902
+ static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
908
903
  rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
909
- #endif
910
904
  }
905
+ #endif
911
906
 
912
907
  /* call-seq:
913
908
  * client.last_id
@@ -1067,9 +1062,9 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1067
1062
  }
1068
1063
 
1069
1064
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1070
- RB_GC_GUARD(current);
1065
+ (void)RB_GC_GUARD(current);
1071
1066
  Check_Type(current, T_HASH);
1072
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
1067
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
1073
1068
 
1074
1069
  return resultObj;
1075
1070
  }
@@ -1138,7 +1133,6 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
1138
1133
  static VALUE set_charset_name(VALUE self, VALUE value) {
1139
1134
  char *charset_name;
1140
1135
  #ifdef HAVE_RUBY_ENCODING_H
1141
- size_t charset_name_len;
1142
1136
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1143
1137
  rb_encoding *enc;
1144
1138
  VALUE rb_enc;
@@ -1148,8 +1142,7 @@ static VALUE set_charset_name(VALUE self, VALUE value) {
1148
1142
  charset_name = RSTRING_PTR(value);
1149
1143
 
1150
1144
  #ifdef HAVE_RUBY_ENCODING_H
1151
- charset_name_len = RSTRING_LEN(value);
1152
- mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, charset_name_len);
1145
+ mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1153
1146
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
1154
1147
  VALUE inspect = rb_inspect(value);
1155
1148
  rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
@@ -1209,7 +1202,19 @@ static VALUE initialize_ext(VALUE self) {
1209
1202
  return self;
1210
1203
  }
1211
1204
 
1205
+ /* call-seq: client.prepare # => Mysql2::Statement
1206
+ *
1207
+ * Create a new prepared statement.
1208
+ */
1209
+ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
1210
+ GET_CLIENT(self);
1211
+ REQUIRE_CONNECTED(wrapper);
1212
+
1213
+ return rb_mysql_stmt_new(self, sql);
1214
+ }
1215
+
1212
1216
  void init_mysql2_client() {
1217
+ #ifdef _WIN32
1213
1218
  /* verify the libmysql we're about to use was the version we were built against
1214
1219
  https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
1215
1220
  int i;
@@ -1224,15 +1229,14 @@ void init_mysql2_client() {
1224
1229
  }
1225
1230
  if (lib[i] != MYSQL_LINK_VERSION[i]) {
1226
1231
  rb_raise(rb_eRuntimeError, "Incorrect MySQL client library version! This gem was compiled for %s but the client library is %s.", MYSQL_LINK_VERSION, lib);
1227
- return;
1228
1232
  }
1229
1233
  }
1234
+ #endif
1230
1235
 
1231
1236
  /* Initializing mysql library, so different threads could call Client.new */
1232
1237
  /* without race condition in the library */
1233
1238
  if (mysql_library_init(0, NULL, NULL) != 0) {
1234
1239
  rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
1235
- return;
1236
1240
  }
1237
1241
 
1238
1242
  #if 0
@@ -1253,6 +1257,7 @@ void init_mysql2_client() {
1253
1257
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
1254
1258
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
1255
1259
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
1260
+ rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
1256
1261
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
1257
1262
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1258
1263
  rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
@@ -1289,10 +1294,10 @@ void init_mysql2_client() {
1289
1294
  sym_array = ID2SYM(rb_intern("array"));
1290
1295
  sym_stream = ID2SYM(rb_intern("stream"));
1291
1296
 
1297
+ intern_brackets = rb_intern("[]");
1292
1298
  intern_merge = rb_intern("merge");
1293
1299
  intern_merge_bang = rb_intern("merge!");
1294
- intern_error_number_eql = rb_intern("error_number=");
1295
- intern_sql_state_eql = rb_intern("sql_state=");
1300
+ intern_new_with_args = rb_intern("new_with_args");
1296
1301
 
1297
1302
  #ifdef CLIENT_LONG_PASSWORD
1298
1303
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),