mysql2 0.3.18 → 0.4.7

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/LICENSE +21 -0
  4. data/README.md +135 -55
  5. data/examples/eventmachine.rb +1 -1
  6. data/examples/threaded.rb +4 -6
  7. data/ext/mysql2/client.c +368 -197
  8. data/ext/mysql2/client.h +13 -3
  9. data/ext/mysql2/extconf.rb +118 -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 +585 -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 +525 -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 +757 -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
158
+
159
+ def run_gc
160
+ if defined?(Rubinius)
161
+ GC.run(true)
162
+ else
163
+ GC.start
164
+ end
165
+ sleep(0.5)
166
+ end
151
167
 
152
- 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
+ }
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)
178
242
 
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
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)
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
260
+
261
+ Process.wait(child)
187
262
 
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
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
516
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
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,38 @@ 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)
890
1027
  end
891
1028
 
892
1029
  unless RUBY_VERSION =~ /1.8/
893
1030
  it "should respond to #encoding" do
894
- @client.should respond_to(:encoding)
1031
+ expect(@client).to respond_to(:encoding)
895
1032
  end
896
1033
  end
897
1034
  end