mysql2 0.4.0-x64-mingw32 → 0.4.1-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/examples/eventmachine.rb +1 -1
  3. data/ext/mysql2/client.c +45 -44
  4. data/ext/mysql2/client.h +1 -2
  5. data/ext/mysql2/extconf.rb +59 -37
  6. data/ext/mysql2/infile.c +2 -2
  7. data/ext/mysql2/mysql2_ext.h +4 -6
  8. data/ext/mysql2/mysql_enc_name_to_ruby.h +8 -8
  9. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  10. data/ext/mysql2/result.c +4 -16
  11. data/ext/mysql2/result.h +3 -3
  12. data/ext/mysql2/statement.c +79 -42
  13. data/ext/mysql2/statement.h +2 -6
  14. data/lib/mysql2.rb +36 -18
  15. data/lib/mysql2/2.0/mysql2.so +0 -0
  16. data/lib/mysql2/2.1/mysql2.so +0 -0
  17. data/lib/mysql2/2.2/mysql2.so +0 -0
  18. data/lib/mysql2/client.rb +28 -27
  19. data/lib/mysql2/console.rb +1 -1
  20. data/lib/mysql2/em.rb +5 -6
  21. data/lib/mysql2/error.rb +10 -7
  22. data/lib/mysql2/field.rb +1 -2
  23. data/lib/mysql2/statement.rb +12 -0
  24. data/lib/mysql2/version.rb +1 -1
  25. data/spec/em/em_spec.rb +8 -8
  26. data/spec/mysql2/client_spec.rb +62 -37
  27. data/spec/mysql2/result_spec.rb +48 -48
  28. data/spec/mysql2/statement_spec.rb +143 -57
  29. data/spec/ssl/ca-cert.pem +17 -0
  30. data/spec/ssl/ca-key.pem +27 -0
  31. data/spec/ssl/ca.cnf +22 -0
  32. data/spec/ssl/cert.cnf +22 -0
  33. data/spec/ssl/client-cert.pem +17 -0
  34. data/spec/ssl/client-key.pem +27 -0
  35. data/spec/ssl/client-req.pem +15 -0
  36. data/spec/ssl/gen_certs.sh +48 -0
  37. data/spec/ssl/pkcs8-client-key.pem +28 -0
  38. data/spec/ssl/pkcs8-server-key.pem +28 -0
  39. data/spec/ssl/server-cert.pem +17 -0
  40. data/spec/ssl/server-key.pem +27 -0
  41. data/spec/ssl/server-req.pem +15 -0
  42. data/support/mysql_enc_to_ruby.rb +7 -8
  43. data/support/ruby_enc_to_mysql.rb +1 -1
  44. metadata +28 -2
@@ -8,16 +8,16 @@ require 'rational' unless RUBY_VERSION >= '1.9.2'
8
8
  # Or to bomb out with a clear error message instead of a linker crash
9
9
  if RUBY_PLATFORM =~ /mswin|mingw/
10
10
  dll_path = if ENV['RUBY_MYSQL2_LIBMYSQL_DLL']
11
- # If this environment variable is set, it overrides any other paths
12
- # The user is advised to use backslashes not forward slashes
13
- ENV['RUBY_MYSQL2_LIBMYSQL_DLL'].dup
14
- elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)))
15
- # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary
16
- File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).gsub('/', '\\')
17
- else
18
- # This will use default / system library paths
19
- 'libmysql.dll'
20
- end
11
+ # If this environment variable is set, it overrides any other paths
12
+ # The user is advised to use backslashes not forward slashes
13
+ ENV['RUBY_MYSQL2_LIBMYSQL_DLL']
14
+ elsif File.exist?(File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)))
15
+ # Use vendor/libmysql.dll if it exists, convert slashes for Win32 LoadLibrary
16
+ File.expand_path('../vendor/libmysql.dll', File.dirname(__FILE__)).tr('/', '\\')
17
+ else
18
+ # This will use default / system library paths
19
+ 'libmysql.dll'
20
+ end
21
21
 
22
22
  require 'Win32API'
23
23
  LoadLibrary = Win32API.new('Kernel32', 'LoadLibrary', ['P'], 'I')
