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.
@@ -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
- 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)
34
32
  end
35
33
 
36
34
  it "should raise an exception on non-string encodings" do
37
- expect {
38
- Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => :fake))
39
- }.to raise_error(TypeError)
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
- Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8"))
45
- }.not_to raise_error
41
+ expect do
42
+ new_client(encoding: "utf8")
43
+ end.not_to raise_error
46
44
 
47
- expect {
48
- Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5"))
49
- }.not_to raise_error
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 :flags => Mysql2::Client::FOUND_ROWS
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 :flags => %w( FOUND_ROWS -PROTOCOL_41 )
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 :flags => "FOUND_ROWS -PROTOCOL_41"
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 = Mysql2::Client.new(options)
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 = Mysql2::Client.new(options)
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
- # rubocop:disable Style/TrailingComma
141
- ssl_client = Mysql2::Client.new(
142
- DatabaseCredentials['root'].merge(
143
- 'host' => 'mysql2gem.example.com', # must match the certificates
144
- :sslkey => '/etc/mysql/client-key.pem',
145
- :sslcert => '/etc/mysql/client-cert.pem',
146
- :sslca => '/etc/mysql/ca-cert.pem',
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
- # rubocop:enable Style/TrailingComma
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
- expect {
175
- Mysql2::Client.new(DatabaseCredentials['root']).close
176
- }.to_not change {
177
- @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
178
- @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
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
- expect {
185
- expect {
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
- }.to change {
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
- }.to_not change {
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
- client = Mysql2::Client.new(DatabaseCredentials['root'])
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 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false))
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 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true))
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 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true))
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 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false))
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 = Mysql2::Client.new(DatabaseCredentials['root'])
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
- # this empty `fork` call fixes this tests on RBX; without it, the next
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
- Mysql2::Client.new(DatabaseCredentials['root'].merge('database' => database))
275
- }.not_to raise_error
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
- }.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)
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
- # http://dev.mysql.com/doc/refman/5.0/en/explain-extended.html
310
- @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')
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(:records => 2, :duplicates => 0, :warnings => 0)
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
- @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true)
348
- local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'"
349
- local_enabled = local.any? { |x| x['Value'] == 'ON' }
350
- pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled
351
-
352
- @client_i.query %[
353
- CREATE TABLE IF NOT EXISTS infileTest (
354
- id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
355
- foo VARCHAR(10),
356
- bar MEDIUMTEXT
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
- @client_i.query "DROP TABLE infileTest"
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 = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => false)
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
- }.to raise_error(Mysql2::Error, /command is not allowed/)
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
- expect {
374
- @client_i.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest"
375
- }.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')
376
437
  end
377
438
 
378
439
  it "should LOAD DATA LOCAL INFILE" do
379
- @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
380
- info = @client_i.query_info
381
- 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)
382
444
 
383
- result = @client_i.query "SELECT * FROM infileTest"
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
- Mysql2::Client.new(DatabaseCredentials['root'].merge(:connect_timeout => -1))
391
- }.to raise_error(Mysql2::Error)
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
- Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => -1))
397
- }.to raise_error(Mysql2::Error)
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
- Mysql2::Client.new(DatabaseCredentials['root'].merge(:write_timeout => -1))
403
- }.to raise_error(Mysql2::Error)
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 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => nil))
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", :stream => true, :cache_rows => false).each.to_a
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", :stream => true, :cache_rows => false)
418
- }.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
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", :stream => true, :cache_rows => false).first
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", :stream => true, :cache_rows => false)
426
- }.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)
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
- }.to raise_error(TypeError)
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", :something => :else
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(:something => :else))
439
- 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))
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.merge!(:something => :else)
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", :as => :array).first).to be_an_instance_of(Array)
463
- @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)
464
546
  end
465
547
 
466
548
  it "should be able to return results with symbolized keys" do
467
- 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)
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
- }.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
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", :async => true)
480
- expect {
580
+ @client.query("SELECT 1", async: true)
581
+ expect do
481
582
  @client.query("SELECT 1")
482
- }.to raise_error(Mysql2::Error)
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", :async => true) }
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 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 0))
494
- expect {
594
+ client = new_client(read_timeout: 0)
595
+ expect do
495
596
  client.query('SELECT SLEEP(0.1)')
496
- }.to raise_error(Mysql2::Error)
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(Fixnum)
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
- }.to raise_error(Mysql2::Error)
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') && Mysql2::Client.info.fetch(:version).start_with?('5.5')
563
- 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.')
564
661
  end
565
662
 
566
- client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true))
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') && Mysql2::Client.info.fetch(:version).start_with?('5.5')
574
- 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.')
575
672
  end
576
673
 
577
- client = Mysql2::Client.new(DatabaseCredentials['root'])
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.times.map do
690
+ threads = Array.new(5) do
594
691
  Thread.new do
595
- client = Mysql2::Client.new(DatabaseCredentials.fetch('root'))
596
- client.query("SELECT SLEEP(#{sleep_time})")
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)", :async => true)).to eql(nil)
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
- loop do
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 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:flags => Mysql2::Client::MULTI_STATEMENTS))
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
- }.to raise_error(Mysql2::Error)
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
- }.to raise_error(Mysql2::Error)
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
- }.not_to raise_error
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
- }.to raise_error(Mysql2::Error)
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
- }.not_to raise_error
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
- }.not_to raise_error
829
+ end.not_to raise_error
738
830
  end
739
831
 
740
- unless RUBY_VERSION =~ /1.8/
741
- it "should carry over the original string's encoding" do
742
- str = "abc'def\"ghi\0jkl%mno"
743
- escaped = Mysql2::Client.escape(str)
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
- str.encode!('us-ascii')
747
- escaped = Mysql2::Client.escape(str)
748
- expect(escaped.encoding).to eql(str.encoding)
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
- }.not_to raise_error
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
- }.not_to raise_error
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
- }.to raise_error(Mysql2::Error)
873
+ end.to raise_error(Mysql2::Error)
784
874
  end
785
875
 
786
876
  context 'when mysql encoding is not utf8' do
787
- before { pending('Encoding is undefined') unless defined?(Encoding) }
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(Fixnum)
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(Fixnum)
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
- }.to raise_error(Mysql2::Error)
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 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
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
- Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
877
- }.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)
878
960
 
879
- expect {
880
- Mysql2::Client.new DatabaseCredentials['root']
881
- }.not_to raise_error
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(Fixnum)
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
- }.to raise_error(Mysql2::Error)
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
- unless RUBY_VERSION =~ /1.8/
983
- it "should respond to #encoding" do
984
- expect(@client).to respond_to(:encoding)
985
- 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)
986
1071
  end
987
1072
  end