mysql2 0.4.6 → 0.5.2
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/README.md +82 -52
- data/examples/eventmachine.rb +0 -2
- data/examples/threaded.rb +2 -4
- data/ext/mysql2/client.c +171 -75
- data/ext/mysql2/client.h +2 -41
- data/ext/mysql2/extconf.rb +30 -23
- data/ext/mysql2/mysql2_ext.c +2 -1
- data/ext/mysql2/mysql2_ext.h +8 -8
- data/ext/mysql2/mysql_enc_to_ruby.h +10 -0
- data/ext/mysql2/result.c +24 -77
- data/ext/mysql2/result.h +2 -3
- data/ext/mysql2/statement.c +101 -73
- data/ext/mysql2/statement.h +0 -2
- data/ext/mysql2/wait_for_single_fd.h +2 -1
- data/lib/mysql2/client.rb +37 -31
- data/lib/mysql2/em.rb +2 -4
- data/lib/mysql2/error.rb +49 -20
- data/lib/mysql2/result.rb +2 -0
- data/lib/mysql2/statement.rb +3 -9
- data/lib/mysql2/version.rb +1 -1
- data/lib/mysql2.rb +14 -15
- data/spec/em/em_spec.rb +6 -6
- data/spec/mysql2/client_spec.rb +300 -215
- data/spec/mysql2/error_spec.rb +3 -9
- data/spec/mysql2/result_spec.rb +124 -158
- data/spec/mysql2/statement_spec.rb +138 -185
- data/spec/spec_helper.rb +79 -61
- data/support/5072E1F5.asc +432 -0
- data/support/mysql_enc_to_ruby.rb +2 -2
- data/support/ruby_enc_to_mysql.rb +5 -5
- metadata +16 -14
data/spec/mysql2/client_spec.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: UTF-8
|
2
1
|
require 'spec_helper'
|
3
2
|
|
4
3
|
RSpec.describe Mysql2::Client do
|
@@ -6,47 +5,46 @@ RSpec.describe Mysql2::Client do
|
|
6
5
|
let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) }
|
7
6
|
|
8
7
|
it "should not raise an exception for valid defaults group" do
|
9
|
-
expect
|
10
|
-
|
11
|
-
|
12
|
-
}.not_to raise_error
|
8
|
+
expect do
|
9
|
+
new_client(default_file: cnf_file, default_group: "test")
|
10
|
+
end.not_to raise_error
|
13
11
|
end
|
14
12
|
|
15
13
|
it "should not raise an exception without default group" do
|
16
|
-
expect
|
17
|
-
|
18
|
-
|
14
|
+
expect do
|
15
|
+
new_client(default_file: cnf_file)
|
16
|
+
end.not_to raise_error
|
19
17
|
end
|
20
18
|
end
|
21
19
|
|
22
|
-
it "should raise
|
23
|
-
expect
|
20
|
+
it "should raise a Mysql::Error::ConnectionError upon connection failure" do
|
21
|
+
expect do
|
24
22
|
# The odd local host IP address forces the mysql client library to
|
25
23
|
# use a TCP socket rather than a domain socket.
|
26
|
-
|
27
|
-
|
24
|
+
new_client('host' => '127.0.0.2', 'port' => 999999)
|
25
|
+
end.to raise_error(Mysql2::Error::ConnectionError)
|
28
26
|
end
|
29
27
|
|
30
28
|
it "should raise an exception on create for invalid encodings" do
|
31
|
-
expect
|
32
|
-
|
33
|
-
|
29
|
+
expect do
|
30
|
+
new_client(encoding: "fake")
|
31
|
+
end.to raise_error(Mysql2::Error)
|
34
32
|
end
|
35
33
|
|
36
34
|
it "should raise an exception on non-string encodings" do
|
37
|
-
expect
|
38
|
-
|
39
|
-
|
35
|
+
expect do
|
36
|
+
new_client(encoding: :fake)
|
37
|
+
end.to raise_error(TypeError)
|
40
38
|
end
|
41
39
|
|
42
40
|
it "should not raise an exception on create for a valid encoding" do
|
43
|
-
expect
|
44
|
-
|
45
|
-
|
41
|
+
expect do
|
42
|
+
new_client(encoding: "utf8")
|
43
|
+
end.not_to raise_error
|
46
44
|
|
47
|
-
expect
|
48
|
-
|
49
|
-
|
45
|
+
expect do
|
46
|
+
new_client(DatabaseCredentials['root'].merge(encoding: "big5"))
|
47
|
+
end.not_to raise_error
|
50
48
|
end
|
51
49
|
|
52
50
|
Klient = Class.new(Mysql2::Client) do
|
@@ -58,18 +56,18 @@ RSpec.describe Mysql2::Client do
|
|
58
56
|
end
|
59
57
|
|
60
58
|
it "should accept connect flags and pass them to #connect" do
|
61
|
-
client = Klient.new :
|
59
|
+
client = Klient.new flags: Mysql2::Client::FOUND_ROWS
|
62
60
|
expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0
|
63
61
|
end
|
64
62
|
|
65
63
|
it "should parse flags array" do
|
66
|
-
client = Klient.new :
|
64
|
+
client = Klient.new flags: %w[FOUND_ROWS -PROTOCOL_41]
|
67
65
|
expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS)
|
68
66
|
expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0)
|
69
67
|
end
|
70
68
|
|
71
69
|
it "should parse flags string" do
|
72
|
-
client = Klient.new :
|
70
|
+
client = Klient.new flags: "FOUND_ROWS -PROTOCOL_41"
|
73
71
|
expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS)
|
74
72
|
expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0)
|
75
73
|
end
|
@@ -81,14 +79,15 @@ RSpec.describe Mysql2::Client do
|
|
81
79
|
Mysql2::Client::LONG_FLAG |
|
82
80
|
Mysql2::Client::TRANSACTIONS |
|
83
81
|
Mysql2::Client::PROTOCOL_41 |
|
84
|
-
Mysql2::Client::SECURE_CONNECTION
|
82
|
+
Mysql2::Client::SECURE_CONNECTION |
|
83
|
+
Mysql2::Client::CONNECT_ATTRS
|
85
84
|
expect(client.connect_args.last[6]).to eql(client_flags)
|
86
85
|
end
|
87
86
|
|
88
87
|
it "should execute init command" do
|
89
88
|
options = DatabaseCredentials['root'].dup
|
90
89
|
options[:init_command] = "SET @something = 'setting_value';"
|
91
|
-
client =
|
90
|
+
client = new_client(options)
|
92
91
|
result = client.query("SELECT @something;")
|
93
92
|
expect(result.first['@something']).to eq('setting_value')
|
94
93
|
end
|
@@ -97,7 +96,7 @@ RSpec.describe Mysql2::Client do
|
|
97
96
|
options = DatabaseCredentials['root'].dup
|
98
97
|
options[:init_command] = "SET @something = 'setting_value';"
|
99
98
|
options[:reconnect] = true
|
100
|
-
client =
|
99
|
+
client = new_client(options)
|
101
100
|
|
102
101
|
result = client.query("SELECT @something;")
|
103
102
|
expect(result.first['@something']).to eq('setting_value')
|
@@ -136,20 +135,16 @@ RSpec.describe Mysql2::Client do
|
|
136
135
|
|
137
136
|
# You may need to adjust the lines below to match your SSL certificate paths
|
138
137
|
ssl_client = nil
|
139
|
-
expect
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
:sslcipher => 'DHE-RSA-AES256-SHA',
|
148
|
-
:sslverify => true
|
149
|
-
)
|
138
|
+
expect do
|
139
|
+
ssl_client = new_client(
|
140
|
+
'host' => 'mysql2gem.example.com', # must match the certificates
|
141
|
+
:sslkey => '/etc/mysql/client-key.pem',
|
142
|
+
:sslcert => '/etc/mysql/client-cert.pem',
|
143
|
+
:sslca => '/etc/mysql/ca-cert.pem',
|
144
|
+
:sslcipher => 'DHE-RSA-AES256-SHA',
|
145
|
+
:sslverify => true,
|
150
146
|
)
|
151
|
-
|
152
|
-
}.not_to raise_error
|
147
|
+
end.not_to raise_error
|
153
148
|
|
154
149
|
results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }]
|
155
150
|
expect(results['Ssl_cipher']).not_to be_empty
|
@@ -157,8 +152,6 @@ RSpec.describe Mysql2::Client do
|
|
157
152
|
|
158
153
|
expect(ssl_client.ssl_cipher).not_to be_empty
|
159
154
|
expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher)
|
160
|
-
|
161
|
-
ssl_client.close
|
162
155
|
end
|
163
156
|
|
164
157
|
def run_gc
|
@@ -171,64 +164,112 @@ RSpec.describe Mysql2::Client do
|
|
171
164
|
end
|
172
165
|
|
173
166
|
it "should terminate connections when calling close" do
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
167
|
+
# rubocop:disable Lint/AmbiguousBlockAssociation
|
168
|
+
expect do
|
169
|
+
client = Mysql2::Client.new(DatabaseCredentials['root'])
|
170
|
+
connection_id = client.thread_id
|
171
|
+
client.close
|
172
|
+
|
173
|
+
# mysql_close sends a quit command without waiting for a response
|
174
|
+
# so give the server some time to handle the detect the closed connection
|
175
|
+
closed = false
|
176
|
+
10.times do
|
177
|
+
closed = @client.query("SHOW PROCESSLIST").none? { |row| row['Id'] == connection_id }
|
178
|
+
break if closed
|
179
|
+
sleep(0.1)
|
180
|
+
end
|
181
|
+
expect(closed).to eq(true)
|
182
|
+
end.to_not change {
|
183
|
+
@client.query("SHOW STATUS LIKE 'Aborted_%'").to_a
|
179
184
|
}
|
185
|
+
# rubocop:enable Lint/AmbiguousBlockAssociation
|
180
186
|
end
|
181
187
|
|
182
188
|
it "should not leave dangling connections after garbage collection" do
|
183
189
|
run_gc
|
184
|
-
|
185
|
-
|
190
|
+
# rubocop:disable Lint/AmbiguousBlockAssociation
|
191
|
+
expect do
|
192
|
+
expect do
|
186
193
|
10.times do
|
187
194
|
Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
|
188
195
|
end
|
189
|
-
|
196
|
+
end.to change {
|
190
197
|
@client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
|
191
198
|
}.by(10)
|
192
199
|
|
193
200
|
run_gc
|
194
|
-
|
201
|
+
end.to_not change {
|
195
202
|
@client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
|
196
203
|
@client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
|
197
204
|
}
|
205
|
+
# rubocop:enable Lint/AmbiguousBlockAssociation
|
206
|
+
end
|
207
|
+
|
208
|
+
context "#set_server_option" do
|
209
|
+
let(:client) do
|
210
|
+
new_client.tap do |client|
|
211
|
+
client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'returns true when multi_statements is enable' do
|
216
|
+
expect(client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)).to be true
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'returns true when multi_statements is disable' do
|
220
|
+
expect(client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)).to be true
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'returns false when multi_statements is neither OPTION_MULTI_STATEMENTS_OFF or OPTION_MULTI_STATEMENTS_ON' do
|
224
|
+
expect(client.set_server_option(344)).to be false
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'enables multiple-statement' do
|
228
|
+
client.query("SELECT 1;SELECT 2;")
|
229
|
+
|
230
|
+
expect(client.next_result).to be true
|
231
|
+
expect(client.store_result.first).to eql('2' => 2)
|
232
|
+
expect(client.next_result).to be false
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'disables multiple-statement' do
|
236
|
+
client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
|
237
|
+
|
238
|
+
expect { client.query("SELECT 1;SELECT 2;") }.to raise_error(Mysql2::Error)
|
239
|
+
end
|
198
240
|
end
|
199
241
|
|
200
242
|
context "#automatic_close" do
|
201
243
|
it "is enabled by default" do
|
202
|
-
|
203
|
-
expect(client.automatic_close?).to be(true)
|
244
|
+
expect(new_client.automatic_close?).to be(true)
|
204
245
|
end
|
205
246
|
|
206
247
|
if RUBY_PLATFORM =~ /mingw|mswin/
|
207
248
|
it "cannot be disabled" do
|
208
249
|
expect do
|
209
|
-
client =
|
250
|
+
client = new_client(automatic_close: false)
|
210
251
|
expect(client.automatic_close?).to be(true)
|
211
252
|
end.to output(/always closed by garbage collector/).to_stderr
|
212
253
|
|
213
254
|
expect do
|
214
|
-
client =
|
255
|
+
client = new_client(automatic_close: true)
|
215
256
|
expect(client.automatic_close?).to be(true)
|
216
257
|
end.to_not output(/always closed by garbage collector/).to_stderr
|
217
258
|
|
218
259
|
expect do
|
219
|
-
client =
|
260
|
+
client = new_client(automatic_close: true)
|
220
261
|
client.automatic_close = false
|
221
262
|
expect(client.automatic_close?).to be(true)
|
222
263
|
end.to output(/always closed by garbage collector/).to_stderr
|
223
264
|
end
|
224
265
|
else
|
225
266
|
it "can be configured" do
|
226
|
-
client =
|
267
|
+
client = new_client(automatic_close: false)
|
227
268
|
expect(client.automatic_close?).to be(false)
|
228
269
|
end
|
229
270
|
|
230
271
|
it "can be assigned" do
|
231
|
-
client =
|
272
|
+
client = new_client
|
232
273
|
client.automatic_close = false
|
233
274
|
expect(client.automatic_close?).to be(false)
|
234
275
|
|
@@ -247,21 +288,18 @@ RSpec.describe Mysql2::Client do
|
|
247
288
|
client = Mysql2::Client.new(DatabaseCredentials['root'])
|
248
289
|
client.automatic_close = false
|
249
290
|
|
250
|
-
|
251
|
-
# `fork` call hangs forever. WTF?
|
252
|
-
fork {}
|
253
|
-
|
254
|
-
fork do
|
291
|
+
child = fork do
|
255
292
|
client.query('SELECT 1')
|
256
293
|
client = nil
|
257
294
|
run_gc
|
258
295
|
end
|
259
296
|
|
260
|
-
Process.wait
|
297
|
+
Process.wait(child)
|
261
298
|
|
262
299
|
# this will throw an error if the underlying socket was shutdown by the
|
263
300
|
# child's GC
|
264
301
|
expect { client.query('SELECT 1') }.to_not raise_exception
|
302
|
+
client.close
|
265
303
|
end
|
266
304
|
end
|
267
305
|
end
|
@@ -270,9 +308,9 @@ RSpec.describe Mysql2::Client do
|
|
270
308
|
database = 1235
|
271
309
|
@client.query "CREATE DATABASE IF NOT EXISTS `#{database}`"
|
272
310
|
|
273
|
-
expect
|
274
|
-
|
275
|
-
|
311
|
+
expect do
|
312
|
+
new_client('database' => database)
|
313
|
+
end.not_to raise_error
|
276
314
|
|
277
315
|
@client.query "DROP DATABASE IF EXISTS `#{database}`"
|
278
316
|
end
|
@@ -283,9 +321,28 @@ RSpec.describe Mysql2::Client do
|
|
283
321
|
|
284
322
|
it "should be able to close properly" do
|
285
323
|
expect(@client.close).to be_nil
|
286
|
-
expect
|
324
|
+
expect do
|
287
325
|
@client.query "SELECT 1"
|
288
|
-
|
326
|
+
end.to raise_error(Mysql2::Error)
|
327
|
+
end
|
328
|
+
|
329
|
+
context "#closed?" do
|
330
|
+
it "should return false when connected" do
|
331
|
+
expect(@client.closed?).to eql(false)
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should return true after close" do
|
335
|
+
@client.close
|
336
|
+
expect(@client.closed?).to eql(true)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
it "should not try to query closed mysql connection" do
|
341
|
+
client = new_client(reconnect: true)
|
342
|
+
expect(client.close).to be_nil
|
343
|
+
expect do
|
344
|
+
client.query "SELECT 1"
|
345
|
+
end.to raise_error(Mysql2::Error)
|
289
346
|
end
|
290
347
|
|
291
348
|
it "should respond to #query" do
|
@@ -306,8 +363,8 @@ RSpec.describe Mysql2::Client do
|
|
306
363
|
context "when has a warnings" do
|
307
364
|
it "should > 0" do
|
308
365
|
# "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS"
|
309
|
-
#
|
310
|
-
@client.query(
|
366
|
+
# https://dev.mysql.com/doc/refman/5.7/en/show-warnings.html
|
367
|
+
@client.query('DROP TABLE IF EXISTS test.no_such_table')
|
311
368
|
expect(@client.warning_count).to be > 0
|
312
369
|
end
|
313
370
|
end
|
@@ -334,7 +391,7 @@ RSpec.describe Mysql2::Client do
|
|
334
391
|
# # Note that mysql_info() returns a non-NULL value for INSERT ... VALUES only for the multiple-row form of the statement (that is, only if multiple value lists are specified).
|
335
392
|
@client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)")
|
336
393
|
|
337
|
-
expect(@client.query_info).to eql(:
|
394
|
+
expect(@client.query_info).to eql(records: 2, duplicates: 0, warnings: 0)
|
338
395
|
expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0')
|
339
396
|
|
340
397
|
@client.query "DROP TABLE infoTest"
|
@@ -344,99 +401,124 @@ RSpec.describe Mysql2::Client do
|
|
344
401
|
|
345
402
|
context ":local_infile" do
|
346
403
|
before(:all) do
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
404
|
+
new_client(local_infile: true) do |client|
|
405
|
+
local = client.query "SHOW VARIABLES LIKE 'local_infile'"
|
406
|
+
local_enabled = local.any? { |x| x['Value'] == 'ON' }
|
407
|
+
skip("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled
|
408
|
+
|
409
|
+
client.query %[
|
410
|
+
CREATE TABLE IF NOT EXISTS infileTest (
|
411
|
+
id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
412
|
+
foo VARCHAR(10),
|
413
|
+
bar MEDIUMTEXT
|
414
|
+
)
|
415
|
+
]
|
416
|
+
end
|
359
417
|
end
|
360
418
|
|
361
419
|
after(:all) do
|
362
|
-
|
420
|
+
new_client do |client|
|
421
|
+
client.query "DROP TABLE IF EXISTS infileTest"
|
422
|
+
end
|
363
423
|
end
|
364
424
|
|
365
425
|
it "should raise an error when local_infile is disabled" do
|
366
|
-
client =
|
367
|
-
expect
|
426
|
+
client = new_client(local_infile: false)
|
427
|
+
expect do
|
368
428
|
client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
|
369
|
-
|
429
|
+
end.to raise_error(Mysql2::Error, /command is not allowed/)
|
370
430
|
end
|
371
431
|
|
372
432
|
it "should raise an error when a non-existent file is loaded" do
|
373
|
-
|
374
|
-
|
375
|
-
|
433
|
+
client = new_client(local_infile: true)
|
434
|
+
expect do
|
435
|
+
client.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest"
|
436
|
+
end.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here')
|
376
437
|
end
|
377
438
|
|
378
439
|
it "should LOAD DATA LOCAL INFILE" do
|
379
|
-
|
380
|
-
|
381
|
-
|
440
|
+
client = new_client(local_infile: true)
|
441
|
+
client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
|
442
|
+
info = client.query_info
|
443
|
+
expect(info).to eql(records: 1, deleted: 0, skipped: 0, warnings: 0)
|
382
444
|
|
383
|
-
result =
|
445
|
+
result = client.query "SELECT * FROM infileTest"
|
384
446
|
expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World')
|
385
447
|
end
|
386
448
|
end
|
387
449
|
|
388
450
|
it "should expect connect_timeout to be a positive integer" do
|
389
|
-
expect
|
390
|
-
|
391
|
-
|
451
|
+
expect do
|
452
|
+
new_client(connect_timeout: -1)
|
453
|
+
end.to raise_error(Mysql2::Error)
|
392
454
|
end
|
393
455
|
|
394
456
|
it "should expect read_timeout to be a positive integer" do
|
395
|
-
expect
|
396
|
-
|
397
|
-
|
457
|
+
expect do
|
458
|
+
new_client(read_timeout: -1)
|
459
|
+
end.to raise_error(Mysql2::Error)
|
398
460
|
end
|
399
461
|
|
400
462
|
it "should expect write_timeout to be a positive integer" do
|
401
|
-
expect
|
402
|
-
|
403
|
-
|
463
|
+
expect do
|
464
|
+
new_client(write_timeout: -1)
|
465
|
+
end.to raise_error(Mysql2::Error)
|
404
466
|
end
|
405
467
|
|
406
468
|
it "should allow nil read_timeout" do
|
407
|
-
client =
|
469
|
+
client = new_client(read_timeout: nil)
|
408
470
|
|
409
471
|
expect(client.read_timeout).to be_nil
|
410
472
|
end
|
411
473
|
|
474
|
+
it "should set default program_name in connect_attrs" do
|
475
|
+
client = new_client
|
476
|
+
if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/)
|
477
|
+
pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.')
|
478
|
+
end
|
479
|
+
result = client.query("SELECT attr_value FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id() AND attr_name = 'program_name'")
|
480
|
+
expect(result.first['attr_value']).to eq($PROGRAM_NAME)
|
481
|
+
end
|
482
|
+
|
483
|
+
it "should set custom connect_attrs" do
|
484
|
+
client = new_client(connect_attrs: { program_name: 'my_program_name', foo: 'fooval', bar: 'barval' })
|
485
|
+
if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/)
|
486
|
+
pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.')
|
487
|
+
end
|
488
|
+
results = Hash[client.query("SELECT * FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id()").map { |x| x.values_at('ATTR_NAME', 'ATTR_VALUE') }]
|
489
|
+
expect(results['program_name']).to eq('my_program_name')
|
490
|
+
expect(results['foo']).to eq('fooval')
|
491
|
+
expect(results['bar']).to eq('barval')
|
492
|
+
end
|
493
|
+
|
412
494
|
context "#query" do
|
413
495
|
it "should let you query again if iterating is finished when streaming" do
|
414
|
-
@client.query("SELECT 1 UNION SELECT 2", :
|
496
|
+
@client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false).each.to_a
|
415
497
|
|
416
|
-
expect
|
417
|
-
@client.query("SELECT 1 UNION SELECT 2", :
|
418
|
-
|
498
|
+
expect do
|
499
|
+
@client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false)
|
500
|
+
end.to_not raise_error
|
419
501
|
end
|
420
502
|
|
421
503
|
it "should not let you query again if iterating is not finished when streaming" do
|
422
|
-
@client.query("SELECT 1 UNION SELECT 2", :
|
504
|
+
@client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false).first
|
423
505
|
|
424
|
-
expect
|
425
|
-
@client.query("SELECT 1 UNION SELECT 2", :
|
426
|
-
|
506
|
+
expect do
|
507
|
+
@client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false)
|
508
|
+
end.to raise_exception(Mysql2::Error)
|
427
509
|
end
|
428
510
|
|
429
511
|
it "should only accept strings as the query parameter" do
|
430
|
-
expect
|
512
|
+
expect do
|
431
513
|
@client.query ["SELECT 'not right'"]
|
432
|
-
|
514
|
+
end.to raise_error(TypeError)
|
433
515
|
end
|
434
516
|
|
435
517
|
it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do
|
436
|
-
result = @client.query "SELECT 1", :
|
518
|
+
result = @client.query "SELECT 1", something: :else
|
437
519
|
expect(@client.query_options[:something]).to be_nil
|
438
|
-
expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(:
|
439
|
-
expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(:
|
520
|
+
expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(something: :else))
|
521
|
+
expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(something: :else))
|
440
522
|
|
441
523
|
result = @client.query "SELECT 1"
|
442
524
|
expect(result.instance_variable_get('@query_options')).to eql(@client.query_options)
|
@@ -444,7 +526,7 @@ RSpec.describe Mysql2::Client do
|
|
444
526
|
end
|
445
527
|
|
446
528
|
it "should allow changing query options for subsequent queries" do
|
447
|
-
@client.query_options
|
529
|
+
@client.query_options[:something] = :else
|
448
530
|
result = @client.query "SELECT 1"
|
449
531
|
expect(@client.query_options[:something]).to eql(:else)
|
450
532
|
expect(result.instance_variable_get('@query_options')[:something]).to eql(:else)
|
@@ -459,41 +541,60 @@ RSpec.describe Mysql2::Client do
|
|
459
541
|
end
|
460
542
|
|
461
543
|
it "should be able to return results as an array" do
|
462
|
-
expect(@client.query("SELECT 1", :
|
463
|
-
@client.query("SELECT 1").each(:
|
544
|
+
expect(@client.query("SELECT 1", as: :array).first).to be_an_instance_of(Array)
|
545
|
+
@client.query("SELECT 1").each(as: :array)
|
464
546
|
end
|
465
547
|
|
466
548
|
it "should be able to return results with symbolized keys" do
|
467
|
-
expect(@client.query("SELECT 1", :
|
549
|
+
expect(@client.query("SELECT 1", symbolize_keys: true).first.keys[0]).to be_an_instance_of(Symbol)
|
468
550
|
end
|
469
551
|
|
470
552
|
it "should require an open connection" do
|
471
553
|
@client.close
|
472
|
-
expect
|
554
|
+
expect do
|
473
555
|
@client.query "SELECT 1"
|
474
|
-
|
556
|
+
end.to raise_error(Mysql2::Error)
|
557
|
+
end
|
558
|
+
|
559
|
+
it "should detect closed connection on query read error" do
|
560
|
+
connection_id = @client.thread_id
|
561
|
+
Thread.new do
|
562
|
+
sleep(0.1)
|
563
|
+
Mysql2::Client.new(DatabaseCredentials['root']).tap do |supervisor|
|
564
|
+
supervisor.query("KILL #{connection_id}")
|
565
|
+
end.close
|
566
|
+
end
|
567
|
+
expect do
|
568
|
+
@client.query("SELECT SLEEP(1)")
|
569
|
+
end.to raise_error(Mysql2::Error, /Lost connection to MySQL server/)
|
570
|
+
|
571
|
+
if RUBY_PLATFORM !~ /mingw|mswin/
|
572
|
+
expect do
|
573
|
+
@client.socket
|
574
|
+
end.to raise_error(Mysql2::Error, 'MySQL client is not connected')
|
575
|
+
end
|
475
576
|
end
|
476
577
|
|
477
578
|
if RUBY_PLATFORM !~ /mingw|mswin/
|
478
579
|
it "should not allow another query to be sent without fetching a result first" do
|
479
|
-
@client.query("SELECT 1", :
|
480
|
-
expect
|
580
|
+
@client.query("SELECT 1", async: true)
|
581
|
+
expect do
|
481
582
|
@client.query("SELECT 1")
|
482
|
-
|
583
|
+
end.to raise_error(Mysql2::Error)
|
483
584
|
end
|
484
585
|
|
485
586
|
it "should describe the thread holding the active query" do
|
486
|
-
thr = Thread.new { @client.query("SELECT 1", :
|
587
|
+
thr = Thread.new { @client.query("SELECT 1", async: true) }
|
487
588
|
|
488
589
|
thr.join
|
489
590
|
expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect)))
|
490
591
|
end
|
491
592
|
|
492
593
|
it "should timeout if we wait longer than :read_timeout" do
|
493
|
-
client =
|
494
|
-
expect
|
594
|
+
client = new_client(read_timeout: 0)
|
595
|
+
expect do
|
495
596
|
client.query('SELECT SLEEP(0.1)')
|
496
|
-
|
597
|
+
end.to raise_error(Mysql2::Error::TimeoutError)
|
497
598
|
end
|
498
599
|
|
499
600
|
# XXX this test is not deterministic (because Unix signal handling is not)
|
@@ -527,22 +628,18 @@ RSpec.describe Mysql2::Client do
|
|
527
628
|
end
|
528
629
|
|
529
630
|
it "#socket should return a Fixnum (file descriptor from C)" do
|
530
|
-
expect(@client.socket).to be_an_instance_of(
|
631
|
+
expect(@client.socket).to be_an_instance_of(0.class)
|
531
632
|
expect(@client.socket).not_to eql(0)
|
532
633
|
end
|
533
634
|
|
534
635
|
it "#socket should require an open connection" do
|
535
636
|
@client.close
|
536
|
-
expect
|
637
|
+
expect do
|
537
638
|
@client.socket
|
538
|
-
|
639
|
+
end.to raise_error(Mysql2::Error)
|
539
640
|
end
|
540
641
|
|
541
642
|
it 'should be impervious to connection-corrupting timeouts in #execute' do
|
542
|
-
# the statement handle gets corrupted and will segfault the tests if interrupted,
|
543
|
-
# so we can't even use pending on this test, really have to skip it on older Rubies.
|
544
|
-
skip('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt)
|
545
|
-
|
546
643
|
# attempt to break the connection
|
547
644
|
stmt = @client.prepare('SELECT SLEEP(?)')
|
548
645
|
expect { Timeout.timeout(0.1) { stmt.execute(0.2) } }.to raise_error(Timeout::Error)
|
@@ -559,22 +656,22 @@ RSpec.describe Mysql2::Client do
|
|
559
656
|
end
|
560
657
|
|
561
658
|
it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
|
562
|
-
if RUBY_PLATFORM.include?('darwin') &&
|
563
|
-
pending('
|
659
|
+
if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5')
|
660
|
+
pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.')
|
564
661
|
end
|
565
662
|
|
566
|
-
client =
|
663
|
+
client = new_client(reconnect: true)
|
567
664
|
|
568
665
|
expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
|
569
666
|
expect { client.query('SELECT 1') }.to_not raise_error
|
570
667
|
end
|
571
668
|
|
572
669
|
it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do
|
573
|
-
if RUBY_PLATFORM.include?('darwin') &&
|
574
|
-
pending('
|
670
|
+
if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5')
|
671
|
+
pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.')
|
575
672
|
end
|
576
673
|
|
577
|
-
client =
|
674
|
+
client = new_client
|
578
675
|
|
579
676
|
expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
|
580
677
|
expect { client.query('SELECT 1') }.to raise_error(Mysql2::Error)
|
@@ -590,10 +687,11 @@ RSpec.describe Mysql2::Client do
|
|
590
687
|
sleep_time = 0.5
|
591
688
|
|
592
689
|
# Note that each thread opens its own database connection
|
593
|
-
threads = 5
|
690
|
+
threads = Array.new(5) do
|
594
691
|
Thread.new do
|
595
|
-
|
596
|
-
|
692
|
+
new_client do |client|
|
693
|
+
client.query("SELECT SLEEP(#{sleep_time})")
|
694
|
+
end
|
597
695
|
Thread.current.object_id
|
598
696
|
end
|
599
697
|
end
|
@@ -607,17 +705,11 @@ RSpec.describe Mysql2::Client do
|
|
607
705
|
|
608
706
|
it "evented async queries should be supported" do
|
609
707
|
# should immediately return nil
|
610
|
-
expect(@client.query("SELECT sleep(0.1)", :
|
708
|
+
expect(@client.query("SELECT sleep(0.1)", async: true)).to eql(nil)
|
611
709
|
|
612
|
-
io_wrapper = IO.for_fd(@client.socket)
|
710
|
+
io_wrapper = IO.for_fd(@client.socket, autoclose: false)
|
613
711
|
loops = 0
|
614
|
-
|
615
|
-
if IO.select([io_wrapper], nil, nil, 0.05)
|
616
|
-
break
|
617
|
-
else
|
618
|
-
loops += 1
|
619
|
-
end
|
620
|
-
end
|
712
|
+
loops += 1 until IO.select([io_wrapper], nil, nil, 0.05)
|
621
713
|
|
622
714
|
# make sure we waited some period of time
|
623
715
|
expect(loops >= 1).to be true
|
@@ -629,15 +721,15 @@ RSpec.describe Mysql2::Client do
|
|
629
721
|
|
630
722
|
context "Multiple results sets" do
|
631
723
|
before(:each) do
|
632
|
-
@multi_client =
|
724
|
+
@multi_client = new_client(flags: Mysql2::Client::MULTI_STATEMENTS)
|
633
725
|
end
|
634
726
|
|
635
727
|
it "should raise an exception when one of multiple statements fails" do
|
636
728
|
result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';")
|
637
729
|
expect(result.first['set_1']).to be(1)
|
638
|
-
expect
|
730
|
+
expect do
|
639
731
|
@multi_client.next_result
|
640
|
-
|
732
|
+
end.to raise_error(Mysql2::Error)
|
641
733
|
expect(@multi_client.next_result).to be false
|
642
734
|
end
|
643
735
|
|
@@ -659,17 +751,17 @@ RSpec.describe Mysql2::Client do
|
|
659
751
|
|
660
752
|
it "will raise on query if there are outstanding results to read" do
|
661
753
|
@multi_client.query("SELECT 1; SELECT 2; SELECT 3")
|
662
|
-
expect
|
754
|
+
expect do
|
663
755
|
@multi_client.query("SELECT 4")
|
664
|
-
|
756
|
+
end.to raise_error(Mysql2::Error)
|
665
757
|
end
|
666
758
|
|
667
759
|
it "#abandon_results! should work" do
|
668
760
|
@multi_client.query("SELECT 1; SELECT 2; SELECT 3")
|
669
761
|
@multi_client.abandon_results!
|
670
|
-
expect
|
762
|
+
expect do
|
671
763
|
@multi_client.query("SELECT 4")
|
672
|
-
|
764
|
+
end.not_to raise_error
|
673
765
|
end
|
674
766
|
|
675
767
|
it "#more_results? should work" do
|
@@ -705,9 +797,9 @@ RSpec.describe Mysql2::Client do
|
|
705
797
|
|
706
798
|
if RUBY_PLATFORM =~ /mingw|mswin/
|
707
799
|
it "#socket should raise as it's not supported" do
|
708
|
-
expect
|
800
|
+
expect do
|
709
801
|
@client.socket
|
710
|
-
|
802
|
+
end.to raise_error(Mysql2::Error, /Raw access to the mysql file descriptor isn't supported on Windows/)
|
711
803
|
end
|
712
804
|
end
|
713
805
|
|
@@ -726,27 +818,25 @@ RSpec.describe Mysql2::Client do
|
|
726
818
|
end
|
727
819
|
|
728
820
|
it "should not overflow the thread stack" do
|
729
|
-
expect
|
821
|
+
expect do
|
730
822
|
Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join
|
731
|
-
|
823
|
+
end.not_to raise_error
|
732
824
|
end
|
733
825
|
|
734
826
|
it "should not overflow the process stack" do
|
735
|
-
expect
|
827
|
+
expect do
|
736
828
|
Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join
|
737
|
-
|
829
|
+
end.not_to raise_error
|
738
830
|
end
|
739
831
|
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
expect(escaped.encoding).to eql(str.encoding)
|
832
|
+
it "should carry over the original string's encoding" do
|
833
|
+
str = "abc'def\"ghi\0jkl%mno"
|
834
|
+
escaped = Mysql2::Client.escape(str)
|
835
|
+
expect(escaped.encoding).to eql(str.encoding)
|
745
836
|
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
end
|
837
|
+
str.encode!('us-ascii')
|
838
|
+
escaped = Mysql2::Client.escape(str)
|
839
|
+
expect(escaped.encoding).to eql(str.encoding)
|
750
840
|
end
|
751
841
|
end
|
752
842
|
|
@@ -765,28 +855,26 @@ RSpec.describe Mysql2::Client do
|
|
765
855
|
end
|
766
856
|
|
767
857
|
it "should not overflow the thread stack" do
|
768
|
-
expect
|
858
|
+
expect do
|
769
859
|
Thread.new { @client.escape("'" * 256 * 1024) }.join
|
770
|
-
|
860
|
+
end.not_to raise_error
|
771
861
|
end
|
772
862
|
|
773
863
|
it "should not overflow the process stack" do
|
774
|
-
expect
|
864
|
+
expect do
|
775
865
|
Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
|
776
|
-
|
866
|
+
end.not_to raise_error
|
777
867
|
end
|
778
868
|
|
779
869
|
it "should require an open connection" do
|
780
870
|
@client.close
|
781
|
-
expect
|
871
|
+
expect do
|
782
872
|
@client.escape ""
|
783
|
-
|
873
|
+
end.to raise_error(Mysql2::Error)
|
784
874
|
end
|
785
875
|
|
786
876
|
context 'when mysql encoding is not utf8' do
|
787
|
-
|
788
|
-
|
789
|
-
let(:client) { Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "ujis")) }
|
877
|
+
let(:client) { new_client(encoding: "ujis") }
|
790
878
|
|
791
879
|
it 'should return a internal encoding string if Encoding.default_internal is set' do
|
792
880
|
with_internal_encoding Encoding::UTF_8 do
|
@@ -805,14 +893,12 @@ RSpec.describe Mysql2::Client do
|
|
805
893
|
info = @client.info
|
806
894
|
expect(info).to be_an_instance_of(Hash)
|
807
895
|
expect(info).to have_key(:id)
|
808
|
-
expect(info[:id]).to be_an_instance_of(
|
896
|
+
expect(info[:id]).to be_an_instance_of(0.class)
|
809
897
|
expect(info).to have_key(:version)
|
810
898
|
expect(info[:version]).to be_an_instance_of(String)
|
811
899
|
end
|
812
900
|
|
813
901
|
context "strings returned by #info" do
|
814
|
-
before { pending('Encoding is undefined') unless defined?(Encoding) }
|
815
|
-
|
816
902
|
it "should be tagged as ascii" do
|
817
903
|
expect(@client.info[:version].encoding).to eql(Encoding::US_ASCII)
|
818
904
|
expect(@client.info[:header_version].encoding).to eql(Encoding::US_ASCII)
|
@@ -820,8 +906,6 @@ RSpec.describe Mysql2::Client do
|
|
820
906
|
end
|
821
907
|
|
822
908
|
context "strings returned by .info" do
|
823
|
-
before { pending('Encoding is undefined') unless defined?(Encoding) }
|
824
|
-
|
825
909
|
it "should be tagged as ascii" do
|
826
910
|
expect(Mysql2::Client.info[:version].encoding).to eql(Encoding::US_ASCII)
|
827
911
|
expect(Mysql2::Client.info[:header_version].encoding).to eql(Encoding::US_ASCII)
|
@@ -836,26 +920,24 @@ RSpec.describe Mysql2::Client do
|
|
836
920
|
server_info = @client.server_info
|
837
921
|
expect(server_info).to be_an_instance_of(Hash)
|
838
922
|
expect(server_info).to have_key(:id)
|
839
|
-
expect(server_info[:id]).to be_an_instance_of(
|
923
|
+
expect(server_info[:id]).to be_an_instance_of(0.class)
|
840
924
|
expect(server_info).to have_key(:version)
|
841
925
|
expect(server_info[:version]).to be_an_instance_of(String)
|
842
926
|
end
|
843
927
|
|
844
928
|
it "#server_info should require an open connection" do
|
845
929
|
@client.close
|
846
|
-
expect
|
930
|
+
expect do
|
847
931
|
@client.server_info
|
848
|
-
|
932
|
+
end.to raise_error(Mysql2::Error)
|
849
933
|
end
|
850
934
|
|
851
935
|
context "strings returned by #server_info" do
|
852
|
-
before { pending('Encoding is undefined') unless defined?(Encoding) }
|
853
|
-
|
854
936
|
it "should default to the connection's encoding if Encoding.default_internal is nil" do
|
855
937
|
with_internal_encoding nil do
|
856
938
|
expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8)
|
857
939
|
|
858
|
-
client2 =
|
940
|
+
client2 = new_client(encoding: 'ascii')
|
859
941
|
expect(client2.server_info[:version].encoding).to eql(Encoding::ASCII)
|
860
942
|
end
|
861
943
|
end
|
@@ -871,14 +953,14 @@ RSpec.describe Mysql2::Client do
|
|
871
953
|
end
|
872
954
|
end
|
873
955
|
|
874
|
-
it "should raise a Mysql2::Error exception upon connection failure" do
|
875
|
-
expect
|
876
|
-
|
877
|
-
|
956
|
+
it "should raise a Mysql2::Error::ConnectionError exception upon connection failure due to invalid credentials" do
|
957
|
+
expect do
|
958
|
+
new_client(host: 'localhost', username: 'asdfasdf8d2h', password: 'asdfasdfw42')
|
959
|
+
end.to raise_error(Mysql2::Error::ConnectionError)
|
878
960
|
|
879
|
-
expect
|
880
|
-
|
881
|
-
|
961
|
+
expect do
|
962
|
+
new_client(DatabaseCredentials['root'])
|
963
|
+
end.not_to raise_error
|
882
964
|
end
|
883
965
|
|
884
966
|
context 'write operations api' do
|
@@ -927,7 +1009,7 @@ RSpec.describe Mysql2::Client do
|
|
927
1009
|
end
|
928
1010
|
|
929
1011
|
it "#thread_id should be a Fixnum" do
|
930
|
-
expect(@client.thread_id).to be_an_instance_of(
|
1012
|
+
expect(@client.thread_id).to be_an_instance_of(0.class)
|
931
1013
|
end
|
932
1014
|
|
933
1015
|
it "should respond to #ping" do
|
@@ -963,9 +1045,9 @@ RSpec.describe Mysql2::Client do
|
|
963
1045
|
end
|
964
1046
|
|
965
1047
|
it "should raise a Mysql2::Error when the database doesn't exist" do
|
966
|
-
expect
|
1048
|
+
expect do
|
967
1049
|
@client.select_db("nopenothere")
|
968
|
-
|
1050
|
+
end.to raise_error(Mysql2::Error)
|
969
1051
|
end
|
970
1052
|
|
971
1053
|
it "should return the database switched to" do
|
@@ -979,9 +1061,12 @@ RSpec.describe Mysql2::Client do
|
|
979
1061
|
expect(@client.ping).to eql(false)
|
980
1062
|
end
|
981
1063
|
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
1064
|
+
it "should be able to connect using plaintext password" do
|
1065
|
+
client = new_client(enable_cleartext_plugin: true)
|
1066
|
+
client.query('SELECT 1')
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
it "should respond to #encoding" do
|
1070
|
+
expect(@client).to respond_to(:encoding)
|
986
1071
|
end
|
987
1072
|
end
|