mysql2 0.3.11-x86-mswin32-60 → 0.3.18-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +280 -75
  3. data/ext/mysql2/client.c +721 -206
  4. data/ext/mysql2/client.h +26 -12
  5. data/ext/mysql2/extconf.rb +120 -16
  6. data/ext/mysql2/infile.c +122 -0
  7. data/ext/mysql2/infile.h +1 -0
  8. data/ext/mysql2/mysql2_ext.h +7 -4
  9. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  10. data/ext/mysql2/mysql_enc_to_ruby.h +246 -0
  11. data/ext/mysql2/result.c +230 -112
  12. data/ext/mysql2/result.h +4 -1
  13. data/lib/mysql2.rb +46 -3
  14. data/lib/mysql2/1.8/mysql2.so +0 -0
  15. data/lib/mysql2/1.9/mysql2.so +0 -0
  16. data/lib/mysql2/2.0/mysql2.so +0 -0
  17. data/lib/mysql2/2.1/mysql2.so +0 -0
  18. data/lib/mysql2/client.rb +48 -200
  19. data/lib/mysql2/console.rb +5 -0
  20. data/lib/mysql2/em.rb +22 -3
  21. data/lib/mysql2/error.rb +71 -6
  22. data/lib/mysql2/mysql2.rb +2 -0
  23. data/lib/mysql2/version.rb +1 -1
  24. data/spec/configuration.yml.example +17 -0
  25. data/spec/em/em_spec.rb +90 -5
  26. data/spec/my.cnf.example +9 -0
  27. data/spec/mysql2/client_spec.rb +501 -69
  28. data/spec/mysql2/error_spec.rb +58 -44
  29. data/spec/mysql2/result_spec.rb +191 -74
  30. data/spec/spec_helper.rb +23 -3
  31. data/spec/test_data +1 -0
  32. data/support/libmysql.def +219 -0
  33. data/support/mysql_enc_to_ruby.rb +82 -0
  34. data/support/ruby_enc_to_mysql.rb +61 -0
  35. data/vendor/README +654 -0
  36. data/vendor/libmysql.dll +0 -0
  37. metadata +86 -221
  38. data/.gitignore +0 -12
  39. data/.rspec +0 -3
  40. data/.rvmrc +0 -1
  41. data/.travis.yml +0 -7
  42. data/CHANGELOG.md +0 -244
  43. data/Gemfile +0 -3
  44. data/MIT-LICENSE +0 -20
  45. data/Rakefile +0 -5
  46. data/benchmark/active_record.rb +0 -51
  47. data/benchmark/active_record_threaded.rb +0 -42
  48. data/benchmark/allocations.rb +0 -33
  49. data/benchmark/escape.rb +0 -36
  50. data/benchmark/query_with_mysql_casting.rb +0 -80
  51. data/benchmark/query_without_mysql_casting.rb +0 -56
  52. data/benchmark/sequel.rb +0 -37
  53. data/benchmark/setup_db.rb +0 -119
  54. data/benchmark/threaded.rb +0 -44
  55. data/mysql2.gemspec +0 -29
  56. data/tasks/benchmarks.rake +0 -20
  57. data/tasks/compile.rake +0 -71
  58. data/tasks/rspec.rake +0 -16
  59. data/tasks/vendor_mysql.rake +0 -40
@@ -0,0 +1,2 @@
1
+ RUBY_VERSION =~ /(\d+.\d+)/
2
+ require "mysql2/#{$1}/mysql2"
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.3.11"
2
+ VERSION = "0.3.18"
3
3
  end
@@ -0,0 +1,17 @@
1
+ root:
2
+ host: localhost
3
+ username: root
4
+ password:
5
+ database: test
6
+
7
+ user:
8
+ host: localhost
9
+ username: LOCALUSERNAME
10
+ password:
11
+ database: mysql2_test
12
+
13
+ numericuser:
14
+ host: localhost
15
+ username: LOCALUSERNAME
16
+ password:
17
+ database: 12345
@@ -8,17 +8,19 @@ begin
8
8
  it "should support async queries" do
9
9
  results = []
10
10
  EM.run do
11
- client1 = Mysql2::EM::Client.new
11
+ client1 = Mysql2::EM::Client.new DatabaseCredentials['root']
12
12
  defer1 = client1.query "SELECT sleep(0.1) as first_query"
13
13
  defer1.callback do |result|
14
14
  results << result.first
15
+ client1.close
15
16
  EM.stop_event_loop
16
17
  end
17
18
 
18
- client2 = Mysql2::EM::Client.new
19
+ client2 = Mysql2::EM::Client.new DatabaseCredentials['root']
19
20
  defer2 = client2.query "SELECT sleep(0.025) second_query"
20
21
  defer2.callback do |result|
