mysql2 0.3.18 → 0.4.9

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