mysql2 0.3.20 → 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 (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