mysql2 0.3.20 → 0.4.10

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