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