mysql2 0.4.1 → 0.4.6

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.
@@ -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