@@ -53,14 +53,32 @@ if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING < "3
53
53
  end
54
54
 
55
55
  # For holding utility methods
56
- module Mysql2::Util
56
+ module Mysql2
57
+ module Util
58
+ #
59
+ # Rekey a string-keyed hash with equivalent symbols.
60
+ #
61
+ def self.key_hash_as_symbols(hash)
62
+ return nil unless hash
63
+ Hash[hash.map { |k, v| [k.to_sym, v] }]
64
+ end
57
65
 
58
- #
59
- # Rekey a string-keyed hash with equivalent symbols.
60
- #
61
- def self.key_hash_as_symbols(hash)
62
- return nil unless hash
63
- Hash[hash.map { |k,v| [k.to_sym, v] }]
66
+ #
67
+ # In Mysql2::Client#query and Mysql2::Statement#execute,
68
+ # Thread#handle_interrupt is used to prevent Timeout#timeout
69
+ # from interrupting query execution.
70
+ #
71
+ # Timeout::ExitException was removed in Ruby 2.3.0, 2.2.3, and 2.1.8,
72
+ # but is present in earlier 2.1.x and 2.2.x, so we provide a shim.
73
+ #
74
+ if Thread.respond_to?(:handle_interrupt)
75
+ require 'timeout'
76
+ # rubocop:disable Style/ConstantName
77
+ TimeoutError = if defined?(::Timeout::ExitException)
78
+ ::Timeout::ExitException
79
+ else
80
+ ::Timeout::Error
81
+ end
82
+ end
64
83
  end
65
-
66
84
  end
Binary file
Binary file
Binary file
@@ -1,24 +1,27 @@
1
1
  module Mysql2
2
2
  class Client
3
3
  attr_reader :query_options, :read_timeout
4
- @@default_query_options = {
5
- :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
6
- :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
7
- :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
8
- :symbolize_keys => false, # return field names as symbols instead of strings
9
- :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
10
- :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
11
- :cache_rows => true, # tells Mysql2 to use it's internal row cache for results
12
- :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
13
- :cast => true,
14
- :default_file => nil,
15
- :default_group => nil
16
- }
4
+
5
+ def self.default_query_options
6
+ @default_query_options ||= {
7
+ :as => :hash, # the type of object you want each row back as; also supports :array (an array of values)
8
+ :async => false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
9
+ :cast_booleans => false, # cast tinyint(1) fields as true/false in ruby
10
+ :symbolize_keys => false, # return field names as symbols instead of strings
11
+ :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
12
+ :application_timezone => nil, # timezone Mysql2 will convert to before handing the object back to the caller
13
+ :cache_rows => true, # tells Mysql2 to use it's internal row cache for results
14
+ :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
15
+ :cast => true,
16
+ :default_file => nil,
17
+ :default_group => nil,
18
+ }
19
+ end
17
20
 
18
21
  def initialize(opts = {})
19
- opts = Mysql2::Util.key_hash_as_symbols( opts )
22
+ opts = Mysql2::Util.key_hash_as_symbols(opts)
20
23
  @read_timeout = nil
21
- @query_options = @@default_query_options.dup
24
+ @query_options = self.class.default_query_options.dup
22
25
  @query_options.merge! opts
23
26
 
24
27
  initialize_ext
@@ -26,11 +29,12 @@ module Mysql2
26
29
  # Set default connect_timeout to avoid unlimited retries from signal interruption
27
30
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
28
31
 
32
+ # TODO: stricter validation rather than silent massaging
29
33
  [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key|
30
34
  next unless opts.key?(key)
31
35
  case key
32
36
  when :reconnect, :local_infile, :secure_auth
33
- send(:"#{key}=", !!opts[key])
37
+ send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
34
38
  when :connect_timeout, :read_timeout, :write_timeout
35
39
  send(:"#{key}=", opts[key].to_i)
36
40
  else
@@ -48,9 +52,9 @@ module Mysql2
48
52
  flags = 0
49
53
  flags |= @query_options[:connect_flags]
50
54
  flags |= opts[:flags] if opts[:flags]
51
- flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] and ssl_options.any?
55
+ flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any?
52
56
 
