mysql2 0.3.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -220
  3. data/LICENSE +21 -0
  4. data/README.md +370 -79
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +1017 -305
  8. data/ext/mysql2/client.h +35 -11
  9. data/ext/mysql2/extconf.rb +222 -34
  10. data/ext/mysql2/infile.c +122 -0
  11. data/ext/mysql2/infile.h +1 -0
  12. data/ext/mysql2/mysql2_ext.c +1 -0
  13. data/ext/mysql2/mysql2_ext.h +12 -14
  14. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  15. data/ext/mysql2/mysql_enc_to_ruby.h +249 -0
  16. data/ext/mysql2/result.c +664 -166
  17. data/ext/mysql2/result.h +16 -6
  18. data/ext/mysql2/statement.c +595 -0
  19. data/ext/mysql2/statement.h +19 -0
  20. data/lib/mysql2/client.rb +118 -211
  21. data/lib/mysql2/console.rb +5 -0
  22. data/lib/mysql2/em.rb +23 -5
  23. data/lib/mysql2/error.rb +62 -6
  24. data/lib/mysql2/field.rb +3 -0
  25. data/lib/mysql2/statement.rb +17 -0
  26. data/lib/mysql2/version.rb +1 -1
  27. data/lib/mysql2.rb +66 -3
  28. data/spec/configuration.yml.example +11 -0
  29. data/spec/em/em_spec.rb +96 -10
  30. data/spec/my.cnf.example +9 -0
  31. data/spec/mysql2/client_spec.rb +779 -205
  32. data/spec/mysql2/error_spec.rb +58 -45
  33. data/spec/mysql2/result_spec.rb +316 -159
  34. data/spec/mysql2/statement_spec.rb +776 -0
  35. data/spec/spec_helper.rb +97 -56
  36. data/spec/ssl/ca-cert.pem +17 -0
  37. data/spec/ssl/ca-key.pem +27 -0
  38. data/spec/ssl/ca.cnf +22 -0
  39. data/spec/ssl/cert.cnf +22 -0
  40. data/spec/ssl/client-cert.pem +17 -0
  41. data/spec/ssl/client-key.pem +27 -0
  42. data/spec/ssl/client-req.pem +15 -0
  43. data/spec/ssl/gen_certs.sh +48 -0
  44. data/spec/ssl/pkcs8-client-key.pem +28 -0
  45. data/spec/ssl/pkcs8-server-key.pem +28 -0
  46. data/spec/ssl/server-cert.pem +17 -0
  47. data/spec/ssl/server-key.pem +27 -0
  48. data/spec/ssl/server-req.pem +15 -0
  49. data/spec/test_data +1 -0
  50. data/support/5072E1F5.asc +432 -0
  51. data/support/libmysql.def +219 -0
  52. data/support/mysql_enc_to_ruby.rb +81 -0
  53. data/support/ruby_enc_to_mysql.rb +61 -0
  54. metadata +77 -196
  55. data/.gitignore +0 -12
  56. data/.rspec +0 -3
  57. data/.rvmrc +0 -1
  58. data/.travis.yml +0 -7
  59. data/Gemfile +0 -3
  60. data/MIT-LICENSE +0 -20
  61. data/Rakefile +0 -5
  62. data/benchmark/active_record.rb +0 -51
  63. data/benchmark/active_record_threaded.rb +0 -42
  64. data/benchmark/allocations.rb +0 -33
  65. data/benchmark/escape.rb +0 -36
  66. data/benchmark/query_with_mysql_casting.rb +0 -80
  67. data/benchmark/query_without_mysql_casting.rb +0 -56
  68. data/benchmark/sequel.rb +0 -37
  69. data/benchmark/setup_db.rb +0 -119
  70. data/benchmark/threaded.rb +0 -44
  71. data/mysql2.gemspec +0 -29
  72. data/tasks/benchmarks.rake +0 -20
  73. data/tasks/compile.rake +0 -71
  74. data/tasks/rspec.rake +0 -16
  75. data/tasks/vendor_mysql.rake +0 -40
@@ -1,228 +1,662 @@
1
1
  # encoding: UTF-8
2
2
  require 'spec_helper'
3
3
 
4
- describe Mysql2::Client do
5
- before(:each) do
6
- @client = Mysql2::Client.new
4
+ RSpec.describe Mysql2::Client do
5
+ context "using defaults file" do
6
+ let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) }
7
+
8
+ it "should not raise an exception for valid defaults group" do
9
+ expect {
10
+ new_client(:default_file => cnf_file, :default_group => "test")
11
+ }.not_to raise_error
12
+ end
13
+
14
+ it "should not raise an exception without default group" do
15
+ expect {
16
+ new_client(:default_file => cnf_file)
17
+ }.not_to raise_error
18
+ end
19
+ end
20
+
21
+ it "should raise an exception upon connection failure" do
22
+ expect {
23
+ # The odd local host IP address forces the mysql client library to
24
+ # use a TCP socket rather than a domain socket.
25
+ new_client('host' => '127.0.0.2', 'port' => 999999)
26
+ }.to raise_error(Mysql2::Error)
27
+ end
28
+
29
+ it "should raise an exception on create for invalid encodings" do
30
+ expect {
31
+ new_client(:encoding => "fake")
32
+ }.to raise_error(Mysql2::Error)
7
33
  end
8
34
 
9
- if defined? Encoding
10
- it "should raise an exception on create for invalid encodings" do
11
- lambda {
12
- c = Mysql2::Client.new(:encoding => "fake")
13
- }.should raise_error(Mysql2::Error)
35
+ it "should raise an exception on non-string encodings" do
36
+ expect {
37
+ new_client(:encoding => :fake)
38
+ }.to raise_error(TypeError)
39
+ end
40
+
41
+ it "should not raise an exception on create for a valid encoding" do
42
+ expect {
43
+ new_client(:encoding => "utf8")
44
+ }.not_to raise_error
45
+
46
+ expect {
47
+ new_client(DatabaseCredentials['root'].merge(:encoding => "big5"))
48
+ }.not_to raise_error
49
+ end
50
+
51
+ Klient = Class.new(Mysql2::Client) do
52
+ attr_reader :connect_args
53
+ def connect(*args)
54
+ @connect_args ||= []
55
+ @connect_args << args
14
56
  end
15
57
  end
16
58
 
17
59
  it "should accept connect flags and pass them to #connect" do
