mysql2 0.3.21 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +132 -55
  4. data/examples/eventmachine.rb +1 -1
  5. data/examples/threaded.rb +4 -6
  6. data/ext/mysql2/client.c +314 -118
  7. data/ext/mysql2/client.h +13 -3
  8. data/ext/mysql2/extconf.rb +111 -44
  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 -10
  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 +489 -101
  15. data/ext/mysql2/result.h +12 -4
  16. data/ext/mysql2/statement.c +595 -0
  17. data/ext/mysql2/statement.h +19 -0
  18. data/lib/mysql2/client.rb +70 -27
  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/configuration.yml.example +0 -6
  27. data/spec/em/em_spec.rb +22 -21
  28. data/spec/mysql2/client_spec.rb +484 -346
  29. data/spec/mysql2/error_spec.rb +38 -39
  30. data/spec/mysql2/result_spec.rb +256 -230
  31. data/spec/mysql2/statement_spec.rb +776 -0
  32. data/spec/spec_helper.rb +80 -59
  33. data/spec/ssl/ca-cert.pem +17 -0
  34. data/spec/ssl/ca-key.pem +27 -0
  35. data/spec/ssl/ca.cnf +22 -0
  36. data/spec/ssl/cert.cnf +22 -0
  37. data/spec/ssl/client-cert.pem +17 -0
  38. data/spec/ssl/client-key.pem +27 -0
  39. data/spec/ssl/client-req.pem +15 -0
  40. data/spec/ssl/gen_certs.sh +48 -0
  41. data/spec/ssl/pkcs8-client-key.pem +28 -0
  42. data/spec/ssl/pkcs8-server-key.pem +28 -0
  43. data/spec/ssl/server-cert.pem +17 -0
  44. data/spec/ssl/server-key.pem +27 -0
  45. data/spec/ssl/server-req.pem +15 -0
  46. data/support/5072E1F5.asc +432 -0
  47. data/support/mysql_enc_to_ruby.rb +7 -8
  48. data/support/ruby_enc_to_mysql.rb +1 -1
  49. metadata +50 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e3404975106474ef92ef68d239b4c69901a380e
4
- data.tar.gz: 77464b6c2940d1b8be408918678f4a6abb47c95f
3
+ metadata.gz: 75b3d925930b92cf7b1a36fa196d334e245919ac
4
+ data.tar.gz: 2bbe0a78b156f8c5b59643c4d57a7ce19b764bcc
5
5
  SHA512:
6
- metadata.gz: e4e545f707900e4338c9965d068d81260b63cf3d7501926b5d2bce98415a1f589d88a541b256dc81f2e8519869103894303357f44dbc14b132fb3cfeca0d0f1d
7
- data.tar.gz: 0e3d0943c65f739827fd394e471fa30e04a9c0a9499ed896e944f1b513de4c4c066b0c8e168d4bf66ebbc6905b0df9ffe7fe904094ff49ebff29abdb5c6c6ffc
6
+ metadata.gz: 602f336b5ed83421862b9dec36a9ddbd477dcbacc3ef16d58d5252072dba0bc7f7a955000482414eda1d104bda72ded87f3f4c795f8b4b4d36999bc6ee171e4b
7
+ data.tar.gz: 20281fda66cf4595edc05ac6a933d5f641c2f9f87771e8ace1e9de00902ecea54ddbc2d1b743c3dbd97b48c795d0ad32f9ab785e5848a4f3de92c7ddebeef659
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`;
@@ -69,6 +85,9 @@ You may use MacPorts, Homebrew, or a native MySQL installer package. The most
69
85
  common paths will be automatically searched. If you want to select a specific
70
86
  MySQL directory, use the `--with-mysql-dir` or `--with-mysql-config` options above.
71
87
 
88
+ If you have not done so already, you will need to install the XCode select tools by running
89
+ `xcode-select --install`.
90
+
72
91
  ### Windows
73
92
  Make sure that you have Ruby and the DevKit compilers installed. We recommend
74
93
  the [Ruby Installer](http://rubyinstaller.org) distribution.
@@ -93,7 +112,7 @@ Connect to a database:
93
112
  ``` ruby
94
113
  # this takes a hash of options, almost all of which map directly
95
114
  # to the familiar database.yml in rails
96
- # See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html
115
+ # See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html
97
116
  client = Mysql2::Client.new(:host => "localhost", :username => "root")
98
117
  ```
99
118
 
@@ -148,11 +167,25 @@ by the query like this:
148
167
  ``` ruby
149
168
  headers = results.fields # <= that's an array of field names, in order
150
169
  results.each(:as => :array) do |row|
151
- # Each row is an array, ordered the same as the query results
152
- # An otter's den is called a "holt" or "couch"
170
+ # Each row is an array, ordered the same as the query results
171
+ # An otter's den is called a "holt" or "couch"
153
172
  end
154
173
  ```
155
174
 
175
+ Prepared statements are supported, as well. In a prepared statement, use a `?`
176
+ in place of each value and then execute the statement to retrieve a result set.
177
+ Pass your arguments to the execute method in the same number and order as the
178
+ question marks in the statement.
179
+
180
+ ``` ruby
181
+ statement = @client.prepare("SELECT * FROM users WHERE login_count = ?")
182
+ result1 = statement.execute(1)
183
+ result2 = statement.execute(2)
184
+
185
+ statement = @client.prepare("SELECT * FROM users WHERE last_login >= ? AND location LIKE ?")
186
+ result = statement.execute(1, "CA")
187
+ ```
188
+
156
189
  ## Connection options
157
190
 
158
191
  You may set the following connection options in Mysql2::Client.new(...):
@@ -173,12 +206,25 @@ Mysql2::Client.new(
173
206
  :reconnect = true/false,
174
207
  :local_infile = true/false,
175
208
  :secure_auth = true/false,
209
+ :ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity,
176
210
  :default_file = '/path/to/my.cfg',
177
211
  :default_group = 'my.cfg section',
178
212
  :init_command => sql
179
213
  )
180
214
  ```
181
215
 