53
- if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) }
57
+ if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
54
58
  warn "============= WARNING FROM mysql2 ============="
55
59
  warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future."
56
60
  warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
@@ -75,15 +79,9 @@ module Mysql2
75
79
  connect user, pass, host, port, database, socket, flags
76
80
  end
77
81
 
78
- def self.default_query_options
79
- @@default_query_options
80
- end
81
-
82
82
  if Thread.respond_to?(:handle_interrupt)
83
- require 'timeout'
84
-
85
83
  def query(sql, options = {})
86
- Thread.handle_interrupt(::Timeout::ExitException => :never) do
84
+ Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
87
85
  _query(sql, @query_options.merge(options))
88
86
  end
89
87
  end
@@ -105,9 +103,12 @@ module Mysql2
105
103
  self.class.info
106
104
  end
107
105
 
108
- private
109
- def self.local_offset
106
+ class << self
107
+ private
108
+
109
+ def local_offset
110
110
  ::Time.local(2010).utc_offset.to_r / 86400
111
111
  end
112
+ end
112
113
  end
113
114
  end
@@ -1,5 +1,5 @@
1
1
  # Loaded by script/console. Land helpers here.
2
2
 
3
- Pry.config.prompt = lambda do |context, nesting, pry|
3
+ Pry.config.prompt = lambda do |context, *|
4
4
  "[mysql2] #{context}> "
5
5
  end
@@ -17,7 +17,7 @@ module Mysql2
17
17
  detach
18
18
  begin
19
19
  result = @client.async_result
20
- rescue Exception => e
20
+ rescue => e
21
21
  @deferable.fail(e)
22
22
  else
23
23
  @deferable.succeed(result)
@@ -34,17 +34,16 @@ module Mysql2
34
34
  end
35
35
 
36
36
  def close(*args)
37
- if @watch
38
- @watch.detach if @watch.watching?
39
- end
37
+ @watch.detach if @watch && @watch.watching?
38
+
40
39
  super(*args)
41
40
  end
42
41
 
43
- def query(sql, opts={})
42
+ def query(sql, opts = {})
44
43
  if ::EM.reactor_running?
45
44
  super(sql, opts.merge(:async => true))
46
45
  deferable = ::EM::DefaultDeferrable.new
47
- @watch = ::EM.watch(self.socket, Watcher, self, deferable)
46
+ @watch = ::EM.watch(socket, Watcher, self, deferable)
48
47
  @watch.notify_readable = true
49
48
  deferable
50
49
  else
@@ -8,22 +8,25 @@ module Mysql2
8
8
  :replace => '?'.freeze,
9
9
  }.freeze
10
10
 
11
- attr_accessor :error_number
12
- attr_reader :sql_state
13
- attr_writer :server_version
11
+ attr_reader :error_number, :sql_state
14
12
 
15
13
  # Mysql gem compatibility
16
14
  alias_method :errno, :error_number
17
15
  alias_method :error, :message
18
16
 
19
- def initialize(msg, server_version=nil)
20
- self.server_version = server_version
17
+ def initialize(msg)
18
+ @server_version ||= nil
21
19
 
22
20
  super(clean_message(msg))
23
21
  end
24
22
 
25
- def sql_state=(state)
26
- @sql_state = state.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state
23
+ def self.new_with_args(msg, server_version, error_number, sql_state)
24
+ err = allocate
25
+ err.instance_variable_set('@server_version', server_version)
26
+ err.instance_variable_set('@error_number', error_number)
27
+ err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state)
28
+ err.send(:initialize, msg)
29
+ err
27
30
  end
28
31
 
29
32
  private
@@ -1,4 +1,3 @@
1
1
  module Mysql2
2
- class Field < Struct.new(:name, :type)
3
- end
2
+ Field = Struct.new(:name, :type)
4
3
  end
@@ -1,5 +1,17 @@
1
1
  module Mysql2
2
2
  class Statement
3
3
  include Enumerable
