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