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