4
+
5
+ if Thread.respond_to?(:handle_interrupt)
6
+ def execute(*args)
7
+ Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
8
+ _execute(*args)
9
+ end
10
+ end
11
+ else
12
+ def execute(*args)
13
+ _execute(*args)
14
+ end
15
+ end
4
16
  end
5
17
  end
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -53,11 +53,11 @@ begin
53
53
  EM.run do
54
54
  client = Mysql2::EM::Client.new DatabaseCredentials['root']
55
55
  defer = client.query "SELECT sleep(0.1) as first_query"
56
- defer.callback do |result|
56
+ defer.callback do
57
57
  client.close
58
- raise 'some error'
58
+ fail 'some error'
59
59
  end
60
- defer.errback do |err|
60
+ defer.errback do
61
61
  # This _shouldn't_ be run, but it needed to prevent the specs from
62
62
  # freezing if this test fails.
63
63
  EM.stop_event_loop
@@ -75,7 +75,7 @@ begin
75
75
  errors = []
76
76
  EM.run do
77
77
  defer = client.query "SELECT sleep(0.1) as first_query"
78
- defer.callback do |result|
78
+ defer.callback do
79
79
  # This _shouldn't_ be run, but it is needed to prevent the specs from
80
80
  # freezing if this test fails.
81
81
  EM.stop_event_loop
@@ -93,13 +93,13 @@ begin
93
93
  EM.run do
94
94
  defer = client.query "SELECT sleep(0.025) as first_query"
95
95
  EM.add_timer(0.1) do
96
- defer.callback do |result|
96
+ defer.callback do
97
97
  callbacks_run << :callback
98
98
  # This _shouldn't_ be run, but it is needed to prevent the specs from
99
99
  # freezing if this test fails.
100
100
  EM.stop_event_loop
101
101
  end
102
- defer.errback do |err|
102
+ defer.errback do
103
103
  callbacks_run << :errback
104
104
  EM.stop_event_loop
105
105
  end
@@ -114,10 +114,10 @@ begin
114
114
  EM.run do
115
115
  client = Mysql2::EM::Client.new DatabaseCredentials['root']
116
116
  defer = client.query("select sleep(0.025)")
117
- defer.callback do |result|
117
+ defer.callback do
118
118
  callbacks_run << :callback
119
119
  end
120
- defer.errback do |err|
120
+ defer.errback do
121
121
  callbacks_run << :errback
122
122
  end
123
123
  EM.add_timer(0.1) do
@@ -46,7 +46,7 @@ RSpec.describe Mysql2::Client do
46
46
  it "should accept connect flags and pass them to #connect" do
47
47
  klient = Class.new(Mysql2::Client) do
48
48
  attr_reader :connect_args
49
- def connect *args
49
+ def connect(*args)
50
50
  @connect_args ||= []
51
51
  @connect_args << args
52
52
  end
@@ -58,7 +58,7 @@ RSpec.describe Mysql2::Client do
58
58
  it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
59
59
  klient = Class.new(Mysql2::Client) do
60
60
  attr_reader :connect_args
61
- def connect *args
61
+ def connect(*args)
62
62
  @connect_args ||= []
63
63
  @connect_args << args
64
64
  end
@@ -117,32 +117,37 @@ RSpec.describe Mysql2::Client do
117
117
 
118
118
  it "should be able to connect via SSL options" do
119
119
  ssl = @client.query "SHOW VARIABLES LIKE 'have_ssl'"
120
- ssl_uncompiled = ssl.any? {|x| x['Value'] == 'OFF'}
120
+ ssl_uncompiled = ssl.any? { |x| x['Value'] == 'OFF' }
121
121
  pending("DON'T WORRY, THIS TEST PASSES - but SSL is not compiled into your MySQL daemon.") if ssl_uncompiled
122
- ssl_disabled = ssl.any? {|x| x['Value'] == 'DISABLED'}
122
+ ssl_disabled = ssl.any? { |x| x['Value'] == 'DISABLED' }
123
123
  pending("DON'T WORRY, THIS TEST PASSES - but SSL is not enabled in your MySQL daemon.") if ssl_disabled
124
124
 
125
125
  # You may need to adjust the lines below to match your SSL certificate paths
126
126
  ssl_client = nil
