mysql2 0.4.10 → 0.5.6

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 +167 -48
  3. data/ext/mysql2/client.c +335 -108
  4. data/ext/mysql2/client.h +10 -41
  5. data/ext/mysql2/extconf.rb +84 -26
  6. data/ext/mysql2/mysql2_ext.c +8 -2
  7. data/ext/mysql2/mysql2_ext.h +21 -4
  8. data/ext/mysql2/mysql_enc_name_to_ruby.h +60 -55
  9. data/ext/mysql2/mysql_enc_to_ruby.h +79 -3
  10. data/ext/mysql2/result.c +298 -92
  11. data/ext/mysql2/result.h +3 -3
  12. data/ext/mysql2/statement.c +137 -81
  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 +55 -28
  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 +5 -5
  24. data/support/C74CD1D8.asc +104 -0
  25. data/support/mysql_enc_to_ruby.rb +9 -3
  26. data/support/ruby_enc_to_mysql.rb +8 -5
  27. metadata +19 -62
  28. data/examples/eventmachine.rb +0 -21
  29. data/examples/threaded.rb +0 -18
  30. data/spec/configuration.yml.example +0 -11
  31. data/spec/em/em_spec.rb +0 -136
  32. data/spec/my.cnf.example +0 -9
  33. data/spec/mysql2/client_spec.rb +0 -1039
  34. data/spec/mysql2/error_spec.rb +0 -82
  35. data/spec/mysql2/result_spec.rb +0 -545
  36. data/spec/mysql2/statement_spec.rb +0 -776
  37. data/spec/rcov.opts +0 -3
  38. data/spec/spec_helper.rb +0 -108
  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,1039 +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