18
- klient = Class.new(Mysql2::Client) do
19
- attr_reader :connect_args
20
- def connect *args
21
- @connect_args ||= []
22
- @connect_args << args
23
- end
24
- end
25
- client = klient.new :flags => Mysql2::Client::FOUND_ROWS
26
- (client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true
60
+ client = Klient.new :flags => Mysql2::Client::FOUND_ROWS
61
+ expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0
62
+ end
63
+
64
+ it "should parse flags array" do
65
+ client = Klient.new :flags => %w( FOUND_ROWS -PROTOCOL_41 )
66
+ expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS)
67
+ expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0)
68
+ end
69
+
70
+ it "should parse flags string" do
71
+ client = Klient.new :flags => "FOUND_ROWS -PROTOCOL_41"
72
+ expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS)
73
+ expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0)
27
74
  end
28
75
 
29
76
  it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
30
- klient = Class.new(Mysql2::Client) do
31
- attr_reader :connect_args
32
- def connect *args
33
- @connect_args ||= []
34
- @connect_args << args
35
- end
36
- end
37
- client = klient.new
38
- (client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS |
39
- Mysql2::Client::LONG_PASSWORD |
40
- Mysql2::Client::LONG_FLAG |
41
- Mysql2::Client::TRANSACTIONS |
42
- Mysql2::Client::PROTOCOL_41 |
43
- Mysql2::Client::SECURE_CONNECTION)).should be_true
77
+ client = Klient.new
78
+ client_flags = Mysql2::Client::REMEMBER_OPTIONS |
79
+ Mysql2::Client::LONG_PASSWORD |
80
+ Mysql2::Client::LONG_FLAG |
81
+ Mysql2::Client::TRANSACTIONS |
82
+ Mysql2::Client::PROTOCOL_41 |
83
+ Mysql2::Client::SECURE_CONNECTION
84
+ expect(client.connect_args.last[6]).to eql(client_flags)
85
+ end
86
+
87
+ it "should execute init command" do
88
+ options = DatabaseCredentials['root'].dup
89
+ options[:init_command] = "SET @something = 'setting_value';"
90
+ client = new_client(options)
91
+ result = client.query("SELECT @something;")
92
+ expect(result.first['@something']).to eq('setting_value')
93
+ end
94
+
95
+ it "should send init_command after reconnect" do
96
+ options = DatabaseCredentials['root'].dup
97
+ options[:init_command] = "SET @something = 'setting_value';"
98
+ options[:reconnect] = true
99
+ client = new_client(options)
100
+
101
+ result = client.query("SELECT @something;")
102
+ expect(result.first['@something']).to eq('setting_value')
103
+
104
+ # get the current connection id
105
+ result = client.query("SELECT CONNECTION_ID()")
106
+ first_conn_id = result.first['CONNECTION_ID()']
107
+
108
+ # break the current connection
109
+ expect { client.query("KILL #{first_conn_id}") }.to raise_error(Mysql2::Error)
110
+
111
+ client.ping # reconnect now
112
+
113
+ # get the new connection id
114
+ result = client.query("SELECT CONNECTION_ID()")
115
+ second_conn_id = result.first['CONNECTION_ID()']
116
+
117
+ # confirm reconnect by checking the new connection id
118
+ expect(first_conn_id).not_to eq(second_conn_id)
119
+
120
+ # At last, check that the init command executed
121
+ result = client.query("SELECT @something;")
122
+ expect(result.first['@something']).to eq('setting_value')
44
123
  end
45
124
 
46
125
  it "should have a global default_query_options hash" do
47
- Mysql2::Client.should respond_to(:default_query_options)
126
+ expect(Mysql2::Client).to respond_to(:default_query_options)
48
127
  end
49
128
 
50
129
  it "should be able to connect via SSL options" do
51
- pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.")
130
+ ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'"
131
+ ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' }
132
+ pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled
133
+ ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' }
134
+ pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled
135
+
136
+ # You may need to adjust the lines below to match your SSL certificate paths
52
137
  ssl_client = nil
53
- lambda {
54
- ssl_client = Mysql2::Client.new(
55
- :sslkey => '/path/to/client-key.pem',
56
- :sslcert => '/path/to/client-cert.pem',
57
- :sslca => '/path/to/ca-cert.pem',
58
- :sslcapath => '/path/to/newcerts/',
59
- :sslcipher => 'DHE-RSA-AES256-SHA'
138
+ expect {
139
+ # rubocop:disable Style/TrailingComma
140
+ ssl_client = new_client(
141
+ 'host' => 'mysql2gem.example.com', # must match the certificates
142
+ :sslkey => '/etc/mysql/client-key.pem',
143
+ :sslcert => '/etc/mysql/client-cert.pem',
144
+ :sslca => '/etc/mysql/ca-cert.pem',
145
+ :sslcipher => 'DHE-RSA-AES256-SHA',
146
+ :sslverify => true
60
147
  )
61
- }.should_not raise_error(Mysql2::Error)
148
+ # rubocop:enable Style/TrailingComma
149
+ }.not_to raise_error
150
+
151
+ results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }]
152
+ expect(results['Ssl_cipher']).not_to be_empty
153
+ expect(results['Ssl_version']).not_to be_empty
154
+
155
+ expect(ssl_client.ssl_cipher).not_to be_empty
156
+ expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher)
157
+ end
158
+
159
+ def run_gc
160
+ if defined?(Rubinius)
161
+ GC.run(true)
162
+ else
163
+ GC.start
164
+ end
165
+ sleep(0.5)
166
+ end
167
+
168
+ it "should terminate connections when calling close" do
169
+ expect {
170
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
171
+ connection_id = client.thread_id
172
+ client.close
173
+
174
+ # mysql_close sends a quit command without waiting for a response
175
+ # so give the server some time to handle the detect the closed connection
176
+ closed = false
177
+ 10.times do
178
+ closed = @client.query("SHOW PROCESSLIST").none? { |row| row['Id'] == connection_id }
179
+ break if closed
180
+ sleep(0.1)
181
+ end
182
+ expect(closed).to eq(true)
183
+ }.to_not change {
184
+ @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a
185
+ }
186
+ end
187
+
188
+ it "should not leave dangling connections after garbage collection" do
189
+ run_gc
190
+ expect {
191
+ expect {
192
+ 10.times do
193
+ Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
194
+ end
195
+ }.to change {
196
+ @client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
197
+ }.by(10)
198
+
199
+ run_gc
200
+ }.to_not change {
201
+ @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
202
+ @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
203
+ }
204
+ end
205
+
206
+ context "#automatic_close" do
207
+ it "is enabled by default" do
208
+ expect(new_client.automatic_close?).to be(true)
209
+ end
210
+
211
+ if RUBY_PLATFORM =~ /mingw|mswin/
212
+ it "cannot be disabled" do
213
+ expect do
214
+ client = new_client(:automatic_close => false)
215
+ expect(client.automatic_close?).to be(true)
216
+ end.to output(/always closed by garbage collector/).to_stderr
217
+
218
+ expect do
219
+ client = new_client(:automatic_close => true)
220
+ expect(client.automatic_close?).to be(true)
221
+ end.to_not output(/always closed by garbage collector/).to_stderr
222
+
223
+ expect do
224
+ client = new_client(:automatic_close => true)
225
+ client.automatic_close = false
226
+ expect(client.automatic_close?).to be(true)
227
+ end.to output(/always closed by garbage collector/).to_stderr
228
+ end
229
+ else
230
+ it "can be configured" do
231
+ client = new_client(:automatic_close => false)
232
+ expect(client.automatic_close?).to be(false)
233
+ end
234
+
235
+ it "can be assigned" do
236
+ client = new_client
237
+ client.automatic_close = false
238
+ expect(client.automatic_close?).to be(false)
239
+
240
+ client.automatic_close = true
241
+ expect(client.automatic_close?).to be(true)
242
+
243
+ client.automatic_close = nil
244
+ expect(client.automatic_close?).to be(false)
245
+
246
+ client.automatic_close = 9
247
+ expect(client.automatic_close?).to be(true)
248
+ end
249
+
250
+ it "should not close connections when running in a child process" do
251
+ run_gc
252
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
253
+ client.automatic_close = false
62
254
 