21
22
  results << result.first
23
+ client2.close
22
24
  end
23
25
  end
24
26
 
@@ -29,13 +31,14 @@ begin
29
31
  it "should support queries in callbacks" do
30
32
  results = []
31
33
  EM.run do
32
- client = Mysql2::EM::Client.new
34
+ client = Mysql2::EM::Client.new DatabaseCredentials['root']
33
35
  defer1 = client.query "SELECT sleep(0.025) as first_query"
34
36
  defer1.callback do |result|
35
37
  results << result.first
36
38
  defer2 = client.query "SELECT sleep(0.025) as second_query"
37
- defer2.callback do |result|
38
- results << result.first
39
+ defer2.callback do |r|
40
+ results << r.first
41
+ client.close
39
42
  EM.stop_event_loop
40
43
  end
41
44
  end
@@ -44,6 +47,88 @@ begin
44
47
  results[0].keys.should include("first_query")
45
48
  results[1].keys.should include("second_query")
46
49
  end
50
+
51
+ it "should not swallow exceptions raised in callbacks" do
52
+ lambda {
53
+ EM.run do
54
+ client = Mysql2::EM::Client.new DatabaseCredentials['root']
55
+ defer = client.query "SELECT sleep(0.1) as first_query"
56
+ defer.callback do |result|
57
+ client.close
58
+ raise 'some error'
59
+ end
60
+ defer.errback do |err|
61
+ # This _shouldn't_ be run, but it needed to prevent the specs from
62
+ # freezing if this test fails.
63
+ EM.stop_event_loop
64
+ end
65
+ end
66
+ }.should raise_error
67
+ end
68
+
69
+ context 'when an exception is raised by the client' do
70
+ let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] }
71
+ let(:error) { StandardError.new('some error') }
72
+ before { client.stub(:async_result).and_raise(error) }
73
+
74
+ it "should swallow exceptions raised in by the client" do
75
+ errors = []
76
+ EM.run do
77
+ defer = client.query "SELECT sleep(0.1) as first_query"
78
+ defer.callback do |result|
79
+ # This _shouldn't_ be run, but it is needed to prevent the specs from
80
+ # freezing if this test fails.
81
+ EM.stop_event_loop
82
+ end
83
+ defer.errback do |err|
84
+ errors << err
85
+ EM.stop_event_loop
86
+ end
87
+ end
88
+ errors.should == [error]
89
+ end
90
+
91
+ it "should fail the deferrable" do
92
+ callbacks_run = []
93
+ EM.run do
94
+ defer = client.query "SELECT sleep(0.025) as first_query"
95
+ EM.add_timer(0.1) do
96
+ defer.callback do |result|
97
+ callbacks_run << :callback
98
+ # This _shouldn't_ be run, but it is needed to prevent the specs from
99
+ # freezing if this test fails.
100
+ EM.stop_event_loop
101
+ end
102
+ defer.errback do |err|
103
+ callbacks_run << :errback
104
+ EM.stop_event_loop
105
+ end
106
+ end
107
+ end
108
+ callbacks_run.should == [:errback]
109
+ end
110
+ end
111
+
112
+ it "should not raise error when closing client with no query running" do
113
+ callbacks_run = []
114
+ EM.run do
115
+ client = Mysql2::EM::Client.new DatabaseCredentials['root']
116
+ defer = client.query("select sleep(0.025)")
117
+ defer.callback do |result|
118
+ callbacks_run << :callback
119
+ end
120
+ defer.errback do |err|
121
+ callbacks_run << :errback
122
+ end
123
+ EM.add_timer(0.1) do
124
+ callbacks_run.should == [:callback]
125
+ lambda {
126
+ client.close
127
+ }.should_not raise_error(/invalid binding to detach/)
128
+ EM.stop_event_loop
129
+ end
130
+ end
131
+ end
47
132
  end
48
133
  rescue LoadError
49
134
  puts "EventMachine not installed, skipping the specs that use it"
@@ -0,0 +1,9 @@
1
+ [root]
2
+ host=localhost
3
+ user=LOCALUSERNAME
4
+ password=
5
+
6
+ [client]
7
+ host=localhost
8
+ user=LOCALUSERNAME
9
+ password=
@@ -2,16 +2,47 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe Mysql2::Client do
5
- before(:each) do
6
- @client = Mysql2::Client.new
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
+ lambda {
10
+ opts = DatabaseCredentials['root'].merge(:default_file => cnf_file, :default_group => "test")
11
+ @client = Mysql2::Client.new(opts)
12
+ }.should_not raise_error(Mysql2::Error)
13
+ end
14
+
15
+ it "should not raise an exception without default group" do
16
+ lambda {
17
+ @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:default_file => cnf_file))
18
+ }.should_not raise_error(Mysql2::Error)
19
+ end
20
+ end
21
+
22
+ it "should raise an exception upon connection failure" do
23
+ lambda {
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
+ }.should raise_error(Mysql2::Error)
7
28
  end