- 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)
33
- end
34
-
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
56
- end
57
- end
58
-
59
- it "should accept connect flags and pass them to #connect" do
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)
74
- end
75
-
76
- it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
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')
123
- end
124
-
125
- it "should have a global default_query_options hash" do
126
- expect(Mysql2::Client).to respond_to(:default_query_options)
127
- end
128
-
129
- it "should be able to connect via SSL options" do
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
137
- ssl_client = nil
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
147
- )
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
254
-
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}`"
274
-
275
- expect {
276
- new_client('database' => database)
277
- }.not_to raise_error
278
-
279
- @client.query "DROP DATABASE IF EXISTS `#{database}`"
280
- end
281
-
282
- it "should respond to #close" do
283
- expect(@client).to respond_to(:close)
284
- end
285
-
286
- it "should be able to close properly" do
287
- expect(@client.close).to be_nil
288
- expect {
289
- @client.query "SELECT 1"
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)
310
- end
311
-
312
- it "should respond to #query" do
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)
418
- end
419
-
420
- it "should expect read_timeout to be a positive integer" do
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
436
- end
437
-
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
-
455
- it "should only accept strings as the query parameter" do
456
- expect {
457
- @client.query ["SELECT 'not right'"]
458
- }.to raise_error(TypeError)
459
- end
460
-
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
481
- end
482
-
483
- it "should return results as a hash by default" do
484
- expect(@client.query("SELECT 1").first).to be_an_instance_of(Hash)
485
- end
486
-
487
- it "should be able to return results as an array" do
488
- expect(@client.query("SELECT 1", :as => :array).first).to be_an_instance_of(Array)
489
- @client.query("SELECT 1").each(:as => :array)
490
- end
491
-
492
- it "should be able to return results with symbolized keys" do
493
- expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0]).to be_an_instance_of(Symbol)
494
- end
495
-
496
- it "should require an open connection" do
497
- @client.close
498
- expect {
499
- @client.query "SELECT 1"
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
520
- end
521
-
522
- if RUBY_PLATFORM !~ /mingw|mswin/
523
- it "should not allow another query to be sent without fetching a result first" do
524
- @client.query("SELECT 1", :async => true)
525
- expect {
526
- @client.query("SELECT 1")
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)))
535
- end
536
-
537
- it "should timeout if we wait longer than :read_timeout" do
538
- client = new_client(:read_timeout => 0)
539
- expect {
540
- client.query('SELECT SLEEP(0.1)')
541
- }.to raise_error(Mysql2::Error)
542
- end
543
-
544
- # XXX this test is not deterministic (because Unix signal handling is not)
545
- # and may fail on a loaded system
546
- it "should run signal handlers while waiting for a response" do
547
- kill_time = 0.1
548
- query_time = 2 * kill_time
549
-
550
- mark = {}
551
-
552
- begin
553
- trap(:USR1) { mark.store(:USR1, Time.now) }
554
- pid = fork do
555
- sleep kill_time # wait for client query to start
556
- Process.kill(:USR1, Process.ppid)
557
- sleep # wait for explicit kill to prevent GC disconnect
558
- end
559
- mark.store(:QUERY_START, Time.now)
560
- @client.query("SELECT SLEEP(#{query_time})")
561
- mark.store(:QUERY_END, Time.now)
562
- ensure
563
- Process.kill(:TERM, pid)
564
- Process.waitpid2(pid)
565
- trap(:USR1, 'DEFAULT')
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))
572
- end
573
-
574
- it "#socket should return a Fixnum (file descriptor from C)" do
575
- expect(@client.socket).to be_an_instance_of(Fixnum)
576
- expect(@client.socket).not_to eql(0)
577
- end
578
-
579
- it "#socket should require an open connection" do
580
- @client.close
581
- expect {
582
- @client.socket
583
- }.to raise_error(Mysql2::Error)
584
- end
585
-
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
598
- end
599
-
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.')
609
- end
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
615
- end
616
-
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
632
- end
633
-
634
- it "threaded queries should be supported" do
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})")
642
- end
643
- Thread.current.object_id
644
- end
645
- end
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))
652
- end
653
-
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.")
656
- # should immediately return nil
657
- expect(@client.query("SELECT sleep(0.1)", :async => true)).to eql(nil)
658
-
659
- io_wrapper = IO.for_fd(@client.socket, :autoclose => false)
660
- loops = 0
661
- loop do
662
- if IO.select([io_wrapper], nil, nil, 0.05)
663
- break
664
- else
665
- loops += 1
666
- end
667
- end
668
-
669
- # make sure we waited some period of time
670
- expect(loops >= 1).to be true
671
-
672
- result = @client.async_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
745
- end
746
- end
747
- end
748
-
749
- it "should respond to #socket" do
750
- expect(@client).to respond_to(:socket)
751
- end
752
-
753
- if RUBY_PLATFORM =~ /mingw|mswin/
754
- it "#socket should raise as it's not supported" do
755
- expect {
756
- @client.socket
757
- }.to raise_error(Mysql2::Error, /Raw access to the mysql file descriptor isn't supported on Windows/)
758
- end
759
- end
760
-
761
- it "should respond to escape" do
762
- expect(Mysql2::Client).to respond_to(:escape)
763
- end
764
-
765
- context "escape" do
766
- it "should return a new SQL-escape version of the passed string" do
767
- expect(Mysql2::Client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno")
768
- end
769
-
770
- it "should return the passed string if nothing was escaped" do
771
- str = "plain"
772
- expect(Mysql2::Client.escape(str).object_id).to eql(str.object_id)
773
- end
774
-
775
- it "should not overflow the thread stack" do
776
- expect {
777
- Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join
778
- }.not_to raise_error
779
- end
780
-
781
- it "should not overflow the process stack" do
782
- expect {
783
- Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join
784
- }.not_to raise_error
785
- end
786
-
787
- unless RUBY_VERSION =~ /1.8/
788
- it "should carry over the original string's encoding" do
789
- str = "abc'def\"ghi\0jkl%mno"
790
- escaped = Mysql2::Client.escape(str)
791
- expect(escaped.encoding).to eql(str.encoding)
792
-
793
- str.encode!('us-ascii')
794
- escaped = Mysql2::Client.escape(str)
795
- expect(escaped.encoding).to eql(str.encoding)
796
- end
797
- end
798
- end
799
-
800
- it "should respond to #escape" do
801
- expect(@client).to respond_to(:escape)
802
- end
803
-
804
- context "#escape" do
805
- it "should return a new SQL-escape version of the passed string" do
806
- expect(@client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno")
807
- end
808
-
809
- it "should return the passed string if nothing was escaped" do
810
- str = "plain"
811
- expect(@client.escape(str).object_id).to eql(str.object_id)
812
- end
813
-
814
- it "should not overflow the thread stack" do
815
- expect {
816
- Thread.new { @client.escape("'" * 256 * 1024) }.join
817
- }.not_to raise_error
818
- end
819
-
820
- it "should not overflow the process stack" do
821
- expect {
822
- Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
823
- }.not_to raise_error
824
- end
825
-
826
- it "should require an open connection" do
827
- @client.close
828
- expect {
829
- @client.escape ""
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
844
- end
845
- end
846
-
847
- it "should respond to #info" do
848
- expect(@client).to respond_to(:info)
849
- end
850
-
851
- it "#info should return a hash containing the client version ID and String" do
852
- info = @client.info
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)
858
- end
859
-
860
- context "strings returned by #info" do
861
- before { pending('Encoding is undefined') unless defined?(Encoding) }
862
-
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
868
-
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)
875
- end
876
- end
877
-
878
- it "should respond to #server_info" do
879
- expect(@client).to respond_to(:server_info)
880
- end
881
-
882
- it "#server_info should return a hash containing the client version ID and String" do
883
- server_info = @client.server_info
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)
889
- end
890
-
891
- it "#server_info should require an open connection" do
892
- @client.close
893
- expect {
894
- @client.server_info
895
- }.to raise_error(Mysql2::Error)
896
- end
897
-
898
- context "strings returned by #server_info" do
899
- before { pending('Encoding is undefined') unless defined?(Encoding) }
900
-
901
- it "should default to the connection's encoding if Encoding.default_internal is nil" do
902
- with_internal_encoding nil do
903
- expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8)
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)
913
- end
914
-
915
- with_internal_encoding Encoding::ASCII do
916
- expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal)
917
- end
918
- end
919
- end
920
-
921
- it "should raise a Mysql2::Error exception upon connection failure" do
922
- expect {
923
- new_client(:host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42')
924
- }.to raise_error(Mysql2::Error)
925
-
926
- expect {
927
- new_client(DatabaseCredentials['root'])
928
- }.not_to raise_error
929
- end
930
-
931
- context 'write operations api' do
932
- before(:each) do
933
- @client.query "USE test"
934
- @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
935
- end
936
-
937
- after(:each) do
938
- @client.query "DROP TABLE lastIdTest"
939
- end
940
-
941
- it "should respond to #last_id" do
942
- expect(@client).to respond_to(:last_id)
943
- end
944
-
945
- it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
946
- expect(@client.last_id).to eql(0)
947
- @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
948
- expect(@client.last_id).to eql(1)
949
- end
950
-
951
- it "should respond to #last_id" do
952
- expect(@client).to respond_to(:last_id)
953
- end
954
-
955
- it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
956
- @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
957
- expect(@client.affected_rows).to eql(1)
958
- @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
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)
969
- end
970
- end
971
-
972
- it "should respond to #thread_id" do
973
- expect(@client).to respond_to(:thread_id)
974
- end
975
-
976
- it "#thread_id should be a Fixnum" do
977
- expect(@client.thread_id).to be_an_instance_of(Fixnum)
978
- end
979
-
980
- it "should respond to #ping" do
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
1021
- end
1022
-
1023
- it "#thread_id should return a boolean" do
1024
- expect(@client.ping).to eql(true)
1025
- @client.close
1026
- expect(@client.ping).to eql(false)
1027
- end
1028
-
1029
- it "should be able to connect using plaintext password" do
1030
- client = new_client(:enable_cleartext_plugin => true)
1031
- client.query('SELECT 1')
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
1038
- end
1039
- end