mysql2 0.4.1 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,7 @@ typedef struct {
7
7
  VALUE client;
8
8
  MYSQL_STMT *stmt;
9
9
  int refcount;
10
+ int closed;
10
11
  } mysql_stmt_wrapper;
11
12
 
12
13
  void init_mysql2_statement(void);
data/lib/mysql2/client.rb CHANGED
@@ -10,7 +10,7 @@ module Mysql2
10
10
  :symbolize_keys => false, # return field names as symbols instead of strings
11
11
  :database_timezone => :local, # timezone Mysql2 will assume datetime objects are stored in
12
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
13
+ :cache_rows => true, # tells Mysql2 to use its internal row cache for results
14
14
  :connect_flags => REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION,
15
15
  :cast => true,
16
16
  :default_file => nil,
@@ -19,6 +19,7 @@ module Mysql2
19
19
  end
20
20
 
21
21
  def initialize(opts = {})
22
+ fail Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
22
23
  opts = Mysql2::Util.key_hash_as_symbols(opts)
23
24
  @read_timeout = nil
24
25
  @query_options = self.class.default_query_options.dup
@@ -30,13 +31,13 @@ module Mysql2
30
31
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
31
32
 
32
33
  # TODO: stricter validation rather than silent massaging
33
- [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command].each do |key|
34
+ [:reconnect, :connect_timeout, :local_infile, :read_timeout, :write_timeout, :default_file, :default_group, :secure_auth, :init_command, :automatic_close].each do |key|
34
35
  next unless opts.key?(key)
35
36
  case key
36
- when :reconnect, :local_infile, :secure_auth
37
+ when :reconnect, :local_infile, :secure_auth, :automatic_close
37
38
  send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
38
39
  when :connect_timeout, :read_timeout, :write_timeout
39
- send(:"#{key}=", opts[key].to_i)
40
+ send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
40
41
  else
41
42
  send(:"#{key}=", opts[key])
42
43
  end
@@ -47,11 +48,20 @@ module Mysql2
47
48
 
48
49
  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
49
50
  ssl_set(*ssl_options) if ssl_options.any?
51
+ self.ssl_mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
52
+
53
+ case opts[:flags]
54
+ when Array
55
+ flags = parse_flags_array(opts[:flags], @query_options[:connect_flags])
56
+ when String
57
+ flags = parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
58
+ when Integer
59
+ flags = @query_options[:connect_flags] | opts[:flags]
60
+ else
61
+ flags = @query_options[:connect_flags]
62
+ end
50
63
 
51
64
  # SSL verify is a connection flag rather than a mysql_ssl_set option
52
- flags = 0
53
- flags |= @query_options[:connect_flags]
54
- flags |= opts[:flags] if opts[:flags]
55
65
  flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify] && ssl_options.any?
56
66
 
57
67
  if [:user, :pass, :hostname, :dbname, :db, :sock].any? { |k| @query_options.key?(k) }
@@ -79,6 +89,31 @@ module Mysql2
79
89
  connect user, pass, host, port, database, socket, flags
80
90
  end
81
91
 
92
+ def parse_ssl_mode(mode)
93
+ m = mode.to_s.upcase
94
+ if m.start_with?('SSL_MODE_')
95
+ return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
96
+ else
97
+ x = 'SSL_MODE_' + m
98
+ return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
99
+ end
100
+ warn "Unknown MySQL ssl_mode flag: #{mode}"
101
+ end
102
+
103
+ def parse_flags_array(flags, initial = 0)
104
+ flags.reduce(initial) do |memo, f|
105
+ fneg = f.start_with?('-') ? f[1..-1] : nil
106
+ if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg)
107
+ memo & ~ Mysql2::Client.const_get(fneg)
108
+ elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f)
109
+ memo | Mysql2::Client.const_get(f)
110
+ else
111
+ warn "Unknown MySQL connection flag: '#{f}'"
112
+ memo
113
+ end
114
+ end
115
+ end
116
+
82
117
  if Thread.respond_to?(:handle_interrupt)
