mysql2 0.4.10 → 0.5.4

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.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +98 -38
  3. data/ext/mysql2/client.c +223 -76
  4. data/ext/mysql2/client.h +1 -39
  5. data/ext/mysql2/extconf.rb +46 -26
  6. data/ext/mysql2/mysql2_ext.c +8 -2
  7. data/ext/mysql2/mysql2_ext.h +8 -4
  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 +242 -86
  11. data/ext/mysql2/result.h +3 -3
  12. data/ext/mysql2/statement.c +90 -73
  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 +51 -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 +18 -15
  22. data/support/3A79BD29.asc +49 -0
  23. data/support/5072E1F5.asc +5 -5
  24. data/support/mysql_enc_to_ruby.rb +8 -3
  25. data/support/ruby_enc_to_mysql.rb +7 -5
  26. metadata +14 -58
  27. data/examples/eventmachine.rb +0 -21
  28. data/examples/threaded.rb +0 -18
  29. data/spec/configuration.yml.example +0 -11
  30. data/spec/em/em_spec.rb +0 -136
  31. data/spec/my.cnf.example +0 -9
  32. data/spec/mysql2/client_spec.rb +0 -1039
  33. data/spec/mysql2/error_spec.rb +0 -82
  34. data/spec/mysql2/result_spec.rb +0 -545
  35. data/spec/mysql2/statement_spec.rb +0 -776
  36. data/spec/rcov.opts +0 -3
  37. data/spec/spec_helper.rb +0 -108
  38. data/spec/ssl/ca-cert.pem +0 -17
  39. data/spec/ssl/ca-key.pem +0 -27
  40. data/spec/ssl/ca.cnf +0 -22
  41. data/spec/ssl/cert.cnf +0 -22
  42. data/spec/ssl/client-cert.pem +0 -17
  43. data/spec/ssl/client-key.pem +0 -27
  44. data/spec/ssl/client-req.pem +0 -15
  45. data/spec/ssl/gen_certs.sh +0 -48
  46. data/spec/ssl/pkcs8-client-key.pem +0 -28
  47. data/spec/ssl/pkcs8-server-key.pem +0 -28
  48. data/spec/ssl/server-cert.pem +0 -17
  49. data/spec/ssl/server-key.pem +0 -27
  50. data/spec/ssl/server-req.pem +0 -15
  51. 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