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