83
118
  def query(sql, options = {})
84
119
  Thread.handle_interrupt(::Mysql2::Util::TimeoutError => :never) do
@@ -1,3 +1,3 @@
1
1
  module Mysql2
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.6"
3
3
  end
@@ -9,9 +9,3 @@ user:
9
9
  username: LOCALUSERNAME
10
10
  password:
11
11
  database: mysql2_test
12
-
13
- numericuser:
14
- host: localhost
15
- username: LOCALUSERNAME
16
- password:
17
- database: 12345
@@ -33,6 +33,12 @@ RSpec.describe Mysql2::Client do
33
33
  }.to raise_error(Mysql2::Error)
34
34
  end
35
35
 
36
+ it "should raise an exception on non-string encodings" do
37
+ expect {
38
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => :fake))
39
+ }.to raise_error(TypeError)
40
+ end
41
+
36
42
  it "should not raise an exception on create for a valid encoding" do
37
43
  expect {
38
44
  Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8"))
@@ -43,27 +49,33 @@ RSpec.describe Mysql2::Client do
43
49
  }.not_to raise_error
44
50
  end
45
51
 
46
- it "should accept connect flags and pass them to #connect" do
47
- klient = Class.new(Mysql2::Client) do
48
- attr_reader :connect_args
49
- def connect(*args)
50
- @connect_args ||= []
51
- @connect_args << args
52
- end
52
+ Klient = Class.new(Mysql2::Client) do
53
+ attr_reader :connect_args
54
+ def connect(*args)
55
+ @connect_args ||= []
56
+ @connect_args << args
53
57
  end
54
- client = klient.new :flags => Mysql2::Client::FOUND_ROWS
58
+ end
59
+
60
+ it "should accept connect flags and pass them to #connect" do
61
+ client = Klient.new :flags => Mysql2::Client::FOUND_ROWS
55
62
  expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to be > 0
56
63
  end
57
64
 
65
+ it "should parse flags array" do
66
+ client = Klient.new :flags => %w( FOUND_ROWS -PROTOCOL_41 )
67
+ expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS)
68
+ expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0)
69
+ end
70
+
71
+ it "should parse flags string" do
72
+ client = Klient.new :flags => "FOUND_ROWS -PROTOCOL_41"
73
+ expect(client.connect_args.last[6] & Mysql2::Client::FOUND_ROWS).to eql(Mysql2::Client::FOUND_ROWS)
74
+ expect(client.connect_args.last[6] & Mysql2::Client::PROTOCOL_41).to eql(0)
75
+ end
76
+
58
77
  it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
59
- klient = Class.new(Mysql2::Client) do
60
- attr_reader :connect_args
61
- def connect(*args)
62
- @connect_args ||= []
63
- @connect_args << args
64
- end
65
- end
66
- client = klient.new
78
+ client = Klient.new
67
79
  client_flags = Mysql2::Client::REMEMBER_OPTIONS |
68
80
  Mysql2::Client::LONG_PASSWORD |
69
81
  Mysql2::Client::LONG_FLAG |
@@ -139,16 +151,12 @@ RSpec.describe Mysql2::Client do
139
151
  # rubocop:enable Style/TrailingComma
140
152
  }.not_to raise_error
141
153
 
142
- results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
143
- expect(results[0]['Variable_name']).to eql('Ssl_cipher')
144
- expect(results[0]['Value']).not_to be_nil
145
- expect(results[0]['Value']).to be_an_instance_of(String)
146
- expect(results[0]['Value']).not_to be_empty
154
+ results = Hash[ssl_client.query('SHOW STATUS WHERE Variable_name LIKE "Ssl_%"').map { |x| x.values_at('Variable_name', 'Value') }]
155
+ expect(results['Ssl_cipher']).not_to be_empty
156
+ expect(results['Ssl_version']).not_to be_empty
147
157
 
