mysql2 0.2.24 → 0.3.0
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.
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.md +148 -0
- data/Gemfile +3 -0
- data/README.rdoc +257 -0
- data/Rakefile +5 -0
- data/benchmark/active_record.rb +51 -0
- data/benchmark/active_record_threaded.rb +42 -0
- data/benchmark/allocations.rb +33 -0
- data/benchmark/escape.rb +36 -0
- data/benchmark/query_with_mysql_casting.rb +80 -0
- data/benchmark/query_without_mysql_casting.rb +47 -0
- data/benchmark/sequel.rb +37 -0
- data/benchmark/setup_db.rb +119 -0
- data/benchmark/threaded.rb +44 -0
- data/ext/mysql2/client.c +272 -849
- data/ext/mysql2/client.h +12 -27
- data/ext/mysql2/extconf.rb +14 -72
- data/ext/mysql2/mysql2_ext.h +4 -7
- data/ext/mysql2/result.c +123 -319
- data/ext/mysql2/result.h +1 -4
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +64 -0
- data/lib/active_record/fiber_patches.rb +104 -0
- data/lib/mysql2.rb +5 -20
- data/lib/mysql2/client.rb +200 -50
- data/lib/mysql2/em.rb +3 -13
- data/lib/mysql2/em_fiber.rb +31 -0
- data/lib/mysql2/error.rb +6 -71
- data/lib/mysql2/version.rb +2 -2
- data/mysql2.gemspec +32 -0
- data/spec/em/em_fiber_spec.rb +22 -0
- data/spec/em/em_spec.rb +9 -74
- data/spec/mysql2/client_spec.rb +126 -593
- data/spec/mysql2/error_spec.rb +44 -58
- data/spec/mysql2/result_spec.rb +85 -257
- data/spec/spec_helper.rb +3 -24
- data/tasks/benchmarks.rake +20 -0
- data/tasks/compile.rake +71 -0
- data/tasks/rspec.rake +16 -0
- data/tasks/vendor_mysql.rake +40 -0
- metadata +179 -92
- checksums.yaml +0 -7
- data/README.md +0 -524
- data/ext/mysql2/infile.c +0 -122
- data/ext/mysql2/infile.h +0 -1
- data/ext/mysql2/mysql_enc_name_to_ruby.h +0 -168
- data/ext/mysql2/mysql_enc_to_ruby.h +0 -246
- data/ext/mysql2/wait_for_single_fd.h +0 -36
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +0 -635
- data/lib/arel/engines/sql/compilers/mysql2_compiler.rb +0 -11
- data/lib/mysql2/console.rb +0 -5
- data/spec/configuration.yml.example +0 -17
- data/spec/my.cnf.example +0 -9
- data/spec/test_data +0 -1
- data/support/mysql_enc_to_ruby.rb +0 -82
- data/support/ruby_enc_to_mysql.rb +0 -61
data/lib/mysql2/em.rb
CHANGED
@@ -15,28 +15,18 @@ module Mysql2
|
|
15
15
|
def notify_readable
|
16
16
|
detach
|
17
17
|
begin
|
18
|
-
|
18
|
+
@deferable.succeed(@client.async_result)
|
19
19
|
rescue Exception => e
|
20
20
|
@deferable.fail(e)
|
21
|
-
else
|
22
|
-
@deferable.succeed(result)
|
23
21
|
end
|
24
22
|
end
|
25
23
|
end
|
26
24
|
|
27
|
-
def close(*args)
|
28
|
-
if @watch
|
29
|
-
@watch.detach
|
30
|
-
end
|
31
|
-
super(*args)
|
32
|
-
end
|
33
|
-
|
34
25
|
def query(sql, opts={})
|
35
26
|
if ::EM.reactor_running?
|
36
27
|
super(sql, opts.merge(:async => true))
|
37
28
|
deferable = ::EM::DefaultDeferrable.new
|
38
|
-
|
39
|
-
@watch.notify_readable = true
|
29
|
+
::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
|
40
30
|
deferable
|
41
31
|
else
|
42
32
|
super(sql, opts)
|
@@ -44,4 +34,4 @@ module Mysql2
|
|
44
34
|
end
|
45
35
|
end
|
46
36
|
end
|
47
|
-
end
|
37
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'mysql2/em'
|
4
|
+
require 'fiber'
|
5
|
+
|
6
|
+
module Mysql2
|
7
|
+
module EM
|
8
|
+
module Fiber
|
9
|
+
class Client < ::Mysql2::EM::Client
|
10
|
+
def query(sql, opts={})
|
11
|
+
if ::EM.reactor_running?
|
12
|
+
deferable = super(sql, opts)
|
13
|
+
|
14
|
+
fiber = ::Fiber.current
|
15
|
+
deferable.callback do |result|
|
16
|
+
fiber.resume(result)
|
17
|
+
end
|
18
|
+
deferable.errback do |err|
|
19
|
+
fiber.resume(err)
|
20
|
+
end
|
21
|
+
::Fiber.yield.tap do |result|
|
22
|
+
raise result if result.is_a?(::Exception)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
super(sql, opts)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/mysql2/error.rb
CHANGED
@@ -1,80 +1,15 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
1
|
module Mysql2
|
4
2
|
class Error < StandardError
|
5
|
-
|
6
|
-
ENCODE_OPTS = {:undef => :replace, :invalid => :replace, :replace => REPLACEMENT_CHAR}
|
3
|
+
attr_accessor :error_number, :sql_state
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
def initialize msg
|
6
|
+
super
|
7
|
+
@error_number = nil
|
8
|
+
@sql_state = nil
|
9
|
+
end
|
11
10
|
|
12
11
|
# Mysql gem compatibility
|
13
12
|
alias_method :errno, :error_number
|
14
13
|
alias_method :error, :message
|
15
|
-
|
16
|
-
def initialize(msg, server_version=nil)
|
17
|
-
self.server_version = server_version
|
18
|
-
|
19
|
-
super(clean_message(msg))
|
20
|
-
end
|
21
|
-
|
22
|
-
def sql_state=(state)
|
23
|
-
@sql_state = ''.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
# In MySQL 5.5+ error messages are always constructed server-side as UTF-8
|
29
|
-
# then returned in the encoding set by the `character_set_results` system
|
30
|
-
# variable.
|
31
|
-
#
|
32
|
-
# See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for
|
33
|
-
# more contetx.
|
34
|
-
#
|
35
|
-
# Before MySQL 5.5 error message template strings are in whatever encoding
|
36
|
-
# is associated with the error message language.
|
37
|
-
# See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html
|
38
|
-
# for more information.
|
39
|
-
#
|
40
|
-
# The issue is that the user-data inserted in the message could potentially
|
41
|
-
# be in any encoding MySQL supports and is insert into the latin1, euckr or
|
42
|
-
# koi8r string raw. Meaning there's a high probability the string will be
|
43
|
-
# corrupt encoding-wise.
|
44
|
-
#
|
45
|
-
# See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for
|
46
|
-
# more information.
|
47
|
-
#
|
48
|
-
# So in an attempt to make sure the error message string is always in a valid
|
49
|
-
# encoding, we'll assume UTF-8 and clean the string of anything that's not a
|
50
|
-
# valid UTF-8 character.
|
51
|
-
#
|
52
|
-
# Except for if we're on 1.8, where we'll do nothing ;)
|
53
|
-
#
|
54
|
-
# Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8
|
55
|
-
def clean_message(message)
|
56
|
-
return message if !message.respond_to?(:encoding)
|
57
|
-
|
58
|
-
if @server_version && @server_version > 50500
|
59
|
-
message.encode(ENCODE_OPTS)
|
60
|
-
else
|
61
|
-
if message.respond_to? :scrub
|
62
|
-
message.scrub(REPLACEMENT_CHAR).encode(ENCODE_OPTS)
|
63
|
-
else
|
64
|
-
# This is ugly as hell but Ruby 1.9 doesn't provide a way to clean a string
|
65
|
-
# and retain it's valid UTF-8 characters, that I know of.
|
66
|
-
|
67
|
-
new_message = "".force_encoding(Encoding::UTF_8)
|
68
|
-
message.chars.each do |char|
|
69
|
-
if char.valid_encoding?
|
70
|
-
new_message << char
|
71
|
-
else
|
72
|
-
new_message << REPLACEMENT_CHAR
|
73
|
-
end
|
74
|
-
end
|
75
|
-
new_message.encode(ENCODE_OPTS)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
14
|
end
|
80
15
|
end
|
data/lib/mysql2/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Mysql2
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
2
|
+
VERSION = "0.3.0"
|
3
|
+
end
|
data/mysql2.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path('../lib/mysql2/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{mysql2}
|
5
|
+
s.version = Mysql2::VERSION
|
6
|
+
s.authors = ["Brian Lopez"]
|
7
|
+
s.date = Time.now.utc.strftime("%Y-%m-%d")
|
8
|
+
s.email = %q{seniorlopez@gmail.com}
|
9
|
+
s.extensions = ["ext/mysql2/extconf.rb"]
|
10
|
+
s.extra_rdoc_files = [
|
11
|
+
"README.rdoc"
|
12
|
+
]
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.homepage = %q{http://github.com/brianmario/mysql2}
|
15
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
16
|
+
s.require_paths = ["lib", "ext"]
|
17
|
+
s.rubygems_version = %q{1.4.2}
|
18
|
+
s.summary = %q{A simple, fast Mysql library for Ruby, binding to libmysql}
|
19
|
+
s.test_files = `git ls-files spec examples`.split("\n")
|
20
|
+
|
21
|
+
# tests
|
22
|
+
s.add_development_dependency 'eventmachine'
|
23
|
+
s.add_development_dependency 'rake-compiler', "~> 0.7.7"
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
# benchmarks
|
26
|
+
s.add_development_dependency 'activerecord'
|
27
|
+
s.add_development_dependency 'mysql'
|
28
|
+
s.add_development_dependency 'do_mysql'
|
29
|
+
s.add_development_dependency 'sequel'
|
30
|
+
s.add_development_dependency 'faker'
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
if defined? EventMachine && defined? Fiber
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'mysql2/em_fiber'
|
5
|
+
|
6
|
+
describe Mysql2::EM::Fiber::Client do
|
7
|
+
it 'should support queries' do
|
8
|
+
results = []
|
9
|
+
EM.run do
|
10
|
+
Fiber.new {
|
11
|
+
client1 = Mysql2::EM::Fiber::Client.new
|
12
|
+
results = client1.query "SELECT sleep(0.1) as first_query"
|
13
|
+
EM.stop_event_loop
|
14
|
+
}.resume
|
15
|
+
end
|
16
|
+
|
17
|
+
results.first.keys.should include("first_query")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
puts "Either EventMachine or Fibers not available. Skipping tests that use them."
|
22
|
+
end
|
data/spec/em/em_spec.rb
CHANGED
@@ -1,26 +1,23 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
|
3
|
-
|
4
|
-
require 'eventmachine'
|
2
|
+
if defined? EventMachine
|
3
|
+
require 'spec_helper'
|
5
4
|
require 'mysql2/em'
|
6
5
|
|
7
6
|
describe Mysql2::EM::Client do
|
8
7
|
it "should support async queries" do
|
9
8
|
results = []
|
10
9
|
EM.run do
|
11
|
-
client1 = Mysql2::EM::Client.new
|
10
|
+
client1 = Mysql2::EM::Client.new
|
12
11
|
defer1 = client1.query "SELECT sleep(0.1) as first_query"
|
13
12
|
defer1.callback do |result|
|
14
13
|
results << result.first
|
15
|
-
client1.close
|
16
14
|
EM.stop_event_loop
|
17
15
|
end
|
18
16
|
|
19
|
-
client2 = Mysql2::EM::Client.new
|
17
|
+
client2 = Mysql2::EM::Client.new
|
20
18
|
defer2 = client2.query "SELECT sleep(0.025) second_query"
|
21
19
|
defer2.callback do |result|
|
22
20
|
results << result.first
|
23
|
-
client2.close
|
24
21
|
end
|
25
22
|
end
|
26
23
|
|
@@ -31,14 +28,13 @@ begin
|
|
31
28
|
it "should support queries in callbacks" do
|
32
29
|
results = []
|
33
30
|
EM.run do
|
34
|
-
client = Mysql2::EM::Client.new
|
31
|
+
client = Mysql2::EM::Client.new
|
35
32
|
defer1 = client.query "SELECT sleep(0.025) as first_query"
|
36
33
|
defer1.callback do |result|
|
37
34
|
results << result.first
|
38
35
|
defer2 = client.query "SELECT sleep(0.025) as second_query"
|
39
|
-
defer2.callback do |
|
40
|
-
results <<
|
41
|
-
client.close
|
36
|
+
defer2.callback do |result|
|
37
|
+
results << result.first
|
42
38
|
EM.stop_event_loop
|
43
39
|
end
|
44
40
|
end
|
@@ -47,68 +43,7 @@ begin
|
|
47
43
|
results[0].keys.should include("first_query")
|
48
44
|
results[1].keys.should include("second_query")
|
49
45
|
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
46
|
end
|
112
|
-
|
47
|
+
else
|
113
48
|
puts "EventMachine not installed, skipping the specs that use it"
|
114
|
-
end
|
49
|
+
end
|
data/spec/mysql2/client_spec.rb
CHANGED
@@ -2,47 +2,16 @@
|
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
4
|
describe Mysql2::Client do
|
5
|
-
|
6
|
-
|
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)
|
5
|
+
before(:each) do
|
6
|
+
@client = Mysql2::Client.new
|
28
7
|
end
|
29
8
|
|
30
9
|
if defined? Encoding
|
31
10
|
it "should raise an exception on create for invalid encodings" do
|
32
11
|
lambda {
|
33
|
-
Mysql2::Client.new(
|
12
|
+
c = Mysql2::Client.new(:encoding => "fake")
|
34
13
|
}.should raise_error(Mysql2::Error)
|
35
14
|
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
|
46
15
|
end
|
47
16
|
|
48
17
|
it "should accept connect flags and pass them to #connect" do
|
@@ -54,7 +23,7 @@ describe Mysql2::Client do
|
|
54
23
|
end
|
55
24
|
end
|
56
25
|
client = klient.new :flags => Mysql2::Client::FOUND_ROWS
|
57
|
-
(client.connect_args.last
|
26
|
+
(client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true
|
58
27
|
end
|
59
28
|
|
60
29
|
it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
|
@@ -66,7 +35,7 @@ describe Mysql2::Client do
|
|
66
35
|
end
|
67
36
|
end
|
68
37
|
client = klient.new
|
69
|
-
(client.connect_args.last
|
38
|
+
(client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS |
|
70
39
|
Mysql2::Client::LONG_PASSWORD |
|
71
40
|
Mysql2::Client::LONG_FLAG |
|
72
41
|
Mysql2::Client::TRANSACTIONS |
|
@@ -74,66 +43,19 @@ describe Mysql2::Client do
|
|
74
43
|
Mysql2::Client::SECURE_CONNECTION)).should be_true
|
75
44
|
end
|
76
45
|
|
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
|
-
|
118
46
|
it "should have a global default_query_options hash" do
|
119
47
|
Mysql2::Client.should respond_to(:default_query_options)
|
120
48
|
end
|
121
49
|
|
122
50
|
it "should be able to connect via SSL options" do
|
123
|
-
|
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
|
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.")
|
130
52
|
ssl_client = nil
|
131
53
|
lambda {
|
132
54
|
ssl_client = Mysql2::Client.new(
|
133
|
-
:sslkey
|
134
|
-
:sslcert
|
135
|
-
:sslca
|
136
|
-
:sslcapath => '/
|
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/',
|
137
59
|
:sslcipher => 'DHE-RSA-AES256-SHA'
|
138
60
|
)
|
139
61
|
}.should_not raise_error(Mysql2::Error)
|
@@ -141,64 +63,11 @@ describe Mysql2::Client do
|
|
141
63
|
results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
|
142
64
|
results[0]['Variable_name'].should eql('Ssl_cipher')
|
143
65
|
results[0]['Value'].should_not be_nil
|
144
|
-
results[0]['Value'].should
|
145
|
-
results[0]['Value'].should_not be_empty
|
66
|
+
results[0]['Value'].class.should eql(String)
|
146
67
|
|
147
68
|
results[1]['Variable_name'].should eql('Ssl_version')
|
148
69
|
results[1]['Value'].should_not be_nil
|
149
|
-
results[1]['Value'].should
|
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
|
70
|
+
results[1]['Value'].class.should eql(String)
|
202
71
|
end
|
203
72
|
|
204
73
|
it "should respond to #close" do
|
@@ -216,160 +85,22 @@ describe Mysql2::Client do
|
|
216
85
|
@client.should respond_to(:query)
|
217
86
|
end
|
218
87
|
|
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
|
-
|
318
88
|
it "should expect read_timeout to be a positive integer" do
|
319
89
|
lambda {
|
320
90
|
Mysql2::Client.new(:read_timeout => -1)
|
321
91
|
}.should raise_error(Mysql2::Error)
|
322
92
|
end
|
323
93
|
|
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
|
-
|
330
94
|
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
|
-
|
347
95
|
it "should only accept strings as the query parameter" do
|
348
96
|
lambda {
|
349
97
|
@client.query ["SELECT 'not right'"]
|
350
98
|
}.should raise_error(TypeError)
|
351
99
|
end
|
352
100
|
|
353
|
-
it "should
|
354
|
-
|
355
|
-
@client.query_options
|
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
|
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))
|
373
104
|
end
|
374
105
|
|
375
106
|
it "should return results as a hash by default" do
|
@@ -385,6 +116,13 @@ describe Mysql2::Client do
|
|
385
116
|
@client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class.should eql(Symbol)
|
386
117
|
end
|
387
118
|
|
119
|
+
it "should not allow another query to be sent without fetching a result first" do
|
120
|
+
@client.query("SELECT 1", :async => true)
|
121
|
+
lambda {
|
122
|
+
@client.query("SELECT 1")
|
123
|
+
}.should raise_error(Mysql2::Error)
|
124
|
+
end
|
125
|
+
|
388
126
|
it "should require an open connection" do
|
389
127
|
@client.close
|
390
128
|
lambda {
|
@@ -392,241 +130,41 @@ describe Mysql2::Client do
|
|
392
130
|
}.should raise_error(Mysql2::Error)
|
393
131
|
end
|
394
132
|
|
395
|
-
if
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
end
|
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
|
-
|
416
|
-
it "should timeout if we wait longer than :read_timeout" do
|
417
|
-
client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => 1))
|
418
|
-
lambda {
|
419
|
-
client.query("SELECT sleep(2)")
|
420
|
-
}.should raise_error(Mysql2::Error)
|
421
|
-
end
|
422
|
-
|
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')
|
448
|
-
end
|
449
|
-
end
|
450
|
-
end
|
451
|
-
|
452
|
-
it "#socket should return a Fixnum (file descriptor from C)" do
|
453
|
-
@client.socket.class.should eql(Fixnum)
|
454
|
-
@client.socket.should_not eql(0)
|
455
|
-
end
|
456
|
-
|
457
|
-
it "#socket should require an open connection" do
|
458
|
-
@client.close
|
459
|
-
lambda {
|
460
|
-
@client.socket
|
461
|
-
}.should raise_error(Mysql2::Error)
|
462
|
-
end
|
463
|
-
|
464
|
-
it "should close the connection when an exception is raised" do
|
465
|
-
begin
|
466
|
-
Timeout.timeout(1, Timeout::Error) do
|
467
|
-
@client.query("SELECT sleep(2)")
|
468
|
-
end
|
469
|
-
rescue Timeout::Error
|
470
|
-
end
|
471
|
-
|
472
|
-
lambda {
|
473
|
-
@client.query("SELECT 1")
|
474
|
-
}.should raise_error(Mysql2::Error, 'closed MySQL connection')
|
475
|
-
end
|
476
|
-
|
477
|
-
it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
|
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
|
133
|
+
it "should timeout if we wait longer than :read_timeout" do
|
134
|
+
client = Mysql2::Client.new(:read_timeout => 1)
|
135
|
+
lambda {
|
136
|
+
client.query("SELECT sleep(2)")
|
137
|
+
}.should raise_error(Mysql2::Error)
|
138
|
+
end
|
505
139
|
|
140
|
+
# XXX this test is not deterministic (because Unix signal handling is not)
|
141
|
+
# and may fail on a loaded system
|
142
|
+
if RUBY_PLATFORM !~ /mingw|mswin/
|
143
|
+
it "should run signal handlers while waiting for a response" do
|
144
|
+
mark = {}
|
145
|
+
trap(:USR1) { mark[:USR1] = Time.now }
|
506
146
|
begin
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
lambda {
|
514
|
-
client.query("SELECT 1")
|
515
|
-
}.should_not raise_error(Mysql2::Error)
|
516
|
-
|
517
|
-
end
|
518
|
-
|
519
|
-
it "threaded queries should be supported" do
|
520
|
-
threads, results = [], {}
|
521
|
-
lock = Mutex.new
|
522
|
-
connect = lambda{
|
523
|
-
Mysql2::Client.new(DatabaseCredentials['root'])
|
524
|
-
}
|
525
|
-
Timeout.timeout(0.7) do
|
526
|
-
5.times {
|
527
|
-
threads << Thread.new do
|
528
|
-
result = connect.call.query("SELECT sleep(0.5) as result")
|
529
|
-
lock.synchronize do
|
530
|
-
results[Thread.current.object_id] = result
|
531
|
-
end
|
532
|
-
end
|
533
|
-
}
|
534
|
-
end
|
535
|
-
threads.each{|t| t.join }
|
536
|
-
results.keys.sort.should eql(threads.map{|t| t.object_id }.sort)
|
537
|
-
end
|
538
|
-
|
539
|
-
it "evented async queries should be supported" do
|
540
|
-
# should immediately return nil
|
541
|
-
@client.query("SELECT sleep(0.1)", :async => true).should eql(nil)
|
542
|
-
|
543
|
-
io_wrapper = IO.for_fd(@client.socket)
|
544
|
-
loops = 0
|
545
|
-
loop do
|
546
|
-
if IO.select([io_wrapper], nil, nil, 0.05)
|
547
|
-
break
|
548
|
-
else
|
549
|
-
loops += 1
|
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
|
550
152
|
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')
|
551
165
|
end
|
552
|
-
|
553
|
-
# make sure we waited some period of time
|
554
|
-
(loops >= 1).should be_true
|
555
|
-
|
556
|
-
result = @client.async_result
|
557
|
-
result.class.should eql(Mysql2::Result)
|
558
166
|
end
|
559
167
|
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
|
618
|
-
end
|
619
|
-
|
620
|
-
it "should respond to #socket" do
|
621
|
-
@client.should respond_to(:socket)
|
622
|
-
end
|
623
|
-
|
624
|
-
if RUBY_PLATFORM =~ /mingw|mswin/
|
625
|
-
it "#socket should raise as it's not supported" do
|
626
|
-
lambda {
|
627
|
-
@client.socket
|
628
|
-
}.should raise_error(Mysql2::Error)
|
629
|
-
end
|
630
168
|
end
|
631
169
|
|
632
170
|
it "should respond to escape" do
|
@@ -655,7 +193,7 @@ describe Mysql2::Client do
|
|
655
193
|
}.should_not raise_error(SystemStackError)
|
656
194
|
end
|
657
195
|
|
658
|
-
|
196
|
+
if RUBY_VERSION =~ /1.9/
|
659
197
|
it "should carry over the original string's encoding" do
|
660
198
|
str = "abc'def\"ghi\0jkl%mno"
|
661
199
|
escaped = Mysql2::Client.escape(str)
|
@@ -718,22 +256,18 @@ describe Mysql2::Client do
|
|
718
256
|
if defined? Encoding
|
719
257
|
context "strings returned by #info" do
|
720
258
|
it "should default to the connection's encoding if Encoding.default_internal is nil" do
|
721
|
-
|
722
|
-
|
259
|
+
Encoding.default_internal = nil
|
260
|
+
@client.info[:version].encoding.should eql(Encoding.find('utf-8'))
|
723
261
|
|
724
|
-
|
725
|
-
|
726
|
-
end
|
262
|
+
client2 = Mysql2::Client.new :encoding => 'ascii'
|
263
|
+
client2.info[:version].encoding.should eql(Encoding.find('us-ascii'))
|
727
264
|
end
|
728
265
|
|
729
266
|
it "should use Encoding.default_internal" do
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
with_internal_encoding 'us-ascii' do
|
735
|
-
@client.info[:version].encoding.should eql(Encoding.default_internal)
|
736
|
-
end
|
267
|
+
Encoding.default_internal = Encoding.find('utf-8')
|
268
|
+
@client.info[:version].encoding.should eql(Encoding.default_internal)
|
269
|
+
Encoding.default_internal = Encoding.find('us-ascii')
|
270
|
+
@client.info[:version].encoding.should eql(Encoding.default_internal)
|
737
271
|
end
|
738
272
|
end
|
739
273
|
end
|
@@ -761,40 +295,87 @@ describe Mysql2::Client do
|
|
761
295
|
if defined? Encoding
|
762
296
|
context "strings returned by #server_info" do
|
763
297
|
it "should default to the connection's encoding if Encoding.default_internal is nil" do
|
764
|
-
|
765
|
-
|
298
|
+
Encoding.default_internal = nil
|
299
|
+
@client.server_info[:version].encoding.should eql(Encoding.find('utf-8'))
|
766
300
|
|
767
|
-
|
768
|
-
|
769
|
-
end
|
301
|
+
client2 = Mysql2::Client.new :encoding => 'ascii'
|
302
|
+
client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii'))
|
770
303
|
end
|
771
304
|
|
772
305
|
it "should use Encoding.default_internal" do
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
with_internal_encoding 'us-ascii' do
|
778
|
-
@client.server_info[:version].encoding.should eql(Encoding.default_internal)
|
779
|
-
end
|
306
|
+
Encoding.default_internal = Encoding.find('utf-8')
|
307
|
+
@client.server_info[:version].encoding.should eql(Encoding.default_internal)
|
308
|
+
Encoding.default_internal = Encoding.find('us-ascii')
|
309
|
+
@client.server_info[:version].encoding.should eql(Encoding.default_internal)
|
780
310
|
end
|
781
311
|
end
|
782
312
|
end
|
783
313
|
|
314
|
+
it "should respond to #socket" do
|
315
|
+
@client.should respond_to(:socket)
|
316
|
+
end
|
317
|
+
|
318
|
+
it "#socket should return a Fixnum (file descriptor from C)" do
|
319
|
+
@client.socket.class.should eql(Fixnum)
|
320
|
+
@client.socket.should_not eql(0)
|
321
|
+
end
|
322
|
+
|
323
|
+
it "#socket should require an open connection" do
|
324
|
+
@client.close
|
325
|
+
lambda {
|
326
|
+
@client.socket
|
327
|
+
}.should raise_error(Mysql2::Error)
|
328
|
+
end
|
329
|
+
|
784
330
|
it "should raise a Mysql2::Error exception upon connection failure" do
|
785
331
|
lambda {
|
786
|
-
Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
|
332
|
+
bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
|
787
333
|
}.should raise_error(Mysql2::Error)
|
788
334
|
|
789
335
|
lambda {
|
790
|
-
Mysql2::Client.new
|
336
|
+
good_client = Mysql2::Client.new
|
791
337
|
}.should_not raise_error(Mysql2::Error)
|
792
338
|
end
|
793
339
|
|
340
|
+
it "threaded queries should be supported" do
|
341
|
+
threads, results = [], {}
|
342
|
+
connect = lambda{ Mysql2::Client.new(:host => "localhost", :username => "root") }
|
343
|
+
Timeout.timeout(0.7) do
|
344
|
+
5.times {
|
345
|
+
threads << Thread.new do
|
346
|
+
results[Thread.current.object_id] = connect.call.query("SELECT sleep(0.5) as result")
|
347
|
+
end
|
348
|
+
}
|
349
|
+
end
|
350
|
+
threads.each{|t| t.join }
|
351
|
+
results.keys.sort.should eql(threads.map{|t| t.object_id }.sort)
|
352
|
+
end
|
353
|
+
|
354
|
+
it "evented async queries should be supported" do
|
355
|
+
# should immediately return nil
|
356
|
+
@client.query("SELECT sleep(0.1)", :async => true).should eql(nil)
|
357
|
+
|
358
|
+
io_wrapper = IO.for_fd(@client.socket)
|
359
|
+
loops = 0
|
360
|
+
loop do
|
361
|
+
if IO.select([io_wrapper], nil, nil, 0.05)
|
362
|
+
break
|
363
|
+
else
|
364
|
+
loops += 1
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# make sure we waited some period of time
|
369
|
+
(loops >= 1).should be_true
|
370
|
+
|
371
|
+
result = @client.async_result
|
372
|
+
result.class.should eql(Mysql2::Result)
|
373
|
+
end
|
374
|
+
|
794
375
|
context 'write operations api' do
|
795
376
|
before(:each) do
|
796
377
|
@client.query "USE test"
|
797
|
-
@client.query "CREATE TABLE
|
378
|
+
@client.query "CREATE TABLE lastIdTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
|
798
379
|
end
|
799
380
|
|
800
381
|
after(:each) do
|
@@ -821,15 +402,6 @@ describe Mysql2::Client do
|
|
821
402
|
@client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
|
822
403
|
@client.affected_rows.should eql(1)
|
823
404
|
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
|
833
405
|
end
|
834
406
|
|
835
407
|
it "should respond to #thread_id" do
|
@@ -844,54 +416,15 @@ describe Mysql2::Client do
|
|
844
416
|
@client.should respond_to(:ping)
|
845
417
|
end
|
846
418
|
|
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
|
-
|
886
419
|
it "#thread_id should return a boolean" do
|
887
420
|
@client.ping.should eql(true)
|
888
421
|
@client.close
|
889
422
|
@client.ping.should eql(false)
|
890
423
|
end
|
891
424
|
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
end
|
425
|
+
if RUBY_VERSION =~ /1.9/
|
426
|
+
it "should respond to #encoding" do
|
427
|
+
@client.should respond_to(:encoding)
|
896
428
|
end
|
897
429
|
end
|
430
|
+
end
|