127
127
  expect {
128
+ # rubocop:disable Style/TrailingComma
128
129
  ssl_client = Mysql2::Client.new(
129
- :sslkey => '/etc/mysql/client-key.pem',
130
- :sslcert => '/etc/mysql/client-cert.pem',
131
- :sslca => '/etc/mysql/ca-cert.pem',
132
- :sslcapath => '/etc/mysql/',
133
- :sslcipher => 'DHE-RSA-AES256-SHA'
130
+ DatabaseCredentials['root'].merge(
131
+ 'host' => 'mysql2gem.example.com', # must match the certificates
132
+ :sslkey => '/etc/mysql/client-key.pem',
133
+ :sslcert => '/etc/mysql/client-cert.pem',
134
+ :sslca => '/etc/mysql/ca-cert.pem',
135
+ :sslcipher => 'DHE-RSA-AES256-SHA',
136
+ :sslverify => true
137
+ )
134
138
  )
139
+ # rubocop:enable Style/TrailingComma
135
140
  }.not_to raise_error
136
141
 
137
142
  results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
138
143
  expect(results[0]['Variable_name']).to eql('Ssl_cipher')
139
144
  expect(results[0]['Value']).not_to be_nil
140
- expect(results[0]['Value']).to be_kind_of(String)
145
+ expect(results[0]['Value']).to be_an_instance_of(String)
141
146
  expect(results[0]['Value']).not_to be_empty
142
147
 
143
148
  expect(results[1]['Variable_name']).to eql('Ssl_version')
144
149
  expect(results[1]['Value']).not_to be_nil
145
- expect(results[1]['Value']).to be_kind_of(String)
150
+ expect(results[1]['Value']).to be_an_instance_of(String)
146
151
  expect(results[1]['Value']).not_to be_empty
147
152
 
148
153
  ssl_client.close
@@ -157,6 +162,14 @@ RSpec.describe Mysql2::Client do
157
162
  sleep(0.5)
158
163
  end
159
164
 
165
+ it "should terminate connections when calling close" do
166
+ expect {
167
+ Mysql2::Client.new(DatabaseCredentials['root']).close
168
+ }.to_not change {
169
+ @client.query("SHOW STATUS LIKE 'Aborted_clients'").first['Value'].to_i
170
+ }
171
+ end
172
+
160
173
  it "should not leave dangling connections after garbage collection" do
161
174
  run_gc
162
175
 
@@ -182,7 +195,7 @@ RSpec.describe Mysql2::Client do
182
195
 
183
196
  # this empty `fork` call fixes this tests on RBX; without it, the next
184
197
  # `fork` call hangs forever. WTF?
185
- fork { }
198
+ fork {}
186
199
 
187
200
  fork do
188
201
  client.query('SELECT 1')
@@ -264,7 +277,7 @@ RSpec.describe Mysql2::Client do
264
277
  # # 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).
265
278
  @client.query("INSERT INTO infoTest (blah) VALUES (1234),(4535)")
266
279
 
267
- expect(@client.query_info).to eql({:records => 2, :duplicates => 0, :warnings => 0})
280
+ expect(@client.query_info).to eql(:records => 2, :duplicates => 0, :warnings => 0)
268
281
  expect(@client.query_info_string).to eq('Records: 2 Duplicates: 0 Warnings: 0')
269
282
 
270
283
  @client.query "DROP TABLE infoTest"
@@ -276,7 +289,7 @@ RSpec.describe Mysql2::Client do
276
289
  before(:all) do
277
290
  @client_i = Mysql2::Client.new DatabaseCredentials['root'].merge(:local_infile => true)
278
291
  local = @client_i.query "SHOW VARIABLES LIKE 'local_infile'"
279
- local_enabled = local.any? {|x| x['Value'] == 'ON'}
292
+ local_enabled = local.any? { |x| x['Value'] == 'ON' }
280
293
  pending("DON'T WORRY, THIS TEST PASSES - but LOCAL INFILE is not enabled in your MySQL daemon.") unless local_enabled
281
294
 
