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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -151
  3. data/LICENSE +21 -0
  4. data/README.md +634 -0
  5. data/examples/eventmachine.rb +1 -3
  6. data/examples/threaded.rb +5 -9
  7. data/ext/mysql2/client.c +1154 -342
  8. data/ext/mysql2/client.h +20 -33
  9. data/ext/mysql2/extconf.rb +229 -37
  10. data/ext/mysql2/infile.c +122 -0
  11. data/ext/mysql2/infile.h +1 -0
  12. data/ext/mysql2/mysql2_ext.c +3 -1
  13. data/ext/mysql2/mysql2_ext.h +18 -16
  14. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  15. data/ext/mysql2/mysql_enc_to_ruby.h +259 -0
  16. data/ext/mysql2/result.c +708 -191
  17. data/ext/mysql2/result.h +15 -6
  18. data/ext/mysql2/statement.c +602 -0
  19. data/ext/mysql2/statement.h +17 -0
  20. data/ext/mysql2/wait_for_single_fd.h +37 -0
  21. data/lib/mysql2.rb +69 -7
  22. data/lib/mysql2/client.rb +126 -211
  23. data/lib/mysql2/console.rb +5 -0
  24. data/lib/mysql2/em.rb +24 -8
  25. data/lib/mysql2/error.rb +93 -8
  26. data/lib/mysql2/field.rb +3 -0
  27. data/lib/mysql2/result.rb +2 -0
  28. data/lib/mysql2/statement.rb +11 -0
  29. data/lib/mysql2/version.rb +2 -2
  30. data/spec/configuration.yml.example +11 -0
  31. data/spec/em/em_spec.rb +101 -15
  32. data/spec/my.cnf.example +9 -0
  33. data/spec/mysql2/client_spec.rb +874 -232
  34. data/spec/mysql2/error_spec.rb +55 -46
  35. data/spec/mysql2/result_spec.rb +306 -154
  36. data/spec/mysql2/statement_spec.rb +712 -0
  37. data/spec/spec_helper.rb +103 -57
  38. data/spec/ssl/ca-cert.pem +17 -0
  39. data/spec/ssl/ca-key.pem +27 -0
  40. data/spec/ssl/ca.cnf +22 -0
  41. data/spec/ssl/cert.cnf +22 -0
  42. data/spec/ssl/client-cert.pem +17 -0
  43. data/spec/ssl/client-key.pem +27 -0
  44. data/spec/ssl/client-req.pem +15 -0
  45. data/spec/ssl/gen_certs.sh +48 -0
  46. data/spec/ssl/pkcs8-client-key.pem +28 -0
  47. data/spec/ssl/pkcs8-server-key.pem +28 -0
  48. data/spec/ssl/server-cert.pem +17 -0
  49. data/spec/ssl/server-key.pem +27 -0
  50. data/spec/ssl/server-req.pem +15 -0
  51. data/spec/test_data +1 -0
  52. data/support/5072E1F5.asc +432 -0
  53. data/support/libmysql.def +219 -0
  54. data/support/mysql_enc_to_ruby.rb +81 -0
  55. data/support/ruby_enc_to_mysql.rb +61 -0
  56. metadata +82 -188
  57. data/.gitignore +0 -12
  58. data/.rspec +0 -2
  59. data/.rvmrc +0 -1
  60. data/Gemfile +0 -3
  61. data/MIT-LICENSE +0 -20
  62. data/README.rdoc +0 -257
  63. data/Rakefile +0 -5
  64. data/benchmark/active_record.rb +0 -51
  65. data/benchmark/active_record_threaded.rb +0 -42
  66. data/benchmark/allocations.rb +0 -33
  67. data/benchmark/escape.rb +0 -36
  68. data/benchmark/query_with_mysql_casting.rb +0 -80
  69. data/benchmark/query_without_mysql_casting.rb +0 -47
  70. data/benchmark/sequel.rb +0 -37
  71. data/benchmark/setup_db.rb +0 -119
  72. data/benchmark/threaded.rb +0 -44
  73. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +0 -64
  74. data/lib/active_record/fiber_patches.rb +0 -104
  75. data/lib/mysql2/em_fiber.rb +0 -31
  76. data/mysql2.gemspec +0 -32
  77. data/spec/em/em_fiber_spec.rb +0 -22
  78. data/tasks/benchmarks.rake +0 -20
  79. data/tasks/compile.rake +0 -71
  80. data/tasks/rspec.rake +0 -16
  81. data/tasks/vendor_mysql.rake +0 -40
@@ -0,0 +1,5 @@
1
+ # Loaded by script/console. Land helpers here.
2
+
3
+ Pry.config.prompt = lambda do |context, *|
4
+ "[mysql2] #{context}> "
5
+ end
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
- @deferable.succeed(@client.async_result)
19
- rescue Exception => e
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(:async => true))
42
+ super(sql, opts.merge(async: true))
28
43
  deferable = ::EM::DefaultDeferrable.new