216
+ ### Connecting to MySQL on localhost and elsewhere
217
+
218
+ The underlying MySQL client library uses the `:host` parameter to determine the
219
+ type of connection to make, with special interpretation you should be aware of:
220
+
221
+ * An empty value or `"localhost"` will attempt a local connection:
222
+ * On Unix, connect to the default local socket path. (To set a custom socket
223
+ path, use the `:socket` parameter).
224
+ * On Windows, connect using a shared-memory connection, if enabled, or TCP.
225
+ * A value of `"."` on Windows specifies a named-pipe connection.
226
+ * An IPv4 or IPv6 address will result in a TCP connection.
227
+ * Any other value will be looked up as a hostname for a TCP connection.
182
228
 
183
229
  ### SSL options
184
230
 
@@ -186,7 +232,8 @@ Setting any of the following options will enable an SSL connection, but only if
186
232
  your MySQL client library and server have been compiled with SSL support.
187
233
  MySQL client library defaults will be used for any parameters that are left out
188
234
  or set to nil. Relative paths are allowed, and may be required by managed
189
- hosting providers such as Heroku.
235
+ hosting providers such as Heroku. Set `:sslverify => true` to require that the
236
+ server presents a valid certificate.
190
237
 
191
238
  ``` ruby
192
239
  Mysql2::Client.new(
@@ -195,10 +242,67 @@ Mysql2::Client.new(
195
242
  :sslcert => '/path/to/client-cert.pem',
196
243
  :sslca => '/path/to/ca-cert.pem',
197
244
  :sslcapath => '/path/to/cacerts',
198
- :sslcipher => 'DHE-RSA-AES256-SHA'
245
+ :sslcipher => 'DHE-RSA-AES256-SHA',
246
+ :sslverify => true,
199
247
  )
200
248
  ```
201
249
 
250
+ ### Secure auth
251
+
252
+ Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this).
253
+ When secure_auth is enabled, the server will refuse a connection if the account password is stored in old pre-MySQL 4.1 format.
254
+ The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password.
255
+ To bypass this restriction in the client, pass the option `:secure_auth => false` to Mysql2::Client.new().
256
+
257
+ ### Flags option parsing
258
+
259
+ The `:flags` parameter accepts an integer, a string, or an array. The integer
260
+ form allows the client to assemble flags from constants defined under
261
+ `Mysql2::Client` such as `Mysql2::Client::FOUND_ROWS`. Use a bitwise `|` (OR)
262
+ to specify several flags.
263
+
264
+ The string form will be split on whitespace and parsed as with the array form:
265
+ Plain flags are added to the default flags, while flags prefixed with `-`
266
+ (minus) are removed from the default flags.
267
+
268
+ This allows easier use with ActiveRecord's database.yml, avoiding the need for magic flag numbers.
269
+ For example, to disable protocol compression, and enable multiple statements and result sets:
270
+
271
+ ``` yaml
272
+ development:
273
+ adapter: mysql2
274
+ encoding: utf8
275
+ database: my_db_name
276
+ username: root
277
+ password: my_password
278
+ host: 127.0.0.1
279
+ port: 3306
280
+ flags:
281
+ - -COMPRESS
282
+ - FOUND_ROWS
283
+ - MULTI_STATEMENTS
284
+ secure_auth: false
285
+ ```
286
+
287
+ ### Reading a MySQL config file
288
+
289
+ You may read configuration options from a MySQL configuration file by passing
290
+ the `:default_file` and `:default_group` parameters. For example:
291
+
292
+ ``` ruby
293
+ Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client')
294
+ ```
295
+
296
+ ### Initial command on connect and reconnect
297
+
298
+ If you specify the `:init_command` option, the SQL string you provide will be executed after the connection is established.
299
+ If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect.
300
+ It is useful if you want to provide session options which survive reconnection.
301
+
302
+ ``` ruby
303
+ Mysql2::Client.new(:init_command => "SET @@SESSION.sql_mode = 'STRICT_ALL_TABLES'")
304
+ ```
305
+
202
306
  ### Multiple result sets
203
307
 
204
308
  You can also retrieve multiple result sets. For this to work you need to
@@ -240,47 +344,6 @@ Yields:
240
344
  next_result: Unknown column 'A' in 'field list' (Mysql2::Error)
241
345
  ```
242
346
 
243
- See https://gist.github.com/1367987 for using MULTI_STATEMENTS with Active Record.
244
-
245
- ### Secure auth
246
-
247
- Starting wih MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this).
248
- 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
- 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:
252
-
253
- ``` yaml
254
- development:
255
- adapter: mysql2
256
- encoding: utf8
257
- database: my_db_name
258
- username: root
259
- password: my_password
260
- host: 127.0.0.1
261
- port: 3306
262
- secure_auth: false
263
- ```
264
-
265
- ### Reading a MySQL config file
266
-
267
- You may read configuration options from a MySQL configuration file by passing
268
- the `:default_file` and `:default_group` paramters. For example:
269
-
270
- ``` ruby
271
- Mysql2::Client.new(:default_file => '/user/.my.cnf', :default_group => 'client')
272
- ```
273
-
274
- ### Initial command on connect and reconnect
275
-
276
- If you specify the init_command option, the SQL string you provide will be executed after the connection is established.
277
- If `:reconnect` is set to `true`, init_command will also be executed after a successful reconnect.
278
- It is useful if you want to provide session options which survive reconnection.
279
-
280
- ``` ruby
281
- Mysql2::Client.new(:init_command => "SET @@SESSION.sql_mode = 'STRICT_ALL_TABLES'")
282
- ```
283
-
284
347
  ## Cascading config
285
348
 
286
349
  The default config hash is at:
@@ -351,6 +414,15 @@ client = Mysql2::Client.new
351
414
  result = client.query("SELECT * FROM table_with_boolean_field", :cast_booleans => true)
352
415
  ```
353
416
 
417
+ Keep in mind that this works only with fields and not with computed values, e.g. this result will contain `1`, not `true`:
418
+
419
+ ``` ruby
420
+ client = Mysql2::Client.new
421
+ result = client.query("SELECT true", :cast_booleans => true)
422
+ ```
423
+
424
+ CAST function wouldn't help here as there's no way to cast to TINYINT(1). Apparently the only way to solve this is to use a stored procedure with return type set to TINYINT(1).
425
+
354
426
  ### Skipping casting
355
427
 
356
428
  Mysql2 casting is fast, but not as fast as not casting data. In rare cases where typecasting is not needed, it will be faster to disable it by providing :cast => false. (Note that :cast => false overrides :cast_booleans => true.)
@@ -437,20 +509,21 @@ As for field values themselves, I'm workin on it - but expect that soon.
437
509
 
438
510
  This gem is tested with the following Ruby versions on Linux and Mac OS X:
439
511
 
440
- * Ruby MRI 1.8.7, 1.9.2, 1.9.3, 2.0.0, 2.1.x, 2.2.x (ongoing patch releases)
512
+ * Ruby MRI 1.8.7, 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x
441
513
  * Ruby Enterprise Edition (based on MRI 1.8.7)
442
- * Rubinius 2.x
514
+ * Rubinius 2.x and 3.x do work but may fail under some workloads
443
515
 
444
516
  This gem is tested with the following MySQL and MariaDB versions:
445
517
 
446
- * MySQL 5.0, 5.1, 5.5, 5.6, 5.7
518
+ * MySQL 5.5, 5.6, 5.7, 8.0
447
519
  * MySQL Connector/C 6.0 and 6.1 (primarily on Windows)
448
- * MariaDB 5.5, 10.0
520
+ * MariaDB 5.5, 10.0, 10.1
449
521
 
450
- ### Active Record
522
+ ### Ruby on Rails / Active Record
451
523
 
452
- * mysql2 0.2.x includes an Active Record driver compatible with AR 2.3 and 3.0
453
- * mysql2 0.3.x does not include an AR driver because it is included in AR 3.1 and above
524
+ * mysql2 0.4.x works with Rails / Active Record 4.2.5 - 5.0 and higher.
525
+ * mysql2 0.3.x works with Rails / Active Record 3.1, 3.2, 4.x, 5.0.
526
+ * mysql2 0.2.x works with Rails / Active Record 2.3 - 3.0.
454
527
 
455
528
  ### Asynchronous Active Record
456
529
 
