mysql2 0.3.18 → 0.4.9

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