63
- results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
64
- results[0]['Variable_name'].should eql('Ssl_cipher')
65
- results[0]['Value'].should_not be_nil
66
- results[0]['Value'].class.should eql(String)
255
+ child = fork do
256
+ client.query('SELECT 1')
257
+ client = nil
258
+ run_gc
259
+ end
260
+
261
+ Process.wait(child)
262
+
263
+ # this will throw an error if the underlying socket was shutdown by the
264
+ # child's GC
265
+ expect { client.query('SELECT 1') }.to_not raise_exception
266
+ client.close
267
+ end
268
+ end
269
+ end
270
+
271
+ it "should be able to connect to database with numeric-only name" do
272
+ database = 1235
273
+ @client.query "CREATE DATABASE IF NOT EXISTS `#{database}`"
67
274
 
68
- results[1]['Variable_name'].should eql('Ssl_version')
69
- results[1]['Value'].should_not be_nil
70
- results[1]['Value'].class.should eql(String)
275
+ expect {
276
+ new_client('database' => database)
277
+ }.not_to raise_error
278
+
279
+ @client.query "DROP DATABASE IF EXISTS `#{database}`"
71
280
  end
72
281
 
73
282
  it "should respond to #close" do
74
- @client.should respond_to(:close)
283
+ expect(@client).to respond_to(:close)
75
284
  end
76
285
 
77
286
  it "should be able to close properly" do
78
- @client.close.should be_nil
79
- lambda {
287
+ expect(@client.close).to be_nil
288
+ expect {
80
289
  @client.query "SELECT 1"
81
- }.should raise_error(Mysql2::Error)
290
+ }.to raise_error(Mysql2::Error)
291
+ end
292
+
293
+ context "#closed?" do
294
+ it "should return false when connected" do
295
+ expect(@client.closed?).to eql(false)
296
+ end
297
+
298
+ it "should return true after close" do
299
+ @client.close
300
+ expect(@client.closed?).to eql(true)
301
+ end
302
+ end
303
+
304
+ it "should not try to query closed mysql connection" do
305
+ client = new_client(:reconnect => true)
306
+ expect(client.close).to be_nil
307
+ expect {
308
+ client.query "SELECT 1"
309
+ }.to raise_error(Mysql2::Error)
82
310
  end
83
311
 
84
312
  it "should respond to #query" do
