mysql2 0.3.20 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +132 -55
  4. data/examples/eventmachine.rb +1 -1
  5. data/examples/threaded.rb +4 -6
  6. data/ext/mysql2/client.c +323 -140
  7. data/ext/mysql2/client.h +13 -3
  8. data/ext/mysql2/extconf.rb +111 -44
  9. data/ext/mysql2/infile.c +2 -2
  10. data/ext/mysql2/mysql2_ext.c +1 -0
  11. data/ext/mysql2/mysql2_ext.h +5 -10
  12. data/ext/mysql2/mysql_enc_name_to_ruby.h +2 -2
  13. data/ext/mysql2/mysql_enc_to_ruby.h +25 -22
  14. data/ext/mysql2/result.c +491 -97
  15. data/ext/mysql2/result.h +12 -4
  16. data/ext/mysql2/statement.c +595 -0
  17. data/ext/mysql2/statement.h +19 -0
  18. data/lib/mysql2/client.rb +82 -27
  19. data/lib/mysql2/console.rb +1 -1
  20. data/lib/mysql2/em.rb +5 -6
  21. data/lib/mysql2/error.rb +17 -26
  22. data/lib/mysql2/field.rb +3 -0
  23. data/lib/mysql2/statement.rb +17 -0
  24. data/lib/mysql2/version.rb +1 -1
  25. data/lib/mysql2.rb +38 -18
  26. data/spec/configuration.yml.example +0 -6
  27. data/spec/em/em_spec.rb +22 -21
  28. data/spec/mysql2/client_spec.rb +498 -380
  29. data/spec/mysql2/error_spec.rb +38 -39
  30. data/spec/mysql2/result_spec.rb +260 -229
  31. data/spec/mysql2/statement_spec.rb +776 -0
  32. data/spec/spec_helper.rb +80 -59
  33. data/spec/ssl/ca-cert.pem +17 -0
  34. data/spec/ssl/ca-key.pem +27 -0
  35. data/spec/ssl/ca.cnf +22 -0
  36. data/spec/ssl/cert.cnf +22 -0
  37. data/spec/ssl/client-cert.pem +17 -0
  38. data/spec/ssl/client-key.pem +27 -0
  39. data/spec/ssl/client-req.pem +15 -0
  40. data/spec/ssl/gen_certs.sh +48 -0
  41. data/spec/ssl/pkcs8-client-key.pem +28 -0
  42. data/spec/ssl/pkcs8-server-key.pem +28 -0
  43. data/spec/ssl/server-cert.pem +17 -0
  44. data/spec/ssl/server-key.pem +27 -0
  45. data/spec/ssl/server-req.pem +15 -0
  46. data/support/5072E1F5.asc +432 -0
  47. data/support/mysql_enc_to_ruby.rb +7 -8
  48. data/support/ruby_enc_to_mysql.rb +1 -1
  49. metadata +50 -55
