mysql2 0.3.18 → 0.4.7

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