mysql2 0.3.21 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -0
- data/README.md +132 -55
- data/examples/eventmachine.rb +1 -1
- data/examples/threaded.rb +4 -6
- data/ext/mysql2/client.c +314 -118
- data/ext/mysql2/client.h +13 -3
- data/ext/mysql2/extconf.rb +111 -44
- data/ext/mysql2/infile.c +2 -2
- data/ext/mysql2/mysql2_ext.c +1 -0
- data/ext/mysql2/mysql2_ext.h +5 -10
- data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
- data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
- data/ext/mysql2/result.c +489 -101
- data/ext/mysql2/result.h +12 -4
- data/ext/mysql2/statement.c +595 -0
- data/ext/mysql2/statement.h +19 -0
- data/lib/mysql2/client.rb +70 -27
- data/lib/mysql2/console.rb +1 -1
- data/lib/mysql2/em.rb +5 -6
- data/lib/mysql2/error.rb +17 -26
- data/lib/mysql2/field.rb +3 -0
- data/lib/mysql2/statement.rb +17 -0
- data/lib/mysql2/version.rb +1 -1
- data/lib/mysql2.rb +37 -35
- data/spec/configuration.yml.example +0 -6
- data/spec/em/em_spec.rb +22 -21
- data/spec/mysql2/client_spec.rb +484 -346
- data/spec/mysql2/error_spec.rb +38 -39
- data/spec/mysql2/result_spec.rb +256 -230
- data/spec/mysql2/statement_spec.rb +776 -0
- data/spec/spec_helper.rb +80 -59
- data/spec/ssl/ca-cert.pem +17 -0
- data/spec/ssl/ca-key.pem +27 -0
- data/spec/ssl/ca.cnf +22 -0
- data/spec/ssl/cert.cnf +22 -0
- data/spec/ssl/client-cert.pem +17 -0
- data/spec/ssl/client-key.pem +27 -0
- data/spec/ssl/client-req.pem +15 -0
- data/spec/ssl/gen_certs.sh +48 -0
- data/spec/ssl/pkcs8-client-key.pem +28 -0
- data/spec/ssl/pkcs8-server-key.pem +28 -0
- data/spec/ssl/server-cert.pem +17 -0
- data/spec/ssl/server-key.pem +27 -0
- data/spec/ssl/server-req.pem +15 -0
- data/support/5072E1F5.asc +432 -0
- data/support/mysql_enc_to_ruby.rb +7 -8
- data/support/ruby_enc_to_mysql.rb +1 -1
- metadata +50 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75b3d925930b92cf7b1a36fa196d334e245919ac
|
4
|
+
data.tar.gz: 2bbe0a78b156f8c5b59643c4d57a7ce19b764bcc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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/
|
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.
|
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.
|
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.
|
453
|
-
* mysql2 0.3.x
|
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
|
data/examples/eventmachine.rb
CHANGED
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
|
-
|
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
|
-
|
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,
|
20
|
+
static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
|
21
21
|
|
22
22
|
#ifndef HAVE_RB_HASH_DUP
|
23
|
-
|
24
|
-
return rb_funcall(rb_cHash,
|
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
|
36
|
-
rb_raise(cMysql2Error, "
|
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
|
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
|
-
#
|
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,
|
140
|
-
|
141
|
-
|
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 =
|
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
|
-
|
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 =
|
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
|
-
|
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
|
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->
|
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
|
-
|
427
|
-
|
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
|
-
|
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
|
-
|
490
|
-
|
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
|
-
|
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 (
|
533
|
-
|
534
|
-
|
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*
|
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
|
-
#
|
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
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
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
|
-
|
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
|
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
|
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,
|
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
|
-
|
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
|
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
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
}
|