85
- @client.should respond_to(:query)
313
+ expect(@client).to respond_to(:query)
314
+ end
315
+
316
+ it "should respond to #warning_count" do
317
+ expect(@client).to respond_to(:warning_count)
318
+ end
319
+
320
+ context "#warning_count" do
321
+ context "when no warnings" do
322
+ it "should 0" do
323
+ @client.query('select 1')
324
+ expect(@client.warning_count).to eq(0)
325
+ end
326
+ end
327
+ context "when has a warnings" do
328
+ it "should > 0" do
329
+ # "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS"
330
+ # https://dev.mysql.com/doc/refman/5.7/en/show-warnings.html
331
+ @client.query('DROP TABLE IF EXISTS test.no_such_table')
332
+ expect(@client.warning_count).to be > 0
333
+ end
334
+ end
335
+ end
336
+
337
+ it "should respond to #query_info" do
338
+ expect(@client).to respond_to(:query_info)
339
+ end
340
+
341
+ context "#query_info" do
342
+ context "when no info present" do
343
+ it "should 0" do
344
+ @client.query('select 1')
345
+ expect(@client.query_info).to be_empty
346
+ expect(@client.query_info_string).to be_nil
347
+ end
348
+ end
349
+ context "when has some info" do
350
+ it "should retrieve it" do
351
+ @client.query "USE test"
352
+ @client.query "CREATE TABLE IF NOT EXISTS infoTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
353
+
354
+ # http://dev.mysql.com/doc/refman/5.0/en/mysql-info.html says
355
+ # # 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).
356
+ @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)")
357
+
358
+ expect(@client.query_info).to eql(:records => 2, :duplicates => 0, :warnings => 0)
359
+ expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0')
360
+
361
+ @client.query "DROP TABLE infoTest"
362
+ end
363
+ end
364
+ end
365
+
366
+ context ":local_infile" do
367
+ before(:all) do
368
+ new_client(:local_infile => true) do |client|
369
+ local = client.query "SHOW VARIABLES LIKE 'local_infile'"
370
+ local_enabled = local.any? { |x| x['Value'] == 'ON' }
371
+ skip("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled
372
+
373
+ client.query %[
374
+ CREATE TABLE IF NOT EXISTS infileTest (
375
+ id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
376
+ foo VARCHAR(10),
377
+ bar MEDIUMTEXT
378
+ )
379
+ ]
380
+ end
381
+ end
382
+
383
+ after(:all) do
384
+ new_client do |client|
385
+ client.query "DROP TABLE IF EXISTS infileTest"
386
+ end
387
+ end
388
+
389
+ it "should raise an error when local_infile is disabled" do
390
+ client = new_client(:local_infile => false)
391
+ expect {
392
+ client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
393
+ }.to raise_error(Mysql2::Error, /command is not allowed/)
394
+ end
395
+
396
+ it "should raise an error when a non-existent file is loaded" do
397
+ client = new_client(:local_infile => true)
398
+ expect {
399
+ client.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest"
400
+ }.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here')
401
+ end
402
+
403
+ it "should LOAD DATA LOCAL INFILE" do
404
+ client = new_client(:local_infile => true)
405
+ client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
406
+ info = client.query_info
407
+ expect(info).to eql(:records => 1, :deleted => 0, :skipped => 0, :warnings => 0)
408
+
409
+ result = client.query "SELECT * FROM infileTest"
410
+ expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World')
411
+ end
412
+ end
413
+
414
+ it "should expect connect_timeout to be a positive integer" do
415
+ expect {
416
+ new_client(:connect_timeout => -1)
417
+ }.to raise_error(Mysql2::Error)
86
418
  end
87
419
 
88
420
  it "should expect read_timeout to be a positive integer" do
89
- lambda {
90
- Mysql2::Client.new(:read_timeout => -1)
91
- }.should raise_error(Mysql2::Error)
421
+ expect {
422
+ new_client(:read_timeout => -1)
423
+ }.to raise_error(Mysql2::Error)
424
+ end
425
+
426
+ it "should expect write_timeout to be a positive integer" do
427
+ expect {
428
+ new_client(:write_timeout => -1)
429
+ }.to raise_error(Mysql2::Error)
430
+ end
431
+
432
+ it "should allow nil read_timeout" do
433
+ client = new_client(:read_timeout => nil)
434
+
435
+ expect(client.read_timeout).to be_nil
92
436
  end
93
437
 
94
438
  context "#query" do
439
+ it "should let you query again if iterating is finished when streaming" do
440
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a
441
+
442
+ expect {
443
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false)
444
+ }.to_not raise_error
445
+ end
446
+
447
+ it "should not let you query again if iterating is not finished when streaming" do
448
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).first
449
+
450
+ expect {
451
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false)
452
+ }.to raise_exception(Mysql2::Error)
453
+ end
454
+
95
455
  it "should only accept strings as the query parameter" do
96
- lambda {
456
+ expect {
97
457
  @client.query ["SELECT 'not right'"]
98
- }.should raise_error(TypeError)
458
+ }.to raise_error(TypeError)
99
459
  end
100
460
 
101
- it "should accept an options hash that inherits from Mysql2::Client.default_query_options" do
102
- @client.query "SELECT 1", :something => :else
103
- @client.query_options.should eql(@client.query_options.merge(:something => :else))
461
+ it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do
462
+ result = @client.query "SELECT 1", :something => :else
463
+ expect(@client.query_options[:something]).to be_nil
464
+ expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(:something => :else))
465
+ expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(:something => :else))
466
+
467
+ result = @client.query "SELECT 1"
468
+ expect(result.instance_variable_get('@query_options')).to eql(@client.query_options)
469
+ expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options)
470
+ end
471
+
472
+ it "should allow changing query options for subsequent queries" do
473
+ @client.query_options.merge!(:something => :else)
474
+ result = @client.query "SELECT 1"
475
+ expect(@client.query_options[:something]).to eql(:else)
476
+ expect(result.instance_variable_get('@query_options')[:something]).to eql(:else)
477
+
478
+ # Clean up after this test
479
+ @client.query_options.delete(:something)
480
+ expect(@client.query_options[:something]).to be_nil
104
481
  end
105
482
 
106
483
  it "should return results as a hash by default" do
107
- @client.query("SELECT 1").first.class.should eql(Hash)
484
+ expect(@client.query("SELECT 1").first).to be_an_instance_of(Hash)
108
485
  end
109
486
 
110
487
  it "should be able to return results as an array" do
111
- @client.query("SELECT 1", :as => :array).first.class.should eql(Array)
488
+ expect(@client.query("SELECT 1", :as => :array).first).to be_an_instance_of(Array)
112
489
  @client.query("SELECT 1").each(:as => :array)
113
490
  end
114
491
 
115
492
  it "should be able to return results with symbolized keys" do
116
- @client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class.should eql(Symbol)
493
+ expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0]).to be_an_instance_of(Symbol)
117
494
  end
118
495
 
119
496
  it "should require an open connection" do
120
497
  @client.close
121
- lambda {
498
+ expect {
122
499
  @client.query "SELECT 1"
123
- }.should raise_error(Mysql2::Error)
500
+ }.to raise_error(Mysql2::Error)
501
+ end
502
+
503
+ it "should detect closed connection on query read error" do
504
+ connection_id = @client.thread_id
505
+ Thread.new do
506
+ sleep(0.1)
507
+ Mysql2::Client.new(DatabaseCredentials['root']).tap do |supervisor|
508
+ supervisor.query("KILL #{connection_id}")
509
+ end.close
510
+ end
511
+ expect {
512
+ @client.query("SELECT SLEEP(1)")
513
+ }.to raise_error(Mysql2::Error, /Lost connection to MySQL server/)
514
+
515
+ if RUBY_PLATFORM !~ /mingw|mswin/
516
+ expect {
517
+ @client.socket
518
+ }.to raise_error(Mysql2::Error, 'MySQL client is not connected')
519
+ end
124
520
  end
125
521
 
126
522
  if RUBY_PLATFORM !~ /mingw|mswin/
127
523
  it "should not allow another query to be sent without fetching a result first" do
128
524
  @client.query("SELECT 1", :async => true)
129
- lambda {
525
+ expect {
130
526
  @client.query("SELECT 1")
131
- }.should raise_error(Mysql2::Error)
527
+ }.to raise_error(Mysql2::Error)
528
+ end
529
+
530
+ it "should describe the thread holding the active query" do
531
+ thr = Thread.new { @client.query("SELECT 1", :async => true) }
532
+
533
+ thr.join
534
+ expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect)))
132
535
  end