8
29
 
9
30
  if defined? Encoding
10
31
  it "should raise an exception on create for invalid encodings" do
11
32
  lambda {
12
- c = Mysql2::Client.new(:encoding => "fake")
33
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "fake"))
13
34
  }.should raise_error(Mysql2::Error)
14
35
  end
36
+
37
+ it "should not raise an exception on create for a valid encoding" do
38
+ lambda {
39
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8"))
40
+ }.should_not raise_error(Mysql2::Error)
41
+
42
+ lambda {
43
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "big5"))
44
+ }.should_not raise_error(Mysql2::Error)
45
+ end
15
46
  end
16
47
 
17
48
  it "should accept connect flags and pass them to #connect" do
@@ -23,7 +54,7 @@ describe Mysql2::Client do
23
54
  end
24
55
  end
25
56
  client = klient.new :flags => Mysql2::Client::FOUND_ROWS
26
- (client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true
57
+ (client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).should be_true
27
58
  end
28
59
 
29
60
  it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
@@ -35,7 +66,7 @@ describe Mysql2::Client do
35
66
  end
36
67
  end
37
68
  client = klient.new
38
- (client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS |
69
+ (client.connect_args.last[6] & (Mysql2::Client::REMEMBER_OPTIONS |
39
70
  Mysql2::Client::LONG_PASSWORD |
40
71
  Mysql2::Client::LONG_FLAG |
41
72
  Mysql2::Client::TRANSACTIONS |
@@ -43,19 +74,66 @@ describe Mysql2::Client do
43
74
  Mysql2::Client::SECURE_CONNECTION)).should be_true
44
75
  end
45
76
 
77
+ it "should execute init command" do
78
+ options = DatabaseCredentials['root'].dup
79
+ options[:init_command] = "SET @something = 'setting_value';"
80
+ client = Mysql2::Client.new(options)
81
+ result = client.query("SELECT @something;")
82
+ result.first['@something'].should eq('setting_value')
83
+ end
84
+
85
+ it "should send init_command after reconnect" do
86
+ options = DatabaseCredentials['root'].dup
87
+ options[:init_command] = "SET @something = 'setting_value';"
88
+ options[:reconnect] = true
89
+ client = Mysql2::Client.new(options)
90
+
91
+ result = client.query("SELECT @something;")
92
+ result.first['@something'].should eq('setting_value')
93
+
94
+ # get the current connection id
95
+ result = client.query("SELECT CONNECTION_ID()")
96
+ first_conn_id = result.first['CONNECTION_ID()']
97
+
98
+ # break the current connection
99
+ begin
100
+ client.query("KILL #{first_conn_id}")
101
+ rescue Mysql2::Error
102
+ end
103
+
104
+ client.ping # reconnect now
105
+
106
+ # get the new connection id
107
+ result = client.query("SELECT CONNECTION_ID()")
108
+ second_conn_id = result.first['CONNECTION_ID()']
109
+
110
+ # confirm reconnect by checking the new connection id
111
+ first_conn_id.should_not == second_conn_id
112
+
113
+ # At last, check that the init command executed
114
+ result = client.query("SELECT @something;")
115
+ result.first['@something'].should eq('setting_value')
116
+ end
117
+
46
118
  it "should have a global default_query_options hash" do
47
119
  Mysql2::Client.should respond_to(:default_query_options)
48
120
  end
49
121
 
50
122
  it "should be able to connect via SSL options" do
51
- pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.")
123
+ ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'"
124
+ ssl_uncompiled = ssl.any? {|x| x['Value'] == 'OFF'}
125
+ pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled
126
+ ssl_disabled = ssl.any? {|x| x['Value'] == 'DISABLED'}
127
+ pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled
128
+
129
+ # You may need to adjust the lines below to match your SSL certificate paths
52
130
  ssl_client = nil
53
131
  lambda {
54
132
  ssl_client = Mysql2::Client.new(
55
- :sslkey => '/path/to/client-key.pem',
56
- :sslcert => '/path/to/client-cert.pem',
57
- :sslca => '/path/to/ca-cert.pem',
58
- :sslcapath => '/path/to/newcerts/',
133
+ :sslkey => '/etc/mysql/client-key.pem',
134
+ :sslcert => '/etc/mysql/client-cert.pem',
135
+ :sslca => '/etc/mysql/ca-cert.pem',
136
+ :sslcapath => '/etc/mysql/',
59
137
  :sslcipher => 'DHE-RSA-AES256-SHA'
60
138
  )
61
139
  }.should_not raise_error(Mysql2::Error)
@@ -63,11 +141,64 @@ describe Mysql2::Client do
63
141
  results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
64
142
  results[0]['Variable_name'].should eql('Ssl_cipher')
65
143
  results[0]['Value'].should_not be_nil
66
- results[0]['Value'].class.should eql(String)
144
+ results[0]['Value'].should be_kind_of(String)
145
+ results[0]['Value'].should_not be_empty
67
146
 
68
147
  results[1]['Variable_name'].should eql('Ssl_version')
69
148
  results[1]['Value'].should_not be_nil
70
- results[1]['Value'].class.should eql(String)
149
+ results[1]['Value'].should be_kind_of(String)
150
+ results[1]['Value'].should_not be_empty
151
+
152
+ ssl_client.close
153
+ end
154
+
155
+ it "should not leave dangling connections after garbage collection" do
156
+ GC.start
157
+ sleep 0.300 # Let GC do its work
158
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
159
+ before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
160
+
161
+ 10.times do
162
+ Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
163
+ end
164
+ after_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
165
+ after_count.should == before_count + 10
166
+
167
+ GC.start
168
+ sleep 0.300 # Let GC do its work
169
+ final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
170
+ final_count.should == before_count
171
+ end
172
+
173
+ if Process.respond_to?(:fork)
174
+ it "should not close connections when running in a child process" do
175
+ GC.start
176
+ sleep 1 if defined? Rubinius # Let the rbx GC thread do its work
177
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
178
+
179
+ fork do
180
+ client.query('SELECT 1')
181
+ client = nil
182
+ GC.start
183
+ sleep 1 if defined? Rubinius # Let the rbx GC thread do its work
184
+ end
185
+
186
+ Process.wait
187
+
188
+ # this will throw an error if the underlying socket was shutdown by the
189
+ # child's GC
190
+ expect { client.query('SELECT 1') }.to_not raise_exception
191
+ end
192
+ end
193
+
194
+ it "should be able to connect to database with numeric-only name" do
195
+ lambda {
196
+ creds = DatabaseCredentials['numericuser']
197
+ @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`"
198
+ @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`"
199
+ client = Mysql2::Client.new creds
200
+ @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`"
201
+ }.should_not raise_error
71
202
  end
72
203
 
73
204
  it "should respond to #close" do
@@ -85,22 +216,160 @@ describe Mysql2::Client do
85
216
  @client.should respond_to(:query)
86
217
  end
87
218
 
219
+ it "should respond to #warning_count" do
220
+ @client.should respond_to(:warning_count)
221
+ end
222
+
223
+ context "#warning_count" do
224
+ context "when no warnings" do
225
+ it "should 0" do
226
+ @client.query('select 1')
227
+ @client.warning_count.should == 0
228
+ end
229
+ end
230
+ context "when has a warnings" do
231
+ it "should > 0" do
232
+ # "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS"
233
+ # http://dev.mysql.com/doc/refman/5.0/en/explain-extended.html
234
+ @client.query("explain extended select 1")
235
+ @client.warning_count.should > 0
236
+ end
237
+ end
238
+ end
239
+
240
+ it "should respond to #query_info" do
241
+ @client.should respond_to(:query_info)
242
+ end
243
+
244
+ context "#query_info" do
245
+ context "when no info present" do
246
+ it "should 0" do
247
+ @client.query('select 1')
248
+ @client.query_info.should be_empty
249
+ @client.query_info_string.should be_nil
250
+ end
251
+ end
252
+ context "when has some info" do
253
+ it "should retrieve it" do
254
+ @client.query "USE test"
255
+ @client.query "CREATE TABLE IF NOT EXISTS infoTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
256
+
257
+ # http://dev.mysql.com/doc/refman/5.0/en/mysql-info.html says
258
+ # # 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).
259
+ @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)")
260
+
261
+ @client.query_info.should eql({:records => 2, :duplicates => 0, :warnings => 0})
262
+ @client.query_info_string.should eq('Records: 2 Duplicates: 0 Warnings: 0')
263
+
264
+ @client.query "DROP TABLE infoTest"
265
+ end
266
+ end
267
+ end
268
+
269
+ context ":local_infile" do
270
+ before(:all) do
271
+ @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true)
272
+ local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'"
273
+ local_enabled = local.any? {|x| x['Value'] == 'ON'}
274
+ pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled
275
+
276
+ @client_i.query %[
277
+ CREATE TABLE IF NOT EXISTS infileTest (
278
+ id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
279
+ foo VARCHAR(10),
280
+ bar MEDIUMTEXT
281
+ )
282
+ ]
283
+ end
284
+
285
+ after(:all) do
286
+ @client_i.query "DROP TABLE infileTest"
287
+ end
288
+
289
+ it "should raise an error when local_infile is disabled" do
290
+ client = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => false)
291
+ lambda {
292
+ client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
293
+ }.should raise_error(Mysql2::Error, %r{command is not allowed})
294
+ end
295
+
296
+ it "should raise an error when a non-existent file is loaded" do
297
+ lambda {
298
+ @client_i.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest"
299
+ }.should_not raise_error(Mysql2::Error, %r{file not found: this/file/is/not/here})
300
+ end
301
+
302
+ it "should LOAD DATA LOCAL INFILE" do
303
+ @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
304
+ info = @client_i.query_info
305
+ info.should eql({:records => 1, :deleted => 0, :skipped => 0, :warnings => 0})
306
+
307
+ result = @client_i.query "SELECT * FROM infileTest"
308
+ result.first.should eql({'id' => 1, 'foo' => 'Hello', 'bar' => 'World'})
309
+ end
310
+ end
311
+
312
+ it "should expect connect_timeout to be a positive integer" do
313
+ lambda {
314
+ Mysql2::Client.new(:connect_timeout => -1)
315
+ }.should raise_error(Mysql2::Error)
316
+ end
317
+
88
318
  it "should expect read_timeout to be a positive integer" do
89
319
  lambda {
90
320
  Mysql2::Client.new(:read_timeout => -1)
91
321
  }.should raise_error(Mysql2::Error)
92
322
  end
93
323
 
324
+ it "should expect write_timeout to be a positive integer" do
325
+ lambda {
326
+ Mysql2::Client.new(:write_timeout => -1)
327
+ }.should raise_error(Mysql2::Error)
328
+ end
329
+
94
330
  context "#query" do
331
+ it "should let you query again if iterating is finished when streaming" do
332
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a
333
+
334
+ expect {
335
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false)
336
+ }.to_not raise_exception(Mysql2::Error)
337
+ end
338
+
339
+ it "should not let you query again if iterating is not finished when streaming" do
340
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).first
341
+
342
+ expect {
343
+ @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false)
344
+ }.to raise_exception(Mysql2::Error)
345
+ end
346
+
95
347
  it "should only accept strings as the query parameter" do
96
348
  lambda {
97
349
  @client.query ["SELECT 'not right'"]
98
350
  }.should raise_error(TypeError)
99
351
  end
100
352
 
101
- it "should accept an options hash that inherits from Mysql2::Client.default_query_options" do
102
- @client.query "SELECT 1", :something => :else
103
- @client.query_options.should eql(@client.query_options.merge(:something => :else))
353
+ it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do
354
+ result = @client.query "SELECT 1", :something => :else
355
+ @client.query_options[:something].should be_nil
356
+ result.instance_variable_get('@query_options').should eql(@client.query_options.merge(:something => :else))
357
+ @client.instance_variable_get('@current_query_options').should eql(@client.query_options.merge(:something => :else))
358
+
359
+ result = @client.query "SELECT 1"
360
+ result.instance_variable_get('@query_options').should eql(@client.query_options)
361
+ @client.instance_variable_get('@current_query_options').should eql(@client.query_options)
362
+ end
363
+
364
+ it "should allow changing query options for subsequent queries" do
365
+ @client.query_options.merge!(:something => :else)
366
+ result = @client.query "SELECT 1"
367
+ @client.query_options[:something].should eql(:else)
368
+ result.instance_variable_get('@query_options')[:something].should eql(:else)
369
+
370
+ # Clean up after this test
371
+ @client.query_options.delete(:something)
372
+ @client.query_options[:something].should be_nil
104
373
  end
105
374
 
106
375
  it "should return results as a hash by default" do
@@ -131,37 +400,52 @@ describe Mysql2::Client do
131
400
  }.should raise_error(Mysql2::Error)
132
401
  end
133
402
 
403
+ it "should describe the thread holding the active query" do
404
+ thr = Thread.new { @client.query("SELECT 1", :async => true) }
405
+
406
+ thr.join
407
+ begin
408
+ @client.query("SELECT 1")
409
+ rescue Mysql2::Error => e
410
+ message = e.message
411
+ end
412
+ re = Regexp.escape(thr.inspect)
413
+ message.should match(Regexp.new(re))
414
+ end
415
+
134
416
  it "should timeout if we wait longer than :read_timeout" do
135
- client = Mysql2::Client.new(:read_timeout => 1)
417
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 1))
136
418
  lambda {
137
419
  client.query("SELECT sleep(2)")
138
420
  }.should raise_error(Mysql2::Error)
139
421
  end
140
422
 
141
- # XXX this test is not deterministic (because Unix signal handling is not)
142
- # and may fail on a loaded system
143
- it "should run signal handlers while waiting for a response" do
144
- mark = {}
145
- trap(:USR1) { mark[:USR1] = Time.now }
146
- begin
147
- mark[:START] = Time.now
148
- pid = fork do
149
- sleep 1 # wait for client "SELECT sleep(2)" query to start
150
- Process.kill(:USR1, Process.ppid)
151
- sleep # wait for explicit kill to prevent GC disconnect
423
+ if !defined? Rubinius
424
+ # XXX this test is not deterministic (because Unix signal handling is not)
425
+ # and may fail on a loaded system
426
+ it "should run signal handlers while waiting for a response" do
427
+ mark = {}
428
+ trap(:USR1) { mark[:USR1] = Time.now }
429
+ begin
430
+ mark[:START] = Time.now
431
+ pid = fork do
432
+ sleep 1 # wait for client "SELECT sleep(2)" query to start
433
+ Process.kill(:USR1, Process.ppid)
434
+ sleep # wait for explicit kill to prevent GC disconnect
435
+ end
436
+ @client.query("SELECT sleep(2)")
437
+ mark[:END] = Time.now
438
+ mark.include?(:USR1).should be_true
439
+ (mark[:USR1] - mark[:START]).should >= 1
440
+ (mark[:USR1] - mark[:START]).should < 1.3
441
+ (mark[:END] - mark[:USR1]).should > 0.9
442
+ (mark[:END] - mark[:START]).should >= 2
443
+ (mark[:END] - mark[:START]).should < 2.3
444
+ Process.kill(:TERM, pid)
445
+ Process.waitpid2(pid)
446
+ ensure
447
+ trap(:USR1, 'DEFAULT')
152
448
  end
153
- @client.query("SELECT sleep(2)")
154
- mark[:END] = Time.now
155
- mark.include?(:USR1).should be_true
156
- (mark[:USR1] - mark[:START]).should >= 1
157
- (mark[:USR1] - mark[:START]).should < 1.1
158
- (mark[:END] - mark[:USR1]).should > 0.9
159
- (mark[:END] - mark[:START]).should >= 2
160
- (mark[:END] - mark[:START]).should < 2.1
161
- Process.kill(:TERM, pid)
162
- Process.waitpid2(pid)
163
- ensure
164
- trap(:USR1, 'DEFAULT')
165
449
  end
166
450
  end
167
451
 
@@ -179,21 +463,48 @@ describe Mysql2::Client do
179
463
 
180
464
  it "should close the connection when an exception is raised" do
181
465
  begin
182
- Timeout.timeout(1) do
466
+ Timeout.timeout(1, Timeout::Error) do
183
467
  @client.query("SELECT sleep(2)")
184
468
  end
185
469
  rescue Timeout::Error
186
470
  end
187
-
471
+
188
472
  lambda {
189
473
  @client.query("SELECT 1")
190
474
  }.should raise_error(Mysql2::Error, 'closed MySQL connection')
191
475
  end
192
476
 
193
477
  it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
194
- client = Mysql2::Client.new(:reconnect => true)
478
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:reconnect => true))
479
+ begin
480
+ Timeout.timeout(1, Timeout::Error) do
481
+ client.query("SELECT sleep(2)")
482
+ end
483
+ rescue Timeout::Error
484
+ end
485
+
486
+ lambda {
487
+ client.query("SELECT 1")
488
+ }.should_not raise_error(Mysql2::Error)
489
+ end
490
+
491
+ it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction true" do
492
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
493
+ begin
494
+ Timeout.timeout(1, Timeout::Error) do
495
+ client.query("SELECT sleep(2)")
496
+ end
497
+ rescue Timeout::Error
498
+ end
499
+
500
+ lambda {
501
+ client.query("SELECT 1")
502
+ }.should raise_error(Mysql2::Error)
503
+
504
+ client.reconnect = true
505
+
195
506
  begin
196
- Timeout.timeout(1) do
507
+ Timeout.timeout(1, Timeout::Error) do
197
508
  client.query("SELECT sleep(2)")
198
509
  end
199
510
  rescue Timeout::Error
@@ -202,15 +513,22 @@ describe Mysql2::Client do
202
513
  lambda {
203
514
  client.query("SELECT 1")
204
515
  }.should_not raise_error(Mysql2::Error)
516
+
205
517
  end
206
518
 
207
519
  it "threaded queries should be supported" do
208
520
  threads, results = [], {}
209
- connect = lambda{ Mysql2::Client.new(:host => "localhost", :username => "root") }
521
+ lock = Mutex.new
522
+ connect = lambda{
523
+ Mysql2::Client.new(DatabaseCredentials['root'])
524
+ }
210
525
  Timeout.timeout(0.7) do
211
526
  5.times {
212
527
  threads << Thread.new do
213
- results[Thread.current.object_id] = connect.call.query("SELECT sleep(0.5) as result")
528
+ result = connect.call.query("SELECT sleep(0.5) as result")
529
+ lock.synchronize do
530
+ results[Thread.current.object_id] = result
531
+ end
214
532
  end
215
533
  }
216
534
  end
@@ -239,6 +557,64 @@ describe Mysql2::Client do
239
557
  result.class.should eql(Mysql2::Result)
240
558
  end
241
559
  end
560
+
561
+ context "Multiple results sets" do
562
+ before(:each) do
563
+ @multi_client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:flags => Mysql2::Client::MULTI_STATEMENTS))
564
+ end
565
+
566
+ it "should raise an exception when one of multiple statements fails" do
567
+ result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';")
568
+ result.first['set_1'].should be(1)
569
+ lambda {
570
+ @multi_client.next_result
571
+ }.should raise_error(Mysql2::Error)
572
+ @multi_client.next_result.should be_false
573
+ end
574
+
575
+ it "returns multiple result sets" do
576
+ @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first.should eql({ 'set_1' => 1 })
577
+
578
+ @multi_client.next_result.should be_true
579
+ @multi_client.store_result.first.should eql({ 'set_2' => 2 })
580
+
581
+ @multi_client.next_result.should be_false
582
+ end
583
+
584
+ it "does not interfere with other statements" do
585
+ @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
586
+ while( @multi_client.next_result )
587
+ @multi_client.store_result
588
+ end
589
+
590
+ @multi_client.query("SELECT 3 AS 'next'").first.should == { 'next' => 3 }
591
+ end
592
+
593
+ it "will raise on query if there are outstanding results to read" do
594
+ @multi_client.query("SELECT 1; SELECT 2; SELECT 3")
595
+ lambda {
596
+ @multi_client.query("SELECT 4")
597
+ }.should raise_error(Mysql2::Error)
598
+ end
599
+
600
+ it "#abandon_results! should work" do
601
+ @multi_client.query("SELECT 1; SELECT 2; SELECT 3")
602
+ @multi_client.abandon_results!
603
+ lambda {
604
+ @multi_client.query("SELECT 4")
605
+ }.should_not raise_error(Mysql2::Error)
606
+ end
607
+
608
+ it "#more_results? should work" do
609
+ @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
610
+ @multi_client.more_results?.should be_true
611
+
612
+ @multi_client.next_result
613
+ @multi_client.store_result
614
+
615
+ @multi_client.more_results?.should be_false
616
+ end
617
+ end
242
618
  end
243
619
 
244
620
  it "should respond to #socket" do
@@ -279,7 +655,7 @@ describe Mysql2::Client do
279
655
  }.should_not raise_error(SystemStackError)
280
656
  end
281
657
 
282
- if RUBY_VERSION =~ /1.9/
658
+ unless RUBY_VERSION =~ /1.8/
283
659
  it "should carry over the original string's encoding" do
284
660
  str = "abc'def\"ghi\0jkl%mno"
285
661
  escaped = Mysql2::Client.escape(str)
@@ -342,18 +718,22 @@ describe Mysql2::Client do
342
718
  if defined? Encoding
343
719
  context "strings returned by #info" do
344
720
  it "should default to the connection's encoding if Encoding.default_internal is nil" do
345
- Encoding.default_internal = nil
346
- @client.info[:version].encoding.should eql(Encoding.find('utf-8'))
721
+ with_internal_encoding nil do
722
+ @client.info[:version].encoding.should eql(Encoding.find('utf-8'))
347
723
 
348
- client2 = Mysql2::Client.new :encoding => 'ascii'
349
- client2.info[:version].encoding.should eql(Encoding.find('us-ascii'))
724
+ client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
725
+ client2.info[:version].encoding.should eql(Encoding.find('us-ascii'))
726
+ end
350
727
  end
351
728
 
352
729
  it "should use Encoding.default_internal" do
353
- Encoding.default_internal = Encoding.find('utf-8')
354
- @client.info[:version].encoding.should eql(Encoding.default_internal)
355
- Encoding.default_internal = Encoding.find('us-ascii')
356
- @client.info[:version].encoding.should eql(Encoding.default_internal)
730
+ with_internal_encoding 'utf-8' do
731
+ @client.info[:version].encoding.should eql(Encoding.default_internal)
732
+ end
733
+
734
+ with_internal_encoding 'us-ascii' do
735
+ @client.info[:version].encoding.should eql(Encoding.default_internal)
736
+ end
357
737
  end
358
738
  end
359
739
  end
@@ -381,36 +761,40 @@ describe Mysql2::Client do
381
761
  if defined? Encoding
382
762
  context "strings returned by #server_info" do
383
763
  it "should default to the connection's encoding if Encoding.default_internal is nil" do
384
- Encoding.default_internal = nil
385
- @client.server_info[:version].encoding.should eql(Encoding.find('utf-8'))
764
+ with_internal_encoding nil do
765
+ @client.server_info[:version].encoding.should eql(Encoding.find('utf-8'))
386
766
 
387
- client2 = Mysql2::Client.new :encoding => 'ascii'
388
- client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii'))
767
+ client2 = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => 'ascii'))
768
+ client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii'))
769
+ end
389
770
  end
390
771
 
391
772
  it "should use Encoding.default_internal" do
392
- Encoding.default_internal = Encoding.find('utf-8')
393
- @client.server_info[:version].encoding.should eql(Encoding.default_internal)
394
- Encoding.default_internal = Encoding.find('us-ascii')
395
- @client.server_info[:version].encoding.should eql(Encoding.default_internal)
773
+ with_internal_encoding 'utf-8' do
774
+ @client.server_info[:version].encoding.should eql(Encoding.default_internal)
775
+ end
776
+
777
+ with_internal_encoding 'us-ascii' do
778
+ @client.server_info[:version].encoding.should eql(Encoding.default_internal)
779
+ end
396
780
  end
397
781
  end
398
782
  end
399
783
 
400
784
  it "should raise a Mysql2::Error exception upon connection failure" do
401
785
  lambda {
402
- bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
786
+ Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
403
787
  }.should raise_error(Mysql2::Error)
404
788
 
405
789
  lambda {
406
- good_client = Mysql2::Client.new
790
+ Mysql2::Client.new DatabaseCredentials['root']
407
791
  }.should_not raise_error(Mysql2::Error)
408
792
  end
409
793
 
410
794
  context 'write operations api' do
411
795
  before(:each) do
412
796
  @client.query "USE test"
413
- @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
797
+ @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
414
798
  end
415
799
 
416
800
  after(:each) do
@@ -437,6 +821,15 @@ describe Mysql2::Client do
437
821
  @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
438
822
  @client.affected_rows.should eql(1)
439
823
  end
824
+
825
+ it "#last_id should handle BIGINT auto-increment ids above 32 bits" do
826
+ # The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x.
827
+ # Insert a row with a given ID, this should raise the auto-increment state
828
+ @client.query "INSERT INTO lastIdTest (id, blah) VALUES (5000000000, 5000)"
829
+ @client.last_id.should eql(5000000000)
830
+ @client.query "INSERT INTO lastIdTest (blah) VALUES (5001)"
831
+ @client.last_id.should eql(5000000001)
832
+ end
440
833
  end
441
834
 
442
835
  it "should respond to #thread_id" do
@@ -451,15 +844,54 @@ describe Mysql2::Client do
451
844
  @client.should respond_to(:ping)
452
845
  end
453
846
 
847
+ context "select_db" do
848
+ before(:each) do
849
+ 2.times do |i|
850
+ @client.query("CREATE DATABASE test_selectdb_#{i}")
851
+ @client.query("USE test_selectdb_#{i}")
852
+ @client.query("CREATE TABLE test#{i} (`id` int NOT NULL PRIMARY KEY)")
853
+ end
854
+ end
855
+
856
+ after(:each) do
857
+ 2.times do |i|
858
+ @client.query("DROP DATABASE test_selectdb_#{i}")
859
+ end
860
+ end
861
+
862
+ it "should respond to #select_db" do
863
+ @client.should respond_to(:select_db)
864
+ end
865
+
866
+ it "should switch databases" do
867
+ @client.select_db("test_selectdb_0")
868
+ @client.query("SHOW TABLES").first.values.first.should eql("test0")
869
+ @client.select_db("test_selectdb_1")
870
+ @client.query("SHOW TABLES").first.values.first.should eql("test1")
871
+ @client.select_db("test_selectdb_0")
872
+ @client.query("SHOW TABLES").first.values.first.should eql("test0")
873
+ end
874
+
875
+ it "should raise a Mysql2::Error when the database doesn't exist" do
876
+ lambda {
877
+ @client.select_db("nopenothere")
878
+ }.should raise_error(Mysql2::Error)
879
+ end
880
+
881
+ it "should return the database switched to" do
882
+ @client.select_db("test_selectdb_1").should eq("test_selectdb_1")
883
+ end
884
+ end
885
+
454
886
  it "#thread_id should return a boolean" do
455
887
  @client.ping.should eql(true)
456
888
  @client.close
457
889
  @client.ping.should eql(false)
458
890
  end
459
891
 
460
- if RUBY_VERSION =~ /1.9/
461
- it "should respond to #encoding" do
462
- @client.should respond_to(:encoding)
892
+ unless RUBY_VERSION =~ /1.8/
893
+ it "should respond to #encoding" do
894
+ @client.should respond_to(:encoding)
895
+ end
463
896
  end
464
897
  end
465
- end