mysql2 0.3.11-x86-mingw32 → 0.3.18-x86-mingw32

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 (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
data/spec/em/em_spec.rb CHANGED
@@ -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