mysql2 0.4.2 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +231 -86
  3. data/ext/mysql2/client.c +527 -128
  4. data/ext/mysql2/client.h +11 -52
  5. data/ext/mysql2/extconf.rb +100 -21
  6. data/ext/mysql2/mysql2_ext.c +8 -2
  7. data/ext/mysql2/mysql2_ext.h +21 -8
  8. data/ext/mysql2/mysql_enc_name_to_ruby.h +60 -56
  9. data/ext/mysql2/mysql_enc_to_ruby.h +64 -3
  10. data/ext/mysql2/result.c +333 -109
  11. data/ext/mysql2/result.h +1 -0
  12. data/ext/mysql2/statement.c +247 -90
  13. data/ext/mysql2/statement.h +0 -2
  14. data/ext/mysql2/wait_for_single_fd.h +2 -1
  15. data/lib/mysql2/client.rb +71 -31
  16. data/lib/mysql2/em.rb +2 -4
  17. data/lib/mysql2/error.rb +52 -22
  18. data/lib/mysql2/result.rb +2 -0
  19. data/lib/mysql2/statement.rb +3 -11
  20. data/lib/mysql2/version.rb +1 -1
  21. data/lib/mysql2.rb +19 -15
  22. data/support/3A79BD29.asc +49 -0
  23. data/support/5072E1F5.asc +432 -0
  24. data/support/C74CD1D8.asc +104 -0
  25. data/support/mysql_enc_to_ruby.rb +8 -3
  26. data/support/ruby_enc_to_mysql.rb +7 -5
  27. metadata +19 -61
  28. data/examples/eventmachine.rb +0 -21
  29. data/examples/threaded.rb +0 -18
  30. data/spec/configuration.yml.example +0 -17
  31. data/spec/em/em_spec.rb +0 -135
  32. data/spec/my.cnf.example +0 -9
  33. data/spec/mysql2/client_spec.rb +0 -939
  34. data/spec/mysql2/error_spec.rb +0 -84
  35. data/spec/mysql2/result_spec.rb +0 -510
  36. data/spec/mysql2/statement_spec.rb +0 -684
  37. data/spec/rcov.opts +0 -3
  38. data/spec/spec_helper.rb +0 -94
  39. data/spec/ssl/ca-cert.pem +0 -17
  40. data/spec/ssl/ca-key.pem +0 -27
  41. data/spec/ssl/ca.cnf +0 -22
  42. data/spec/ssl/cert.cnf +0 -22
  43. data/spec/ssl/client-cert.pem +0 -17
  44. data/spec/ssl/client-key.pem +0 -27
  45. data/spec/ssl/client-req.pem +0 -15
  46. data/spec/ssl/gen_certs.sh +0 -48
  47. data/spec/ssl/pkcs8-client-key.pem +0 -28
  48. data/spec/ssl/pkcs8-server-key.pem +0 -28
  49. data/spec/ssl/server-cert.pem +0 -17
  50. data/spec/ssl/server-key.pem +0 -27
  51. data/spec/ssl/server-req.pem +0 -15
  52. data/spec/test_data +0 -1