29
- ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
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
- attr_accessor :error_number, :sql_state
3
+ ENCODE_OPTS = {
4
+ undef: :replace,
5
+ invalid: :replace,
6
+ replace: '?'.freeze,
7
+ }.freeze
4
8
 
5
- def initialize msg
6
- super
7
- @error_number = nil
8
- @sql_state = nil
9
- end
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
- alias_method :errno, :error_number
13
- alias_method :error, :message
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
@@ -0,0 +1,3 @@
1
+ module Mysql2
2
+ Field = Struct.new(:name, :type)
3
+ end
data/lib/mysql2/result.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Mysql2
2
2
  class Result
3
+ attr_reader :server_flags
4
+
3
5
  include Enumerable
4
6
  end
5
7
  end
@@ -0,0 +1,11 @@
1
+ module Mysql2
2
+ class Statement
3
+ include Enumerable
4
+
5
+ def execute(*args, **kwargs)
6
+ Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_CLASS => :never) do
7
+ _execute(*args, **kwargs)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.3.1"
3
- end
2
+ VERSION = "0.5.2".freeze
3
+ end
@@ -0,0 +1,11 @@
1
+ root:
2
+ host: localhost
3
+ username: root
4
+ password:
5
+ database: test
6
+
7
+ user:
8
+ host: localhost
9
+ username: LOCALUSERNAME
10
+ password:
11
+ database: mysql2_test
data/spec/em/em_spec.rb CHANGED
@@ -1,49 +1,135 @@
1
- # encoding: UTF-8
2
- if defined? EventMachine
3
- require 'spec_helper'
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.should include("second_query")
25
- results[1].keys.should include("first_query")
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 |result|
37
- results << result.first
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.should include("first_query")
44
- results[1].keys.should include("second_query")
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
- else
133
+ rescue LoadError
48
134
  puts "EventMachine not installed, skipping the specs that use it"
49
- end
135
+ end
@@ -0,0 +1,9 @@
1
+ [root]
2
+ host=localhost
3
+ user=LOCALUSERNAME
4
+ password=
5
+
6
+ [client]
7
+ host=localhost
8
+ user=LOCALUSERNAME
9
+ password=
@@ -1,381 +1,972 @@
1
- # encoding: UTF-8
2
1
  require 'spec_helper'
3
2
 
4
- describe Mysql2::Client do
5
- before(:each) do
6
- @client = Mysql2::Client.new
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
- if defined? Encoding
10
- it "should raise an exception on create for invalid encodings" do
11
- lambda {
12
- c = Mysql2::Client.new(:encoding => "fake")
13
- }.should raise_error(Mysql2::Error)
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
- klient = Class.new(Mysql2::Client) do
19
- attr_reader :connect_args
20
- def connect *args
21
- @connect_args ||= []
22
- @connect_args << args
23
- end
24
- end
25
- client = klient.new :flags => Mysql2::Client::FOUND_ROWS
26
- (client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true
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
- klient = Class.new(Mysql2::Client) do
31
- attr_reader :connect_args
32
- def connect *args
33
- @connect_args ||= []
34
- @connect_args << args
35
- end
36
- end
37
- client = klient.new
38
- (client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS |
39
- Mysql2::Client::LONG_PASSWORD |
40
- Mysql2::Client::LONG_FLAG |
41
- Mysql2::Client::TRANSACTIONS |
42
- Mysql2::Client::PROTOCOL_41 |
43
- Mysql2::Client::SECURE_CONNECTION)).should be_true
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.should respond_to(:default_query_options)
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
- 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
+ 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
- lambda {
54
- ssl_client = Mysql2::Client.new(
55
- :sslkey => '/path/to/client-key.pem',
56
- :sslcert => '/path/to/client-cert.pem',
57
- :sslca => '/path/to/ca-cert.pem',
58
- :sslcapath => '/path/to/newcerts/',
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
- }.should_not raise_error(Mysql2::Error)
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
- results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
64
- results[0]['Variable_name'].should eql('Ssl_cipher')
65
- results[0]['Value'].should_not be_nil
66
- results[0]['Value'].class.should eql(String)
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
- results[1]['Variable_name'].should eql('Ssl_version')
69
- results[1]['Value'].should_not be_nil
70
- results[1]['Value'].class.should eql(String)
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.should respond_to(:close)
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.should be_nil
79
- lambda {
323
+ expect(@client.close).to be_nil
324
+ expect do
80
325
  @client.query "SELECT 1"
81
- }.should raise_error(Mysql2::Error)
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.should respond_to(:query)
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
- lambda {
90
- Mysql2::Client.new(:read_timeout => -1)
91
- }.should raise_error(Mysql2::Error)
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
- lambda {
512
+ expect do
97
513
  @client.query ["SELECT 'not right'"]
98
- }.should raise_error(TypeError)
514
+ end.to raise_error(TypeError)
99
515
  end
100
516
 
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))
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.class.should eql(Hash)
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", :as => :array).first.class.should eql(Array)
112
- @client.query("SELECT 1").each(:as => :array)
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", :symbolize_keys => true).first.keys[0].class.should eql(Symbol)
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
- lambda {
554
+ expect do
129
555
  @client.query "SELECT 1"
130
- }.should raise_error(Mysql2::Error)
556
+ end.to raise_error(Mysql2::Error)
131
557
  end