148
- expect(results[1]['Variable_name']).to eql('Ssl_version')
149
- expect(results[1]['Value']).not_to be_nil
150
- expect(results[1]['Value']).to be_an_instance_of(String)
151
- expect(results[1]['Value']).not_to be_empty
158
+ expect(ssl_client.ssl_cipher).not_to be_empty
159
+ expect(results['Ssl_cipher']).to eql(ssl_client.ssl_cipher)
152
160
 
153
161
  ssl_client.close
154
162
  end
@@ -166,58 +174,107 @@ RSpec.describe Mysql2::Client do
166
174
  expect {
167
175
  Mysql2::Client.new(DatabaseCredentials['root']).close
168
176
  }.to_not change {
169
- @client.query("SHOW STATUS LIKE 'Aborted_clients'").first['Value'].to_i
177
+ @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
178
+ @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
170
179
  }
171
180
  end
172
181
 
173
182
  it "should not leave dangling connections after garbage collection" do
174
183
  run_gc
184
+ expect {
185
+ expect {
186
+ 10.times do
187
+ Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
188
+ end
189
+ }.to change {
190
+ @client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
191
+ }.by(10)
175
192
 
176
- client = Mysql2::Client.new(DatabaseCredentials['root'])
177
- before_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
193
+ run_gc
194
+ }.to_not change {
195
+ @client.query("SHOW STATUS LIKE 'Aborted_%'").to_a +
196
+ @client.query("SHOW STATUS LIKE 'Threads_connected'").to_a
197
+ }
198
+ end
178
199
 
179
- 10.times do
180
- Mysql2::Client.new(DatabaseCredentials['root']).query('SELECT 1')
200
+ context "#automatic_close" do
201
+ it "is enabled by default" do
202
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
203
+ expect(client.automatic_close?).to be(true)
181
204
  end
182
- after_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
183
- expect(after_count).to eq(before_count + 10)
184
205
 
185
- run_gc
186
- final_count = client.query("SHOW STATUS LIKE 'Threads_connected'").first['Value'].to_i
187
- expect(final_count).to eq(before_count)
188
- end
206
+ if RUBY_PLATFORM =~ /mingw|mswin/
207
+ it "cannot be disabled" do
208
+ expect do
209
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false))
210
+ expect(client.automatic_close?).to be(true)
211
+ end.to output(/always closed by garbage collector/).to_stderr
189
212
 
190
- it "should not close connections when running in a child process" do
191
- pending("fork is not available on this platform") unless Process.respond_to?(:fork)
213
+ expect do
214
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true))
215
+ expect(client.automatic_close?).to be(true)
216
+ end.to_not output(/always closed by garbage collector/).to_stderr
192
217
 
193
- run_gc
194
- client = Mysql2::Client.new(DatabaseCredentials['root'])
218
+ expect do
219
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => true))
220
+ client.automatic_close = false
221
+ expect(client.automatic_close?).to be(true)
222
+ end.to output(/always closed by garbage collector/).to_stderr
223
+ end
224
+ else
225
+ it "can be configured" do
226
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:automatic_close => false))
227
+ expect(client.automatic_close?).to be(false)
228
+ end
195
229
 
196
- # this empty `fork` call fixes this tests on RBX; without it, the next
197
- # `fork` call hangs forever. WTF?
198
- fork {}
230
+ it "can be assigned" do
231
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
232
+ client.automatic_close = false
233
+ expect(client.automatic_close?).to be(false)
199
234
 
200
- fork do
201
- client.query('SELECT 1')
202
- client = nil
203
- run_gc
204
- end
235
+ client.automatic_close = true
236
+ expect(client.automatic_close?).to be(true)
237
+
238
+ client.automatic_close = nil
239
+ expect(client.automatic_close?).to be(false)
240
+
241
+ client.automatic_close = 9
242
+ expect(client.automatic_close?).to be(true)
243
+ end
244
+
245
+ it "should not close connections when running in a child process" do
246
+ run_gc
247
+ client = Mysql2::Client.new(DatabaseCredentials['root'])
248
+ client.automatic_close = false
249
+
250
+ # this empty `fork` call fixes this tests on RBX; without it, the next
251
+ # `fork` call hangs forever. WTF?
252
+ fork {}
205
253
 