@@ -536,4 +609,8 @@ though.
536
609
  * Yury Korolev (http://github.com/yury) - for TONS of help testing the Active Record adapter
537
610
  * Aaron Patterson (http://github.com/tenderlove) - tons of contributions, suggestions and general badassness
538
611
  * 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.
612
+ * Aaron Stone (http://github.com/sodabrew) - additional client settings, local files, microsecond time, maintenance support
613
+ * Kouhei Ueno (https://github.com/nyaxt) - for the original work on Prepared Statements way back in 2012
614
+ * John Cant (http://github.com/johncant) - polishing and updating Prepared Statements support
615
+ * Justin Case (http://github.com/justincase) - polishing and updating Prepared Statements support and getting it merged
616
+ * 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,36 +30,47 @@ static VALUE rb_hash_dup(VALUE other) {
30
30
  rb_raise(cMysql2Error, "MySQL client is not initialized"); \
31
31
  }
32
32
 
33
+ #if defined(HAVE_MYSQL_NET_VIO) || defined(HAVE_ST_NET_VIO)
34
+ #define CONNECTED(wrapper) (wrapper->client->net.vio != NULL && wrapper->client->net.fd != -1)
35
+ #elif defined(HAVE_MYSQL_NET_PVIO) || defined(HAVE_ST_NET_PVIO)
36
+ #define CONNECTED(wrapper) (wrapper->client->net.pvio != NULL && wrapper->client->net.fd != -1)
37
+ #endif
38
+
33
39
  #define REQUIRE_CONNECTED(wrapper) \
34
40
  REQUIRE_INITIALIZED(wrapper) \
35
- if (!wrapper->connected && !wrapper->reconnect_enabled) { \
36
- rb_raise(cMysql2Error, "closed MySQL connection"); \
41
+ if (!CONNECTED(wrapper) && !wrapper->reconnect_enabled) { \
42
+ rb_raise(cMysql2Error, "MySQL client is not connected"); \
37
43
  }
38
44
 
39
45
  #define REQUIRE_NOT_CONNECTED(wrapper) \
40
46
  REQUIRE_INITIALIZED(wrapper) \
41
- if (wrapper->connected) { \
47
+ if (CONNECTED(wrapper)) { \
42
48
  rb_raise(cMysql2Error, "MySQL connection is already open"); \
43
49
  }
44
50
 
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
51
  /*
53
52
  * compatability with mysql-connector-c, where LIBMYSQL_VERSION is the correct
54
53
  * variable to use, but MYSQL_SERVER_VERSION gives the correct numbers when
55
54
  * linking against the server itself
56
55
  */
57
- #ifdef LIBMYSQL_VERSION
56
+ #if defined(MARIADB_CLIENT_VERSION_STR)
57
+ #define MYSQL_LINK_VERSION MARIADB_CLIENT_VERSION_STR
58
+ #elif defined(LIBMYSQL_VERSION)
58
59
  #define MYSQL_LINK_VERSION LIBMYSQL_VERSION
59
60
  #else
60
61
  #define MYSQL_LINK_VERSION MYSQL_SERVER_VERSION
61
62
  #endif
62
63
 
64
+ /*
65
+ * compatibility with mysql-connector-c 6.1.x, and with MySQL 5.7.3 - 5.7.10.
66
+ */
67
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
68
+ #define SSL_MODE_DISABLED 1
69
+ #define SSL_MODE_REQUIRED 3
70
+ #define HAVE_CONST_SSL_MODE_DISABLED
71
+ #define HAVE_CONST_SSL_MODE_REQUIRED
72
+ #endif
73
+
63
74
  /*
64
75
  * used to pass all arguments to mysql_real_connect while inside
65
76
  * rb_thread_call_without_gvl
@@ -96,6 +107,42 @@ struct nogvl_select_db_args {
96
107
  char *db;
97
108
  };
98
109
 
110
+ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
111
+ unsigned long version = mysql_get_client_version();
112
+
113
+ if (version < 50703) {
114
+ rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." );
115
+ return Qnil;
116
+ }
117
+ #ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
118
+ GET_CLIENT(self);
119
+ int val = NUM2INT( setting );
120
+ if (version >= 50703 && version < 50711) {
121
+ if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
122
+ bool b = ( val == SSL_MODE_REQUIRED );
123
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b );
124
+ return INT2NUM(result);
125
+ } else {
126
+ rb_warn( "MySQL client libraries between 5.7.3 and 5.7.10 only support SSL_MODE_DISABLED and SSL_MODE_REQUIRED" );
127
+ return Qnil;
128
+ }
129
+ }
130
+ #endif
131
+ #ifdef FULL_SSL_MODE_SUPPORT
132
+ GET_CLIENT(self);
133
+ int val = NUM2INT( setting );
134
+
135
+ if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
136
+ rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
137
+ }
138
+ int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val );
139
+
140
+ return INT2NUM(result);
141
+ #endif
142
+ #ifdef NO_SSL_MODE_SUPPORT
143
+ return Qnil;
144
+ #endif
145
+ }
99
146
  /*
100
147
  * non-blocking mysql_*() functions that we won't be wrapping since
101
148
  * they do not appear to hit the network nor issue any interruptible
@@ -136,16 +183,17 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
136
183
  rb_enc_associate(rb_sql_state, rb_usascii_encoding());
137
184
  #endif
138
185
 
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);
186
+ e = rb_funcall(cMysql2Error, intern_new_with_args, 4,
187
+ rb_error_msg,
188
+ LONG2FIX(wrapper->server_version),
189
+ UINT2NUM(mysql_errno(wrapper->client)),
190
+ rb_sql_state);
142
191
  rb_exc_raise(e);
143
- return Qnil;
144
192
  }
145
193
 
146
194
  static void *nogvl_init(void *ptr) {
147
195
  MYSQL *client;
148
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
196
+ mysql_client_wrapper *wrapper = ptr;
149
197
 
150
198
  /* may initialize embedded server and read /etc/services off disk */
151
199
  client = mysql_init(wrapper->client);
@@ -221,41 +269,46 @@ static VALUE invalidate_fd(int clientfd)
221
269
  #endif /* _WIN32 */
222
270
 
223
271
  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
272
+ mysql_client_wrapper *wrapper = ptr;
243
273
 
244
- mysql_close(wrapper->client); /* only used to free memory at this point */
274
+ if (!wrapper->closed) {
275
+ mysql_close(wrapper->client);
276
+ wrapper->closed = 1;
277
+ wrapper->reconnect_enabled = 0;
278
+ wrapper->active_thread = Qnil;
245
279
  }
246
280
 
247
281
  return NULL;
248
282
  }
249
283
 
284
+ /* this is called during GC */
250
285
  static void rb_mysql_client_free(void *ptr) {
251
- mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
286
+ mysql_client_wrapper *wrapper = ptr;
252
287
  decr_mysql2_client(wrapper);
253
288
  }
254
289
 
255
290
  void decr_mysql2_client(mysql_client_wrapper *wrapper)
256
291
  {
257
292
  wrapper->refcount--;
293
+
258
294
  if (wrapper->refcount == 0) {
295
+ #ifndef _WIN32
296
+ if (CONNECTED(wrapper) && !wrapper->automatic_close) {
297
+ /* The client is being garbage collected while connected. Prevent
298
+ * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
299
+ * the socket by invalidating it. invalidate_fd() will drop this
300
+ * process's reference to the socket only, while a QUIT or shutdown()
301
+ * would render the underlying connection unusable, interrupting other
302
+ * processes which share this object across a fork().
303
+ */
304
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
305
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely\n");
306
+ close(wrapper->client->net.fd);
307
+ }
308
+ wrapper->client->net.fd = -1;
309
+ }
310
+ #endif
311
+
259
312
  nogvl_close(wrapper);
260
313
  xfree(wrapper->client);
261
314
  xfree(wrapper);
@@ -268,13 +321,15 @@ static VALUE allocate(VALUE klass) {
268
321
  obj = Data_Make_Struct(klass, mysql_client_wrapper, rb_mysql_client_mark, rb_mysql_client_free, wrapper);
269
322
  wrapper->encoding = Qnil;
270
323
  wrapper->active_thread = Qnil;
324
+ wrapper->automatic_close = 1;
271
325
  wrapper->server_version = 0;
272
326
  wrapper->reconnect_enabled = 0;
273
327
  wrapper->connect_timeout = 0;
274
- wrapper->connected = 0; /* means that a database connection is open */
275
328
  wrapper->initialized = 0; /* means that that the wrapper is initialized */
276
329
  wrapper->refcount = 1;
330
+ wrapper->closed = 0;
277
331
  wrapper->client = (MYSQL*)xmalloc(sizeof(MYSQL));
332
+
278
333
  return obj;
279
334
  }
280
335
 
@@ -338,10 +393,29 @@ static VALUE rb_mysql_info(VALUE self) {
338
393
  return rb_str;
339
394
  }
340
395
 
396
+ static VALUE rb_mysql_get_ssl_cipher(VALUE self)
397
+ {
398
+ const char *cipher;
399
+ VALUE rb_str;
400
+ GET_CLIENT(self);
401
+
402
+ cipher = mysql_get_ssl_cipher(wrapper->client);
403
+
404
+ if (cipher == NULL) {
405
+ return Qnil;
406
+ }
407
+
408
+ rb_str = rb_str_new2(cipher);
409
+ #ifdef HAVE_RUBY_ENCODING_H
410
+ rb_enc_associate(rb_str, rb_utf8_encoding());
411
+ #endif
412
+
413
+ return rb_str;
414
+ }
415
+
341
416
  static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags) {
342
417
  struct nogvl_connect_args args;
343
- time_t start_time, end_time;
344
- unsigned int elapsed_time, connect_timeout;
418
+ time_t start_time, end_time, elapsed_time, connect_timeout;
345
419
  VALUE rv;
346
420
  GET_CLIENT(self);
347
421
 
@@ -368,7 +442,7 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
368
442
  /* avoid an early timeout due to time truncating milliseconds off the start time */
369
443
  if (elapsed_time > 0)
370
444
  elapsed_time--;
371
- if (elapsed_time >= wrapper->connect_timeout)
445
+ if (elapsed_time >= (time_t)wrapper->connect_timeout)
372
446
  break;
373
447
  connect_timeout = wrapper->connect_timeout - elapsed_time;
374
448
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
@@ -380,30 +454,41 @@ static VALUE rb_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE po
380
454
  if (wrapper->connect_timeout)
381
455
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
382
456
  if (rv == Qfalse)
383
- return rb_raise_mysql2_error(wrapper);
457
+ rb_raise_mysql2_error(wrapper);
384
458
  }
385
459
 
386
460
  wrapper->server_version = mysql_get_server_version(wrapper->client);
387
- wrapper->connected = 1;
388
461
  return self;
389
462
  }
390
463
 
391
464
  /*
392
- * Immediately disconnect from the server, normally the garbage collector
465
+ * Immediately disconnect from the server; normally the garbage collector
393
466
  * will disconnect automatically when a connection is no longer needed.
394
467
  * Explicitly closing this will free up server resources sooner than waiting
395
468
  * for the garbage collector.
469
+ *
470
+ * @return [nil]
396
471
  */
397
472
  static VALUE rb_mysql_client_close(VALUE self) {
398
473
  GET_CLIENT(self);
399
474
 
400
- if (wrapper->connected) {
475
+ if (wrapper->client) {
401
476
  rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
402
477
  }
403
478
 
404
479
  return Qnil;
405
480
  }
406
481
 
482
+ /* call-seq:
483
+ * client.closed?
484
+ *
485
+ * @return [Boolean]
486
+ */
487
+ static VALUE rb_mysql_client_closed(VALUE self) {
488
+ GET_CLIENT(self);
489
+ return CONNECTED(wrapper) ? Qfalse : Qtrue;
490
+ }
491
+
407
492
  /*
408
493
  * mysql_send_query is unlikely to block since most queries are small
409
494
  * enough to fit in a socket buffer, but sometimes large UPDATE and
@@ -423,8 +508,8 @@ static VALUE do_send_query(void *args) {
423
508
  mysql_client_wrapper *wrapper = query_args->wrapper;
424
509
  if ((VALUE)rb_thread_call_without_gvl(nogvl_send_query, args, RUBY_UBF_IO, 0) == Qfalse) {
425
510
  /* an error occurred, we're not active anymore */
426
- MARK_CONN_INACTIVE(self);
427
- return rb_raise_mysql2_error(wrapper);
511
+ wrapper->active_thread = Qnil;
512
+ rb_raise_mysql2_error(wrapper);
428
513
  }
429
514
  return Qnil;
430
515
  }
@@ -436,16 +521,15 @@ static VALUE do_send_query(void *args) {
436
521
  */
437
522
  static void *nogvl_read_query_result(void *ptr) {
438
523
  MYSQL * client = ptr;
439
- my_bool res = mysql_read_query_result(client);
524
+ bool res = mysql_read_query_result(client);
440
525
 
441
526
  return (void *)(res == 0 ? Qtrue : Qfalse);
442
527
  }
443
528
 
444
529
  static void *nogvl_do_result(void *ptr, char use_result) {
445
- mysql_client_wrapper *wrapper;
530
+ mysql_client_wrapper *wrapper = ptr;
446
531
  MYSQL_RES *result;
447
532
 
448
- wrapper = (mysql_client_wrapper *)ptr;
449
533
  if (use_result) {
450
534
  result = mysql_use_result(wrapper->client);
451
535
  } else {
@@ -486,8 +570,8 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
486
570
  REQUIRE_CONNECTED(wrapper);
487
571
  if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, wrapper->client, RUBY_UBF_IO, 0) == Qfalse) {
488
572
  /* an error occurred, mark this connection inactive */
489
- MARK_CONN_INACTIVE(self);
490
- return rb_raise_mysql2_error(wrapper);
573
+ wrapper->active_thread = Qnil;
574
+ rb_raise_mysql2_error(wrapper);
491
575
  }
492
576
 
493
577
  is_streaming = rb_hash_aref(rb_iv_get(self, "@current_query_options"), sym_stream);
@@ -499,7 +583,7 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
499
583
 
500
584
  if (result == NULL) {
501
585
  if (mysql_errno(wrapper->client) != 0) {
502
- MARK_CONN_INACTIVE(self);
586
+ wrapper->active_thread = Qnil;
503
587
  rb_raise_mysql2_error(wrapper);
504
588
  }
505
589
  /* no data and no error, so query was not a SELECT */
@@ -507,9 +591,9 @@ static VALUE rb_mysql_client_async_result(VALUE self) {
507
591
  }
508
592
 
509
593
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
510
- RB_GC_GUARD(current);
594
+ (void)RB_GC_GUARD(current);
511
595
  Check_Type(current, T_HASH);
512
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
596
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
513
597
 
514
598
  return resultObj;
515
599
  }
@@ -524,30 +608,29 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
524
608
  GET_CLIENT(self);
525
609
 
526
610
  wrapper->active_thread = Qnil;
527
- wrapper->connected = 0;
528
611
 
529
612
  /* Invalidate the MySQL socket to prevent further communication.
530
613
  * The GC will come along later and call mysql_close to free it.
531
614
  */
532
- if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
533
- fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
534
- close(wrapper->client->net.fd);
615
+ if (CONNECTED(wrapper)) {
616
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
617
+ fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, closing unsafely\n");
618
+ close(wrapper->client->net.fd);
619
+ }
620
+ wrapper->client->net.fd = -1;
535
621
  }
536
622
 
537
623
  rb_exc_raise(error);
538
-
539
- return Qnil;
540
624
  }
541
625
 
542
626
  static VALUE do_query(void *args) {
543
- struct async_query_args *async_args;
627
+ struct async_query_args *async_args = args;
544
628
  struct timeval tv;
545
- struct timeval* tvp;
629
+ struct timeval *tvp;
546
630
  long int sec;
547
631
  int retval;
548
632
  VALUE read_timeout;
549
633
 
550
- async_args = (struct async_query_args *)args;
551
634
  read_timeout = rb_iv_get(async_args->self, "@read_timeout");
552
635
 
553
636
  tvp = NULL;
@@ -583,28 +666,50 @@ static VALUE do_query(void *args) {
583
666
 
584
667
  return Qnil;
585
668
  }
586
- #else
587
- static VALUE finish_and_mark_inactive(void *args) {
588
- VALUE self;
589
- MYSQL_RES *result;
590
-
591
- self = (VALUE)args;
669
+ #endif
592
670
 
671
+ static VALUE disconnect_and_mark_inactive(VALUE self) {
593
672
  GET_CLIENT(self);
594
673
 
674
+ /* Check if execution terminated while result was still being read. */
595
675
  if (!NIL_P(wrapper->active_thread)) {
596
- /* if we got here, the result hasn't been read off the wire yet
597
- so lets do that and then throw it away because we have no way
598
- of getting it back up to the caller from here */
599
- result = (MYSQL_RES *)rb_thread_call_without_gvl(nogvl_store_result, wrapper, RUBY_UBF_IO, 0);
600
- mysql_free_result(result);
601
-
676
+ if (CONNECTED(wrapper)) {
677
+ /* Invalidate the MySQL socket to prevent further communication. */
678
+ #ifndef _WIN32
679
+ if (invalidate_fd(wrapper->client->net.fd) == Qfalse) {
680
+ rb_warn("mysql2 failed to invalidate FD safely, closing unsafely\n");
681
+ close(wrapper->client->net.fd);
682
+ }
683
+ #else
684
+ close(wrapper->client->net.fd);
685
+ #endif
686
+ wrapper->client->net.fd = -1;
687
+ }
688
+ /* Skip mysql client check performed before command execution. */
689
+ wrapper->client->status = MYSQL_STATUS_READY;
602
690
  wrapper->active_thread = Qnil;
603
691
  }
604
692
 
605
693
  return Qnil;
606
694
  }
607
- #endif
695
+
696
+ void rb_mysql_client_set_active_thread(VALUE self) {
697
+ VALUE thread_current = rb_thread_current();
698
+ GET_CLIENT(self);
699
+
700
+ // see if this connection is still waiting on a result from a previous query
701
+ if (NIL_P(wrapper->active_thread)) {
702
+ // mark this connection active
703
+ wrapper->active_thread = thread_current;
704
+ } else if (wrapper->active_thread == thread_current) {
705
+ rb_raise(cMysql2Error, "This connection is still waiting for a result, try again once you have the result");
706
+ } else {
707
+ VALUE inspect = rb_inspect(wrapper->active_thread);
708
+ const char *thr = StringValueCStr(inspect);
709
+
710
+ rb_raise(cMysql2Error, "This connection is in use by: %s", thr);
711
+ }
712
+ }
608
713
 
609
714
  /* call-seq:
610
715
  * client.abandon_results!
@@ -640,20 +745,19 @@ static VALUE rb_mysql_client_abandon_results(VALUE self) {
640
745
  * client.query(sql, options = {})
641
746
  *
642
747
  * Query the database with +sql+, with optional +options+. For the possible
643
- * options, see @@default_query_options on the Mysql2::Client class.
748
+ * options, see default_query_options on the Mysql2::Client class.
644
749
  */
645
750
  static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
646
751
  #ifndef _WIN32
647
752
  struct async_query_args async_args;
648
753
  #endif
649
754
  struct nogvl_send_query_args args;
650
- VALUE thread_current = rb_thread_current();
651
755
  GET_CLIENT(self);
652
756
 
653
757
  REQUIRE_CONNECTED(wrapper);
654
758
  args.mysql = wrapper->client;
655
759
 
656
- RB_GC_GUARD(current);
760
+ (void)RB_GC_GUARD(current);
657
761
  Check_Type(current, T_HASH);
658
762
  rb_iv_set(self, "@current_query_options", current);
659
763
 
@@ -666,23 +770,10 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
666
770
  #endif
667
771
  args.sql_ptr = RSTRING_PTR(args.sql);
668
772
  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
773
  args.wrapper = wrapper;
685
774
 
775
+ rb_mysql_client_set_active_thread(self);
776
+
686
777
  #ifndef _WIN32
687
778
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
688
779
 
@@ -694,13 +785,13 @@ static VALUE rb_query(VALUE self, VALUE sql, VALUE current) {
694
785
 
695
786
  rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
696
787
 
697
- return rb_mysql_client_async_result(self);
788
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
698
789
  }
699
790
  #else
700
791
  do_send_query(&args);
701
792
 
702
793
  /* this will just block until the result is ready */
703
- return rb_ensure(rb_mysql_client_async_result, self, finish_and_mark_inactive, self);
794
+ return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
704
795
  #endif
705
796
  }
706
797
 
@@ -759,7 +850,7 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
759
850
  const void *retval = NULL;
760
851
  unsigned int intval = 0;
761
852
  const char * charval = NULL;
762
- my_bool boolval;
853
+ bool boolval;
763
854
 
764
855
  GET_CLIENT(self);
765
856
 
@@ -794,10 +885,12 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
794
885
  retval = &boolval;
795
886
  break;
796
887
 
888
+ #ifdef MYSQL_SECURE_AUTH
797
889
  case MYSQL_SECURE_AUTH:
798
890
  boolval = (value == Qfalse ? 0 : 1);
799
891
  retval = &boolval;
800
892
  break;
893
+ #endif
801
894
 
802
895
  case MYSQL_READ_DEFAULT_FILE:
803
896
  charval = (const char *)StringValueCStr(value);
@@ -814,6 +907,13 @@ static VALUE _mysql_client_options(VALUE self, int opt, VALUE value) {
814
907
  retval = charval;
815
908
  break;
816
909
 
910
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
911
+ case MYSQL_ENABLE_CLEARTEXT_PLUGIN:
912
+ boolval = (value == Qfalse ? 0 : 1);
913
+ retval = &boolval;
914
+ break;
915
+ #endif
916
+
817
917
  default:
818
918
  return Qfalse;
819
919
  }
@@ -899,15 +999,17 @@ static VALUE rb_mysql_client_server_info(VALUE self) {
899
999
  *
900
1000
  * Return the file descriptor number for this client.
901
1001
  */
902
- static VALUE rb_mysql_client_socket(VALUE self) {
903
1002
  #ifndef _WIN32
1003
+ static VALUE rb_mysql_client_socket(VALUE self) {
904
1004
  GET_CLIENT(self);
905
1005
  REQUIRE_CONNECTED(wrapper);
906
1006
  return INT2NUM(wrapper->client->net.fd);
1007
+ }
907
1008
  #else
1009
+ static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
908
1010
  rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
909
- #endif
910
1011
  }
1012
+ #endif
911
1013
 
912
1014
  /* call-seq:
913
1015
  * client.last_id
@@ -1001,7 +1103,7 @@ static void *nogvl_ping(void *ptr) {
1001
1103
  static VALUE rb_mysql_client_ping(VALUE self) {
1002
1104
  GET_CLIENT(self);
1003
1105
 
1004
- if (!wrapper->connected) {
1106
+ if (!CONNECTED(wrapper)) {
1005
1107
  return Qfalse;
1006
1108
  } else {
1007
1109
  return (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
@@ -1016,10 +1118,10 @@ static VALUE rb_mysql_client_ping(VALUE self) {
1016
1118
  static VALUE rb_mysql_client_more_results(VALUE self)
1017
1119
  {
1018
1120
  GET_CLIENT(self);
1019
- if (mysql_more_results(wrapper->client) == 0)
1020
- return Qfalse;
1021
- else
1022
- return Qtrue;
1121
+ if (mysql_more_results(wrapper->client) == 0)
1122
+ return Qfalse;
1123
+ else
1124
+ return Qtrue;
1023
1125
  }
1024
1126
 
1025
1127
  /* call-seq:
@@ -1067,9 +1169,9 @@ static VALUE rb_mysql_client_store_result(VALUE self)
1067
1169
  }
1068
1170
 
1069
1171
  current = rb_hash_dup(rb_iv_get(self, "@current_query_options"));
1070
- RB_GC_GUARD(current);
1172
+ (void)RB_GC_GUARD(current);
1071
1173
  Check_Type(current, T_HASH);
1072
- resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result);
1174
+ resultObj = rb_mysql_result_to_obj(self, wrapper->encoding, current, result, Qnil);
1073
1175
 
1074
1176
  return resultObj;
1075
1177
  }
@@ -1086,6 +1188,39 @@ static VALUE rb_mysql_client_encoding(VALUE self) {
1086
1188
  }
1087
1189
  #endif
1088
1190
 
1191
+ /* call-seq:
1192
+ * client.automatic_close?
1193
+ *
1194
+ * @return [Boolean]
1195
+ */
1196
+ static VALUE get_automatic_close(VALUE self) {
1197
+ GET_CLIENT(self);
1198
+ return wrapper->automatic_close ? Qtrue : Qfalse;
1199
+ }
1200
+
1201
+ /* call-seq:
1202
+ * client.automatic_close = false
1203
+ *
1204
+ * Set this to +false+ to leave the connection open after it is garbage
1205
+ * collected. To avoid "Aborted connection" errors on the server, explicitly
1206
+ * call +close+ when the connection is no longer needed.
1207
+ *
1208
+ * @see http://dev.mysql.com/doc/en/communication-errors.html
1209
+ */
1210
+ static VALUE set_automatic_close(VALUE self, VALUE value) {
1211
+ GET_CLIENT(self);
1212
+ if (RTEST(value)) {
1213
+ wrapper->automatic_close = 1;
1214
+ } else {
1215
+ #ifndef _WIN32
1216
+ wrapper->automatic_close = 0;
1217
+ #else
1218
+ rb_warn("Connections are always closed by garbage collector on Windows");
1219
+ #endif
1220
+ }
1221
+ return value;
1222
+ }
1223
+
1089
1224
  /* call-seq:
1090
1225
  * client.reconnect = true
1091
1226
  *
@@ -1138,18 +1273,17 @@ static VALUE set_write_timeout(VALUE self, VALUE value) {
1138
1273
  static VALUE set_charset_name(VALUE self, VALUE value) {
1139
1274
  char *charset_name;
1140
1275
  #ifdef HAVE_RUBY_ENCODING_H
1141
- size_t charset_name_len;
1142
1276
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
1143
1277
  rb_encoding *enc;
1144
1278
  VALUE rb_enc;
1145
1279
  #endif
1146
1280
  GET_CLIENT(self);
1147
1281
 
1282
+ Check_Type(value, T_STRING);
1148
1283
  charset_name = RSTRING_PTR(value);
1149
1284
 
1150
1285
  #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);
1286
+ mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
1153
1287
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
1154
1288
  VALUE inspect = rb_inspect(value);
1155
1289
  rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
@@ -1182,7 +1316,12 @@ static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE
1182
1316
  }
1183
1317
 
1184
1318
  static VALUE set_secure_auth(VALUE self, VALUE value) {
1319
+ /* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */
1320
+ #ifdef MYSQL_SECURE_AUTH
1185
1321
  return _mysql_client_options(self, MYSQL_SECURE_AUTH, value);
1322
+ #else
1323
+ return Qfalse;
1324
+ #endif
1186
1325
  }
1187
1326
 
1188
1327
  static VALUE set_read_default_file(VALUE self, VALUE value) {
@@ -1197,19 +1336,39 @@ static VALUE set_init_command(VALUE self, VALUE value) {
1197
1336
  return _mysql_client_options(self, MYSQL_INIT_COMMAND, value);
1198
1337
  }
1199
1338
 
1339
+ static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) {
1340
+ #ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
1341
+ return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value);
1342
+ #else
1343
+ rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library");
1344
+ #endif
1345
+ }
1346
+
1200
1347
  static VALUE initialize_ext(VALUE self) {
1201
1348
  GET_CLIENT(self);
1202
1349
 
1203
1350
  if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
1204
1351
  /* TODO: warning - not enough memory? */
1205
- return rb_raise_mysql2_error(wrapper);
1352
+ rb_raise_mysql2_error(wrapper);
1206
1353
  }
1207
1354
 
1208
1355
  wrapper->initialized = 1;
1209
1356
  return self;
1210
1357
  }
1211
1358
 
1359
+ /* call-seq: client.prepare # => Mysql2::Statement
1360
+ *
1361
+ * Create a new prepared statement.
1362
+ */
1363
+ static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
1364
+ GET_CLIENT(self);
1365
+ REQUIRE_CONNECTED(wrapper);
1366
+
1367
+ return rb_mysql_stmt_new(self, sql);
1368
+ }
1369
+
1212
1370
  void init_mysql2_client() {
1371
+ #ifdef _WIN32
1213
1372
  /* verify the libmysql we're about to use was the version we were built against
1214
1373
  https://github.com/luislavena/mysql-gem/commit/a600a9c459597da0712f70f43736e24b484f8a99 */
1215
1374
  int i;
@@ -1224,15 +1383,14 @@ void init_mysql2_client() {
1224
1383
  }
1225
1384
  if (lib[i] != MYSQL_LINK_VERSION[i]) {
1226
1385
  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
1386
  }
1229
1387
  }
1388
+ #endif
1230
1389
 
1231
1390
  /* Initializing mysql library, so different threads could call Client.new */
1232
1391
  /* without race condition in the library */
1233
1392
  if (mysql_library_init(0, NULL, NULL) != 0) {
1234
1393
  rb_raise(rb_eRuntimeError, "Could not initialize MySQL client library");
1235
- return;
1236
1394
  }
1237
1395
 
1238
1396
  #if 0
@@ -1246,6 +1404,7 @@ void init_mysql2_client() {
1246
1404
  rb_define_singleton_method(cMysql2Client, "info", rb_mysql_client_info, 0);
1247
1405
 
1248
1406
  rb_define_method(cMysql2Client, "close", rb_mysql_client_close, 0);
1407
+ rb_define_method(cMysql2Client, "closed?", rb_mysql_client_closed, 0);
1249
1408
  rb_define_method(cMysql2Client, "abandon_results!", rb_mysql_client_abandon_results, 0);
1250
1409
  rb_define_method(cMysql2Client, "escape", rb_mysql_client_real_escape, 1);
1251
1410
  rb_define_method(cMysql2Client, "server_info", rb_mysql_client_server_info, 0);
@@ -1253,15 +1412,19 @@ void init_mysql2_client() {
1253
1412
  rb_define_method(cMysql2Client, "async_result", rb_mysql_client_async_result, 0);
1254
1413
  rb_define_method(cMysql2Client, "last_id", rb_mysql_client_last_id, 0);
1255
1414
  rb_define_method(cMysql2Client, "affected_rows", rb_mysql_client_affected_rows, 0);
1415
+ rb_define_method(cMysql2Client, "prepare", rb_mysql_client_prepare_statement, 1);
1256
1416
  rb_define_method(cMysql2Client, "thread_id", rb_mysql_client_thread_id, 0);
1257
1417
  rb_define_method(cMysql2Client, "ping", rb_mysql_client_ping, 0);
1258
1418
  rb_define_method(cMysql2Client, "select_db", rb_mysql_client_select_db, 1);
1259
1419
  rb_define_method(cMysql2Client, "more_results?", rb_mysql_client_more_results, 0);
1260
1420
  rb_define_method(cMysql2Client, "next_result", rb_mysql_client_next_result, 0);
1261
1421
  rb_define_method(cMysql2Client, "store_result", rb_mysql_client_store_result, 0);
1422
+ rb_define_method(cMysql2Client, "automatic_close?", get_automatic_close, 0);
1423
+ rb_define_method(cMysql2Client, "automatic_close=", set_automatic_close, 1);
1262
1424
  rb_define_method(cMysql2Client, "reconnect=", set_reconnect, 1);
1263
1425
  rb_define_method(cMysql2Client, "warning_count", rb_mysql_client_warning_count, 0);
1264
1426
  rb_define_method(cMysql2Client, "query_info_string", rb_mysql_info, 0);
1427
+ rb_define_method(cMysql2Client, "ssl_cipher", rb_mysql_get_ssl_cipher, 0);
1265
1428
  #ifdef HAVE_RUBY_ENCODING_H
1266
1429
  rb_define_method(cMysql2Client, "encoding", rb_mysql_client_encoding, 0);
1267
1430
  #endif
@@ -1276,6 +1439,8 @@ void init_mysql2_client() {
1276
1439
  rb_define_private_method(cMysql2Client, "default_group=", set_read_default_group, 1);
1277
1440
  rb_define_private_method(cMysql2Client, "init_command=", set_init_command, 1);
1278
1441
  rb_define_private_method(cMysql2Client, "ssl_set", set_ssl_options, 5);
1442
+ rb_define_private_method(cMysql2Client, "ssl_mode=", rb_set_ssl_mode_option, 1);
1443
+ rb_define_private_method(cMysql2Client, "enable_cleartext_plugin=", set_enable_cleartext_plugin, 1);
1279
1444
  rb_define_private_method(cMysql2Client, "initialize_ext", initialize_ext, 0);
1280
1445
  rb_define_private_method(cMysql2Client, "connect", rb_connect, 7);
1281
1446
  rb_define_private_method(cMysql2Client, "_query", rb_query, 2);
@@ -1289,14 +1454,18 @@ void init_mysql2_client() {
1289
1454
  sym_array = ID2SYM(rb_intern("array"));
1290
1455
  sym_stream = ID2SYM(rb_intern("stream"));
1291
1456
 
1457
+ intern_brackets = rb_intern("[]");
1292
1458
  intern_merge = rb_intern("merge");
1293
1459
  intern_merge_bang = rb_intern("merge!");
1294
- intern_error_number_eql = rb_intern("error_number=");
1295
- intern_sql_state_eql = rb_intern("sql_state=");
1460
+ intern_new_with_args = rb_intern("new_with_args");
1296
1461
 
1297
1462
  #ifdef CLIENT_LONG_PASSWORD
1298
1463
  rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"),
1299
1464
  LONG2NUM(CLIENT_LONG_PASSWORD));
1465
+ #else
1466
+ /* HACK because MariaDB 10.2 no longer defines this constant,
1467
+ * but we're using it in our default connection flags. */
1468
+ rb_const_set(cMysql2Client, rb_intern("LONG_PASSWORD"), INT2NUM(0));
1300
1469
  #endif
1301
1470
 
1302
1471
  #ifdef CLIENT_FOUND_ROWS
@@ -1403,4 +1572,31 @@ void init_mysql2_client() {
1403
1572
  rb_const_set(cMysql2Client, rb_intern("BASIC_FLAGS"),
1404
1573
  LONG2NUM(CLIENT_BASIC_FLAGS));
1405
1574
  #endif
1575
+
1576
+ #if defined(FULL_SSL_MODE_SUPPORT) // MySQL 5.7.11 and above
1577
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1578
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(SSL_MODE_PREFERRED));
1579
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1580
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(SSL_MODE_VERIFY_CA));
1581
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(SSL_MODE_VERIFY_IDENTITY));
1582
+ #elif defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE) // MySQL 5.7.3 - 5.7.10
1583
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(SSL_MODE_DISABLED));
1584
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(SSL_MODE_REQUIRED));
1585
+ #endif
1586
+
1587
+ #ifndef HAVE_CONST_SSL_MODE_DISABLED
1588
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_DISABLED"), INT2NUM(0));
1589
+ #endif
1590
+ #ifndef HAVE_CONST_SSL_MODE_PREFERRED
1591
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_PREFERRED"), INT2NUM(0));
1592
+ #endif
1593
+ #ifndef HAVE_CONST_SSL_MODE_REQUIRED
1594
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_REQUIRED"), INT2NUM(0));
1595
+ #endif
1596
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_CA
1597
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_CA"), INT2NUM(0));
1598
+ #endif
1599
+ #ifndef HAVE_CONST_SSL_MODE_VERIFY_IDENTITY
1600
+ rb_const_set(cMysql2Client, rb_intern("SSL_MODE_VERIFY_IDENTITY"), INT2NUM(0));
1601
+ #endif
1406
1602
  }