133
536
 
134
537
  it "should timeout if we wait longer than :read_timeout" do
135
- client = Mysql2::Client.new(:read_timeout => 1)
136
- lambda {
137
- client.query("SELECT sleep(2)")
138
- }.should raise_error(Mysql2::Error)
538
+ client = new_client(:read_timeout => 0)
539
+ expect {
540
+ client.query('SELECT SLEEP(0.1)')
541
+ }.to raise_error(Mysql2::Error)
139
542
  end
140
543
 
141
544
  # XXX this test is not deterministic (because Unix signal handling is not)
142
545
  # and may fail on a loaded system
143
546
  it "should run signal handlers while waiting for a response" do
547
+ kill_time = 0.1
548
+ query_time = 2 * kill_time
549
+
144
550
  mark = {}
145
- trap(:USR1) { mark[:USR1] = Time.now }
551
+
146
552
  begin
147
- mark[:START] = Time.now
553
+ trap(:USR1) { mark.store(:USR1, Time.now) }
148
554
  pid = fork do
149
- sleep 1 # wait for client "SELECT sleep(2)" query to start
555
+ sleep kill_time # wait for client query to start
150
556
  Process.kill(:USR1, Process.ppid)
151
557
  sleep # wait for explicit kill to prevent GC disconnect
152
558
  end
153
- @client.query("SELECT sleep(2)")
154
- mark[:END] = Time.now
155
- mark.include?(:USR1).should be_true
156
- (mark[:USR1] - mark[:START]).should >= 1
157
- (mark[:USR1] - mark[:START]).should < 1.1
158
- (mark[:END] - mark[:USR1]).should > 0.9
159
- (mark[:END] - mark[:START]).should >= 2
160
- (mark[:END] - mark[:START]).should < 2.1
559
+ mark.store(:QUERY_START, Time.now)
560
+ @client.query("SELECT SLEEP(#{query_time})")
561
+ mark.store(:QUERY_END, Time.now)
562
+ ensure
161
563
  Process.kill(:TERM, pid)
162
564
  Process.waitpid2(pid)
163
- ensure
164
565
  trap(:USR1, 'DEFAULT')
165
566
  end
567
+
568
+ # the query ran uninterrupted
569
+ expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.02).of(query_time)
570
+ # signals fired while the query was running
571
+ expect(mark.fetch(:USR1)).to be_between(mark.fetch(:QUERY_START), mark.fetch(:QUERY_END))
166
572
  end
167
573
 
168
574
  it "#socket should return a Fixnum (file descriptor from C)" do
169
- @client.socket.class.should eql(Fixnum)
170
- @client.socket.should_not eql(0)
575
+ expect(@client.socket).to be_an_instance_of(Fixnum)
576
+ expect(@client.socket).not_to eql(0)
171
577
  end
172
578
 
173
579
  it "#socket should require an open connection" do
174
580
  @client.close
175
- lambda {
581
+ expect {
176
582
  @client.socket
177
- }.should raise_error(Mysql2::Error)
583
+ }.to raise_error(Mysql2::Error)
178
584
  end
179
585
 
180
- it "should close the connection when an exception is raised" do
181
- begin
182
- Timeout.timeout(1) do
183
- @client.query("SELECT sleep(2)")
184
- end
185
- rescue Timeout::Error
186
- end
187
-
188
- lambda {
189
- @client.query("SELECT 1")
190
- }.should raise_error(Mysql2::Error, 'closed MySQL connection')
586
+ it 'should be impervious to connection-corrupting timeouts in #execute' do
587
+ # the statement handle gets corrupted and will segfault the tests if interrupted,
588
+ # so we can't even use pending on this test, really have to skip it on older Rubies.
589
+ skip('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt)
590
+
591
+ # attempt to break the connection
592
+ stmt = @client.prepare('SELECT SLEEP(?)')
593
+ expect { Timeout.timeout(0.1) { stmt.execute(0.2) } }.to raise_error(Timeout::Error)
594
+ stmt.close
595
+
596
+ # expect the connection to not be broken
597
+ expect { @client.query('SELECT 1') }.to_not raise_error
191
598
  end
192
599
 
193
- it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
194
- client = Mysql2::Client.new(:reconnect => true)
195
- begin
196
- Timeout.timeout(1) do
197
- client.query("SELECT sleep(2)")
600
+ context 'when a non-standard exception class is raised' do
601
+ it "should close the connection when an exception is raised" do
602
+ expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
603
+ expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'MySQL client is not connected')
604
+ end
605
+
606
+ it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
607
+ if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5')
608
+ pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.')
198
609
  end
199
- rescue Timeout::Error
610
+
611
+ client = new_client(:reconnect => true)
612
+
613
+ expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
614
+ expect { client.query('SELECT 1') }.to_not raise_error
200
615
  end
201
616
 
202
- lambda {
203
- client.query("SELECT 1")
204
- }.should_not raise_error(Mysql2::Error)
617
+ it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do
618
+ if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5')
619
+ pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.')
620
+ end
621
+
622
+ client = new_client
623
+
624
+ expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
625
+ expect { client.query('SELECT 1') }.to raise_error(Mysql2::Error)
626
+
627
+ client.reconnect = true
628
+
629
+ expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
630
+ expect { client.query('SELECT 1') }.to_not raise_error
631
+ end
205
632
  end
206
633
 
207
634
  it "threaded queries should be supported" do
208
- threads, results = [], {}
209
- connect = lambda{ Mysql2::Client.new(:host => "localhost", :username => "root") }
210
- Timeout.timeout(0.7) do
211
- 5.times {
212
- threads << Thread.new do
213
- results[Thread.current.object_id] = connect.call.query("SELECT sleep(0.5) as result")
635
+ sleep_time = 0.5
636
+
637
+ # Note that each thread opens its own database connection
638
+ threads = 5.times.map do
639
+ Thread.new do
640
+ new_client do |client|
641
+ client.query("SELECT SLEEP(#{sleep_time})")
214
642
  end
215
- }
643
+ Thread.current.object_id
644
+ end
216
645
  end
217
- threads.each{|t| t.join }
218
- results.keys.sort.should eql(threads.map{|t| t.object_id }.sort)
646
+
647
+ # This timeout demonstrates that the threads are sleeping concurrently:
648
+ # In the serial case, the timeout would fire and the test would fail
649
+ values = Timeout.timeout(sleep_time * 1.1) { threads.map(&:value) }
650
+
651
+ expect(values).to match_array(threads.map(&:object_id))
219
652
  end