206
- Process.wait
254
+ fork do
255
+ client.query('SELECT 1')
256
+ client = nil
257
+ run_gc
258
+ end
259
+
260
+ Process.wait
207
261
 
208
- # this will throw an error if the underlying socket was shutdown by the
209
- # child's GC
210
- expect { client.query('SELECT 1') }.to_not raise_exception
262
+ # this will throw an error if the underlying socket was shutdown by the
263
+ # child's GC
264
+ expect { client.query('SELECT 1') }.to_not raise_exception
265
+ end
266
+ end
211
267
  end
212
268
 
213
269
  it "should be able to connect to database with numeric-only name" do
214
- creds = DatabaseCredentials['numericuser']
215
- @client.query "CREATE DATABASE IF NOT EXISTS `#{creds['database']}`"
216
- @client.query "GRANT ALL ON `#{creds['database']}`.* TO #{creds['username']}@`#{creds['host']}`"
270
+ database = 1235
271
+ @client.query "CREATE DATABASE IF NOT EXISTS `#{database}`"
217
272
 
218
- expect { Mysql2::Client.new(creds) }.not_to raise_error
273
+ expect {
274
+ Mysql2::Client.new(DatabaseCredentials['root'].merge('database' => database))
275
+ }.not_to raise_error
219
276
 
220
- @client.query "DROP DATABASE IF EXISTS `#{creds['database']}`"
277
+ @client.query "DROP DATABASE IF EXISTS `#{database}`"
221
278
  end
222
279
 
223
280
  it "should respond to #close" do
@@ -330,22 +387,28 @@ RSpec.describe Mysql2::Client do
330
387
 
331
388
  it "should expect connect_timeout to be a positive integer" do
332
389
  expect {
333
- Mysql2::Client.new(:connect_timeout => -1)
390
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:connect_timeout => -1))
334
391
  }.to raise_error(Mysql2::Error)
335
392
  end
336
393
 
337
394
  it "should expect read_timeout to be a positive integer" do
338
395
  expect {
339
- Mysql2::Client.new(:read_timeout => -1)
396
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => -1))
340
397
  }.to raise_error(Mysql2::Error)
341
398
  end
342
399
 
343
400
  it "should expect write_timeout to be a positive integer" do
344
401
  expect {
345
- Mysql2::Client.new(:write_timeout => -1)
402
+ Mysql2::Client.new(DatabaseCredentials['root'].merge(:write_timeout => -1))
346
403
  }.to raise_error(Mysql2::Error)
347
404
  end
348
405
 
406
+ it "should allow nil read_timeout" do
407
+ client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:read_timeout => nil))
408
+
409
+ expect(client.read_timeout).to be_nil
410
+ end
411
+
349
412
  context "#query" do
350
413
  it "should let you query again if iterating is finished when streaming" do
351
414
  @client.query("SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false).each.to_a
@@ -475,15 +538,6 @@ RSpec.describe Mysql2::Client do
475
538
  }.to raise_error(Mysql2::Error)
476
539
  end
477
540
 
478
- it 'should be impervious to connection-corrupting timeouts in #query' do
479
- pending('`Thread.handle_interrupt` is not defined') unless Thread.respond_to?(:handle_interrupt)
480
- # attempt to break the connection
481
- expect { Timeout.timeout(0.1) { @client.query('SELECT SLEEP(0.2)') } }.to raise_error(Timeout::Error)
482
-
483
- # expect the connection to not be broken
484
- expect { @client.query('SELECT 1') }.to_not raise_error
485
- end
486
-
487
541
  it 'should be impervious to connection-corrupting timeouts in #execute' do
488
542
  # the statement handle gets corrupted and will segfault the tests if interrupted,
489
543
  # so we can't even use pending on this test, really have to skip it on older Rubies.
@@ -501,7 +555,7 @@ RSpec.describe Mysql2::Client do
501
555
  context 'when a non-standard exception class is raised' do