282
295
  @client_i.query %[
@@ -308,10 +321,10 @@ RSpec.describe Mysql2::Client do
308
321
  it "should LOAD DATA LOCAL INFILE" do
309
322
  @client_i.query "LOAD DATA LOCAL INFILE 'spec/test_data' INTO TABLE infileTest"
310
323
  info = @client_i.query_info
311
- expect(info).to eql({:records => 1, :deleted => 0, :skipped => 0, :warnings => 0})
324
+ expect(info).to eql(:records => 1, :deleted => 0, :skipped => 0, :warnings => 0)
312
325
 
313
326
  result = @client_i.query "SELECT * FROM infileTest"
314
- expect(result.first).to eql({'id' => 1, 'foo' => 'Hello', 'bar' => 'World'})
327
+ expect(result.first).to eql('id' => 1, 'foo' => 'Hello', 'bar' => 'World')
315
328
  end
316
329
  end
317
330
 
@@ -379,16 +392,16 @@ RSpec.describe Mysql2::Client do
379
392
  end
380
393
 
381
394
  it "should return results as a hash by default" do
382
- expect(@client.query("SELECT 1").first.class).to eql(Hash)
395
+ expect(@client.query("SELECT 1").first).to be_an_instance_of(Hash)
383
396
  end
384
397
 
385
398
  it "should be able to return results as an array" do
386
- expect(@client.query("SELECT 1", :as => :array).first.class).to eql(Array)
399
+ expect(@client.query("SELECT 1", :as => :array).first).to be_an_instance_of(Array)
387
400
  @client.query("SELECT 1").each(:as => :array)
388
401
  end
389
402
 
390
403
  it "should be able to return results with symbolized keys" do
391
- expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class).to eql(Symbol)
404
+ expect(@client.query("SELECT 1", :symbolize_keys => true).first.keys[0]).to be_an_instance_of(Symbol)
392
405
  end
393
406
 
394
407
  it "should require an open connection" do
@@ -451,7 +464,7 @@ RSpec.describe Mysql2::Client do
451
464
  end
452
465
 
453
466
  it "#socket should return a Fixnum (file descriptor from C)" do
454
- expect(@client.socket.class).to eql(Fixnum)
467
+ expect(@client.socket).to be_an_instance_of(Fixnum)
455
468
  expect(@client.socket).not_to eql(0)
456
469
  end
457
470
 
@@ -462,7 +475,7 @@ RSpec.describe Mysql2::Client do
462
475
  }.to raise_error(Mysql2::Error)
463
476
  end
464
477
 
