mysql2 0.4.2 → 0.5.5

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 (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