132
558
 
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)
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
- trap(:USR1) { mark[:USR1] = Time.now }
607
+
146
608
  begin
147
- mark[:START] = Time.now
609
+ trap(:USR1) { mark.store(:USR1, Time.now) }
148
610
  pid = fork do
149
- sleep 1 # wait for client "SELECT sleep(2)" query to start
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
- @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
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.should respond_to(:escape)
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").should eql("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.should eql(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
- lambda {
821
+ expect do
186
822
  Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join
187
- }.should_not raise_error(SystemStackError)
823
+ end.not_to raise_error
188
824
  end
189
825
 
190
826
  it "should not overflow the process stack" do
191
- lambda {
827
+ expect do
192
828
  Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join
193
- }.should_not raise_error(SystemStackError)
829
+ end.not_to raise_error
194
830
  end
195
831
 
196
- if RUBY_VERSION =~ /1.9/
197
- it "should carry over the original string's encoding" do
198
- str = "abc'def\"ghi\0jkl%mno"
199
- escaped = Mysql2::Client.escape(str)
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
- str.encode!('us-ascii')
203
- escaped = Mysql2::Client.escape(str)
204
- escaped.encoding.should eql(str.encoding)
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.should respond_to(:escape)
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").should eql("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.should eql(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
- lambda {
858
+ expect do
225
859
  Thread.new { @client.escape("'" * 256 * 1024) }.join
226
- }.should_not raise_error(SystemStackError)
860
+ end.not_to raise_error
227
861
  end
228
862
 
229
863
  it "should not overflow the process stack" do
230
- lambda {
864
+ expect do
231
865
  Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
232
- }.should_not raise_error(SystemStackError)
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
- lambda {
871
+ expect do
238
872
  @client.escape ""
239
- }.should raise_error(Mysql2::Error)
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.should respond_to(:info)
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.class.should eql(Hash)
250
- info.should have_key(:id)
251
- info[:id].class.should eql(Fixnum)
252
- info.should have_key(:version)
253
- info[:version].class.should eql(String)
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
- if defined? Encoding
257
- context "strings returned by #info" do
258
- it "should default to the connection's encoding if Encoding.default_internal is nil" do
259
- Encoding.default_internal = nil
260
- @client.info[:version].encoding.should eql(Encoding.find('utf-8'))
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
- it "should use Encoding.default_internal" do
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)
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.should respond_to(:server_info)
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.class.should eql(Hash)
282
- server_info.should have_key(:id)
283
- server_info[:id].class.should eql(Fixnum)
284
- server_info.should have_key(:version)
285
- server_info[:version].class.should eql(String)
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
- lambda {
930
+ expect do
291
931
  @client.server_info
292
- }.should raise_error(Mysql2::Error)
932
+ end.to raise_error(Mysql2::Error)
293
933
  end
294
934
 
295
- if defined? Encoding
296
- context "strings returned by #server_info" do
297
- it "should default to the connection's encoding if Encoding.default_internal is nil" do
298
- Encoding.default_internal = nil
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 = Mysql2::Client.new :encoding => 'ascii'
302
- client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii'))
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
- 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
-
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
- 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
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
- # make sure we waited some period of time
369
- (loops >= 1).should be_true
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
- result = @client.async_result
372
- result.class.should eql(Mysql2::Result)
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` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`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.should respond_to(:last_id)
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.should eql(0)
981
+ expect(@client.last_id).to eql(0)
391
982
  @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
392
- @client.last_id.should eql(1)
983
+ expect(@client.last_id).to eql(1)
393
984
  end
394
985
 
395
986
  it "should respond to #last_id" do
396
- @client.should respond_to(:last_id)
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.should eql(1)
992
+ expect(@client.affected_rows).to eql(1)
402
993
  @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
403
- @client.affected_rows.should eql(1)
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.should respond_to(:thread_id)
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.class.should eql(Fixnum)
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.should respond_to(:ping)
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.should eql(true)
1059
+ expect(@client.ping).to eql(true)
421
1060
  @client.close
422
- @client.ping.should eql(false)
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.should respond_to(:encoding)
1070
+ expect(@client).to respond_to(:encoding)
428
1071
  end
429
1072
  end
430
- end