mysql2 0.3.1 → 0.5.2
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -151
- data/LICENSE +21 -0
- data/README.md +634 -0
- data/examples/eventmachine.rb +1 -3
- data/examples/threaded.rb +5 -9
- data/ext/mysql2/client.c +1154 -342
- data/ext/mysql2/client.h +20 -33
- data/ext/mysql2/extconf.rb +229 -37
- data/ext/mysql2/infile.c +122 -0
- data/ext/mysql2/infile.h +1 -0
- data/ext/mysql2/mysql2_ext.c +3 -1
- data/ext/mysql2/mysql2_ext.h +18 -16
- data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
- data/ext/mysql2/mysql_enc_to_ruby.h +259 -0
- data/ext/mysql2/result.c +708 -191
- data/ext/mysql2/result.h +15 -6
- data/ext/mysql2/statement.c +602 -0
- data/ext/mysql2/statement.h +17 -0
- data/ext/mysql2/wait_for_single_fd.h +37 -0
- data/lib/mysql2.rb +69 -7
- data/lib/mysql2/client.rb +126 -211
- data/lib/mysql2/console.rb +5 -0
- data/lib/mysql2/em.rb +24 -8
- data/lib/mysql2/error.rb +93 -8
- data/lib/mysql2/field.rb +3 -0
- data/lib/mysql2/result.rb +2 -0
- data/lib/mysql2/statement.rb +11 -0
- data/lib/mysql2/version.rb +2 -2
- data/spec/configuration.yml.example +11 -0
- data/spec/em/em_spec.rb +101 -15
- data/spec/my.cnf.example +9 -0
- data/spec/mysql2/client_spec.rb +874 -232
- data/spec/mysql2/error_spec.rb +55 -46
- data/spec/mysql2/result_spec.rb +306 -154
- data/spec/mysql2/statement_spec.rb +712 -0
- data/spec/spec_helper.rb +103 -57
- data/spec/ssl/ca-cert.pem +17 -0
- data/spec/ssl/ca-key.pem +27 -0
- data/spec/ssl/ca.cnf +22 -0
- data/spec/ssl/cert.cnf +22 -0
- data/spec/ssl/client-cert.pem +17 -0
- data/spec/ssl/client-key.pem +27 -0
- data/spec/ssl/client-req.pem +15 -0
- data/spec/ssl/gen_certs.sh +48 -0
- data/spec/ssl/pkcs8-client-key.pem +28 -0
- data/spec/ssl/pkcs8-server-key.pem +28 -0
- data/spec/ssl/server-cert.pem +17 -0
- data/spec/ssl/server-key.pem +27 -0
- data/spec/ssl/server-req.pem +15 -0
- data/spec/test_data +1 -0
- data/support/5072E1F5.asc +432 -0
- data/support/libmysql.def +219 -0
- data/support/mysql_enc_to_ruby.rb +81 -0
- data/support/ruby_enc_to_mysql.rb +61 -0
- metadata +82 -188
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.rvmrc +0 -1
- data/Gemfile +0 -3
- data/MIT-LICENSE +0 -20
- data/README.rdoc +0 -257
- data/Rakefile +0 -5
- data/benchmark/active_record.rb +0 -51
- data/benchmark/active_record_threaded.rb +0 -42
- data/benchmark/allocations.rb +0 -33
- data/benchmark/escape.rb +0 -36
- data/benchmark/query_with_mysql_casting.rb +0 -80
- data/benchmark/query_without_mysql_casting.rb +0 -47
- data/benchmark/sequel.rb +0 -37
- data/benchmark/setup_db.rb +0 -119
- data/benchmark/threaded.rb +0 -44
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +0 -64
- data/lib/active_record/fiber_patches.rb +0 -104
- data/lib/mysql2/em_fiber.rb +0 -31
- data/mysql2.gemspec +0 -32
- data/spec/em/em_fiber_spec.rb +0 -22
- data/tasks/benchmarks.rake +0 -20
- data/tasks/compile.rake +0 -71
- data/tasks/rspec.rake +0 -16
- data/tasks/vendor_mysql.rake +0 -40
data/lib/mysql2/em.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
require 'eventmachine'
|
4
2
|
require 'mysql2'
|
5
3
|
|
@@ -10,23 +8,41 @@ module Mysql2
|
|
10
8
|
def initialize(client, deferable)
|
11
9
|
@client = client
|
12
10
|
@deferable = deferable
|
11
|
+
@is_watching = true
|
13
12
|
end
|
14
13
|
|
15
14
|
def notify_readable
|
16
15
|
detach
|
17
16
|
begin
|
18
|
-
@
|
19
|
-
rescue
|
17
|
+
result = @client.async_result
|
18
|
+
rescue StandardError => e
|
20
19
|
@deferable.fail(e)
|
20
|
+
else
|
21
|
+
@deferable.succeed(result)
|
21
22
|
end
|
22
23
|
end
|
24
|
+
|
25
|
+
def watching?
|
26
|
+
@is_watching
|
27
|
+
end
|
28
|
+
|
29
|
+
def unbind
|
30
|
+
@is_watching = false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def close(*args)
|
35
|
+
@watch.detach if @watch && @watch.watching?
|
36
|
+
|
37
|
+
super(*args)
|
23
38
|
end
|
24
39
|
|
25
|
-
def query(sql, opts={})
|
40
|
+
def query(sql, opts = {})
|
26
41
|
if ::EM.reactor_running?
|
27
|
-
super(sql, opts.merge(:
|
42
|
+
super(sql, opts.merge(async: true))
|
28
43
|
deferable = ::EM::DefaultDeferrable.new
|
29
|
-
::EM.watch(
|
44
|
+
@watch = ::EM.watch(socket, Watcher, self, deferable)
|
45
|
+
@watch.notify_readable = true
|
30
46
|
deferable
|
31
47
|
else
|
32
48
|
super(sql, opts)
|
@@ -34,4 +50,4 @@ module Mysql2
|
|
34
50
|
end
|
35
51
|
end
|
36
52
|
end
|
37
|
-
end
|
53
|
+
end
|
data/lib/mysql2/error.rb
CHANGED
@@ -1,15 +1,100 @@
|
|
1
1
|
module Mysql2
|
2
2
|
class Error < StandardError
|
3
|
-
|
3
|
+
ENCODE_OPTS = {
|
4
|
+
undef: :replace,
|
5
|
+
invalid: :replace,
|
6
|
+
replace: '?'.freeze,
|
7
|
+
}.freeze
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
ConnectionError = Class.new(Error)
|
10
|
+
TimeoutError = Class.new(Error)
|
11
|
+
|
12
|
+
CODES = {
|
13
|
+
1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT
|
14
|
+
|
15
|
+
1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR
|
16
|
+
1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR
|
17
|
+
1152 => ConnectionError, # ER_ABORTING_CONNECTION
|
18
|
+
1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE
|
19
|
+
1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE
|
20
|
+
1155 => ConnectionError, # ER_NET_FCNTL_ERROR
|
21
|
+
1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER
|
22
|
+
1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR
|
23
|
+
1158 => ConnectionError, # ER_NET_READ_ERROR
|
24
|
+
1159 => ConnectionError, # ER_NET_READ_INTERRUPTED
|
25
|
+
1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE
|
26
|
+
1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED
|
27
|
+
|
28
|
+
2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR
|
29
|
+
2002 => ConnectionError, # CR_CONNECTION_ERROR
|
30
|
+
2003 => ConnectionError, # CR_CONN_HOST_ERROR
|
31
|
+
2004 => ConnectionError, # CR_IPSOCK_ERROR
|
32
|
+
2005 => ConnectionError, # CR_UNKNOWN_HOST
|
33
|
+
2006 => ConnectionError, # CR_SERVER_GONE_ERROR
|
34
|
+
2007 => ConnectionError, # CR_VERSION_ERROR
|
35
|
+
2009 => ConnectionError, # CR_WRONG_HOST_INFO
|
36
|
+
2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR
|
37
|
+
2013 => ConnectionError, # CR_SERVER_LOST
|
38
|
+
2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE
|
39
|
+
2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR
|
40
|
+
2027 => ConnectionError, # CR_MALFORMED_PACKET
|
41
|
+
2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL
|
42
|
+
2048 => ConnectionError, # CR_INVALID_CONN_HANDLE
|
43
|
+
2049 => ConnectionError, # CR_UNUSED_1
|
44
|
+
}.freeze
|
45
|
+
|
46
|
+
attr_reader :error_number, :sql_state
|
10
47
|
|
11
48
|
# Mysql gem compatibility
|
12
|
-
|
13
|
-
|
49
|
+
alias errno error_number
|
50
|
+
alias error message
|
51
|
+
|
52
|
+
def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
|
53
|
+
@server_version = server_version
|
54
|
+
@error_number = error_number
|
55
|
+
@sql_state = sql_state ? sql_state.encode(ENCODE_OPTS) : nil
|
56
|
+
|
57
|
+
super(clean_message(msg))
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.new_with_args(msg, server_version, error_number, sql_state)
|
61
|
+
error_class = CODES.fetch(error_number, self)
|
62
|
+
error_class.new(msg, server_version, error_number, sql_state)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# In MySQL 5.5+ error messages are always constructed server-side as UTF-8
|
68
|
+
# then returned in the encoding set by the `character_set_results` system
|
69
|
+
# variable.
|
70
|
+
#
|
71
|
+
# See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for
|
72
|
+
# more context.
|
73
|
+
#
|
74
|
+
# Before MySQL 5.5 error message template strings are in whatever encoding
|
75
|
+
# is associated with the error message language.
|
76
|
+
# See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html
|
77
|
+
# for more information.
|
78
|
+
#
|
79
|
+
# The issue is that the user-data inserted in the message could potentially
|
80
|
+
# be in any encoding MySQL supports and is insert into the latin1, euckr or
|
81
|
+
# koi8r string raw. Meaning there's a high probability the string will be
|
82
|
+
# corrupt encoding-wise.
|
83
|
+
#
|
84
|
+
# See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for
|
85
|
+
# more information.
|
86
|
+
#
|
87
|
+
# So in an attempt to make sure the error message string is always in a valid
|
88
|
+
# encoding, we'll assume UTF-8 and clean the string of anything that's not a
|
89
|
+
# valid UTF-8 character.
|
90
|
+
#
|
91
|
+
# Returns a valid UTF-8 string.
|
92
|
+
def clean_message(message)
|
93
|
+
if @server_version && @server_version > 50500
|
94
|
+
message.encode(ENCODE_OPTS)
|
95
|
+
else
|
96
|
+
message.encode(Encoding::UTF_8, ENCODE_OPTS)
|
97
|
+
end
|
98
|
+
end
|
14
99
|
end
|
15
100
|
end
|
data/lib/mysql2/field.rb
ADDED
data/lib/mysql2/result.rb
CHANGED
data/lib/mysql2/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Mysql2
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
2
|
+
VERSION = "0.5.2".freeze
|
3
|
+
end
|
data/spec/em/em_spec.rb
CHANGED
@@ -1,49 +1,135 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require '
|
1
|
+
require 'spec_helper'
|
2
|
+
begin
|
3
|
+
require 'eventmachine'
|
4
4
|
require 'mysql2/em'
|
5
5
|
|
6
|
-
describe Mysql2::EM::Client do
|
6
|
+
RSpec.describe Mysql2::EM::Client do
|
7
7
|
it "should support async queries" do
|
8
8
|
results = []
|
9
9
|
EM.run do
|
10
|
-
client1 = Mysql2::EM::Client.new
|
10
|
+
client1 = Mysql2::EM::Client.new DatabaseCredentials['root']
|
11
11
|
defer1 = client1.query "SELECT sleep(0.1) as first_query"
|
12
12
|
defer1.callback do |result|
|
13
13
|
results << result.first
|
14
|
+
client1.close
|
14
15
|
EM.stop_event_loop
|
15
16
|
end
|
16
17
|
|
17
|
-
client2 = Mysql2::EM::Client.new
|
18
|
+
client2 = Mysql2::EM::Client.new DatabaseCredentials['root']
|
18
19
|
defer2 = client2.query "SELECT sleep(0.025) second_query"
|
19
20
|
defer2.callback do |result|
|
20
21
|
results << result.first
|
22
|
+
client2.close
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
|
-
results[0].keys.
|
25
|
-
results[1].keys.
|
26
|
+
expect(results[0].keys).to include("second_query")
|
27
|
+
expect(results[1].keys).to include("first_query")
|
26
28
|
end
|
27
29
|
|
28
30
|
it "should support queries in callbacks" do
|
29
31
|
results = []
|
30
32
|
EM.run do
|
31
|
-
client = Mysql2::EM::Client.new
|
33
|
+
client = Mysql2::EM::Client.new DatabaseCredentials['root']
|
32
34
|
defer1 = client.query "SELECT sleep(0.025) as first_query"
|
33
35
|
defer1.callback do |result|
|
34
36
|
results << result.first
|
35
37
|
defer2 = client.query "SELECT sleep(0.025) as second_query"
|
36
|
-
defer2.callback do |
|
37
|
-
results <<
|
38
|
+
defer2.callback do |r|
|
39
|
+
results << r.first
|
40
|
+
client.close
|
38
41
|
EM.stop_event_loop
|
39
42
|
end
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
|
-
results[0].keys.
|
44
|
-
results[1].keys.
|
46
|
+
expect(results[0].keys).to include("first_query")
|
47
|
+
expect(results[1].keys).to include("second_query")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not swallow exceptions raised in callbacks" do
|
51
|
+
expect do
|
52
|
+
EM.run do
|
53
|
+
client = Mysql2::EM::Client.new DatabaseCredentials['root']
|
54
|
+
defer = client.query "SELECT sleep(0.1) as first_query"
|
55
|
+
defer.callback do
|
56
|
+
client.close
|
57
|
+
raise 'some error'
|
58
|
+
end
|
59
|
+
defer.errback do
|
60
|
+
# This _shouldn't_ be run, but it needed to prevent the specs from
|
61
|
+
# freezing if this test fails.
|
62
|
+
EM.stop_event_loop
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end.to raise_error('some error')
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when an exception is raised by the client' do
|
69
|
+
let(:client) { Mysql2::EM::Client.new DatabaseCredentials['root'] }
|
70
|
+
let(:error) { StandardError.new('some error') }
|
71
|
+
before { allow(client).to receive(:async_result).and_raise(error) }
|
72
|
+
after { client.close }
|
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
|
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
|
+
expect(errors).to eq([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
|
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
|
103
|
+
callbacks_run << :errback
|
104
|
+
EM.stop_event_loop
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
expect(callbacks_run).to eq([: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
|
118
|
+
callbacks_run << :callback
|
119
|
+
end
|
120
|
+
defer.errback do
|
121
|
+
callbacks_run << :errback
|
122
|
+
end
|
123
|
+
EM.add_timer(0.1) do
|
124
|
+
expect(callbacks_run).to eq([:callback])
|
125
|
+
expect do
|
126
|
+
client.close
|
127
|
+
end.not_to raise_error
|
128
|
+
EM.stop_event_loop
|
129
|
+
end
|
130
|
+
end
|
45
131
|
end
|
46
132
|
end
|
47
|
-
|
133
|
+
rescue LoadError
|
48
134
|
puts "EventMachine not installed, skipping the specs that use it"
|
49
|
-
end
|
135
|
+
end
|
data/spec/my.cnf.example
ADDED
data/spec/mysql2/client_spec.rb
CHANGED
@@ -1,381 +1,972 @@
|
|
1
|
-
# encoding: UTF-8
|
2
1
|
require 'spec_helper'
|
3
2
|
|
4
|
-
describe Mysql2::Client do
|
5
|
-
|
6
|
-
|
3
|
+
RSpec.describe Mysql2::Client do
|
4
|
+
context "using defaults file" do
|
5
|
+
let(:cnf_file) { File.expand_path('../../my.cnf', __FILE__) }
|
6
|
+
|
7
|
+
it "should not raise an exception for valid defaults group" do
|
8
|
+
expect do
|
9
|
+
new_client(default_file: cnf_file, default_group: "test")
|
10
|
+
end.not_to raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not raise an exception without default group" do
|
14
|
+
expect do
|
15
|
+
new_client(default_file: cnf_file)
|
16
|
+
end.not_to raise_error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should raise a Mysql::Error::ConnectionError upon connection failure" do
|
21
|
+
expect do
|
22
|
+
# The odd local host IP address forces the mysql client library to
|
23
|
+
# use a TCP socket rather than a domain socket.
|
24
|
+
new_client('host' => '127.0.0.2', 'port' => 999999)
|
25
|
+
end.to raise_error(Mysql2::Error::ConnectionError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should raise an exception on create for invalid encodings" do
|
29
|
+
expect do
|
30
|
+
new_client(encoding: "fake")
|
31
|
+
end.to raise_error(Mysql2::Error)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should raise an exception on non-string encodings" do
|
35
|
+
expect do
|
36
|
+
new_client(encoding: :fake)
|
37
|
+
end.to raise_error(TypeError)
|
7
38
|
end
|
8
39
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
40
|
+
it "should not raise an exception on create for a valid encoding" do
|
41
|
+
expect do
|
42
|
+
new_client(encoding: "utf8")
|
43
|
+
end.not_to raise_error
|
44
|
+
|
45
|
+
expect do
|
46
|
+
new_client(DatabaseCredentials['root'].merge(encoding: "big5"))
|
47
|
+
end.not_to raise_error
|
48
|
+
end
|
49
|
+
|
50
|
+
Klient = Class.new(Mysql2::Client) do
|
51
|
+
attr_reader :connect_args
|
52
|
+
def connect(*args)
|
53
|
+
@connect_args ||= []
|
54
|
+
@connect_args << args
|
14
55
|
end
|
15
56
|
end
|
16
57
|
|
17
58
|
it "should accept connect flags and pass them to #connect" do
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
client
|
26
|
-
|
59
|
+
client = Klient.new flags: Mysql2::Client::FOUND_ROWS
|
60
|
+
expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should parse flags array" do
|
64
|
+
client = Klient.new flags: %w[FOUND_ROWS -PROTOCOL_41]
|
65
|
+
expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS)
|
66
|
+
expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should parse flags string" do
|
70
|
+
client = Klient.new flags: "FOUND_ROWS -PROTOCOL_41"
|
71
|
+
expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS)
|
72
|
+
expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0)
|
27
73
|
end
|
28
74
|
|
29
75
|
it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
(client.connect_args.last.
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
76
|
+
client = Klient.new
|
77
|
+
client_flags = Mysql2::Client::REMEMBER_OPTIONS |
|
78
|
+
Mysql2::Client::LONG_PASSWORD |
|
79
|
+
Mysql2::Client::LONG_FLAG |
|
80
|
+
Mysql2::Client::TRANSACTIONS |
|
81
|
+
Mysql2::Client::PROTOCOL_41 |
|
82
|
+
Mysql2::Client::SECURE_CONNECTION |
|
83
|
+
Mysql2::Client::CONNECT_ATTRS
|
84
|
+
expect(client.connect_args.last[6]).to eql(client_flags)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should execute init command" do
|
88
|
+
options = DatabaseCredentials['root'].dup
|
89
|
+
options[:init_command] = "SET @something = 'setting_value';"
|
90
|
+
client = new_client(options)
|
91
|
+
result = client.query("SELECT @something;")
|
92
|
+
expect(result.first['@something']).to eq('setting_value')
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should send init_command after reconnect" do
|
96
|
+
options = DatabaseCredentials['root'].dup
|
97
|
+
options[:init_command] = "SET @something = 'setting_value';"
|
98
|
+
options[:reconnect] = true
|
99
|
+
client = new_client(options)
|
100
|
+
|
101
|
+
result = client.query("SELECT @something;")
|
102
|
+
expect(result.first['@something']).to eq('setting_value')
|
103
|
+
|
104
|
+
# get the current connection id
|
105
|
+
result = client.query("SELECT CONNECTION_ID()")
|
106
|
+
first_conn_id = result.first['CONNECTION_ID()']
|
107
|
+
|
108
|
+
# break the current connection
|
109
|
+
expect { client.query("KILL #{first_conn_id}") }.to raise_error(Mysql2::Error)
|
110
|
+
|
111
|
+
client.ping # reconnect now
|
112
|
+
|
113
|
+
# get the new connection id
|
114
|
+
result = client.query("SELECT CONNECTION_ID()")
|
115
|
+
second_conn_id = result.first['CONNECTION_ID()']
|
116
|
+
|
117
|
+
# confirm reconnect by checking the new connection id
|
118
|
+
expect(first_conn_id).not_to eq(second_conn_id)
|
119
|
+
|
120
|
+
# At last, check that the init command executed
|
121
|
+
result = client.query("SELECT @something;")
|
122
|
+
expect(result.first['@something']).to eq('setting_value')
|
44
123
|
end
|
45
124
|
|
46
125
|
it "should have a global default_query_options hash" do
|
47
|
-
Mysql2::Client.
|
126
|
+
expect(Mysql2::Client).to respond_to(:default_query_options)
|
48
127
|
end
|
49
128
|
|
50
129
|
it "should be able to connect via SSL options" do
|
51
|
-
|
130
|
+
ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'"
|
131
|
+
ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' }
|
132
|
+
pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled
|
133
|
+
ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' }
|
134
|
+
pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled
|
135
|
+
|
136
|
+
# You may need to adjust the lines below to match your SSL certificate paths
|
52
137
|
ssl_client = nil
|
53
|
-
|
54
|
-
ssl_client =
|
55
|
-
|
56
|
-
:
|
57
|
-
:
|
58
|
-
:
|
59
|
-
:sslcipher => 'DHE-RSA-AES256-SHA'
|
138
|
+
expect do
|
139
|
+
ssl_client = new_client(
|
140
|
+
'host' => 'mysql2gem.example.com', # must match the certificates
|
141
|
+
:sslkey => '/etc/mysql/client-key.pem',
|
142
|
+
:sslcert => '/etc/mysql/client-cert.pem',
|
143
|
+
:sslca => '/etc/mysql/ca-cert.pem',
|
144
|
+
:sslcipher => 'DHE-RSA-AES256-SHA',
|
145
|
+
:sslverify => true,
|
60
146
|
)
|
61
|
-
|
147
|
+
end.not_to raise_error
|
148
|
+
|
149
|
+
results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }]
|
150
|
+
expect(results['Ssl_cipher']).not_to be_empty
|
151
|
+
expect(results['Ssl_version']).not_to be_empty
|
152
|
+
|
153
|
+
expect(ssl_client.ssl_cipher).not_to be_empty
|
154
|
+
expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher)
|
155
|
+
end
|
62
156
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
157
|
+
def run_gc
|
158
|
+
if defined?(Rubinius)
|
159
|
+
GC.run(true)
|
160
|
+
else
|
161
|
+
GC.start
|
162
|
+
end
|
163
|
+
sleep(0.5)
|
164
|
+
end
|
67
165
|
|
68
|
-
|
69
|
-
|
70
|
-
|
166
|
+
it "should terminate connections when calling close" do
|
167
|
+
# rubocop:disable Lint/AmbiguousBlockAssociation
|
168
|
+
expect do
|
169
|
+
client = Mysql2::Client.new(DatabaseCredentials['root'])
|
170
|
+
connection_id = client.thread_id
|
171
|
+
client.close
|
172
|
+
|
173
|
+
# mysql_close sends a quit command without waiting for a response
|
174
|
+
# so give the server some time to handle the detect the closed connection
|
175
|
+
closed = false
|
176
|
+
10.times do
|
177
|
+
closed = @client.query("SHOW PROCESSLIST").none? { |row| row['Id'] == connection_id }
|
178
|
+
break if closed
|
179
|
+
sleep(0.1)
|
180
|
+
end
|
181
|
+
expect(closed).to eq(true)
|
182
|
+
end.to_not change {
|
183
|
+
@client.query("SHOW STATUS LIKE 'Aborted_%'").to_a
|
184
|
+
}
|
185
|
+
# rubocop:enable Lint/AmbiguousBlockAssociation
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should not leave dangling connections after garbage collection" do
|
189
|
+
run_gc
|
190
|
+
# rubocop:disable Lint/AmbiguousBlockAssociation
|
191
|
+
expect do
|
192
|
+
expect do
|
193
|
+
10.times do
|
194
|
+
Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
|
195
|
+
end
|
196
|
+
end.to change {
|
197
|
+
@client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
|
198
|
+
}.by(10)
|
199
|
+
|
200
|
+
run_gc
|
201
|
+
end.to_not change {
|
202
|
+
@client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
|
203
|
+
@client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
|
204
|
+
}
|
205
|
+
# rubocop:enable Lint/AmbiguousBlockAssociation
|
206
|
+
end
|
207
|
+
|
208
|
+
context "#set_server_option" do
|
209
|
+
let(:client) do
|
210
|
+
new_client.tap do |client|
|
211
|
+
client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'returns true when multi_statements is enable' do
|
216
|
+
expect(client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)).to be true
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'returns true when multi_statements is disable' do
|
220
|
+
expect(client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)).to be true
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'returns false when multi_statements is neither OPTION_MULTI_STATEMENTS_OFF or OPTION_MULTI_STATEMENTS_ON' do
|
224
|
+
expect(client.set_server_option(344)).to be false
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'enables multiple-statement' do
|
228
|
+
client.query("SELECT 1;SELECT 2;")
|
229
|
+
|
230
|
+
expect(client.next_result).to be true
|
231
|
+
expect(client.store_result.first).to eql('2' => 2)
|
232
|
+
expect(client.next_result).to be false
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'disables multiple-statement' do
|
236
|
+
client.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
|
237
|
+
|
238
|
+
expect { client.query("SELECT 1;SELECT 2;") }.to raise_error(Mysql2::Error)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
context "#automatic_close" do
|
243
|
+
it "is enabled by default" do
|
244
|
+
expect(new_client.automatic_close?).to be(true)
|
245
|
+
end
|
246
|
+
|
247
|
+
if RUBY_PLATFORM =~ /mingw|mswin/
|
248
|
+
it "cannot be disabled" do
|
249
|
+
expect do
|
250
|
+
client = new_client(automatic_close: false)
|
251
|
+
expect(client.automatic_close?).to be(true)
|
252
|
+
end.to output(/always closed by garbage collector/).to_stderr
|
253
|
+
|
254
|
+
expect do
|
255
|
+
client = new_client(automatic_close: true)
|
256
|
+
expect(client.automatic_close?).to be(true)
|
257
|
+
end.to_not output(/always closed by garbage collector/).to_stderr
|
258
|
+
|
259
|
+
expect do
|
260
|
+
client = new_client(automatic_close: true)
|
261
|
+
client.automatic_close = false
|
262
|
+
expect(client.automatic_close?).to be(true)
|
263
|
+
end.to output(/always closed by garbage collector/).to_stderr
|
264
|
+
end
|
265
|
+
else
|
266
|
+
it "can be configured" do
|
267
|
+
client = new_client(automatic_close: false)
|
268
|
+
expect(client.automatic_close?).to be(false)
|
269
|
+
end
|
270
|
+
|
271
|
+
it "can be assigned" do
|
272
|
+
client = new_client
|
273
|
+
client.automatic_close = false
|
274
|
+
expect(client.automatic_close?).to be(false)
|
275
|
+
|
276
|
+
client.automatic_close = true
|
277
|
+
expect(client.automatic_close?).to be(true)
|
278
|
+
|
279
|
+
client.automatic_close = nil
|
280
|
+
expect(client.automatic_close?).to be(false)
|
281
|
+
|
282
|
+
client.automatic_close = 9
|
283
|
+
expect(client.automatic_close?).to be(true)
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should not close connections when running in a child process" do
|
287
|
+
run_gc
|
288
|
+
client = Mysql2::Client.new(DatabaseCredentials['root'])
|
289
|
+
client.automatic_close = false
|
290
|
+
|
291
|
+
child = fork do
|
292
|
+
client.query('SELECT 1')
|
293
|
+
client = nil
|
294
|
+
run_gc
|
295
|
+
end
|
296
|
+
|
297
|
+
Process.wait(child)
|
298
|
+
|
299
|
+
# this will throw an error if the underlying socket was shutdown by the
|
300
|
+
# child's GC
|
301
|
+
expect { client.query('SELECT 1') }.to_not raise_exception
|
302
|
+
client.close
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should be able to connect to database with numeric-only name" do
|
308
|
+
database = 1235
|
309
|
+
@client.query "CREATE DATABASE IF NOT EXISTS `#{database}`"
|
310
|
+
|
311
|
+
expect do
|
312
|
+
new_client('database' => database)
|
313
|
+
end.not_to raise_error
|
314
|
+
|
315
|
+
@client.query "DROP DATABASE IF EXISTS `#{database}`"
|
71
316
|
end
|
72
317
|
|
73
318
|
it "should respond to #close" do
|
74
|
-
@client.
|
319
|
+
expect(@client).to respond_to(:close)
|
75
320
|
end
|
76
321
|
|
77
322
|
it "should be able to close properly" do
|
78
|
-
@client.close.
|
79
|
-
|
323
|
+
expect(@client.close).to be_nil
|
324
|
+
expect do
|
80
325
|
@client.query "SELECT 1"
|
81
|
-
|
326
|
+
end.to raise_error(Mysql2::Error)
|
327
|
+
end
|
328
|
+
|
329
|
+
context "#closed?" do
|
330
|
+
it "should return false when connected" do
|
331
|
+
expect(@client.closed?).to eql(false)
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should return true after close" do
|
335
|
+
@client.close
|
336
|
+
expect(@client.closed?).to eql(true)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
it "should not try to query closed mysql connection" do
|
341
|
+
client = new_client(reconnect: true)
|
342
|
+
expect(client.close).to be_nil
|
343
|
+
expect do
|
344
|
+
client.query "SELECT 1"
|
345
|
+
end.to raise_error(Mysql2::Error)
|
82
346
|
end
|
83
347
|
|
84
348
|
it "should respond to #query" do
|
85
|
-
@client.
|
349
|
+
expect(@client).to respond_to(:query)
|
350
|
+
end
|
351
|
+
|
352
|
+
it "should respond to #warning_count" do
|
353
|
+
expect(@client).to respond_to(:warning_count)
|
354
|
+
end
|
355
|
+
|
356
|
+
context "#warning_count" do
|
357
|
+
context "when no warnings" do
|
358
|
+
it "should 0" do
|
359
|
+
@client.query('select 1')
|
360
|
+
expect(@client.warning_count).to eq(0)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
context "when has a warnings" do
|
364
|
+
it "should > 0" do
|
365
|
+
# "the statement produces extra information that can be viewed by issuing a SHOW WARNINGS"
|
366
|
+
# https://dev.mysql.com/doc/refman/5.7/en/show-warnings.html
|
367
|
+
@client.query('DROP TABLE IF EXISTS test.no_such_table')
|
368
|
+
expect(@client.warning_count).to be > 0
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
it "should respond to #query_info" do
|
374
|
+
expect(@client).to respond_to(:query_info)
|
375
|
+
end
|
376
|
+
|
377
|
+
context "#query_info" do
|
378
|
+
context "when no info present" do
|
379
|
+
it "should 0" do
|
380
|
+
@client.query('select 1')
|
381
|
+
expect(@client.query_info).to be_empty
|
382
|
+
expect(@client.query_info_string).to be_nil
|
383
|
+
end
|
384
|
+
end
|
385
|
+
context "when has some info" do
|
386
|
+
it "should retrieve it" do
|
387
|
+
@client.query "USE test"
|
388
|
+
@client.query "CREATE TABLE IF NOT EXISTS infoTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
|
389
|
+
|
390
|
+
# http://dev.mysql.com/doc/refman/5.0/en/mysql-info.html says
|
391
|
+
# # 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).
|
392
|
+
@client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)")
|
393
|
+
|
394
|
+
expect(@client.query_info).to eql(records: 2, duplicates: 0, warnings: 0)
|
395
|
+
expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0')
|
396
|
+
|
397
|
+
@client.query "DROP TABLE infoTest"
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
context ":local_infile" do
|
403
|
+
before(:all) do
|
404
|
+
new_client(local_infile: true) do |client|
|
405
|
+
local = client.query "SHOW VARIABLES LIKE 'local_infile'"
|
406
|
+
local_enabled = local.any? { |x| x['Value'] == 'ON' }
|
407
|
+
skip("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled
|
408
|
+
|
409
|
+
client.query %[
|
410
|
+
CREATE TABLE IF NOT EXISTS infileTest (
|
411
|
+
id MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
412
|
+
foo VARCHAR(10),
|
413
|
+
bar MEDIUMTEXT
|
414
|
+
)
|
415
|
+
]
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
after(:all) do
|
420
|
+
new_client do |client|
|
421
|
+
client.query "DROP TABLE IF EXISTS infileTest"
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
it "should raise an error when local_infile is disabled" do
|
426
|
+
client = new_client(local_infile: false)
|
427
|
+
expect do
|
428
|
+
client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
|
429
|
+
end.to raise_error(Mysql2::Error, /command is not allowed/)
|
430
|
+
end
|
431
|
+
|
432
|
+
it "should raise an error when a non-existent file is loaded" do
|
433
|
+
client = new_client(local_infile: true)
|
434
|
+
expect do
|
435
|
+
client.query "LOAD DATA LOCAL INFILE 'this/file/is/not/here' INTO TABLE infileTest"
|
436
|
+
end.to raise_error(Mysql2::Error, 'No such file or directory: this/file/is/not/here')
|
437
|
+
end
|
438
|
+
|
439
|
+
it "should LOAD DATA LOCAL INFILE" do
|
440
|
+
client = new_client(local_infile: true)
|
441
|
+
client.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
|
442
|
+
info = client.query_info
|
443
|
+
expect(info).to eql(records: 1, deleted: 0, skipped: 0, warnings: 0)
|
444
|
+
|
445
|
+
result = client.query "SELECT * FROM infileTest"
|
446
|
+
expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World')
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
it "should expect connect_timeout to be a positive integer" do
|
451
|
+
expect do
|
452
|
+
new_client(connect_timeout: -1)
|
453
|
+
end.to raise_error(Mysql2::Error)
|
86
454
|
end
|
87
455
|
|
88
456
|
it "should expect read_timeout to be a positive integer" do
|
89
|
-
|
90
|
-
|
91
|
-
|
457
|
+
expect do
|
458
|
+
new_client(read_timeout: -1)
|
459
|
+
end.to raise_error(Mysql2::Error)
|
460
|
+
end
|
461
|
+
|
462
|
+
it "should expect write_timeout to be a positive integer" do
|
463
|
+
expect do
|
464
|
+
new_client(write_timeout: -1)
|
465
|
+
end.to raise_error(Mysql2::Error)
|
466
|
+
end
|
467
|
+
|
468
|
+
it "should allow nil read_timeout" do
|
469
|
+
client = new_client(read_timeout: nil)
|
470
|
+
|
471
|
+
expect(client.read_timeout).to be_nil
|
472
|
+
end
|
473
|
+
|
474
|
+
it "should set default program_name in connect_attrs" do
|
475
|
+
client = new_client
|
476
|
+
if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/)
|
477
|
+
pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.')
|
478
|
+
end
|
479
|
+
result = client.query("SELECT attr_value FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id() AND attr_name = 'program_name'")
|
480
|
+
expect(result.first['attr_value']).to eq($PROGRAM_NAME)
|
481
|
+
end
|
482
|
+
|
483
|
+
it "should set custom connect_attrs" do
|
484
|
+
client = new_client(connect_attrs: { program_name: 'my_program_name', foo: 'fooval', bar: 'barval' })
|
485
|
+
if Mysql2::Client::CONNECT_ATTRS.zero? || client.server_info[:version].match(/10.[01].\d+-MariaDB/)
|
486
|
+
pending('Both client and server versions must be MySQL 5.6 or MariaDB 10.2 or later.')
|
487
|
+
end
|
488
|
+
results = Hash[client.query("SELECT * FROM performance_schema.session_account_connect_attrs WHERE processlist_id = connection_id()").map { |x| x.values_at('ATTR_NAME', 'ATTR_VALUE') }]
|
489
|
+
expect(results['program_name']).to eq('my_program_name')
|
490
|
+
expect(results['foo']).to eq('fooval')
|
491
|
+
expect(results['bar']).to eq('barval')
|
92
492
|
end
|
93
493
|
|
94
494
|
context "#query" do
|
495
|
+
it "should let you query again if iterating is finished when streaming" do
|
496
|
+
@client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false).each.to_a
|
497
|
+
|
498
|
+
expect do
|
499
|
+
@client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false)
|
500
|
+
end.to_not raise_error
|
501
|
+
end
|
502
|
+
|
503
|
+
it "should not let you query again if iterating is not finished when streaming" do
|
504
|
+
@client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false).first
|
505
|
+
|
506
|
+
expect do
|
507
|
+
@client.query("SELECT 1 UNION SELECT 2", stream: true, cache_rows: false)
|
508
|
+
end.to raise_exception(Mysql2::Error)
|
509
|
+
end
|
510
|
+
|
95
511
|
it "should only accept strings as the query parameter" do
|
96
|
-
|
512
|
+
expect do
|
97
513
|
@client.query ["SELECT 'not right'"]
|
98
|
-
|
514
|
+
end.to raise_error(TypeError)
|
99
515
|
end
|
100
516
|
|
101
|
-
it "should
|
102
|
-
@client.query "SELECT 1", :
|
103
|
-
|
517
|
+
it "should not retain query options set on a query for subsequent queries, but should retain it in the result" do
|
518
|
+
result = @client.query "SELECT 1", something: :else
|
519
|
+
expect(@client.query_options[:something]).to be_nil
|
520
|
+
expect(result.instance_variable_get('@query_options')).to eql(@client.query_options.merge(something: :else))
|
521
|
+
expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options.merge(something: :else))
|
522
|
+
|
523
|
+
result = @client.query "SELECT 1"
|
524
|
+
expect(result.instance_variable_get('@query_options')).to eql(@client.query_options)
|
525
|
+
expect(@client.instance_variable_get('@current_query_options')).to eql(@client.query_options)
|
526
|
+
end
|
527
|
+
|
528
|
+
it "should allow changing query options for subsequent queries" do
|
529
|
+
@client.query_options[:something] = :else
|
530
|
+
result = @client.query "SELECT 1"
|
531
|
+
expect(@client.query_options[:something]).to eql(:else)
|
532
|
+
expect(result.instance_variable_get('@query_options')[:something]).to eql(:else)
|
533
|
+
|
534
|
+
# Clean up after this test
|
535
|
+
@client.query_options.delete(:something)
|
536
|
+
expect(@client.query_options[:something]).to be_nil
|
104
537
|
end
|
105
538
|
|
106
539
|
it "should return results as a hash by default" do
|
107
|
-
@client.query("SELECT 1").first.
|
540
|
+
expect(@client.query("SELECT 1").first).to be_an_instance_of(Hash)
|
108
541
|
end
|
109
542
|
|
110
543
|
it "should be able to return results as an array" do
|
111
|
-
@client.query("SELECT 1", :
|
112
|
-
@client.query("SELECT 1").each(:
|
544
|
+
expect(@client.query("SELECT 1", as: :array).first).to be_an_instance_of(Array)
|
545
|
+
@client.query("SELECT 1").each(as: :array)
|
113
546
|
end
|
114
547
|
|
115
548
|
it "should be able to return results with symbolized keys" do
|
116
|
-
@client.query("SELECT 1", :
|
117
|
-
end
|
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)
|
549
|
+
expect(@client.query("SELECT 1", symbolize_keys: true).first.keys[0]).to be_an_instance_of(Symbol)
|
124
550
|
end
|
125
551
|
|
126
552
|
it "should require an open connection" do
|
127
553
|
@client.close
|
128
|
-
|
554
|
+
expect do
|
129
555
|
@client.query "SELECT 1"
|
130
|
-
|
556
|
+
end.to raise_error(Mysql2::Error)
|
131
557
|
end
|
132
558
|
|
133
|
-
it "should
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
559
|
+
it "should detect closed connection on query read error" do
|
560
|
+
connection_id = @client.thread_id
|
561
|
+
Thread.new do
|
562
|
+
sleep(0.1)
|
563
|
+
Mysql2::Client.new(DatabaseCredentials['root']).tap do |supervisor|
|
564
|
+
supervisor.query("KILL #{connection_id}")
|
565
|
+
end.close
|
566
|
+
end
|
567
|
+
expect do
|
568
|
+
@client.query("SELECT SLEEP(1)")
|
569
|
+
end.to raise_error(Mysql2::Error, /Lost connection to MySQL server/)
|
570
|
+
|
571
|
+
if RUBY_PLATFORM !~ /mingw|mswin/
|
572
|
+
expect do
|
573
|
+
@client.socket
|
574
|
+
end.to raise_error(Mysql2::Error, 'MySQL client is not connected')
|
575
|
+
end
|
138
576
|
end
|
139
577
|
|
140
|
-
# XXX this test is not deterministic (because Unix signal handling is not)
|
141
|
-
# and may fail on a loaded system
|
142
578
|
if RUBY_PLATFORM !~ /mingw|mswin/
|
579
|
+
it "should not allow another query to be sent without fetching a result first" do
|
580
|
+
@client.query("SELECT 1", async: true)
|
581
|
+
expect do
|
582
|
+
@client.query("SELECT 1")
|
583
|
+
end.to raise_error(Mysql2::Error)
|
584
|
+
end
|
585
|
+
|
586
|
+
it "should describe the thread holding the active query" do
|
587
|
+
thr = Thread.new { @client.query("SELECT 1", async: true) }
|
588
|
+
|
589
|
+
thr.join
|
590
|
+
expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, Regexp.new(Regexp.escape(thr.inspect)))
|
591
|
+
end
|
592
|
+
|
593
|
+
it "should timeout if we wait longer than :read_timeout" do
|
594
|
+
client = new_client(read_timeout: 0)
|
595
|
+
expect do
|
596
|
+
client.query('SELECT SLEEP(0.1)')
|
597
|
+
end.to raise_error(Mysql2::Error::TimeoutError)
|
598
|
+
end
|
599
|
+
|
600
|
+
# XXX this test is not deterministic (because Unix signal handling is not)
|
601
|
+
# and may fail on a loaded system
|
143
602
|
it "should run signal handlers while waiting for a response" do
|
603
|
+
kill_time = 0.1
|
604
|
+
query_time = 2 * kill_time
|
605
|
+
|
144
606
|
mark = {}
|
145
|
-
|
607
|
+
|
146
608
|
begin
|
147
|
-
mark
|
609
|
+
trap(:USR1) { mark.store(:USR1, Time.now) }
|
148
610
|
pid = fork do
|
149
|
-
sleep
|
611
|
+
sleep kill_time # wait for client query to start
|
150
612
|
Process.kill(:USR1, Process.ppid)
|
151
613
|
sleep # wait for explicit kill to prevent GC disconnect
|
152
614
|
end
|
153
|
-
|
154
|
-
|
155
|
-
mark.
|
156
|
-
|
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
|
615
|
+
mark.store(:QUERY_START, Time.now)
|
616
|
+
@client.query("SELECT SLEEP(#{query_time})")
|
617
|
+
mark.store(:QUERY_END, Time.now)
|
618
|
+
ensure
|
161
619
|
Process.kill(:TERM, pid)
|
162
620
|
Process.waitpid2(pid)
|
163
|
-
ensure
|
164
621
|
trap(:USR1, 'DEFAULT')
|
165
622
|
end
|
623
|
+
|
624
|
+
# the query ran uninterrupted
|
625
|
+
expect(mark.fetch(:QUERY_END) - mark.fetch(:QUERY_START)).to be_within(0.02).of(query_time)
|
626
|
+
# signals fired while the query was running
|
627
|
+
expect(mark.fetch(:USR1)).to be_between(mark.fetch(:QUERY_START), mark.fetch(:QUERY_END))
|
628
|
+
end
|
629
|
+
|
630
|
+
it "#socket should return a Fixnum (file descriptor from C)" do
|
631
|
+
expect(@client.socket).to be_an_instance_of(0.class)
|
632
|
+
expect(@client.socket).not_to eql(0)
|
633
|
+
end
|
634
|
+
|
635
|
+
it "#socket should require an open connection" do
|
636
|
+
@client.close
|
637
|
+
expect do
|
638
|
+
@client.socket
|
639
|
+
end.to raise_error(Mysql2::Error)
|
640
|
+
end
|
641
|
+
|
642
|
+
it 'should be impervious to connection-corrupting timeouts in #execute' do
|
643
|
+
# attempt to break the connection
|
644
|
+
stmt = @client.prepare('SELECT SLEEP(?)')
|
645
|
+
expect { Timeout.timeout(0.1) { stmt.execute(0.2) } }.to raise_error(Timeout::Error)
|
646
|
+
stmt.close
|
647
|
+
|
648
|
+
# expect the connection to not be broken
|
649
|
+
expect { @client.query('SELECT 1') }.to_not raise_error
|
650
|
+
end
|
651
|
+
|
652
|
+
context 'when a non-standard exception class is raised' do
|
653
|
+
it "should close the connection when an exception is raised" do
|
654
|
+
expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
|
655
|
+
expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'MySQL client is not connected')
|
656
|
+
end
|
657
|
+
|
658
|
+
it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
|
659
|
+
if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5')
|
660
|
+
pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.')
|
661
|
+
end
|
662
|
+
|
663
|
+
client = new_client(reconnect: true)
|
664
|
+
|
665
|
+
expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
|
666
|
+
expect { client.query('SELECT 1') }.to_not raise_error
|
667
|
+
end
|
668
|
+
|
669
|
+
it "should handle Timeouts without leaving the connection hanging if reconnect is set to true after construction" do
|
670
|
+
if RUBY_PLATFORM.include?('darwin') && @client.server_info.fetch(:version).start_with?('5.5')
|
671
|
+
pending('MySQL 5.5 on OSX is afflicted by an unknown bug that breaks this test. See #633 and #634.')
|
672
|
+
end
|
673
|
+
|
674
|
+
client = new_client
|
675
|
+
|
676
|
+
expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
|
677
|
+
expect { client.query('SELECT 1') }.to raise_error(Mysql2::Error)
|
678
|
+
|
679
|
+
client.reconnect = true
|
680
|
+
|
681
|
+
expect { Timeout.timeout(0.1, ArgumentError) { client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
|
682
|
+
expect { client.query('SELECT 1') }.to_not raise_error
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
it "threaded queries should be supported" do
|
687
|
+
sleep_time = 0.5
|
688
|
+
|
689
|
+
# Note that each thread opens its own database connection
|
690
|
+
threads = Array.new(5) do
|
691
|
+
Thread.new do
|
692
|
+
new_client do |client|
|
693
|
+
client.query("SELECT SLEEP(#{sleep_time})")
|
694
|
+
end
|
695
|
+
Thread.current.object_id
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
# This timeout demonstrates that the threads are sleeping concurrently:
|
700
|
+
# In the serial case, the timeout would fire and the test would fail
|
701
|
+
values = Timeout.timeout(sleep_time * 1.1) { threads.map(&:value) }
|
702
|
+
|
703
|
+
expect(values).to match_array(threads.map(&:object_id))
|
704
|
+
end
|
705
|
+
|
706
|
+
it "evented async queries should be supported" do
|
707
|
+
# should immediately return nil
|
708
|
+
expect(@client.query("SELECT sleep(0.1)", async: true)).to eql(nil)
|
709
|
+
|
710
|
+
io_wrapper = IO.for_fd(@client.socket, autoclose: false)
|
711
|
+
loops = 0
|
712
|
+
loops += 1 until IO.select([io_wrapper], nil, nil, 0.05)
|
713
|
+
|
714
|
+
# make sure we waited some period of time
|
715
|
+
expect(loops >= 1).to be true
|
716
|
+
|
717
|
+
result = @client.async_result
|
718
|
+
expect(result).to be_an_instance_of(Mysql2::Result)
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
context "Multiple results sets" do
|
723
|
+
before(:each) do
|
724
|
+
@multi_client = new_client(flags: Mysql2::Client::MULTI_STATEMENTS)
|
725
|
+
end
|
726
|
+
|
727
|
+
it "should raise an exception when one of multiple statements fails" do
|
728
|
+
result = @multi_client.query("SELECT 1 AS 'set_1'; SELECT * FROM invalid_table_name; SELECT 2 AS 'set_2';")
|
729
|
+
expect(result.first['set_1']).to be(1)
|
730
|
+
expect do
|
731
|
+
@multi_client.next_result
|
732
|
+
end.to raise_error(Mysql2::Error)
|
733
|
+
expect(@multi_client.next_result).to be false
|
734
|
+
end
|
735
|
+
|
736
|
+
it "returns multiple result sets" do
|
737
|
+
expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql('set_1' => 1)
|
738
|
+
|
739
|
+
expect(@multi_client.next_result).to be true
|
740
|
+
expect(@multi_client.store_result.first).to eql('set_2' => 2)
|
741
|
+
|
742
|
+
expect(@multi_client.next_result).to be false
|
743
|
+
end
|
744
|
+
|
745
|
+
it "does not interfere with other statements" do
|
746
|
+
@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
|
747
|
+
@multi_client.store_result while @multi_client.next_result
|
748
|
+
|
749
|
+
expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq('next' => 3)
|
750
|
+
end
|
751
|
+
|
752
|
+
it "will raise on query if there are outstanding results to read" do
|
753
|
+
@multi_client.query("SELECT 1; SELECT 2; SELECT 3")
|
754
|
+
expect do
|
755
|
+
@multi_client.query("SELECT 4")
|
756
|
+
end.to raise_error(Mysql2::Error)
|
757
|
+
end
|
758
|
+
|
759
|
+
it "#abandon_results! should work" do
|
760
|
+
@multi_client.query("SELECT 1; SELECT 2; SELECT 3")
|
761
|
+
@multi_client.abandon_results!
|
762
|
+
expect do
|
763
|
+
@multi_client.query("SELECT 4")
|
764
|
+
end.not_to raise_error
|
765
|
+
end
|
766
|
+
|
767
|
+
it "#more_results? should work" do
|
768
|
+
@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
|
769
|
+
expect(@multi_client.more_results?).to be true
|
770
|
+
|
771
|
+
@multi_client.next_result
|
772
|
+
@multi_client.store_result
|
773
|
+
|
774
|
+
expect(@multi_client.more_results?).to be false
|
775
|
+
end
|
776
|
+
|
777
|
+
it "#more_results? should work with stored procedures" do
|
778
|
+
@multi_client.query("DROP PROCEDURE IF EXISTS test_proc")
|
779
|
+
@multi_client.query("CREATE PROCEDURE test_proc() BEGIN SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'; END")
|
780
|
+
expect(@multi_client.query("CALL test_proc()").first).to eql('set_1' => 1)
|
781
|
+
expect(@multi_client.more_results?).to be true
|
782
|
+
|
783
|
+
@multi_client.next_result
|
784
|
+
expect(@multi_client.store_result.first).to eql('set_2' => 2)
|
785
|
+
|
786
|
+
@multi_client.next_result
|
787
|
+
expect(@multi_client.store_result).to be_nil # this is the result from CALL itself
|
788
|
+
|
789
|
+
expect(@multi_client.more_results?).to be false
|
166
790
|
end
|
167
791
|
end
|
168
792
|
end
|
169
793
|
|
794
|
+
it "should respond to #socket" do
|
795
|
+
expect(@client).to respond_to(:socket)
|
796
|
+
end
|
797
|
+
|
798
|
+
if RUBY_PLATFORM =~ /mingw|mswin/
|
799
|
+
it "#socket should raise as it's not supported" do
|
800
|
+
expect do
|
801
|
+
@client.socket
|
802
|
+
end.to raise_error(Mysql2::Error, /Raw access to the mysql file descriptor isn't supported on Windows/)
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
170
806
|
it "should respond to escape" do
|
171
|
-
Mysql2::Client.
|
807
|
+
expect(Mysql2::Client).to respond_to(:escape)
|
172
808
|
end
|
173
809
|
|
174
810
|
context "escape" do
|
175
811
|
it "should return a new SQL-escape version of the passed string" do
|
176
|
-
Mysql2::Client.escape("abc'def\"ghi\0jkl%mno").
|
812
|
+
expect(Mysql2::Client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno")
|
177
813
|
end
|
178
814
|
|
179
815
|
it "should return the passed string if nothing was escaped" do
|
180
816
|
str = "plain"
|
181
|
-
Mysql2::Client.escape(str).object_id.
|
817
|
+
expect(Mysql2::Client.escape(str).object_id).to eql(str.object_id)
|
182
818
|
end
|
183
819
|
|
184
820
|
it "should not overflow the thread stack" do
|
185
|
-
|
821
|
+
expect do
|
186
822
|
Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join
|
187
|
-
|
823
|
+
end.not_to raise_error
|
188
824
|
end
|
189
825
|
|
190
826
|
it "should not overflow the process stack" do
|
191
|
-
|
827
|
+
expect do
|
192
828
|
Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join
|
193
|
-
|
829
|
+
end.not_to raise_error
|
194
830
|
end
|
195
831
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
escaped.encoding.should eql(str.encoding)
|
832
|
+
it "should carry over the original string's encoding" do
|
833
|
+
str = "abc'def\"ghi\0jkl%mno"
|
834
|
+
escaped = Mysql2::Client.escape(str)
|
835
|
+
expect(escaped.encoding).to eql(str.encoding)
|
201
836
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
end
|
837
|
+
str.encode!('us-ascii')
|
838
|
+
escaped = Mysql2::Client.escape(str)
|
839
|
+
expect(escaped.encoding).to eql(str.encoding)
|
206
840
|
end
|
207
841
|
end
|
208
842
|
|
209
843
|
it "should respond to #escape" do
|
210
|
-
@client.
|
844
|
+
expect(@client).to respond_to(:escape)
|
211
845
|
end
|
212
846
|
|
213
847
|
context "#escape" do
|
214
848
|
it "should return a new SQL-escape version of the passed string" do
|
215
|
-
@client.escape("abc'def\"ghi\0jkl%mno").
|
849
|
+
expect(@client.escape("abc'def\"ghi\0jkl%mno")).to eql("abc\\'def\\\"ghi\\0jkl%mno")
|
216
850
|
end
|
217
851
|
|
218
852
|
it "should return the passed string if nothing was escaped" do
|
219
853
|
str = "plain"
|
220
|
-
@client.escape(str).object_id.
|
854
|
+
expect(@client.escape(str).object_id).to eql(str.object_id)
|
221
855
|
end
|
222
856
|
|
223
857
|
it "should not overflow the thread stack" do
|
224
|
-
|
858
|
+
expect do
|
225
859
|
Thread.new { @client.escape("'" * 256 * 1024) }.join
|
226
|
-
|
860
|
+
end.not_to raise_error
|
227
861
|
end
|
228
862
|
|
229
863
|
it "should not overflow the process stack" do
|
230
|
-
|
864
|
+
expect do
|
231
865
|
Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
|
232
|
-
|
866
|
+
end.not_to raise_error
|
233
867
|
end
|
234
868
|
|
235
869
|
it "should require an open connection" do
|
236
870
|
@client.close
|
237
|
-
|
871
|
+
expect do
|
238
872
|
@client.escape ""
|
239
|
-
|
873
|
+
end.to raise_error(Mysql2::Error)
|
874
|
+
end
|
875
|
+
|
876
|
+
context 'when mysql encoding is not utf8' do
|
877
|
+
let(:client) { new_client(encoding: "ujis") }
|
878
|
+
|
879
|
+
it 'should return a internal encoding string if Encoding.default_internal is set' do
|
880
|
+
with_internal_encoding Encoding::UTF_8 do
|
881
|
+
expect(client.escape("\u{30C6}\u{30B9}\u{30C8}")).to eq "\u{30C6}\u{30B9}\u{30C8}"
|
882
|
+
expect(client.escape("\u{30C6}'\u{30B9}\"\u{30C8}")).to eq "\u{30C6}\\'\u{30B9}\\\"\u{30C8}"
|
883
|
+
end
|
884
|
+
end
|
240
885
|
end
|
241
886
|
end
|
242
887
|
|
243
888
|
it "should respond to #info" do
|
244
|
-
@client.
|
889
|
+
expect(@client).to respond_to(:info)
|
245
890
|
end
|
246
891
|
|
247
892
|
it "#info should return a hash containing the client version ID and String" do
|
248
893
|
info = @client.info
|
249
|
-
info.
|
250
|
-
info.
|
251
|
-
info[:id].
|
252
|
-
info.
|
253
|
-
info[:version].
|
894
|
+
expect(info).to be_an_instance_of(Hash)
|
895
|
+
expect(info).to have_key(:id)
|
896
|
+
expect(info[:id]).to be_an_instance_of(0.class)
|
897
|
+
expect(info).to have_key(:version)
|
898
|
+
expect(info[:version]).to be_an_instance_of(String)
|
254
899
|
end
|
255
900
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
client2 = Mysql2::Client.new :encoding => 'ascii'
|
263
|
-
client2.info[:version].encoding.should eql(Encoding.find('us-ascii'))
|
264
|
-
end
|
901
|
+
context "strings returned by #info" do
|
902
|
+
it "should be tagged as ascii" do
|
903
|
+
expect(@client.info[:version].encoding).to eql(Encoding::US_ASCII)
|
904
|
+
expect(@client.info[:header_version].encoding).to eql(Encoding::US_ASCII)
|
905
|
+
end
|
906
|
+
end
|
265
907
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
@client.info[:version].encoding.should eql(Encoding.default_internal)
|
271
|
-
end
|
908
|
+
context "strings returned by .info" do
|
909
|
+
it "should be tagged as ascii" do
|
910
|
+
expect(Mysql2::Client.info[:version].encoding).to eql(Encoding::US_ASCII)
|
911
|
+
expect(Mysql2::Client.info[:header_version].encoding).to eql(Encoding::US_ASCII)
|
272
912
|
end
|
273
913
|
end
|
274
914
|
|
275
915
|
it "should respond to #server_info" do
|
276
|
-
@client.
|
916
|
+
expect(@client).to respond_to(:server_info)
|
277
917
|
end
|
278
918
|
|
279
919
|
it "#server_info should return a hash containing the client version ID and String" do
|
280
920
|
server_info = @client.server_info
|
281
|
-
server_info.
|
282
|
-
server_info.
|
283
|
-
server_info[:id].
|
284
|
-
server_info.
|
285
|
-
server_info[:version].
|
921
|
+
expect(server_info).to be_an_instance_of(Hash)
|
922
|
+
expect(server_info).to have_key(:id)
|
923
|
+
expect(server_info[:id]).to be_an_instance_of(0.class)
|
924
|
+
expect(server_info).to have_key(:version)
|
925
|
+
expect(server_info[:version]).to be_an_instance_of(String)
|
286
926
|
end
|
287
927
|
|
288
928
|
it "#server_info should require an open connection" do
|
289
929
|
@client.close
|
290
|
-
|
930
|
+
expect do
|
291
931
|
@client.server_info
|
292
|
-
|
932
|
+
end.to raise_error(Mysql2::Error)
|
293
933
|
end
|
294
934
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
@client.server_info[:version].encoding.should eql(Encoding.find('utf-8'))
|
935
|
+
context "strings returned by #server_info" do
|
936
|
+
it "should default to the connection's encoding if Encoding.default_internal is nil" do
|
937
|
+
with_internal_encoding nil do
|
938
|
+
expect(@client.server_info[:version].encoding).to eql(Encoding::UTF_8)
|
300
939
|
|
301
|
-
client2 =
|
302
|
-
client2.server_info[:version].encoding.
|
303
|
-
end
|
304
|
-
|
305
|
-
it "should use Encoding.default_internal" do
|
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)
|
940
|
+
client2 = new_client(encoding: 'ascii')
|
941
|
+
expect(client2.server_info[:version].encoding).to eql(Encoding::ASCII)
|
310
942
|
end
|
311
943
|
end
|
312
|
-
end
|
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
944
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
}.should raise_error(Mysql2::Error)
|
328
|
-
end
|
329
|
-
|
330
|
-
it "should raise a Mysql2::Error exception upon connection failure" do
|
331
|
-
lambda {
|
332
|
-
bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
|
333
|
-
}.should raise_error(Mysql2::Error)
|
334
|
-
|
335
|
-
lambda {
|
336
|
-
good_client = Mysql2::Client.new
|
337
|
-
}.should_not raise_error(Mysql2::Error)
|
338
|
-
end
|
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)
|
945
|
+
it "should use Encoding.default_internal" do
|
946
|
+
with_internal_encoding Encoding::UTF_8 do
|
947
|
+
expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal)
|
948
|
+
end
|
357
949
|
|
358
|
-
|
359
|
-
|
360
|
-
loop do
|
361
|
-
if IO.select([io_wrapper], nil, nil, 0.05)
|
362
|
-
break
|
363
|
-
else
|
364
|
-
loops += 1
|
950
|
+
with_internal_encoding Encoding::ASCII do
|
951
|
+
expect(@client.server_info[:version].encoding).to eql(Encoding.default_internal)
|
365
952
|
end
|
366
953
|
end
|
954
|
+
end
|
367
955
|
|
368
|
-
|
369
|
-
|
956
|
+
it "should raise a Mysql2::Error::ConnectionError exception upon connection failure due to invalid credentials" do
|
957
|
+
expect do
|
958
|
+
new_client(host: 'localhost', username: 'asdfasdf8d2h', password: 'asdfasdfw42')
|
959
|
+
end.to raise_error(Mysql2::Error::ConnectionError)
|
370
960
|
|
371
|
-
|
372
|
-
|
961
|
+
expect do
|
962
|
+
new_client(DatabaseCredentials['root'])
|
963
|
+
end.not_to raise_error
|
373
964
|
end
|
374
965
|
|
375
966
|
context 'write operations api' do
|
376
967
|
before(:each) do
|
377
968
|
@client.query "USE test"
|
378
|
-
@client.query "CREATE TABLE lastIdTest (`id`
|
969
|
+
@client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
|
379
970
|
end
|
380
971
|
|
381
972
|
after(:each) do
|
@@ -383,48 +974,99 @@ describe Mysql2::Client do
|
|
383
974
|
end
|
384
975
|
|
385
976
|
it "should respond to #last_id" do
|
386
|
-
@client.
|
977
|
+
expect(@client).to respond_to(:last_id)
|
387
978
|
end
|
388
979
|
|
389
980
|
it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
|
390
|
-
@client.last_id.
|
981
|
+
expect(@client.last_id).to eql(0)
|
391
982
|
@client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
|
392
|
-
@client.last_id.
|
983
|
+
expect(@client.last_id).to eql(1)
|
393
984
|
end
|
394
985
|
|
395
986
|
it "should respond to #last_id" do
|
396
|
-
@client.
|
987
|
+
expect(@client).to respond_to(:last_id)
|
397
988
|
end
|
398
989
|
|
399
990
|
it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
|
400
991
|
@client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
|
401
|
-
@client.affected_rows.
|
992
|
+
expect(@client.affected_rows).to eql(1)
|
402
993
|
@client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
|
403
|
-
@client.affected_rows.
|
994
|
+
expect(@client.affected_rows).to eql(1)
|
995
|
+
end
|
996
|
+
|
997
|
+
it "#last_id should handle BIGINT auto-increment ids above 32 bits" do
|
998
|
+
# The id column type must be BIGINT. Surprise: INT(x) is limited to 32-bits for all values of x.
|
999
|
+
# Insert a row with a given ID, this should raise the auto-increment state
|
1000
|
+
@client.query "INSERT INTO lastIdTest (id, blah) VALUES (5000000000, 5000)"
|
1001
|
+
expect(@client.last_id).to eql(5000000000)
|
1002
|
+
@client.query "INSERT INTO lastIdTest (blah) VALUES (5001)"
|
1003
|
+
expect(@client.last_id).to eql(5000000001)
|
404
1004
|
end
|
405
1005
|
end
|
406
1006
|
|
407
1007
|
it "should respond to #thread_id" do
|
408
|
-
@client.
|
1008
|
+
expect(@client).to respond_to(:thread_id)
|
409
1009
|
end
|
410
1010
|
|
411
1011
|
it "#thread_id should be a Fixnum" do
|
412
|
-
@client.thread_id.
|
1012
|
+
expect(@client.thread_id).to be_an_instance_of(0.class)
|
413
1013
|
end
|
414
1014
|
|
415
1015
|
it "should respond to #ping" do
|
416
|
-
@client.
|
1016
|
+
expect(@client).to respond_to(:ping)
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
context "select_db" do
|
1020
|
+
before(:each) do
|
1021
|
+
2.times do |i|
|
1022
|
+
@client.query("CREATE DATABASE test_selectdb_#{i}")
|
1023
|
+
@client.query("USE test_selectdb_#{i}")
|
1024
|
+
@client.query("CREATE TABLE test#{i} (`id` int NOT NULL PRIMARY KEY)")
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
after(:each) do
|
1029
|
+
2.times do |i|
|
1030
|
+
@client.query("DROP DATABASE test_selectdb_#{i}")
|
1031
|
+
end
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
it "should respond to #select_db" do
|
1035
|
+
expect(@client).to respond_to(:select_db)
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
it "should switch databases" do
|
1039
|
+
@client.select_db("test_selectdb_0")
|
1040
|
+
expect(@client.query("SHOW TABLES").first.values.first).to eql("test0")
|
1041
|
+
@client.select_db("test_selectdb_1")
|
1042
|
+
expect(@client.query("SHOW TABLES").first.values.first).to eql("test1")
|
1043
|
+
@client.select_db("test_selectdb_0")
|
1044
|
+
expect(@client.query("SHOW TABLES").first.values.first).to eql("test0")
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
it "should raise a Mysql2::Error when the database doesn't exist" do
|
1048
|
+
expect do
|
1049
|
+
@client.select_db("nopenothere")
|
1050
|
+
end.to raise_error(Mysql2::Error)
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
it "should return the database switched to" do
|
1054
|
+
expect(@client.select_db("test_selectdb_1")).to eq("test_selectdb_1")
|
1055
|
+
end
|
417
1056
|
end
|
418
1057
|
|
419
1058
|
it "#thread_id should return a boolean" do
|
420
|
-
@client.ping.
|
1059
|
+
expect(@client.ping).to eql(true)
|
421
1060
|
@client.close
|
422
|
-
@client.ping.
|
1061
|
+
expect(@client.ping).to eql(false)
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
it "should be able to connect using plaintext password" do
|
1065
|
+
client = new_client(enable_cleartext_plugin: true)
|
1066
|
+
client.query('SELECT 1')
|
423
1067
|
end
|
424
1068
|
|
425
|
-
if RUBY_VERSION =~ /1.9/
|
426
1069
|
it "should respond to #encoding" do
|
427
|
-
@client.
|
1070
|
+
expect(@client).to respond_to(:encoding)
|
428
1071
|
end
|
429
1072
|
end
|
430
|
-
end
|