220
653
 
221
654
  it "evented async queries should be supported" do
655
+ skip("ruby 1.8 doesn't support IO.for_fd options") if RUBY_VERSION.start_with?("1.8.")
222
656
  # should immediately return nil
223
- @client.query("SELECT sleep(0.1)", :async => true).should eql(nil)
657
+ expect(@client.query("SELECT sleep(0.1)", :async => true)).to eql(nil)
224
658
 
225
- io_wrapper = IO.for_fd(@client.socket)
659
+ io_wrapper = IO.for_fd(@client.socket, :autoclose => false)
226
660
  loops = 0
227
661
  loop do
228
662
  if IO.select([io_wrapper], nil, nil, 0.05)
@@ -233,184 +667,271 @@ describe Mysql2::Client do
233
667
  end
234
668
 
235
669
  # make sure we waited some period of time
236
- (loops >= 1).should be_true
670
+ expect(loops >= 1).to be true
237
671
 
238
672
  result = @client.async_result
239
- result.class.should eql(Mysql2::Result)
673
+ expect(result).to be_an_instance_of(Mysql2::Result)
674
+ end
675
+ end
676
+
677
+ context "Multiple results sets" do
678
+ before(:each) do
679
+ @multi_client = new_client(:flags => Mysql2::Client::MULTI_STATEMENTS)
680
+ end
681
+
682
+ it "should raise an exception when one of multiple statements fails" do
683
+ result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';")
684
+ expect(result.first['set_1']).to be(1)
685
+ expect {
686
+ @multi_client.next_result
687
+ }.to raise_error(Mysql2::Error)
688
+ expect(@multi_client.next_result).to be false
689
+ end
690
+
691
+ it "returns multiple result sets" do
692
+ expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql('set_1' => 1)
693
+
694
+ expect(@multi_client.next_result).to be true
695
+ expect(@multi_client.store_result.first).to eql('set_2' => 2)
696
+
697
+ expect(@multi_client.next_result).to be false
698
+ end
699
+
700
+ it "does not interfere with other statements" do
701
+ @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
702
+ @multi_client.store_result while @multi_client.next_result
703
+
704
+ expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq('next' => 3)
705
+ end
706
+
707
+ it "will raise on query if there are outstanding results to read" do
708
+ @multi_client.query("SELECT 1; SELECT 2; SELECT 3")
709
+ expect {
710
+ @multi_client.query("SELECT 4")
711
+ }.to raise_error(Mysql2::Error)
712
+ end
713
+
714
+ it "#abandon_results! should work" do
715
+ @multi_client.query("SELECT 1; SELECT 2; SELECT 3")
716
+ @multi_client.abandon_results!
717
+ expect {
718
+ @multi_client.query("SELECT 4")
719
+ }.not_to raise_error
720
+ end
721
+
722
+ it "#more_results? should work" do
723
+ @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
724
+ expect(@multi_client.more_results?).to be true
725
+
726
+ @multi_client.next_result
727
+ @multi_client.store_result
728
+
729
+ expect(@multi_client.more_results?).to be false
730
+ end
731
+
732
+ it "#more_results? should work with stored procedures" do
733
+ @multi_client.query("DROP PROCEDURE IF EXISTS test_proc")
734
+ @multi_client.query("CREATE PROCEDURE test_proc() BEGIN SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'; END")
735
+ expect(@multi_client.query("CALL test_proc()").first).to eql('set_1' => 1)
736
+ expect(@multi_client.more_results?).to be true
737
+
738
+ @multi_client.next_result
739
+ expect(@multi_client.store_result.first).to eql('set_2' => 2)
740
+
741
+ @multi_client.next_result
742
+ expect(@multi_client.store_result).to be_nil # this is the result from CALL itself
743
+
744
+ expect(@multi_client.more_results?).to be false
240
745
  end
241
746
  end
242
747
  end
243
748
 
244
749
  it "should respond to #socket" do
245
- @client.should respond_to(:socket)
750
+ expect(@client).to respond_to(:socket)
246
751
  end
247
752
 
248
753
  if RUBY_PLATFORM =~ /mingw|mswin/
249
754
  it "#socket should raise as it's not supported" do