502
556
  it "should close the connection when an exception is raised" do
503
557
  expect { Timeout.timeout(0.1, ArgumentError) { @client.query('SELECT SLEEP(1)') } }.to raise_error(ArgumentError)
504
- expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'closed MySQL connection')
558
+ expect { @client.query('SELECT 1') }.to raise_error(Mysql2::Error, 'MySQL client is not connected')
505
559
  end
506
560
 
507
561
  it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
@@ -34,7 +34,7 @@ RSpec.describe Mysql2::Error do
34
34
  end
35
35
  end
36
36
 
37
- let(:invalid_utf8) { "\xE5\xC6\x7D\x1F" }
37
+ let(:invalid_utf8) { ["e5c67d1f"].pack('H*').force_encoding(Encoding::UTF_8) }
38
38
  let(:bad_err) do
39
39
  begin
40
40
  client.query(invalid_utf8)
@@ -22,6 +22,10 @@ RSpec.describe Mysql2::Result do
22
22
  expect(@result).to respond_to(:each)
23
23
  end
24
24
 
25
+ it "should respond to #free" do
26
+ expect(@result).to respond_to(:free)
27
+ end
28
+
25
29
  it "should raise a Mysql2::Error exception upon a bad query" do
26
30
  expect {
27
31
  @client.query "bad sql"
@@ -78,6 +82,11 @@ RSpec.describe Mysql2::Result do
78
82
  expect(result.first.object_id).not_to eql(result.first.object_id)
79
83
  end
80
84
 
85
+ it "should be able to iterate a second time even if cache_rows is disabled" do
86
+ result = @client.query "SELECT 1 UNION SELECT 2", :cache_rows => false
87
+ expect(result.to_a).to eql(result.to_a)
88
+ end
89
+
81
90
  it "should yield different value for #first if streaming" do
82
91
  result = @client.query "SELECT 1 UNION SELECT 2", :stream => true, :cache_rows => false
83
92
  expect(result.first).not_to eql(result.first)
@@ -6,11 +6,13 @@ RSpec.describe Mysql2::Statement do
6
6
  @client = Mysql2::Client.new(DatabaseCredentials['root'].merge(:encoding => "utf8"))
7
7
  end
8
8
 
9
+ def stmt_count
10
+ @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i
11
+ end
12
+
9
13
  it "should create a statement" do
10
14
  statement = nil
11
- expect { statement = @client.prepare 'SELECT 1' }.to change {
12
- @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i
13
- }.by(1)
15
+ expect { statement = @client.prepare 'SELECT 1' }.to change(&method(:stmt_count)).by(1)
14
16
  expect(statement).to be_an_instance_of(Mysql2::Statement)
15
17
  end
16
18
 
@@ -59,6 +61,26 @@ RSpec.describe Mysql2::Statement do
59
61
  expect(rows).to eq([{ "1" => 1 }])
60
62
  end
61
63
 
64
+ it "should handle bignum but in int64_t" do
65
+ stmt = @client.prepare('SELECT ? AS max, ? AS min')
66
+ int64_max = (1 << 63) - 1
67
+ int64_min = -(1 << 63)
68
+ result = stmt.execute(int64_max, int64_min)
69
+ expect(result.to_a).to eq(['max' => int64_max, 'min' => int64_min])
70
+ end
71
+
72
+ it "should handle bignum but beyond int64_t" do
73
+ stmt = @client.prepare('SELECT ? AS max1, ? AS max2, ? AS max3, ? AS min1, ? AS min2, ? AS min3')
74
+ int64_max1 = (1 << 63)
75
+ int64_max2 = (1 << 64) - 1
76
+ int64_max3 = 1 << 64
77
+ int64_min1 = -(1 << 63) - 1
78
+ int64_min2 = -(1 << 64) + 1
79
+ int64_min3 = -0xC000000000000000
80
+ result = stmt.execute(int64_max1, int64_max2, int64_max3, int64_min1, int64_min2, int64_min3)
81
+ expect(result.to_a).to eq(['max1' => int64_max1, 'max2' => int64_max2, 'max3' => int64_max3, 'min1' => int64_min1, 'min2' => int64_min2, 'min3' => int64_min3])
82
+ end
83
+
62
84
  it "should keep its result after other query" do
63
85
  @client.query 'USE test'
64
86
  @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int)'
