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