250
- lambda {
755
+ expect {
251
756
  @client.socket
252
- }.should raise_error(Mysql2::Error)
757
+ }.to raise_error(Mysql2::Error, /Raw access to the mysql file descriptor isn't supported on Windows/)
253
758
  end
254
759
  end
255
760
 
256
761
  it "should respond to escape" do
257
- Mysql2::Client.should respond_to(:escape)
762
+ expect(Mysql2::Client).to respond_to(:escape)
258
763
  end
259
764
 
260
765
  context "escape" do
261
766
  it "should return a new SQL-escape version of the passed string" do
262
- Mysql2::Client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
767
+ expect(Mysql2::Client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno")
263
768
  end
264
769
 
265
770
  it "should return the passed string if nothing was escaped" do
266
771
  str = "plain"
267
- Mysql2::Client.escape(str).object_id.should eql(str.object_id)
772
+ expect(Mysql2::Client.escape(str).object_id).to eql(str.object_id)
268
773
  end
269
774
 
270
775
  it "should not overflow the thread stack" do
271
- lambda {
776
+ expect {
272
777
  Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join
273
- }.should_not raise_error(SystemStackError)
778
+ }.not_to raise_error
274
779
  end
275
780
 
276
781
  it "should not overflow the process stack" do
277
- lambda {
782
+ expect {
278
783
  Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join
279
- }.should_not raise_error(SystemStackError)
784
+ }.not_to raise_error
280
785
  end
281
786
 
282
- if RUBY_VERSION =~ /1.9/
787
+ unless RUBY_VERSION =~ /1.8/
283
788
  it "should carry over the original string's encoding" do
284
789
  str = "abc'def\"ghi\0jkl%mno"
285
790
  escaped = Mysql2::Client.escape(str)
286
- escaped.encoding.should eql(str.encoding)
791
+ expect(escaped.encoding).to eql(str.encoding)
287
792
 
288
793
  str.encode!('us-ascii')
289
794
  escaped = Mysql2::Client.escape(str)
290
- escaped.encoding.should eql(str.encoding)
795
+ expect(escaped.encoding).to eql(str.encoding)
291
796
  end
292
797
  end
293
798
  end
294
799
 
295
800
  it "should respond to #escape" do
296
- @client.should respond_to(:escape)
801
+ expect(@client).to respond_to(:escape)
297
802
  end
298
803
 
299
804
  context "#escape" do
300
805
  it "should return a new SQL-escape version of the passed string" do
301
- @client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
806
+ expect(@client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno")
302
807
  end
303
808
 
304
809
  it "should return the passed string if nothing was escaped" do
305
810
  str = "plain"
306
- @client.escape(str).object_id.should eql(str.object_id)
811
+ expect(@client.escape(str).object_id).to eql(str.object_id)
307
812
  end
308
813
 
309
814
  it "should not overflow the thread stack" do
310
- lambda {
815
+ expect {
311
816
  Thread.new { @client.escape("'" * 256 * 1024) }.join
312
- }.should_not raise_error(SystemStackError)
817
+ }.not_to raise_error
313
818
  end
314
819
 
315
820
  it "should not overflow the process stack" do
316
- lambda {
821
+ expect {
317
822
  Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
318
- }.should_not raise_error(SystemStackError)
823
+ }.not_to raise_error
319
824
  end
320
825
 
321
826
  it "should require an open connection" do
322
827
  @client.close
323
- lambda {
828
+ expect {
324
829
  @client.escape ""
325
- }.should raise_error(Mysql2::Error)
830
+ }.to raise_error(Mysql2::Error)
831
+ end
832
+
833
+ context 'when mysql encoding is not utf8' do
834
+ before { pending('Encoding is undefined') unless defined?(Encoding) }
835
+
836
+ let(:client) { new_client(:encoding => "ujis") }
837
+
838
+ it 'should return a internal encoding string if Encoding.default_internal is set' do
839
+ with_internal_encoding Encoding::UTF_8 do
840
+ expect(client.escape("\u{30C6}\u{30B9}\u{30C8}")).to eq "\u{30C6}\u{30B9}\u{30C8}"
841
+ expect(client.escape("\u{30C6}'\u{30B9}\"\u{30C8}")).to eq "\u{30C6}\\'\u{30B9}\\\"\u{30C8}"
842
+ end
843
+ end
326
844
  end
327
845
  end
328
846
 
329
847
  it "should respond to #info" do
330
- @client.should respond_to(:info)
848
+ expect(@client).to respond_to(:info)
331
849
  end
332
850
 
333
851
  it "#info should return a hash containing the client version ID and String" do
334
852
  info = @client.info
335
- info.class.should eql(Hash)
336
- info.should have_key(:id)
337
- info[:id].class.should eql(Fixnum)
338
- info.should have_key(:version)
339
- info[:version].class.should eql(String)
853
+ expect(info).to be_an_instance_of(Hash)
854
+ expect(info).to have_key(:id)
855
+ expect(info[:id]).to be_an_instance_of(Fixnum)
856
+ expect(info).to have_key(:version)
857
+ expect(info[:version]).to be_an_instance_of(String)
340
858
  end
341
859
 
342
- if defined? Encoding
343
- context "strings returned by #info" do
344
- it "should default to the connection's encoding if Encoding.default_internal is nil" do
345
- Encoding.default_internal = nil
346
- @client.info[:version].encoding.should eql(Encoding.find('utf-8'))
860
+ context "strings returned by #info" do
861
+ before { pending('Encoding is undefined') unless defined?(Encoding) }
347
862
 
348
- client2 = Mysql2::Client.new :encoding => 'ascii'
349
- client2.info[:version].encoding.should eql(Encoding.find('us-ascii'))
350
- end
863
+ it "should be tagged as ascii" do
864
+ expect(@client.info[:version].encoding).to eql(Encoding::US_ASCII)
865
+ expect(@client.info[:header_version].encoding).to eql(Encoding::US_ASCII)
866
+ end
867
+ end
351
868
 
352
- it "should use Encoding.default_internal" do
353
- Encoding.default_internal = Encoding.find('utf-8')
354
- @client.info[:version].encoding.should eql(Encoding.default_internal)
355
- Encoding.default_internal = Encoding.find('us-ascii')
356
- @client.info[:version].encoding.should eql(Encoding.default_internal)
357
- end
869
+ context "strings returned by .info" do
870
+ before { pending('Encoding is undefined') unless defined?(Encoding) }
871
+
872
+ it "should be tagged as ascii" do
873
+ expect(Mysql2::Client.info[:version].encoding).to eql(Encoding::US_ASCII)
874
+ expect(Mysql2::Client.info[:header_version].encoding).to eql(Encoding::US_ASCII)
358
875
  end
359
876
  end
360
877
 
361
878
  it "should respond to #server_info" do
362
- @client.should respond_to(:server_info)
879
+ expect(@client).to respond_to(:server_info)
363
880
  end
364
881
 
365
882
  it "#server_info should return a hash containing the client version ID and String" do
366
883
  server_info = @client.server_info
367
- server_info.class.should eql(Hash)
368
- server_info.should have_key(:id)
369
- server_info[:id].class.should eql(Fixnum)
370
- server_info.should have_key(:version)
371
- server_info[:version].class.should eql(String)
884
+ expect(server_info).to be_an_instance_of(Hash)
885
+ expect(server_info).to have_key(:id)
886
+ expect(server_info[:id]).to be_an_instance_of(Fixnum)
887
+ expect(server_info).to have_key(:version)
888
+ expect(server_info[:version]).to be_an_instance_of(String)
372
889
  end
373
890
 
374
891
  it "#server_info should require an open connection" do
375
892
  @client.close
376
- lambda {
893
+ expect {
377
894
  @client.server_info
378
- }.should raise_error(Mysql2::Error)
895
+ }.to raise_error(Mysql2::Error)
379
896
  end
380
897
 
381
- if defined? Encoding
382
- context "strings returned by #server_info" do
383
- it "should default to the connection's encoding if Encoding.default_internal is nil" do
384
- Encoding.default_internal = nil
385
- @client.server_info[:version].encoding.should eql(Encoding.find('utf-8'))
898
+ context "strings returned by #server_info" do
899
+ before { pending('Encoding is undefined') unless defined?(Encoding) }
386
900
 
387
- client2 = Mysql2::Client.new :encoding => 'ascii'
388
- client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii'))
901
+ it "should default to the connection's encoding if Encoding.default_internal is nil" do
902
+ with_internal_encoding nil do
903
+ expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8)
904
+
905
+ client2 = new_client(:encoding => 'ascii')
906
+ expect(client2.server_info[:version].encoding).to eql(Encoding::ASCII)
907
+ end
908
+ end
909
+
910
+ it "should use Encoding.default_internal" do
911
+ with_internal_encoding Encoding::UTF_8 do
912
+ expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal)
389
913
  end
390
914
 
391
- it "should use Encoding.default_internal" do
392
- Encoding.default_internal = Encoding.find('utf-8')
393
- @client.server_info[:version].encoding.should eql(Encoding.default_internal)
394
- Encoding.default_internal = Encoding.find('us-ascii')
395
- @client.server_info[:version].encoding.should eql(Encoding.default_internal)
915
+ with_internal_encoding Encoding::ASCII do
916
+ expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal)
396
917
  end