@@ -0,0 +1,776 @@
1
+ # encoding: UTF-8
2
+ require './spec/spec_helper.rb'
3
+
4
+ RSpec.describe Mysql2::Statement do
5
+ before :each do
6
+ @client = new_client(:encoding => "utf8")
7
+ end
8
+
9
+ def stmt_count
10
+ @client.query("SHOW STATUS LIKE 'Prepared_stmt_count'").first['Value'].to_i
11
+ end
12
+
13
+ it "should create a statement" do
14
+ statement = nil
15
+ expect { statement = @client.prepare 'SELECT 1' }.to change(&method(:stmt_count)).by(1)
16
+ expect(statement).to be_an_instance_of(Mysql2::Statement)
17
+ end
18
+
19
+ it "should raise an exception when server disconnects" do
20
+ @client.close
21
+ expect { @client.prepare 'SELECT 1' }.to raise_error(Mysql2::Error)
22
+ end
23
+
24
+ it "should tell us the param count" do
25
+ statement = @client.prepare 'SELECT ?, ?'
26
+ expect(statement.param_count).to eq(2)
27
+
28
+ statement2 = @client.prepare 'SELECT 1'
29
+ expect(statement2.param_count).to eq(0)
30
+ end
31
+
32
+ it "should tell us the field count" do
33
+ statement = @client.prepare 'SELECT ?, ?'
34
+ expect(statement.field_count).to eq(2)
35
+
36
+ statement2 = @client.prepare 'SELECT 1'
37
+ expect(statement2.field_count).to eq(1)
38
+ end
39
+
40
+ it "should let us execute our statement" do
41
+ statement = @client.prepare 'SELECT 1'
42
+ expect(statement.execute).not_to eq(nil)
43
+ end
44
+
45
+ it "should raise an exception without a block" do
46
+ statement = @client.prepare 'SELECT 1'
47
+ expect { statement.execute.each }.to raise_error(LocalJumpError)
48
+ end
49
+
50
+ it "should tell us the result count" do
51
+ statement = @client.prepare 'SELECT 1'
52
+ result = statement.execute
53
+ expect(result.count).to eq(1)
54
+ end
55
+
56
+ it "should let us iterate over results" do
57
+ statement = @client.prepare 'SELECT 1'
58
+ result = statement.execute
59
+ rows = []
60
+ result.each { |r| rows << r }
61
+ expect(rows).to eq([{ "1" => 1 }])
62
+ end
63
+
64
+ it "should handle booleans" do
65
+ stmt = @client.prepare('SELECT ? AS `true`, ? AS `false`')
66
+ result = stmt.execute(true, false)
67
+ expect(result.to_a).to eq(['true' => 1, 'false' => 0])
68
+ end
69
+
70
+ it "should handle bignum but in int64_t" do
71
+ stmt = @client.prepare('SELECT ? AS max, ? AS min')
72
+ int64_max = (1 << 63) - 1
73
+ int64_min = -(1 << 63)
74
+ result = stmt.execute(int64_max, int64_min)
75
+ expect(result.to_a).to eq(['max' => int64_max, 'min' => int64_min])
76
+ end
77
+
78
+ it "should handle bignum but beyond int64_t" do
79
+ stmt = @client.prepare('SELECT ? AS max1, ? AS max2, ? AS max3, ? AS min1, ? AS min2, ? AS min3')
80
+ int64_max1 = (1 << 63)
81
+ int64_max2 = (1 << 64) - 1
82
+ int64_max3 = 1 << 64
83
+ int64_min1 = -(1 << 63) - 1
84
+ int64_min2 = -(1 << 64) + 1
85
+ int64_min3 = -0xC000000000000000
86
+ result = stmt.execute(int64_max1, int64_max2, int64_max3, int64_min1, int64_min2, int64_min3)
87
+ expect(result.to_a).to eq(['max1' => int64_max1, 'max2' => int64_max2, 'max3' => int64_max3, 'min1' => int64_min1, 'min2' => int64_min2, 'min3' => int64_min3])
88
+ end
89
+
90
+ it "should keep its result after other query" do
91
+ @client.query 'USE test'
92
+ @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int)'
93
+ @client.query 'INSERT INTO mysql2_stmt_q (a) VALUES (1), (2)'
94
+ stmt = @client.prepare('SELECT a FROM mysql2_stmt_q WHERE a = ?')
95
+ result1 = stmt.execute(1)
96
+ result2 = stmt.execute(2)
97
+ expect(result2.first).to eq("a" => 2)
98
+ expect(result1.first).to eq("a" => 1)
99
+ @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q'
100
+ end
101
+
102
+ it "should be reusable 1000 times" do
103
+ statement = @client.prepare 'SELECT 1'
104
+ 1000.times do
105
+ result = statement.execute
106
+ expect(result.to_a.length).to eq(1)
107
+ end
108
+ end
109
+
110
+ it "should be reusable 10000 times" do
111
+ statement = @client.prepare 'SELECT 1'
112
+ 10000.times do
113
+ result = statement.execute
114
+ expect(result.to_a.length).to eq(1)
115
+ end
116
+ end
117
+
118
+ it "should handle comparisons and likes" do
119
+ @client.query 'USE test'
120
+ @client.query 'CREATE TABLE IF NOT EXISTS mysql2_stmt_q(a int, b varchar(10))'
121
+ @client.query 'INSERT INTO mysql2_stmt_q (a, b) VALUES (1, "Hello"), (2, "World")'
122
+ statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE a < ?'
123
+ results = statement.execute(2)
124
+ expect(results.first).to eq("a" => 1, "b" => "Hello")
125
+
126
+ statement = @client.prepare 'SELECT * FROM mysql2_stmt_q WHERE b LIKE ?'
127
+ results = statement.execute('%orld')
128
+ expect(results.first).to eq("a" => 2, "b" => "World")
129
+
130
+ @client.query 'DROP TABLE IF EXISTS mysql2_stmt_q'
131
+ end
132
+
133
+ it "should select dates" do
134
+ statement = @client.prepare 'SELECT NOW()'
135
+ result = statement.execute
136
+ expect(result.first.first[1]).to be_an_instance_of(Time)
137
+ end
138
+
139
+ it "should prepare Date values" do
140
+ now = Date.today
141
+ statement = @client.prepare('SELECT ? AS a')
142
+ result = statement.execute(now)
143
+ expect(result.first['a'].to_s).to eql(now.strftime('%F'))
144
+ end
145
+
146
+ it "should prepare Time values with microseconds" do
147
+ now = Time.now
148
+ statement = @client.prepare('SELECT ? AS a')
149
+ result = statement.execute(now)
150
+ if RUBY_VERSION =~ /1.8/
151
+ expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z'))
152
+ else
153
+ # microseconds is six digits after the decimal, but only test on 5 significant figures
154
+ expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z'))
155
+ end
156
+ end
157
+
158
+ it "should prepare DateTime values with microseconds" do
159
+ now = DateTime.now
160
+ statement = @client.prepare('SELECT ? AS a')
161
+ result = statement.execute(now)
162
+ if RUBY_VERSION =~ /1.8/
163
+ expect(result.first['a'].strftime('%F %T %z')).to eql(now.strftime('%F %T %z'))
164
+ else
165
+ # microseconds is six digits after the decimal, but only test on 5 significant figures
166
+ expect(result.first['a'].strftime('%F %T.%5N %z')).to eql(now.strftime('%F %T.%5N %z'))
167
+ end
168
+ end
169
+
170
+ it "should tell us about the fields" do
171
+ statement = @client.prepare 'SELECT 1 as foo, 2'
172
+ statement.execute
173
+ list = statement.fields
174
+ expect(list.length).to eq(2)
175
+ expect(list.first).to eq('foo')
176
+ expect(list[1]).to eq('2')
177
+ end
178
+
179
+ it "should handle as a decimal binding a BigDecimal" do
180
+ stmt = @client.prepare('SELECT ? AS decimal_test')
181
+ test_result = stmt.execute(BigDecimal.new("123.45")).first
182
+ expect(test_result['decimal_test']).to be_an_instance_of(BigDecimal)
183
+ expect(test_result['decimal_test']).to eql(123.45)
184
+ end
185
+
186
+ it "should update a DECIMAL value passing a BigDecimal" do
187
+ @client.query 'USE test'
188
+ @client.query 'DROP TABLE IF EXISTS mysql2_stmt_decimal_test'
189
+ @client.query 'CREATE TABLE mysql2_stmt_decimal_test (decimal_test DECIMAL(10,3))'
190
+
191
+ @client.prepare("INSERT INTO mysql2_stmt_decimal_test VALUES (?)").execute(BigDecimal.new("123.45"))
192
+
193
+ test_result = @client.query("SELECT * FROM mysql2_stmt_decimal_test").first
194
+ expect(test_result['decimal_test']).to eql(123.45)
195
+ end
196
+
197
+ it "should warn but still work if cache_rows is set to false" do
198
+ @client.query_options.merge!(:cache_rows => false)
199
+ statement = @client.prepare 'SELECT 1'
200
+ result = nil
201
+ expect { result = statement.execute.to_a }.to output(/:cache_rows is forced for prepared statements/).to_stderr
202
+ expect(result.length).to eq(1)
203
+ end
204
+
205
+ context "utf8_db" do
206
+ before(:each) do
207
+ @client.query("DROP DATABASE IF EXISTS test_mysql2_stmt_utf8")
208
+ @client.query("CREATE DATABASE test_mysql2_stmt_utf8")
209
+ @client.query("USE test_mysql2_stmt_utf8")
210
+ @client.query("CREATE TABLE テーブル (整数 int, 文字列 varchar(32)) charset=utf8")
211
+ @client.query("INSERT INTO テーブル (整数, 文字列) VALUES (1, 'イチ'), (2, '弐'), (3, 'さん')")
212
+ end
213
+
214
+ after(:each) do
215
+ @client.query("DROP DATABASE test_mysql2_stmt_utf8")
216
+ end
217
+
218
+ it "should be able to retrieve utf8 field names correctly" do
219
+ stmt = @client.prepare 'SELECT * FROM `テーブル`'
220
+ expect(stmt.fields).to eq(%w(整数 文字列))
221
+ result = stmt.execute
222
+
223
+ expect(result.to_a).to eq([{ "整数" => 1, "文字列" => "イチ" }, { "整数" => 2, "文字列" => "弐" }, { "整数" => 3, "文字列" => "さん" }])
224
+ end
225
+
226
+ it "should be able to retrieve utf8 param query correctly" do
227
+ stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?'
228
+ expect(stmt.param_count).to eq(1)
229
+
230
+ result = stmt.execute 'イチ'
231
+
232
+ expect(result.to_a).to eq([{ "整数" => 1 }])
233
+ end
234
+
235
+ it "should be able to retrieve query with param in different encoding correctly" do
236
+ stmt = @client.prepare 'SELECT 整数 FROM テーブル WHERE 文字列 = ?'
237
+ expect(stmt.param_count).to eq(1)
238
+
239
+ param = 'イチ'.encode("EUC-JP")
240
+ result = stmt.execute param
241
+
242
+ expect(result.to_a).to eq([{ "整数" => 1 }])
243
+ end
244
+ end if defined? Encoding
245
+
246
+ context "streaming result" do
247
+ it "should be able to stream query result" do
248
+ n = 1
249
+ stmt = @client.prepare("SELECT 1 UNION SELECT 2")
250
+
251
+ @client.query_options.merge!(:stream => true, :cache_rows => false, :as => :array)
252
+
253
+ stmt.execute.each do |r|
254
+ case n
255
+ when 1
256
+ expect(r).to eq([1])
257
+ when 2
258
+ expect(r).to eq([2])
259
+ else
260
+ violated "returned more than two rows"
261
+ end
262
+ n += 1
263
+ end
264
+ end
265
+ end
266
+
267
+ context "#each" do
268
+ # note: The current impl. of prepared statement requires results to be cached on #execute except for streaming queries
269
+ # The drawback of this is that args of Result#each is ignored...
270
+
271
+ it "should yield rows as hash's" do
272
+ @result = @client.prepare("SELECT 1").execute
273
+ @result.each do |row|
274
+ expect(row).to be_an_instance_of(Hash)
275
+ end
276
+ end
277
+
278
+ it "should yield rows as hash's with symbol keys if :symbolize_keys was set to true" do
279
+ @client.query_options[:symbolize_keys] = true
280
+ @result = @client.prepare("SELECT 1").execute
281
+ @result.each do |row|
282
+ expect(row.keys.first).to be_an_instance_of(Symbol)
283
+ end
284
+ @client.query_options[:symbolize_keys] = false
285
+ end
286
+
287
+ it "should be able to return results as an array" do
288
+ @client.query_options[:as] = :array
289
+
290
+ @result = @client.prepare("SELECT 1").execute
291
+ @result.each do |row|
292
+ expect(row).to be_an_instance_of(Array)
293
+ end
294
+
295
+ @client.query_options[:as] = :hash
296
+ end
297
+
298
+ it "should cache previously yielded results by default" do
299
+ @result = @client.prepare("SELECT 1").execute
300
+ expect(@result.first.object_id).to eql(@result.first.object_id)
301
+ end
302
+
303
+ it "should yield different value for #first if streaming" do
304
+ @client.query_options[:stream] = true
305
+ @client.query_options[:cache_rows] = false
306
+
307
+ result = @client.prepare("SELECT 1 UNION SELECT 2").execute
308
+ expect(result.first).not_to eql(result.first)
309
+
310
+ @client.query_options[:stream] = false
311
+ @client.query_options[:cache_rows] = true
312
+ end
313
+
314
+ it "should yield the same value for #first if streaming is disabled" do
315
+ @client.query_options[:stream] = false
316
+ result = @client.prepare("SELECT 1 UNION SELECT 2").execute
317
+ expect(result.first).to eql(result.first)
318
+ end
319
+
320
+ it "should throw an exception if we try to iterate twice when streaming is enabled" do
321
+ @client.query_options[:stream] = true
322
+ @client.query_options[:cache_rows] = false
323
+
324
+ result = @client.prepare("SELECT 1 UNION SELECT 2").execute
325
+
326
+ expect {
327
+ result.each {}
328
+ result.each {}
329
+ }.to raise_exception(Mysql2::Error)
330
+
331
+ @client.query_options[:stream] = false
332
+ @client.query_options[:cache_rows] = true
333
+ end
334
+ end
335
+
336
+ context "#fields" do
337
+ it "method should exist" do
338
+ stmt = @client.prepare("SELECT 1")
339
+ expect(stmt).to respond_to(:fields)
340
+ end
341
+
342
+ it "should return an array of field names in proper order" do
343
+ stmt = @client.prepare("SELECT 'a', 'b', 'c'")
344
+ expect(stmt.fields).to eql(%w(a b c))
345
+ end
346
+
347
+ it "should return nil for statement with no result fields" do
348
+ stmt = @client.prepare("INSERT INTO mysql2_test () VALUES ()")
349
+ expect(stmt.fields).to eql(nil)
350
+ end
351
+ end
352
+
353
+ context "row data type mapping" do
354
+ before(:each) do
355
+ @client.query "USE test"
356
+ @test_result = @client.prepare("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").execute.first
357
+ end
358
+
359
+ it "should return nil for a NULL value" do
360
+ expect(@test_result['null_test']).to be_an_instance_of(NilClass)
361
+ expect(@test_result['null_test']).to eql(nil)
362
+ end
363
+
364
+ it "should return String for a BIT(64) value" do
365
+ expect(@test_result['bit_test']).to be_an_instance_of(String)
366
+ expect(@test_result['bit_test']).to eql("\000\000\000\000\000\000\000\005")
367
+ end
368
+
369
+ it "should return String for a BIT(1) value" do
370
+ expect(@test_result['single_bit_test']).to be_an_instance_of(String)
371
+ expect(@test_result['single_bit_test']).to eql("\001")
372
+ end
373
+
374
+ it "should return Fixnum for a TINYINT value" do
375
+ expect([Fixnum, Bignum]).to include(@test_result['tiny_int_test'].class)
376
+ expect(@test_result['tiny_int_test']).to eql(1)
377
+ end
378
+
379
+ context "cast booleans for TINYINT if :cast_booleans is enabled" do
380
+ # rubocop:disable Style/Semicolon
381
+ let(:client) { new_client(:cast_booleans => true) }
382
+ let(:id1) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 1)'; client.last_id }
383
+ let(:id2) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES ( 0)'; client.last_id }
384
+ let(:id3) { client.query 'INSERT INTO mysql2_test (bool_cast_test) VALUES (-1)'; client.last_id }
385
+ # rubocop:enable Style/Semicolon
386
+
387
+ after do
388
+ client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2},#{id3})"
389
+ end
390
+
391
+ it "should return TrueClass or FalseClass for a TINYINT value if :cast_booleans is enabled" do
392
+ query = client.prepare 'SELECT bool_cast_test FROM mysql2_test WHERE id = ?'
393
+ result1 = query.execute id1
394
+ result2 = query.execute id2
395
+ result3 = query.execute id3
396
+ expect(result1.first['bool_cast_test']).to be true
397
+ expect(result2.first['bool_cast_test']).to be false
398
+ expect(result3.first['bool_cast_test']).to be true
399
+ end
400
+ end
401
+
402
+ context "cast booleans for BIT(1) if :cast_booleans is enabled" do
403
+ # rubocop:disable Style/Semicolon
404
+ let(:client) { new_client(:cast_booleans => true) }
405
+ let(:id1) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (1)'; client.last_id }
406
+ let(:id2) { client.query 'INSERT INTO mysql2_test (single_bit_test) VALUES (0)'; client.last_id }
407
+ # rubocop:enable Style/Semicolon
408
+
409
+ after do
410
+ client.query "DELETE from mysql2_test WHERE id IN(#{id1},#{id2})"
411
+ end
412
+
413
+ it "should return TrueClass or FalseClass for a BIT(1) value if :cast_booleans is enabled" do
414
+ query = client.prepare 'SELECT single_bit_test FROM mysql2_test WHERE id = ?'
415
+ result1 = query.execute id1
416
+ result2 = query.execute id2
417
+ expect(result1.first['single_bit_test']).to be true
418
+ expect(result2.first['single_bit_test']).to be false
419
+ end
420
+ end
421
+
422
+ it "should return Fixnum for a SMALLINT value" do
423
+ expect([Fixnum, Bignum]).to include(@test_result['small_int_test'].class)
424
+ expect(@test_result['small_int_test']).to eql(10)
425
+ end
426
+
427
+ it "should return Fixnum for a MEDIUMINT value" do
428
+ expect([Fixnum, Bignum]).to include(@test_result['medium_int_test'].class)
429
+ expect(@test_result['medium_int_test']).to eql(10)
430
+ end
431
+
432
+ it "should return Fixnum for an INT value" do
433
+ expect([Fixnum, Bignum]).to include(@test_result['int_test'].class)
434
+ expect(@test_result['int_test']).to eql(10)
435
+ end
436
+
437
+ it "should return Fixnum for a BIGINT value" do
438
+ expect([Fixnum, Bignum]).to include(@test_result['big_int_test'].class)
439
+ expect(@test_result['big_int_test']).to eql(10)
440
+ end
441
+
442
+ it "should return Fixnum for a YEAR value" do
443
+ expect([Fixnum, Bignum]).to include(@test_result['year_test'].class)
444
+ expect(@test_result['year_test']).to eql(2009)
445
+ end
446
+
447
+ it "should return BigDecimal for a DECIMAL value" do
448
+ expect(@test_result['decimal_test']).to be_an_instance_of(BigDecimal)
449
+ expect(@test_result['decimal_test']).to eql(10.3)
450
+ end
451
+
452
+ it "should return Float for a FLOAT value" do
453
+ expect(@test_result['float_test']).to be_an_instance_of(Float)
454
+ expect(@test_result['float_test']).to be_within(1e-5).of(10.3)
455
+ end
456
+
457
+ it "should return Float for a DOUBLE value" do
458
+ expect(@test_result['double_test']).to be_an_instance_of(Float)
459
+ expect(@test_result['double_test']).to eql(10.3)
460
+ end
461
+
462
+ it "should return Time for a DATETIME value when within the supported range" do
463
+ expect(@test_result['date_time_test']).to be_an_instance_of(Time)
464
+ expect(@test_result['date_time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00')
465
+ end
466
+
467
+ if 1.size == 4 # 32bit
468
+ klass = if RUBY_VERSION =~ /1.8/
469
+ DateTime
470
+ else
471
+ Time
472
+ end
473
+
474
+ it "should return DateTime when timestamp is < 1901-12-13 20:45:52" do
475
+ # 1901-12-13T20:45:52 is the min for 32bit Ruby 1.8
476
+ r = @client.prepare("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test").execute
477
+ expect(r.first['test']).to be_an_instance_of(klass)
478
+ end
479
+
480
+ it "should return DateTime when timestamp is > 2038-01-19T03:14:07" do
481
+ # 2038-01-19T03:14:07 is the max for 32bit Ruby 1.8
482
+ r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute
483
+ expect(r.first['test']).to be_an_instance_of(klass)
484
+ end
485
+ elsif 1.size == 8 # 64bit
486
+ if RUBY_VERSION =~ /1.8/
487
+ it "should return Time when timestamp is > 0138-12-31 11:59:59" do
488
+ r = @client.prepare("SELECT CAST('0139-1-1 00:00:00' AS DATETIME) as test").execute
489
+ expect(r.first['test']).to be_an_instance_of(Time)
490
+ end
491
+
492
+ it "should return DateTime when timestamp is < 0139-1-1T00:00:00" do
493
+ r = @client.prepare("SELECT CAST('0138-12-31 11:59:59' AS DATETIME) as test").execute
494
+ expect(r.first['test']).to be_an_instance_of(DateTime)
495
+ end
496
+
497
+ it "should return Time when timestamp is > 2038-01-19T03:14:07" do
498
+ r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute
499
+ expect(r.first['test']).to be_an_instance_of(Time)
500
+ end
501
+ else
502
+ it "should return Time when timestamp is < 1901-12-13 20:45:52" do
503
+ r = @client.prepare("SELECT CAST('1901-12-13 20:45:51' AS DATETIME) as test").execute
504
+ expect(r.first['test']).to be_an_instance_of(Time)
505
+ end
506
+
507
+ it "should return Time when timestamp is > 2038-01-19T03:14:07" do
508
+ r = @client.prepare("SELECT CAST('2038-01-19 03:14:08' AS DATETIME) as test").execute
509
+ expect(r.first['test']).to be_an_instance_of(Time)
510
+ end
511
+ end
512
+ end
513
+
514
+ it "should return Time for a TIMESTAMP value when within the supported range" do
515
+ expect(@test_result['timestamp_test']).to be_an_instance_of(Time)
516
+ expect(@test_result['timestamp_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2010-04-04 11:44:00')
517
+ end
518
+
519
+ it "should return Time for a TIME value" do
520
+ expect(@test_result['time_test']).to be_an_instance_of(Time)
521
+ expect(@test_result['time_test'].strftime("%Y-%m-%d %H:%M:%S")).to eql('2000-01-01 11:44:00')
522
+ end
523
+
524
+ it "should return Date for a DATE value" do
525
+ expect(@test_result['date_test']).to be_an_instance_of(Date)
526
+ expect(@test_result['date_test'].strftime("%Y-%m-%d")).to eql('2010-04-04')
527
+ end
528
+
529
+ it "should return String for an ENUM value" do
530
+ expect(@test_result['enum_test']).to be_an_instance_of(String)
531
+ expect(@test_result['enum_test']).to eql('val1')
532
+ end
533
+
534
+ it "should raise an error given an invalid DATETIME" do
535
+ expect { @client.query("SELECT CAST('1972-00-27 00:00:00' AS DATETIME) as bad_datetime").each }.to \
536
+ raise_error(Mysql2::Error, "Invalid date in field 'bad_datetime': 1972-00-27 00:00:00")
537
+ end
538
+
539
+ context "string encoding for ENUM values" do
540
+ before { pending('Encoding is undefined') unless defined?(Encoding) }
541
+
542
+ it "should default to the connection's encoding if Encoding.default_internal is nil" do
543
+ with_internal_encoding nil do
544
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
545
+ expect(result['enum_test'].encoding).to eql(Encoding::UTF_8)
546
+
547
+ client2 = new_client(:encoding => 'ascii')
548
+ result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
549
+ expect(result['enum_test'].encoding).to eql(Encoding::US_ASCII)
550
+ end
551
+ end
552
+
553
+ it "should use Encoding.default_internal" do
554
+ with_internal_encoding Encoding::UTF_8 do
555
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
556
+ expect(result['enum_test'].encoding).to eql(Encoding.default_internal)
557
+ end
558
+
559
+ with_internal_encoding Encoding::ASCII do
560
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
561
+ expect(result['enum_test'].encoding).to eql(Encoding.default_internal)
562
+ end
563
+ end
564
+ end
565
+
566
+ it "should return String for a SET value" do
567
+ expect(@test_result['set_test']).to be_an_instance_of(String)
568
+ expect(@test_result['set_test']).to eql('val1,val2')
569
+ end
570
+
571
+ context "string encoding for SET values" do
572
+ before { pending('Encoding is undefined') unless defined?(Encoding) }
573
+
574
+ it "should default to the connection's encoding if Encoding.default_internal is nil" do
575
+ with_internal_encoding nil do
576
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
577
+ expect(result['set_test'].encoding).to eql(Encoding::UTF_8)
578
+
579
+ client2 = new_client(:encoding => 'ascii')
580
+ result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
581
+ expect(result['set_test'].encoding).to eql(Encoding::US_ASCII)
582
+ end
583
+ end
584
+
585
+ it "should use Encoding.default_internal" do
586
+ with_internal_encoding Encoding::UTF_8 do
587
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
588
+ expect(result['set_test'].encoding).to eql(Encoding.default_internal)
589
+ end
590
+
591
+ with_internal_encoding Encoding::ASCII do
592
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
593
+ expect(result['set_test'].encoding).to eql(Encoding.default_internal)
594
+ end
595
+ end
596
+ end
597
+
598
+ it "should return String for a BINARY value" do
599
+ expect(@test_result['binary_test']).to be_an_instance_of(String)
600
+ expect(@test_result['binary_test']).to eql("test#{"\000" * 6}")
601
+ end
602
+
603
+ context "string encoding for BINARY values" do
604
+ before { pending('Encoding is undefined') unless defined?(Encoding) }
605
+
606
+ it "should default to binary if Encoding.default_internal is nil" do
607
+ with_internal_encoding nil do
608
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
609
+ expect(result['binary_test'].encoding).to eql(Encoding::BINARY)
610
+ end
611
+ end
612
+
613
+ it "should not use Encoding.default_internal" do
614
+ with_internal_encoding Encoding::UTF_8 do
615
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
616
+ expect(result['binary_test'].encoding).to eql(Encoding::BINARY)
617
+ end
618
+
619
+ with_internal_encoding Encoding::ASCII do
620
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
621
+ expect(result['binary_test'].encoding).to eql(Encoding::BINARY)
622
+ end
623
+ end
624
+ end
625
+
626
+ {
627
+ 'char_test' => 'CHAR',
628
+ 'varchar_test' => 'VARCHAR',
629
+ 'varbinary_test' => 'VARBINARY',
630
+ 'tiny_blob_test' => 'TINYBLOB',
631
+ 'tiny_text_test' => 'TINYTEXT',
632
+ 'blob_test' => 'BLOB',
633
+ 'text_test' => 'TEXT',
634
+ 'medium_blob_test' => 'MEDIUMBLOB',
635
+ 'medium_text_test' => 'MEDIUMTEXT',
636
+ 'long_blob_test' => 'LONGBLOB',
637
+ 'long_text_test' => 'LONGTEXT',
638
+ }.each do |field, type|
639
+ it "should return a String for #{type}" do
640
+ expect(@test_result[field]).to be_an_instance_of(String)
641
+ expect(@test_result[field]).to eql("test")
642
+ end
643
+
644
+ context "string encoding for #{type} values" do
645
+ before { pending('Encoding is undefined') unless defined?(Encoding) }
646
+
647
+ if %w(VARBINARY TINYBLOB BLOB MEDIUMBLOB LONGBLOB).include?(type)
648
+ it "should default to binary if Encoding.default_internal is nil" do
649
+ with_internal_encoding nil do
650
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
651
+ expect(result['binary_test'].encoding).to eql(Encoding::BINARY)
652
+ end
653
+ end
654
+
655
+ it "should not use Encoding.default_internal" do
656
+ with_internal_encoding Encoding::UTF_8 do
657
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
658
+ expect(result['binary_test'].encoding).to eql(Encoding::BINARY)
659
+ end
660
+
661
+ with_internal_encoding Encoding::ASCII do
662
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
663
+ expect(result['binary_test'].encoding).to eql(Encoding::BINARY)
664
+ end
665
+ end
666
+ else
667
+ it "should default to utf-8 if Encoding.default_internal is nil" do
668
+ with_internal_encoding nil do
669
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
670
+ expect(result[field].encoding).to eql(Encoding::UTF_8)
671
+
672
+ client2 = new_client(:encoding => 'ascii')
673
+ result = client2.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
674
+ expect(result[field].encoding).to eql(Encoding::US_ASCII)
675
+ end
676
+ end
677
+
678
+ it "should use Encoding.default_internal" do
679
+ with_internal_encoding Encoding::UTF_8 do
680
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
681
+ expect(result[field].encoding).to eql(Encoding.default_internal)
682
+ end
683
+
684
+ with_internal_encoding Encoding::ASCII do
685
+ result = @client.query("SELECT * FROM mysql2_test ORDER BY id DESC LIMIT 1").first
686
+ expect(result[field].encoding).to eql(Encoding.default_internal)
687
+ end
688
+ end
689
+ end
690
+ end
691
+ end
692
+ end
693
+
694
+ context 'last_id' do
695
+ before(:each) do
696
+ @client.query 'USE test'
697
+ @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))'
698
+ end
699
+
700
+ after(:each) do
701
+ @client.query 'DROP TABLE lastIdTest'
702
+ end
703
+
704
+ it 'should return last insert id' do
705
+ stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)'
706
+ expect(stmt.last_id).to eq 0
707
+ stmt.execute 1
708
+ expect(stmt.last_id).to eq 1
709
+ end
710
+
711
+ it 'should handle bigint ids' do
712
+ stmt = @client.prepare 'INSERT INTO lastIdTest (id, blah) VALUES (?, ?)'
713
+ stmt.execute 5000000000, 5000
714
+ expect(stmt.last_id).to eql(5000000000)
715
+
716
+ stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)'
717
+ stmt.execute 5001
718
+ expect(stmt.last_id).to eql(5000000001)
719
+ end
720
+ end
721
+
722
+ context 'affected_rows' do
723
+ before :each do
724
+ @client.query 'USE test'
725
+ @client.query 'CREATE TABLE IF NOT EXISTS lastIdTest (`id` BIGINT NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))'
726
+ end
727
+
728
+ after :each do
729
+ @client.query 'DROP TABLE lastIdTest'
730
+ end
731
+
732
+ it 'should return number of rows affected by an insert' do
733
+ stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)'
734
+ expect(stmt.affected_rows).to eq 0
735
+ stmt.execute 1
736
+ expect(stmt.affected_rows).to eq 1
737
+ end
738
+
739
+ it 'should return number of rows affected by an update' do
740
+ stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)'
741
+ stmt.execute 1
742
+ expect(stmt.affected_rows).to eq 1
743
+ stmt.execute 2
744
+ expect(stmt.affected_rows).to eq 1
745
+
746
+ stmt = @client.prepare 'UPDATE lastIdTest SET blah=? WHERE blah=?'
747
+ stmt.execute 0, 1
748
+ expect(stmt.affected_rows).to eq 1
749
+ end
750
+
751
+ it 'should return number of rows affected by a delete' do
752
+ stmt = @client.prepare 'INSERT INTO lastIdTest (blah) VALUES (?)'
753
+ stmt.execute 1
754
+ expect(stmt.affected_rows).to eq 1
755
+ stmt.execute 2
756
+ expect(stmt.affected_rows).to eq 1
757
+
758
+ stmt = @client.prepare 'DELETE FROM lastIdTest WHERE blah=?'
759
+ stmt.execute 1
760
+ expect(stmt.affected_rows).to eq 1
761
+ end
762
+ end
763
+
764
+ context 'close' do
765
+ it 'should free server resources' do
766
+ stmt = @client.prepare 'SELECT 1'
767
+ expect { stmt.close }.to change(&method(:stmt_count)).by(-1)
768
+ end
769
+
770
+ it 'should raise an error on subsequent execution' do
771
+ stmt = @client.prepare 'SELECT 1'
772
+ stmt.close
773
+ expect { stmt.execute }.to raise_error(Mysql2::Error, /Invalid statement handle/)
774
+ end
775
+ end
776
+ end