mysql2 0.3.1 → 0.5.2

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