397
918
  end
398
919
  end
399
920
 
400
921
  it "should raise a Mysql2::Error exception upon connection failure" do
401
- lambda {
402
- bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
403
- }.should raise_error(Mysql2::Error)
922
+ expect {
923
+ new_client(:host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42')
924
+ }.to raise_error(Mysql2::Error)
404
925
 
405
- lambda {
406
- good_client = Mysql2::Client.new
407
- }.should_not raise_error(Mysql2::Error)
926
+ expect {
927
+ new_client(DatabaseCredentials['root'])
928
+ }.not_to raise_error
408
929
  end
409
930
 
410
931
  context 'write operations api' do
411
932
  before(:each) do
412
933
  @client.query "USE test"
413
- @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
934
+ @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
414
935
  end
415
936
 
416
937
  after(:each) do
@@ -418,48 +939,101 @@ describe Mysql2::Client do
418
939
  end
419
940
 
420
941
  it "should respond to #last_id" do
421
- @client.should respond_to(:last_id)
942
+ expect(@client).to respond_to(:last_id)
422
943
  end
423
944
 
424
945
  it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
425
- @client.last_id.should eql(0)
946
+ expect(@client.last_id).to eql(0)
426
947
  @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
427
- @client.last_id.should eql(1)
948
+ expect(@client.last_id).to eql(1)
428
949
  end
429
950
 
430
951
  it "should respond to #last_id" do
431
- @client.should respond_to(:last_id)
952
+ expect(@client).to respond_to(:last_id)
432
953
  end
433
954
 
434
955
  it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
435
956
  @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
436
- @client.affected_rows.should eql(1)
957
+ expect(@client.affected_rows).to eql(1)
437
958
  @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
438
- @client.affected_rows.should eql(1)
959
+ expect(@client.affected_rows).to eql(1)
960
+ end
961
+
962
+ it "#last_id should handle BIGINT auto-increment ids above 32 bits" do
963
+ # The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x.
964
+ # Insert a row with a given ID, this should raise the auto-increment state
965
+ @client.query "INSERT INTO lastIdTest (id, blah) VALUES (5000000000, 5000)"
966
+ expect(@client.last_id).to eql(5000000000)
967
+ @client.query "INSERT INTO lastIdTest (blah) VALUES (5001)"
968
+ expect(@client.last_id).to eql(5000000001)
439
969
  end
440
970
  end
441
971
 
442
972
  it "should respond to #thread_id" do
443
- @client.should respond_to(:thread_id)
973
+ expect(@client).to respond_to(:thread_id)
444
974
  end
445
975
 
446
976
  it "#thread_id should be a Fixnum" do
447
- @client.thread_id.class.should eql(Fixnum)
977
+ expect(@client.thread_id).to be_an_instance_of(Fixnum)
448
978
  end
449
979
 
450
980
  it "should respond to #ping" do
451
- @client.should respond_to(:ping)
981
+ expect(@client).to respond_to(:ping)
982
+ end
983
+
984
+ context "select_db" do
985
+ before(:each) do
986
+ 2.times do |i|
987
+ @client.query("CREATE DATABASE test_selectdb_#{i}")
988
+ @client.query("USE test_selectdb_#{i}")
989
+ @client.query("CREATE TABLE test#{i} (`id` int NOT NULL PRIMARY KEY)")
990
+ end
991
+ end
992
+
993
+ after(:each) do
994
+ 2.times do |i|
995
+ @client.query("DROP DATABASE test_selectdb_#{i}")
996
+ end
997
+ end
998
+
999
+ it "should respond to #select_db" do
1000
+ expect(@client).to respond_to(:select_db)
1001
+ end
1002
+
1003
+ it "should switch databases" do
1004
+ @client.select_db("test_selectdb_0")
1005
+ expect(@client.query("SHOW TABLES").first.values.first).to eql("test0")
1006
+ @client.select_db("test_selectdb_1")
1007
+ expect(@client.query("SHOW TABLES").first.values.first).to eql("test1")
1008
+ @client.select_db("test_selectdb_0")
1009
+ expect(@client.query("SHOW TABLES").first.values.first).to eql("test0")
1010
+ end
1011
+
1012
+ it "should raise a Mysql2::Error when the database doesn't exist" do
1013
+ expect {
1014
+ @client.select_db("nopenothere")
1015
+ }.to raise_error(Mysql2::Error)
1016
+ end
1017
+
1018
+ it "should return the database switched to" do
1019
+ expect(@client.select_db("test_selectdb_1")).to eq("test_selectdb_1")
1020
+ end
452
1021
  end
453
1022
 
454
1023
  it "#thread_id should return a boolean" do
455
- @client.ping.should eql(true)
1024
+ expect(@client.ping).to eql(true)
456
1025
  @client.close
457
- @client.ping.should eql(false)
1026
+ expect(@client.ping).to eql(false)
458
1027
  end
459
1028
 
460
- if RUBY_VERSION =~ /1.9/
461
- it "should respond to #encoding" do
462
- @client.should respond_to(:encoding)
1029
+ it "should be able to connect using plaintext password" do
1030
+ client = new_client(:enable_cleartext_plugin => true)
1031
+ client.query('SELECT 1')
1032
+ end
1033
+
1034
+ unless RUBY_VERSION =~ /1.8/
1035
+ it "should respond to #encoding" do
1036
+ expect(@client).to respond_to(:encoding)
1037
+ end
463
1038
  end
464
- end
465
1039
  end