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