mysql2 0.3.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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