465
- it 'should be impervious to connection-corrupting timeouts ' do
478
+ it 'should be impervious to connection-corrupting timeouts in #query' do
466
479
  pending('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt)
467
480
  # attempt to break the connection
468
481
  expect { Timeout.timeout(0.1) { @client.query('SELECT SLEEP(0.2)') } }.to raise_error(Timeout::Error)
@@ -471,6 +484,20 @@ RSpec.describe Mysql2::Client do
471
484
  expect { @client.query('SELECT 1') }.to_not raise_error
472
485
  end
473
486
 
487
+ it 'should be impervious to connection-corrupting timeouts in #execute' do
488
+ # the statement handle gets corrupted and will segfault the tests if interrupted,
489
+ # so we can't even use pending on this test, really have to skip it on older Rubies.
490
+ skip('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt)
491
+
492
+ # attempt to break the connection
493
+ stmt = @client.prepare('SELECT SLEEP(?)')
494
+ expect { Timeout.timeout(0.1) { stmt.execute(0.2) } }.to raise_error(Timeout::Error)
495
+ stmt.close
496
+
497
+ # expect the connection to not be broken
498
+ expect { @client.query('SELECT 1') }.to_not raise_error
499
+ end
500
+
474
501
  context 'when a non-standard exception class is raised' do
475
502
  it "should close the connection when an exception is raised" do
476
503
  expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
@@ -542,7 +569,7 @@ RSpec.describe Mysql2::Client do
542
569
  expect(loops >= 1).to be true
543
570
 
544
571
  result = @client.async_result
545
- expect(result.class).to eql(Mysql2::Result)
572
+ expect(result).to be_an_instance_of(Mysql2::Result)
546
573
  end
547
574
  end
548
575
 
@@ -561,21 +588,19 @@ RSpec.describe Mysql2::Client do
561
588
  end
562
589
 
563
590
  it "returns multiple result sets" do
564
- expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql({ 'set_1' => 1 })
591
+ expect(@multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'").first).to eql('set_1' => 1)
565
592
 
566
593
  expect(@multi_client.next_result).to be true
567
- expect(@multi_client.store_result.first).to eql({ 'set_2' => 2 })
594
+ expect(@multi_client.store_result.first).to eql('set_2' => 2)
568
595
 
569
596
  expect(@multi_client.next_result).to be false
570
597
  end
571
598
 
572
599
  it "does not interfere with other statements" do
573
600
  @multi_client.query("SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'")
574
- while( @multi_client.next_result )
575
- @multi_client.store_result
576
- end
601
+ @multi_client.store_result while @multi_client.next_result
577
602
 
578
- expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq({ 'next' => 3 })
603
+ expect(@multi_client.query("SELECT 3 AS 'next'").first).to eq('next' => 3)
579
604
  end
580
605
 
581
606
  it "will raise on query if there are outstanding results to read" do
@@ -606,11 +631,11 @@ RSpec.describe Mysql2::Client do
606
631
  it "#more_results? should work with stored procedures" do
607
632
  @multi_client.query("DROP PROCEDURE IF EXISTS test_proc")
608
633
  @multi_client.query("CREATE PROCEDURE test_proc() BEGIN SELECT 1 AS 'set_1'; SELECT 2 AS 'set_2'; END")
609
- expect(@multi_client.query("CALL test_proc()").first).to eql({ 'set_1' => 1 })
634
+ expect(@multi_client.query("CALL test_proc()").first).to eql('set_1' => 1)
610
635
  expect(@multi_client.more_results?).to be true
611
636
 
612
637
  @multi_client.next_result
613
- expect(@multi_client.store_result.first).to eql({ 'set_2' => 2 })
638
+ expect(@multi_client.store_result.first).to eql('set_2' => 2)
614
639
 
615
640
  @multi_client.next_result
616
641
  expect(@multi_client.store_result).to be_nil # this is the result from CALL itself
@@ -724,11 +749,11 @@ RSpec.describe Mysql2::Client do
724
749
 
725
750
  it "#info should return a hash containing the client version ID and String" do
726
751
  info = @client.info
727
- expect(info.class).to eql(Hash)
752
+ expect(info).to be_an_instance_of(Hash)
728
753
  expect(info).to have_key(:id)
729
- expect(info[:id].class).to eql(Fixnum)
754
+ expect(info[:id]).to be_an_instance_of(Fixnum)
730
755
  expect(info).to have_key(:version)
731
- expect(info[:version].class).to eql(String)
756
+ expect(info[:version]).to be_an_instance_of(String)
732
757
  end
733
758
 
734
759
  context "strings returned by #info" do
@@ -755,11 +780,11 @@ RSpec.describe Mysql2::Client do
755
780
 
756
781
  it "#server_info should return a hash containing the client version ID and String" do
757
782
  server_info = @client.server_info
758
- expect(server_info.class).to eql(Hash)
783
+ expect(server_info).to be_an_instance_of(Hash)
759
784
  expect(server_info).to have_key(:id)
760
- expect(server_info[:id].class).to eql(Fixnum)
785
+ expect(server_info[:id]).to be_an_instance_of(Fixnum)
761
786
  expect(server_info).to have_key(:version)
762
- expect(server_info[:version].class).to eql(String)
787
+ expect(server_info[:version]).to be_an_instance_of(String)
763
788
  end
764
789
 
765
790
  it "#server_info should require an open connection" do
@@ -848,7 +873,7 @@ RSpec.describe Mysql2::Client do
848
873
  end
849
874
 
850
875
  it "#thread_id should be a Fixnum" do
851
- expect(@client.thread_id.class).to eql(Fixnum)
876
+ expect(@client.thread_id).to be_an_instance_of(Fixnum)
852
877
  end
853
878
 
854
879
  it "should respond to #ping" do