@@ -1,939 +0,0 @@
1
- # encoding: UTF-8
2
- require 'spec_helper'
3
-
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
- opts = DatabaseCredentials['root'].merge(:default_file => cnf_file, :default_group => "test")
11
- @client = Mysql2::Client.new(opts)
12
- }.not_to raise_error
13
- end
14
-
15
- it "should not raise an exception without default group" do
16
- expect {
17
- @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:default_file => cnf_file))
18
- }.not_to raise_error
19
- end
20
- end
21
-
22
- it "should raise an exception upon connection failure" do
23
- expect {
24
- # The odd local host IP address forces the mysql client library to
25
- # use a TCP socket rather than a domain socket.
26
- Mysql2::Client.new DatabaseCredentials['root'].merge('host' => '127.0.0.2', 'port' => 999999)
27
- }.to raise_error(Mysql2::Error)
28
- end
29
-
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
35
-
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
40
-
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
51
- end
52
- end
53
-
54
- it "should accept connect flags and pass them to #connect" do
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)
69
- end
70
-
71
- it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
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)
80
- end
81
-
82
- it "should execute init command" do
83
- options = DatabaseCredentials['root'].dup
84
- options[:init_command] = "SET @something = 'setting_value';"
85
- client = Mysql2::Client.new(options)
86
- result = client.query("SELECT @something;")
87
- expect(result.first['@something']).to eq('setting_value')
88
- end
89
-
90
- it "should send init_command after reconnect" do
91
- options = DatabaseCredentials['root'].dup
92
- options[:init_command] = "SET @something = 'setting_value';"
93
- options[:reconnect] = true
94
- client = Mysql2::Client.new(options)
95
-
96
- result = client.query("SELECT @something;")
97
- expect(result.first['@something']).to eq('setting_value')
98
-
99
- # get the current connection id
100
- result = client.query("SELECT CONNECTION_ID()")
101
- first_conn_id = result.first['CONNECTION_ID()']
102
-
103
- # break the current connection
104
- expect { client.query("KILL #{first_conn_id}") }.to raise_error(Mysql2::Error)
105
-
106
- client.ping # reconnect now
107
-
108
- # get the new connection id
109
- result = client.query("SELECT CONNECTION_ID()")
110
- second_conn_id = result.first['CONNECTION_ID()']
111
-
112
- # confirm reconnect by checking the new connection id
113
- expect(first_conn_id).not_to eq(second_conn_id)
114
-
115
- # At last, check that the init command executed
116
- result = client.query("SELECT @something;")
117
- expect(result.first['@something']).to eq('setting_value')
118
- end
119
-
120
- it "should have a global default_query_options hash" do
121
- expect(Mysql2::Client).to respond_to(:default_query_options)
122
- end
123
-
124
- it "should be able to connect via SSL options" do
125
- ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'"
126
- ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' }
127
- pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled
128
- ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' }
129
- pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled
130
-
131
- # You may need to adjust the lines below to match your SSL certificate paths
132
- ssl_client = nil
133
- expect {
134
- # rubocop:disable Style/TrailingComma
135
- ssl_client = Mysql2::Client.new(
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
- )
144
- )
145
- # rubocop:enable Style/TrailingComma
146
- }.not_to raise_error
147
-
148
- results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
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
153
-
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
158
-
159
- ssl_client.close
160
- end
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
-
179
- it "should not leave dangling connections after garbage collection" do
180
- run_gc
181
-
182
- client = Mysql2::Client.new(DatabaseCredentials['root'])
183
- before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
184
-
185
- 10.times do
186
- Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
187
- end
188
- after_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
189
- expect(after_count).to eq(before_count + 10)
190
-
191
- run_gc
192
- final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
193
- expect(final_count).to eq(before_count)
194
- end
195
-
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)
198
-
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 {}
205
-
206
- fork do
207
- client.query('SELECT 1')
208
- client = nil
209
- run_gc
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
217
- end
218
-
219
- it "should be able to connect to database with numeric-only name" do
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']}`"
227
- end
228
-
229
- it "should respond to #close" do
230
- expect(@client).to respond_to(:close)
231
- end
232
-
233
- it "should be able to close properly" do
234
- expect(@client.close).to be_nil
235
- expect {
236
- @client.query "SELECT 1"
237
- }.to raise_error(Mysql2::Error)
238
- end
239
-
240
- it "should respond to #query" do
241
- expect(@client).to respond_to(:query)
242
- end
243
-
244
- it "should respond to #warning_count" do
245
- expect(@client).to respond_to(:warning_count)
246
- end
247
-
248
- context "#warning_count" do
249
- context "when no warnings" do
250
- it "should 0" do
251
- @client.query('select 1')
252
- expect(@client.warning_count).to eq(0)
253
- end
254
- end
255
- context "when has a warnings" do
256
- it "should > 0" do
257
- # "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS"
258
- # http://dev.mysql.com/doc/refman/5.0/en/explain-extended.html
259
- @client.query("explain extended select 1")
260
- expect(@client.warning_count).to be > 0
261
- end
262
- end
263
- end
264
-
265
- it "should respond to #query_info" do
266
- expect(@client).to respond_to(:query_info)
267
- end
268
-
269
- context "#query_info" do
270
- context "when no info present" do
271
- it "should 0" do
272
- @client.query('select 1')
273
- expect(@client.query_info).to be_empty
274
- expect(@client.query_info_string).to be_nil
275
- end
276
- end
277
- context "when has some info" do
278
- it "should retrieve it" do
279
- @client.query "USE test"
280
- @client.query "CREATE TABLE IF NOT EXISTS infoTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
281
-
282
- # http://dev.mysql.com/doc/refman/5.0/en/mysql-info.html says
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).
284
- @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)")
285
-
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')
288
-
289
- @client.query "DROP TABLE infoTest"
290
- end
291
- end
292
- end
293
-
294
- context ":local_infile" do
295
- before(:all) do
296
- @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true)
297
- local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'"
298
- local_enabled = local.any? { |x| x['Value'] == 'ON' }
299
- pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled
300
-
301
- @client_i.query %[
302
- CREATE TABLE IF NOT EXISTS infileTest (
303
- id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
304
- foo VARCHAR(10),
305
- bar MEDIUMTEXT
306
- )
307
- ]
308
- end
309
-
310
- after(:all) do
311
- @client_i.query "DROP TABLE infileTest"
312
- end
313
-
314
- it "should raise an error when local_infile is disabled" do
315
- client = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => false)
316
- expect {
317
- client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
318
- }.to raise_error(Mysql2::Error, /command is not allowed/)
319
- end
320
-
321
- it "should raise an error when a non-existent file is loaded" do
322
- expect {
323
- @client_i.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest"
324
- }.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here')
325
- end
326
-
327
- it "should LOAD DATA LOCAL INFILE" do
328
- @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
329
- info = @client_i.query_info
330
- expect(info).to eql(:records => 1, :deleted => 0, :skipped => 0, :warnings => 0)
331
-
332
- result = @client_i.query "SELECT * FROM infileTest"
333
- expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World')
334
- end
335
- end
336
-
337
- it "should expect connect_timeout to be a positive integer" do
338
- expect {
339
- Mysql2::Client.new(:connect_timeout => -1)
340
- }.to raise_error(Mysql2::Error)
341
- end
342
-
343
- it "should expect read_timeout to be a positive integer" do
344
- expect {
345
- Mysql2::Client.new(:read_timeout => -1)
346
- }.to raise_error(Mysql2::Error)
347
- end
348
-
349
- it "should expect write_timeout to be a positive integer" do
350
- expect {
351
- Mysql2::Client.new(:write_timeout => -1)
352
- }.to raise_error(Mysql2::Error)
353
- end
354
-
355
- context "#query" do
356
- it "should let you query again if iterating is finished when streaming" do
357
- @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a
358
-
359
- expect {
360
- @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false)
361
- }.to_not raise_error
362
- end
363
-
364
- it "should not let you query again if iterating is not finished when streaming" do
365
- @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).first
366
-
367
- expect {
368
- @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false)
369
- }.to raise_exception(Mysql2::Error)
370
- end
371
-
372
- it "should only accept strings as the query parameter" do
373
- expect {
374
- @client.query ["SELECT 'not right'"]
375
- }.to raise_error(TypeError)
376
- end
377
-
378
- it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do
379
- result = @client.query "SELECT 1", :something => :else
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))
383
-
384
- result = @client.query "SELECT 1"
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)
387
- end
388
-
389
- it "should allow changing query options for subsequent queries" do
390
- @client.query_options.merge!(:something => :else)
391
- result = @client.query "SELECT 1"
392
- expect(@client.query_options[:something]).to eql(:else)
393
- expect(result.instance_variable_get('@query_options')[:something]).to eql(:else)
394
-
395
- # Clean up after this test
396
- @client.query_options.delete(:something)
397
- expect(@client.query_options[:something]).to be_nil
398
- end
399
-
400
- it "should return results as a hash by default" do
401
- expect(@client.query("SELECT 1").first).to be_an_instance_of(Hash)
402
- end
403
-
404
- it "should be able to return results as an array" do
405
- expect(@client.query("SELECT 1", :as => :array).first).to be_an_instance_of(Array)
406
- @client.query("SELECT 1").each(:as => :array)
407
- end
408
-
409
- it "should be able to return results with symbolized keys" do
410
- expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0]).to be_an_instance_of(Symbol)
411
- end
412
-
413
- it "should require an open connection" do
414
- @client.close
415
- expect {
416
- @client.query "SELECT 1"
417
- }.to raise_error(Mysql2::Error)
418
- end
419
-
420
- if RUBY_PLATFORM !~ /mingw|mswin/
421
- it "should not allow another query to be sent without fetching a result first" do
422
- @client.query("SELECT 1", :async => true)
423
- expect {
424
- @client.query("SELECT 1")
425
- }.to raise_error(Mysql2::Error)
426
- end
427
-
428
- it "should describe the thread holding the active query" do
429
- thr = Thread.new { @client.query("SELECT 1", :async => true) }
430
-
431
- thr.join
432
- expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect)))
433
- end
434
-
435
- it "should timeout if we wait longer than :read_timeout" do
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)
440
- end
441
-
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
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')
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))
470
- end
471
-
472
- it "#socket should return a Fixnum (file descriptor from C)" do
473
- expect(@client.socket).to be_an_instance_of(Fixnum)
474
- expect(@client.socket).not_to eql(0)
475
- end
476
-
477
- it "#socket should require an open connection" do
478
- @client.close
479
- expect {
480
- @client.socket
481
- }.to raise_error(Mysql2::Error)
482
- end
483
-
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)
488
-
489
- # expect the connection to not be broken
490
- expect { @client.query('SELECT 1') }.to_not raise_error
491
- end
492
-
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)
497
-
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
505
- end
506
-
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')
511
- end
512
-
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
517
-
518
- client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true))
519
-
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
522
- end
523
-
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)
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
539
- end
540
-
541
- it "threaded queries should be supported" do
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
551
- end
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))
558
- end
559
-
560
- it "evented async queries should be supported" do
561
- # should immediately return nil
562
- expect(@client.query("SELECT sleep(0.1)", :async => true)).to eql(nil)
563
-
564
- io_wrapper = IO.for_fd(@client.socket)
565
- loops = 0
566
- loop do
567
- if IO.select([io_wrapper], nil, nil, 0.05)
568
- break
569
- else
570
- loops += 1
571
- end
572
- end
573
-
574
- # make sure we waited some period of time
575
- expect(loops >= 1).to be true
576
-
577
- result = @client.async_result
578
- expect(result).to be_an_instance_of(Mysql2::Result)
579
- end
580
- end
581
-
582
- context "Multiple results sets" do
583
- before(:each) do
584
- @multi_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:flags => Mysql2::Client::MULTI_STATEMENTS))
585
- end
586
-
587
- it "should raise an exception when one of multiple statements fails" do
588
- result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';")
589
- expect(result.first['set_1']).to be(1)
590
- expect {
591
- @multi_client.next_result
592
- }.to raise_error(Mysql2::Error)
593
- expect(@multi_client.next_result).to be false
594
- end
595
-
596
- it "returns multiple result sets" do
597
- expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql('set_1' => 1)
598
-
599
- expect(@multi_client.next_result).to be true
600
- expect(@multi_client.store_result.first).to eql('set_2' => 2)
601
-
602
- expect(@multi_client.next_result).to be false
603
- end
604
-
605
- it "does not interfere with other statements" do
606
- @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
607
- @multi_client.store_result while @multi_client.next_result
608
-
609
- expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq('next' => 3)
610
- end
611
-
612
- it "will raise on query if there are outstanding results to read" do
613
- @multi_client.query("SELECT 1; SELECT 2; SELECT 3")
614
- expect {
615
- @multi_client.query("SELECT 4")
616
- }.to raise_error(Mysql2::Error)
617
- end
618
-
619
- it "#abandon_results! should work" do
620
- @multi_client.query("SELECT 1; SELECT 2; SELECT 3")
621
- @multi_client.abandon_results!
622
- expect {
623
- @multi_client.query("SELECT 4")
624
- }.not_to raise_error
625
- end
626
-
627
- it "#more_results? should work" do
628
- @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
629
- expect(@multi_client.more_results?).to be true
630
-
631
- @multi_client.next_result
632
- @multi_client.store_result
633
-
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
650
- end
651
- end
652
- end
653
-
654
- it "should respond to #socket" do
655
- expect(@client).to respond_to(:socket)
656
- end
657
-
658
- if RUBY_PLATFORM =~ /mingw|mswin/
659
- it "#socket should raise as it's not supported" do
660
- expect {
661
- @client.socket
662
- }.to raise_error(Mysql2::Error)
663
- end
664
- end
665
-
666
- it "should respond to escape" do
667
- expect(Mysql2::Client).to respond_to(:escape)
668
- end
669
-
670
- context "escape" do
671
- it "should return a new SQL-escape version of the passed string" do
672
- expect(Mysql2::Client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno")
673
- end
674
-
675
- it "should return the passed string if nothing was escaped" do
676
- str = "plain"
677
- expect(Mysql2::Client.escape(str).object_id).to eql(str.object_id)
678
- end
679
-
680
- it "should not overflow the thread stack" do
681
- expect {
682
- Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join
683
- }.not_to raise_error
684
- end
685
-
686
- it "should not overflow the process stack" do
687
- expect {
688
- Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join
689
- }.not_to raise_error
690
- end
691
-
692
- unless RUBY_VERSION =~ /1.8/
693
- it "should carry over the original string's encoding" do
694
- str = "abc'def\"ghi\0jkl%mno"
695
- escaped = Mysql2::Client.escape(str)
696
- expect(escaped.encoding).to eql(str.encoding)
697
-
698
- str.encode!('us-ascii')
699
- escaped = Mysql2::Client.escape(str)
700
- expect(escaped.encoding).to eql(str.encoding)
701
- end
702
- end
703
- end
704
-
705
- it "should respond to #escape" do
706
- expect(@client).to respond_to(:escape)
707
- end
708
-
709
- context "#escape" do
710
- it "should return a new SQL-escape version of the passed string" do
711
- expect(@client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno")
712
- end
713
-
714
- it "should return the passed string if nothing was escaped" do
715
- str = "plain"
716
- expect(@client.escape(str).object_id).to eql(str.object_id)
717
- end
718
-
719
- it "should not overflow the thread stack" do
720
- expect {
721
- Thread.new { @client.escape("'" * 256 * 1024) }.join
722
- }.not_to raise_error
723
- end
724
-
725
- it "should not overflow the process stack" do
726
- expect {
727
- Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
728
- }.not_to raise_error
729
- end
730
-
731
- it "should require an open connection" do
732
- @client.close
733
- expect {
734
- @client.escape ""
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
749
- end
750
- end
751
-
752
- it "should respond to #info" do
753
- expect(@client).to respond_to(:info)
754
- end
755
-
756
- it "#info should return a hash containing the client version ID and String" do
757
- info = @client.info
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)
763
- end
764
-
765
- context "strings returned by #info" do
766
- before { pending('Encoding is undefined') unless defined?(Encoding) }
767
-
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
773
-
774
- context "strings returned by .info" do
775
- before { pending('Encoding is undefined') unless defined?(Encoding) }
776
-
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)
780
- end
781
- end
782
-
783
- it "should respond to #server_info" do
784
- expect(@client).to respond_to(:server_info)
785
- end
786
-
787
- it "#server_info should return a hash containing the client version ID and String" do
788
- server_info = @client.server_info
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)
794
- end
795
-
796
- it "#server_info should require an open connection" do
797
- @client.close
798
- expect {
799
- @client.server_info
800
- }.to raise_error(Mysql2::Error)
801
- end
802
-
803
- context "strings returned by #server_info" do
804
- before { pending('Encoding is undefined') unless defined?(Encoding) }
805
-
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)
812
- end
813
- end
814
-
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
819
-
820
- with_internal_encoding Encoding::ASCII do
821
- expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal)
822
- end
823
- end
824
- end
825
-
826
- it "should raise a Mysql2::Error exception upon connection failure" do
827
- expect {
828
- Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
829
- }.to raise_error(Mysql2::Error)
830
-
831
- expect {
832
- Mysql2::Client.new DatabaseCredentials['root']
833
- }.not_to raise_error
834
- end
835
-
836
- context 'write operations api' do
837
- before(:each) do
838
- @client.query "USE test"
839
- @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
840
- end
841
-
842
- after(:each) do
843
- @client.query "DROP TABLE lastIdTest"
844
- end
845
-
846
- it "should respond to #last_id" do
847
- expect(@client).to respond_to(:last_id)
848
- end
849
-
850
- it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
851
- expect(@client.last_id).to eql(0)
852
- @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
853
- expect(@client.last_id).to eql(1)
854
- end
855
-
856
- it "should respond to #last_id" do
857
- expect(@client).to respond_to(:last_id)
858
- end
859
-
860
- it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
861
- @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
862
- expect(@client.affected_rows).to eql(1)
863
- @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
864
- expect(@client.affected_rows).to eql(1)
865
- end
866
-
867
- it "#last_id should handle BIGINT auto-increment ids above 32 bits" do
868
- # The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x.
869
- # Insert a row with a given ID, this should raise the auto-increment state
870
- @client.query "INSERT INTO lastIdTest (id, blah) VALUES (5000000000, 5000)"
871
- expect(@client.last_id).to eql(5000000000)
872
- @client.query "INSERT INTO lastIdTest (blah) VALUES (5001)"
873
- expect(@client.last_id).to eql(5000000001)
874
- end
875
- end
876
-
877
- it "should respond to #thread_id" do
878
- expect(@client).to respond_to(:thread_id)
879
- end
880
-
881
- it "#thread_id should be a Fixnum" do
882
- expect(@client.thread_id).to be_an_instance_of(Fixnum)
883
- end
884
-
885
- it "should respond to #ping" do
886
- expect(@client).to respond_to(:ping)
887
- end
888
-
889
- context "select_db" do
890
- before(:each) do
891
- 2.times do |i|
892
- @client.query("CREATE DATABASE test_selectdb_#{i}")
893
- @client.query("USE test_selectdb_#{i}")
894
- @client.query("CREATE TABLE test#{i} (`id` int NOT NULL PRIMARY KEY)")
895
- end
896
- end
897
-
898
- after(:each) do
899
- 2.times do |i|
900
- @client.query("DROP DATABASE test_selectdb_#{i}")
901
- end
902
- end
903
-
904
- it "should respond to #select_db" do
905
- expect(@client).to respond_to(:select_db)
906
- end
907
-
908
- it "should switch databases" do
909
- @client.select_db("test_selectdb_0")
910
- expect(@client.query("SHOW TABLES").first.values.first).to eql("test0")
911
- @client.select_db("test_selectdb_1")
912
- expect(@client.query("SHOW TABLES").first.values.first).to eql("test1")
913
- @client.select_db("test_selectdb_0")
914
- expect(@client.query("SHOW TABLES").first.values.first).to eql("test0")
915
- end
916
-
917
- it "should raise a Mysql2::Error when the database doesn't exist" do
918
- expect {
919
- @client.select_db("nopenothere")
920
- }.to raise_error(Mysql2::Error)
921
- end
922
-
923
- it "should return the database switched to" do
924
- expect(@client.select_db("test_selectdb_1")).to eq("test_selectdb_1")
925
- end
926
- end
927
-
928
- it "#thread_id should return a boolean" do
929
- expect(@client.ping).to eql(true)
930
- @client.close
931
- expect(@client.ping).to eql(false)
932
- end
933
-
934
- unless RUBY_VERSION =~ /1.8/
935
- it "should respond to #encoding" do
936
- expect(@client).to respond_to(:encoding)
937
- end
938
- end
939
- end