@@ -108,6 +130,35 @@ RSpec.describe Mysql2::Statement do
108
130
  expect(result.first.first[1]).to be_an_instance_of(Time)
109
131
  end
110
132
 
133
+ it "should prepare Date values" do
134
+ now = Date.today
135
+ statement = @client.prepare('SELECT ? AS a')
136
+ result = statement.execute(now)
137
+ expect(result.first['a'].to_s).to eql(now.strftime('%F'))
138
+ end
139
+
140
+ it "should prepare Time values with microseconds" do
141
+ now = Time.now
142
+ statement = @client.prepare('SELECT ? AS a')
143
+ result = statement.execute(now)
144
+ if RUBY_VERSION =~ /1.8/
145
+ expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z'))
146
+ else
147
+ expect(result.first['a'].strftime('%F %T.%6N %z')).to eql(now.strftime('%F %T.%6N %z'))
148
+ end
149
+ end
150
+
151
+ it "should prepare DateTime values with microseconds" do
152
+ now = DateTime.now
153
+ statement = @client.prepare('SELECT ? AS a')
154
+ result = statement.execute(now)
155
+ if RUBY_VERSION =~ /1.8/
156
+ expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z'))
157
+ else
158
+ expect(result.first['a'].strftime('%F %T.%6N %z')).to eql(now.strftime('%F %T.%6N %z'))
159
+ end
160
+ end
161
+
111
162
  it "should tell us about the fields" do
112
163
  statement = @client.prepare 'SELECT 1 as foo, 2'
113
164
  statement.execute
@@ -117,6 +168,32 @@ RSpec.describe Mysql2::Statement do
117
168
  expect(list[1]).to eq('2')
118
169
  end
119
170
 
171
+ it "should handle as a decimal binding a BigDecimal" do
172
+ stmt = @client.prepare('SELECT ? AS decimal_test')
173
+ test_result = stmt.execute(BigDecimal.new("123.45")).first
174
+ expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal)
175
+ expect(test_result['decimal_test']).to eql(123.45)
176
+ end
177
+
178
+ it "should update a DECIMAL value passing a BigDecimal" do
179
+ @client.query 'USE test'
180
+ @client.query 'DROP TABLE IF EXISTS mysql2_stmt_decimal_test'
181
+ @client.query 'CREATE TABLE mysql2_stmt_decimal_test (decimal_test DECIMAL(10,3))'
182
+
183
+ @client.prepare("INSERT INTO mysql2_stmt_decimal_test VALUES (?)").execute(BigDecimal.new("123.45"))
184
+
185
+ test_result = @client.query("SELECT * FROM mysql2_stmt_decimal_test").first
186
+ expect(test_result['decimal_test']).to eql(123.45)
187
+ end
188
+
189
+ it "should warn but still work if cache_rows is set to false" do
190
+ @client.query_options.merge!(:cache_rows => false)
191
+ statement = @client.prepare 'SELECT 1'
192
+ result = nil
193
+ expect { result = statement.execute.to_a }.to output(/:cache_rows is forced for prepared statements/).to_stderr
194
+ expect(result.length).to eq(1)
195
+ end
196
+
120
197
  context "utf8_db" do
121
198
  before(:each) do
122
199
  @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8")
@@ -670,9 +747,7 @@ RSpec.describe Mysql2::Statement do
670
747
  context 'close' do
671
748
  it 'should free server resources' do
672
749
  stmt = @client.prepare 'SELECT 1'
673
- expect { stmt.close }.to change {
674
- @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i
675
- }.by(-1)
750
+ expect { stmt.close }.to change(&method(:stmt_count)).by(-1)
676
751
  end
677
752
 
678
753
  it 'should raise an error on subsequent execution' do