extralite-bundle 2.5 → 2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +34 -13
  4. data/Gemfile +4 -0
  5. data/Gemfile-bundle +1 -1
  6. data/LICENSE +1 -1
  7. data/README.md +1059 -247
  8. data/Rakefile +18 -0
  9. data/TODO.md +0 -7
  10. data/examples/kv_store.rb +49 -0
  11. data/examples/multi_fiber.rb +16 -0
  12. data/examples/on_progress.rb +9 -0
  13. data/examples/pubsub_store_polyphony.rb +194 -0
  14. data/examples/pubsub_store_threads.rb +204 -0
  15. data/ext/extralite/changeset.c +463 -0
  16. data/ext/extralite/common.c +177 -91
  17. data/ext/extralite/database.c +745 -276
  18. data/ext/extralite/extconf-bundle.rb +10 -4
  19. data/ext/extralite/extconf.rb +34 -34
  20. data/ext/extralite/extralite.h +104 -47
  21. data/ext/extralite/extralite_ext.c +6 -0
  22. data/ext/extralite/iterator.c +14 -86
  23. data/ext/extralite/query.c +171 -264
  24. data/extralite-bundle.gemspec +1 -1
  25. data/extralite.gemspec +1 -1
  26. data/gemspec.rb +10 -11
  27. data/lib/extralite/version.rb +1 -1
  28. data/lib/extralite.rb +69 -10
  29. data/lib/sequel/adapters/extralite.rb +1 -1
  30. data/test/helper.rb +9 -1
  31. data/test/perf_argv_transform.rb +74 -0
  32. data/test/perf_ary.rb +14 -12
  33. data/test/perf_hash.rb +17 -15
  34. data/test/perf_hash_prepared.rb +58 -0
  35. data/test/perf_hash_transform.rb +66 -0
  36. data/test/perf_polyphony.rb +74 -0
  37. data/test/test_changeset.rb +161 -0
  38. data/test/test_database.rb +720 -104
  39. data/test/test_extralite.rb +2 -2
  40. data/test/test_iterator.rb +28 -13
  41. data/test/test_query.rb +352 -110
  42. data/test/test_sequel.rb +4 -4
  43. metadata +24 -16
  44. data/Gemfile.lock +0 -37
  45. data/test/perf_prepared.rb +0 -64
@@ -4,8 +4,9 @@ require_relative 'helper'
4
4
 
5
5
  require 'date'
6
6
  require 'tempfile'
7
+ require 'json'
7
8
 
8
- class DatabaseTest < MiniTest::Test
9
+ class DatabaseTest < Minitest::Test
9
10
  def setup
10
11
  @db = Extralite::Database.new(':memory:')
11
12
  @db.query('create table if not exists t (x,y,z)')
@@ -47,27 +48,38 @@ class DatabaseTest < MiniTest::Test
47
48
  assert_equal [], r
48
49
  end
49
50
 
50
- def test_query_single_row
51
- r = @db.query_single_row('select * from t order by x desc limit 1')
52
- assert_equal({ x: 4, y: 5, z: 6 }, r)
51
+ def test_query_argv
52
+ r = @db.query_argv('select * from t')
53
+ assert_equal [[1, 2, 3], [4, 5, 6]], r
53
54
 
54
- r = @db.query_single_row('select * from t where x = 2')
55
- assert_nil r
55
+ r = @db.query_argv('select * from t where x = 2')
56
+ assert_equal [], r
57
+
58
+ # with block
59
+ r = []
60
+ @db.query_argv('select * from t') { |a, b, c| r << [a, b, c] }
61
+ assert_equal [[1, 2, 3], [4, 5, 6]], r
62
+ end
63
+
64
+ def test_query_argv_with_too_many_columns
65
+ assert_raises(Extralite::Error) {
66
+ @db.query_argv('select 1, 2, 3, 4, 5, 6, 7, 8, 9');
67
+ }
56
68
  end
57
69
 
58
- def test_query_single_column
59
- r = @db.query_single_column('select y from t')
60
- assert_equal [2, 5], r
70
+ def test_query_single
71
+ r = @db.query_single('select * from t order by x desc limit 1')
72
+ assert_equal({ x: 4, y: 5, z: 6 }, r)
61
73
 
62
- r = @db.query_single_column('select y from t where x = 2')
63
- assert_equal [], r
74
+ r = @db.query_single('select * from t where x = 2')
75
+ assert_nil r
64
76
  end
65
77
 
66
- def test_query_single_value
67
- r = @db.query_single_value('select z from t order by Z desc limit 1')
78
+ def test_query_single_argv
79
+ r = @db.query_single_argv('select z from t order by Z desc limit 1')
68
80
  assert_equal 6, r
69
81
 
70
- r = @db.query_single_value('select z from t where x = 2')
82
+ r = @db.query_single_argv('select z from t where x = 2')
71
83
  assert_nil r
72
84
  end
73
85
 
@@ -87,7 +99,7 @@ class DatabaseTest < MiniTest::Test
87
99
  def test_multiple_statements
88
100
  @db.query("insert into t values ('a', 'b', 'c'); insert into t values ('d', 'e', 'f');")
89
101
 
90
- assert_equal [1, 4, 'a', 'd'], @db.query_single_column('select x from t order by x')
102
+ assert_equal [1, 4, 'a', 'd'], @db.query_argv('select x from t order by x')
91
103
  end
92
104
 
93
105
  def test_multiple_statements_with_error
@@ -111,13 +123,13 @@ class DatabaseTest < MiniTest::Test
111
123
 
112
124
  def test_close
113
125
  assert_equal false, @db.closed?
114
- r = @db.query_single_value('select 42')
126
+ r = @db.query_single_argv('select 42')
115
127
  assert_equal 42, r
116
128
 
117
129
  assert_equal @db, @db.close
118
130
  assert_equal true, @db.closed?
119
131
 
120
- assert_raises(Extralite::Error) { @db.query_single_value('select 42') }
132
+ assert_raises(Extralite::Error) { @db.query_single_argv('select 42') }
121
133
  end
122
134
 
123
135
  def test_parameter_binding_simple
@@ -127,7 +139,7 @@ class DatabaseTest < MiniTest::Test
127
139
  r = @db.query('select x, y, z from t where z = ?', 6)
128
140
  assert_equal [{ x: 4, y: 5, z: 6 }], r
129
141
 
130
- error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', Date.today) }
142
+ error = assert_raises(Extralite::ParameterError) { @db.query_single_argv('select ?', Date.today) }
131
143
  assert_equal error.message, 'Cannot bind parameter at position 1 of type Date'
132
144
  end
133
145
 
@@ -161,33 +173,33 @@ class DatabaseTest < MiniTest::Test
161
173
  class Foo; end
162
174
 
163
175
  def test_parameter_binding_from_hash
164
- assert_equal 42, @db.query_single_value('select :bar', foo: 41, bar: 42)
165
- assert_equal 42, @db.query_single_value('select :bar', 'foo' => 41, 'bar' => 42)
166
- assert_equal 42, @db.query_single_value('select ?8', 7 => 41, 8 => 42)
167
- assert_nil @db.query_single_value('select :bar', foo: 41)
176
+ assert_equal 42, @db.query_single_argv('select :bar', foo: 41, bar: 42)
177
+ assert_equal 42, @db.query_single_argv('select :bar', 'foo' => 41, 'bar' => 42)
178
+ assert_equal 42, @db.query_single_argv('select ?8', 7 => 41, 8 => 42)
179
+ assert_nil @db.query_single_argv('select :bar', foo: 41)
168
180
 
169
- error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', Foo.new => 42) }
181
+ error = assert_raises(Extralite::ParameterError) { @db.query_single_argv('select ?', Foo.new => 42) }
170
182
  assert_equal error.message, 'Cannot bind parameter with a key of type DatabaseTest::Foo'
171
183
 
172
- error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', %w[a b] => 42) }
184
+ error = assert_raises(Extralite::ParameterError) { @db.query_single_argv('select ?', %w[a b] => 42) }
173
185
  assert_equal error.message, 'Cannot bind parameter with a key of type Array'
174
186
  end
175
187
 
176
188
  def test_parameter_binding_from_struct
177
- foo_bar = Struct.new(:":foo", :bar)
189
+ foo_bar = Struct.new(:':foo', :bar)
178
190
  value = foo_bar.new(41, 42)
179
- assert_equal 41, @db.query_single_value('select :foo', value)
180
- assert_equal 42, @db.query_single_value('select :bar', value)
181
- assert_nil @db.query_single_value('select :baz', value)
191
+ assert_equal 41, @db.query_single_argv('select :foo', value)
192
+ assert_equal 42, @db.query_single_argv('select :bar', value)
193
+ assert_nil @db.query_single_argv('select :baz', value)
182
194
  end
183
195
 
184
196
  def test_parameter_binding_from_data_class
185
197
  skip "Data isn't supported in Ruby < 3.2" if RUBY_VERSION < '3.2'
186
198
 
187
- foo_bar = Data.define(:":foo", :bar)
188
- value = foo_bar.new(":foo": 41, bar: 42)
189
- assert_equal 42, @db.query_single_value('select :bar', value)
190
- assert_nil @db.query_single_value('select :baz', value)
199
+ foo_bar = Data.define(:':foo', :bar)
200
+ value = foo_bar.new(':foo': 41, bar: 42)
201
+ assert_equal 42, @db.query_single_argv('select :bar', value)
202
+ assert_nil @db.query_single_argv('select :baz', value)
191
203
  end
192
204
 
193
205
  def test_parameter_binding_for_blobs
@@ -197,69 +209,69 @@ class DatabaseTest < MiniTest::Test
197
209
 
198
210
  # it's a string, not a blob
199
211
  @db.execute('INSERT INTO blobs VALUES (?)', 'Hello, 世界!')
200
- result = @db.query_single_row(sql, @db.last_insert_rowid)
212
+ result = @db.query_single(sql, @db.last_insert_rowid)
201
213
  assert_equal 'text', result[:type]
202
214
  assert_equal Encoding::UTF_8, result[:data].encoding
203
215
 
204
216
  data = File.binread(blob_path)
205
217
  @db.execute('INSERT INTO blobs VALUES (?)', data)
206
- result = @db.query_single_row(sql, @db.last_insert_rowid)
218
+ result = @db.query_single(sql, @db.last_insert_rowid)
207
219
  assert_equal 'blob', result[:type]
208
220
  assert_equal data, result[:data]
209
221
 
210
222
  data = (+'Hello, 世界!').force_encoding(Encoding::ASCII_8BIT)
211
223
  @db.execute('INSERT INTO blobs VALUES (?)', data)
212
- result = @db.query_single_row(sql, @db.last_insert_rowid)
224
+ result = @db.query_single(sql, @db.last_insert_rowid)
213
225
  assert_equal 'blob', result[:type]
214
226
  assert_equal Encoding::ASCII_8BIT, result[:data].encoding
215
227
  assert_equal 'Hello, 世界!', result[:data].force_encoding(Encoding::UTF_8)
216
228
 
217
229
  data = Extralite::Blob.new('Hello, 世界!')
218
230
  @db.execute('INSERT INTO blobs VALUES (?)', data)
219
- result = @db.query_single_row(sql, @db.last_insert_rowid)
231
+ result = @db.query_single(sql, @db.last_insert_rowid)
220
232
  assert_equal 'blob', result[:type]
221
233
  assert_equal Encoding::ASCII_8BIT, result[:data].encoding
222
234
  assert_equal 'Hello, 世界!', result[:data].force_encoding(Encoding::UTF_8)
223
235
  end
224
236
 
225
237
  def test_parameter_binding_for_simple_types
226
- assert_nil @db.query_single_value('select ?', nil)
238
+ assert_nil @db.query_single_argv('select ?', nil)
227
239
 
228
240
  # 32-bit integers
229
- assert_equal -2** 31, @db.query_single_value('select ?', -2**31)
230
- assert_equal 2**31 - 1, @db.query_single_value('select ?', 2**31 - 1)
241
+ assert_equal(-2** 31, @db.query_single_argv('select ?', -2**31))
242
+ assert_equal(2**31 - 1, @db.query_single_argv('select ?', 2**31 - 1))
231
243
 
232
244
  # 64-bit integers
233
- assert_equal -2 ** 63, @db.query_single_value('select ?', -2 ** 63)
234
- assert_equal 2**63 - 1, @db.query_single_value('select ?', 2**63 - 1)
245
+ assert_equal(-2 ** 63, @db.query_single_argv('select ?', -2 ** 63))
246
+ assert_equal(2**63 - 1, @db.query_single_argv('select ?', 2**63 - 1))
235
247
 
236
248
  # floats
237
- assert_equal Float::MIN, @db.query_single_value('select ?', Float::MIN)
238
- assert_equal Float::MAX, @db.query_single_value('select ?', Float::MAX)
249
+ assert_equal Float::MIN, @db.query_single_argv('select ?', Float::MIN)
250
+ assert_equal Float::MAX, @db.query_single_argv('select ?', Float::MAX)
239
251
 
240
252
  # boolean
241
- assert_equal 1, @db.query_single_value('select ?', true)
242
- assert_equal 0, @db.query_single_value('select ?', false)
253
+ assert_equal 1, @db.query_single_argv('select ?', true)
254
+ assert_equal 0, @db.query_single_argv('select ?', false)
243
255
 
244
256
  # strings and symbols
245
- assert_equal 'foo', @db.query_single_value('select ?', 'foo')
246
- assert_equal 'foo', @db.query_single_value('select ?', :foo)
257
+ assert_equal 'foo', @db.query_single_argv('select ?', 'foo')
258
+ assert_equal 'foo', @db.query_single_argv('select ?', :foo)
247
259
  end
248
260
 
249
261
  def test_value_casting
250
- r = @db.query_single_value("select 'abc'")
262
+ r = @db.query_single_argv("select 'abc'")
251
263
  assert_equal 'abc', r
252
264
 
253
- r = @db.query_single_value('select 123')
265
+ r = @db.query_single_argv('select 123')
254
266
  assert_equal 123, r
255
267
 
256
- r = @db.query_single_value('select 12.34')
268
+ r = @db.query_single_argv('select 12.34')
257
269
  assert_equal 12.34, r
258
270
 
259
- r = @db.query_single_value('select zeroblob(4)')
271
+ r = @db.query_single_argv('select zeroblob(4)')
260
272
  assert_equal "\x00\x00\x00\x00", r
261
273
 
262
- r = @db.query_single_value('select null')
274
+ r = @db.query_single_argv('select null')
263
275
  assert_nil r
264
276
  end
265
277
 
@@ -271,7 +283,7 @@ class DatabaseTest < MiniTest::Test
271
283
  @db.load_extension(File.join(__dir__, 'extensions/text.dylib'))
272
284
  end
273
285
 
274
- r = @db.query_single_value("select reverse('abcd')")
286
+ r = @db.query_single_argv("select reverse('abcd')")
275
287
  assert_equal 'dcba', r
276
288
  end
277
289
 
@@ -592,7 +604,7 @@ class DatabaseTest < MiniTest::Test
592
604
  ], array
593
605
  end
594
606
 
595
- def test_batch_query_single_column_with_array
607
+ def test_batch_query_argv_with_array
596
608
  @db.query('create table foo (a, b, c)')
597
609
  assert_equal [], @db.query('select * from foo')
598
610
 
@@ -600,20 +612,20 @@ class DatabaseTest < MiniTest::Test
600
612
  [1, '2', 3],
601
613
  ['4', 5, 6]
602
614
  ]
603
- results = @db.batch_query_single_column('insert into foo values (?, ?, ?) returning c', data)
615
+ results = @db.batch_query_argv('insert into foo values (?, ?, ?) returning c', data)
604
616
  assert_equal [
605
617
  [3],
606
618
  [6]
607
619
  ], results
608
620
 
609
- results = @db.batch_query_single_column('update foo set c = ? returning c * 10 + cast(b as integer)', [42, 43])
621
+ results = @db.batch_query_argv('update foo set c = ? returning c * 10 + cast(b as integer)', [42, 43])
610
622
  assert_equal [
611
623
  [422, 425],
612
624
  [432, 435]
613
625
  ], results
614
626
 
615
627
  array = []
616
- changes = @db.batch_query_single_column('update foo set c = ? returning c * 10 + cast(b as integer)', [44, 45]) do |rows|
628
+ changes = @db.batch_query_argv('update foo set c = ? returning c * 10 + cast(b as integer)', [44, 45]) do |rows|
617
629
  array << rows
618
630
  end
619
631
  assert_equal 4, changes
@@ -623,25 +635,25 @@ class DatabaseTest < MiniTest::Test
623
635
  ], array
624
636
  end
625
637
 
626
- def test_batch_query_single_column_with_enumerable
638
+ def test_batch_query_argv_with_enumerable
627
639
  @db.query('create table foo (a integer primary key, b)')
628
640
  assert_equal [], @db.query('select * from foo')
629
641
 
630
- results = @db.batch_query_single_column('insert into foo (b) values (?) returning b * 10 + a', 11..13)
642
+ results = @db.batch_query_argv('insert into foo (b) values (?) returning b * 10 + a', 11..13)
631
643
  assert_equal [
632
644
  [111],
633
645
  [122],
634
646
  [133]
635
647
  ], results
636
648
 
637
- results = @db.batch_query_single_column('update foo set b = ? returning b * 10 + a', 42..43)
649
+ results = @db.batch_query_argv('update foo set b = ? returning b * 10 + a', 42..43)
638
650
  assert_equal [
639
651
  [421, 422, 423],
640
652
  [431, 432, 433]
641
653
  ], results
642
654
 
643
655
  array = []
644
- changes = @db.batch_query_single_column('update foo set b = ? returning b * 10 + a', 44..45) do |rows|
656
+ changes = @db.batch_query_argv('update foo set b = ? returning b * 10 + a', 44..45) do |rows|
645
657
  array << rows
646
658
  end
647
659
  assert_equal 6, changes
@@ -651,13 +663,13 @@ class DatabaseTest < MiniTest::Test
651
663
  ], array
652
664
  end
653
665
 
654
- def test_batch_query_single_column_with_proc
666
+ def test_batch_query_argv_with_proc
655
667
  @db.query('create table foo (a integer primary key, b)')
656
668
  assert_equal [], @db.query('select * from foo')
657
669
 
658
670
  pr = parameter_source_proc([5, 4, 3])
659
671
  assert_kind_of Proc, pr
660
- results = @db.batch_query_single_column('insert into foo (b) values (?) returning b', pr)
672
+ results = @db.batch_query_argv('insert into foo (b) values (?) returning b', pr)
661
673
  assert_equal [
662
674
  [5],
663
675
  [4],
@@ -665,7 +677,7 @@ class DatabaseTest < MiniTest::Test
665
677
  ], results
666
678
 
667
679
  pr = parameter_source_proc([42, 43])
668
- results = @db.batch_query_single_column('update foo set b = ? returning b * 10 + a', pr)
680
+ results = @db.batch_query_argv('update foo set b = ? returning b * 10 + a', pr)
669
681
  assert_equal [
670
682
  [421, 422, 423],
671
683
  [431, 432, 433]
@@ -673,7 +685,7 @@ class DatabaseTest < MiniTest::Test
673
685
 
674
686
  array = []
675
687
  pr = parameter_source_proc([44, 45])
676
- changes = @db.batch_query_single_column('update foo set b = ? returning b * 10 + a', pr) do |rows|
688
+ changes = @db.batch_query_argv('update foo set b = ? returning b * 10 + a', pr) do |rows|
677
689
  array << rows
678
690
  end
679
691
  assert_equal 6, changes
@@ -703,6 +715,8 @@ class DatabaseTest < MiniTest::Test
703
715
  SQL
704
716
  }
705
717
  t.join
718
+ ensure
719
+ t&.kill
706
720
  end
707
721
 
708
722
  def test_database_status
@@ -766,6 +780,8 @@ class DatabaseTest < MiniTest::Test
766
780
 
767
781
  db2.busy_timeout = nil
768
782
  assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
783
+ ensure
784
+ t&.kill
769
785
  end
770
786
 
771
787
  def test_database_total_changes
@@ -817,30 +833,31 @@ class DatabaseTest < MiniTest::Test
817
833
  db = Extralite::Database.new(':memory:', gvl_release_threshold: 23)
818
834
  assert_equal 23, db.gvl_release_threshold
819
835
 
820
- fn = Tempfile.new('extralite_test_database_initialize_options_1').path
821
- db = Extralite::Database.new(fn, wal_journal_mode: true)
836
+ fn = Tempfile.new('extralite_test_database_initialize_options_wal').path
837
+ db = Extralite::Database.new(fn, wal: true)
822
838
  assert_equal 'wal', db.pragma(:journal_mode)
823
-
824
- fn = Tempfile.new('extralite_test_database_initialize_options_2').path
825
- db = Extralite::Database.new(fn, synchronous: true)
826
839
  assert_equal 1, db.pragma(:synchronous)
840
+
841
+ fn = Tempfile.new('extralite_test_database_initialize_options_pragma').path
842
+ db = Extralite::Database.new(fn, pragma: { application_id: 42 })
843
+ assert_equal 42, db.pragma(:application_id)
827
844
  end
828
845
 
829
846
  def test_database_inspect
830
847
  db = Extralite::Database.new(':memory:')
831
- assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
848
+ assert_match(/^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect)
832
849
  end
833
850
 
834
851
  def test_database_inspect_on_closed_database
835
852
  db = Extralite::Database.new(':memory:')
836
- assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
853
+ assert_match(/^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect)
837
854
  db.close
838
- assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ \(closed\)\>$/, db.inspect
855
+ assert_match(/^\#\<Extralite::Database:0x[0-9a-f]+ \(closed\)\>$/, db.inspect)
839
856
  end
840
857
 
841
858
  def test_string_encoding
842
859
  db = Extralite::Database.new(':memory:')
843
- v = db.query_single_value("select 'foo'")
860
+ v = db.query_single_argv("select 'foo'")
844
861
  assert_equal 'foo', v
845
862
  assert_equal 'UTF-8', v.encoding.name
846
863
  end
@@ -856,7 +873,7 @@ class DatabaseTest < MiniTest::Test
856
873
 
857
874
  q1 = Queue.new
858
875
  q2 = Queue.new
859
- th = Thread.new do
876
+ t = Thread.new do
860
877
  db1.transaction do
861
878
  assert_equal true, db1.transaction_active?
862
879
  db1.execute('insert into foo values (42)')
@@ -871,9 +888,11 @@ class DatabaseTest < MiniTest::Test
871
888
  assert_equal [], db2.query('select * from foo')
872
889
 
873
890
  q2 << true
874
- th.join
891
+ t.join
875
892
  # transaction now committed
876
893
  assert_equal [{ x: 42 }], db2.query('select * from foo')
894
+ ensure
895
+ t&.kill
877
896
  end
878
897
 
879
898
  def test_database_transaction_rollback
@@ -896,9 +915,109 @@ class DatabaseTest < MiniTest::Test
896
915
  assert_kind_of RuntimeError, exception
897
916
  assert_equal 'bar', exception.message
898
917
  end
918
+
919
+ def test_database_transaction_rollback!
920
+ db = Extralite::Database.new(':memory:')
921
+ db.execute('create table foo(x)')
922
+
923
+ exception = nil
924
+ begin
925
+ db.transaction do
926
+ db.execute('insert into foo values (42)')
927
+ db.rollback!
928
+ end
929
+ rescue => e
930
+ exception = e
931
+ end
932
+
933
+ assert_equal [], db.query('select * from foo')
934
+ assert_nil exception
935
+ end
936
+
937
+ def test_database_savepoint
938
+ db = Extralite::Database.new(':memory:')
939
+ db.execute('create table foo(x)')
940
+
941
+ db.transaction do
942
+ assert_equal [], db.query('select * from foo')
943
+
944
+ db.execute('insert into foo values (42)')
945
+ assert_equal [42], db.query_argv('select x from foo')
946
+
947
+ db.savepoint(:a)
948
+
949
+ db.execute('insert into foo values (43)')
950
+ assert_equal [42, 43], db.query_argv('select x from foo')
951
+
952
+ db.savepoint(:b)
953
+
954
+ db.execute('insert into foo values (44)')
955
+ assert_equal [42, 43, 44], db.query_argv('select x from foo')
956
+
957
+ db.rollback_to(:b)
958
+ assert_equal [42, 43], db.query_argv('select x from foo')
959
+
960
+ db.release(:a)
961
+
962
+ assert_equal [42, 43], db.query_argv('select x from foo')
963
+ end
964
+ end
965
+
966
+ def test_prepare
967
+ q = @db.prepare('select * from t order by x')
968
+ assert_kind_of Extralite::Query, q
969
+
970
+ assert_equal [
971
+ { x: 1, y: 2, z: 3},
972
+ { x: 4, y: 5, z: 6}
973
+ ], q.to_a
974
+ end
975
+
976
+ def test_prepare_argv
977
+ q = @db.prepare_argv('select * from t order by x')
978
+ assert_kind_of Extralite::Query, q
979
+
980
+ buf = []
981
+ q.each { |x, y, z| buf << z }
982
+ assert_equal [3, 6], buf
983
+ end
984
+
985
+ def test_prepare_ary
986
+ q = @db.prepare_ary('select * from t order by x')
987
+ assert_kind_of Extralite::Query, q
988
+
989
+ assert_equal [
990
+ [1, 2, 3],
991
+ [4, 5, 6]
992
+ ], q.to_a
993
+ end
994
+
995
+ def test_prepare_with_transform
996
+ q = @db.prepare('select * from t order by x') { |h| h[:x] * 100 + h[:y] * 10 + h[:z] }
997
+ assert_equal [
998
+ 123,
999
+ 456
1000
+ ], q.to_a
1001
+ end
1002
+
1003
+ def test_prepare_argv_with_transform
1004
+ q = @db.prepare_argv('select * from t order by x') { |x, y, z| x * 100 + y * 10 + z }
1005
+ assert_equal [
1006
+ 123,
1007
+ 456
1008
+ ], q.to_a
1009
+ end
1010
+
1011
+ def test_prepare_ary_with_transform
1012
+ q = @db.prepare_ary('select * from t order by x') { |r| r * 2 }
1013
+ assert_equal [
1014
+ [1, 2, 3, 1, 2, 3],
1015
+ [4, 5, 6, 4, 5, 6]
1016
+ ], q.to_a
1017
+ end
899
1018
  end
900
1019
 
901
- class ScenarioTest < MiniTest::Test
1020
+ class ScenarioTest < Minitest::Test
902
1021
  def setup
903
1022
  @fn = Tempfile.new('extralite_scenario_test').path
904
1023
  @db = Extralite::Database.new(@fn)
@@ -927,7 +1046,7 @@ class ScenarioTest < MiniTest::Test
927
1046
 
928
1047
  sleep 0.1
929
1048
  @db.query 'begin deferred'
930
- result = @db.query_single_column('select x from t')
1049
+ result = @db.query_argv('select x from t')
931
1050
  assert_equal [1, 4], result
932
1051
 
933
1052
  assert_raises(Extralite::BusyError) do
@@ -961,8 +1080,10 @@ class ScenarioTest < MiniTest::Test
961
1080
  @db.query('insert into t values (7, 8, 9)')
962
1081
  @db.query('commit')
963
1082
 
964
- result = @db.query_single_column('select x from t')
1083
+ result = @db.query_argv('select x from t')
965
1084
  assert_equal [1, 4, 7], result
1085
+ ensure
1086
+ t&.kill
966
1087
  end
967
1088
 
968
1089
  def test_concurrent_queries
@@ -988,7 +1109,10 @@ class ScenarioTest < MiniTest::Test
988
1109
  t1.join
989
1110
  t2.join
990
1111
 
991
- assert_equal (1..100).to_a, @db.query_single_column('select x from t order by x')
1112
+ assert_equal (1..100).to_a, @db.query_argv('select x from t order by x')
1113
+ ensure
1114
+ t1&.kill
1115
+ t2&.kill
992
1116
  end
993
1117
 
994
1118
  def test_database_trace
@@ -1017,7 +1141,7 @@ class ScenarioTest < MiniTest::Test
1017
1141
  end
1018
1142
  end
1019
1143
 
1020
- class BackupTest < MiniTest::Test
1144
+ class BackupTest < Minitest::Test
1021
1145
  def setup
1022
1146
  @src = Extralite::Database.new(':memory:')
1023
1147
  @dst = Extralite::Database.new(':memory:')
@@ -1053,7 +1177,7 @@ class BackupTest < MiniTest::Test
1053
1177
  end
1054
1178
  end
1055
1179
 
1056
- class GVLReleaseThresholdTest < Minitest::Test
1180
+ class ConcurrencyTest < Minitest::Test
1057
1181
  def setup
1058
1182
  @sql = <<~SQL
1059
1183
  WITH RECURSIVE r(i) AS (
@@ -1097,6 +1221,9 @@ class GVLReleaseThresholdTest < Minitest::Test
1097
1221
 
1098
1222
  assert delays.size > 4
1099
1223
  assert_equal 0, delays.select { |d| d > 0.15 }.size
1224
+ ensure
1225
+ t1&.kill
1226
+ t2&.kill
1100
1227
  end
1101
1228
 
1102
1229
  def test_gvl_always_hold
@@ -1131,6 +1258,9 @@ class GVLReleaseThresholdTest < Minitest::Test
1131
1258
 
1132
1259
  assert delays.size >= 1
1133
1260
  assert delays.first > 0.2
1261
+ ensure
1262
+ t1&.kill
1263
+ t2&.kill
1134
1264
  end
1135
1265
 
1136
1266
  def test_gvl_mode_get_set
@@ -1148,6 +1278,327 @@ class GVLReleaseThresholdTest < Minitest::Test
1148
1278
  db.gvl_release_threshold = nil
1149
1279
  assert_equal 1000, db.gvl_release_threshold
1150
1280
  end
1281
+
1282
+ def test_progress_handler_simple
1283
+ db = Extralite::Database.new(':memory:')
1284
+
1285
+ buf = []
1286
+ db.on_progress(period: 1) { buf << :progress }
1287
+
1288
+ result = db.query_single('select 1 as a, 2 as b, 3 as c')
1289
+ assert_equal({ a: 1, b: 2, c: 3 }, result)
1290
+ assert_in_range 5..7, buf.size
1291
+
1292
+ buf = []
1293
+ db.on_progress(period: 2) { buf << :progress }
1294
+
1295
+ result = db.query_single('select 1 as a, 2 as b, 3 as c')
1296
+ assert_equal({ a: 1, b: 2, c: 3 }, result)
1297
+ assert_in_range 2..4, buf.size
1298
+ end
1299
+
1300
+ def test_progress_handler_normal_mode
1301
+ db = Extralite::Database.new(':memory:')
1302
+
1303
+ count = 0
1304
+ db.on_progress(period: 1) { count += 1 }
1305
+ db.query('select 1 as a')
1306
+ assert count > 0
1307
+ base_count = count
1308
+
1309
+ count = 0
1310
+ db.on_progress(period: 1) { count += 1 }
1311
+ 10.times { db.query('select 1 as a') }
1312
+ assert_equal base_count * 10, count
1313
+
1314
+ count = 0
1315
+ db.on_progress(period: 10, tick: 1) { count += 1 }
1316
+ 10.times { db.query('select 1 as a') }
1317
+ assert_equal base_count, count
1318
+ end
1319
+
1320
+ def test_progress_handler_at_least_once_mode
1321
+ db = Extralite::Database.new(':memory:')
1322
+
1323
+ count = 0
1324
+ db.on_progress(period: 1) { count += 1 }
1325
+ db.query('select 1 as a')
1326
+ assert count > 0
1327
+ base_count = count
1328
+
1329
+ count = 0
1330
+ db.on_progress(period: 1, mode: :at_least_once) { count += 1 }
1331
+ 10.times { db.query('select 1 as a') }
1332
+ assert_equal base_count * 10 + 10, count
1333
+
1334
+ count = 0
1335
+ db.on_progress(period: 10, tick: 1, mode: :at_least_once) { count += 1 }
1336
+ 10.times { db.query('select 1 as a') }
1337
+ assert_equal base_count + 10, count
1338
+ end
1339
+
1340
+ def test_progress_handler_once_mode
1341
+ db = Extralite::Database.new(':memory:')
1342
+
1343
+ count = 0
1344
+ db.on_progress(mode: :once) { count += 1 }
1345
+ 10.times { db.query('select 1 as a') }
1346
+ assert_equal 10, count
1347
+
1348
+ count = 0
1349
+ db.on_progress(period: 1, mode: :once) { count += 1 }
1350
+ 10.times { db.query('select 1 as a') }
1351
+ assert_equal 10, count
1352
+
1353
+ count = 0
1354
+ db.on_progress(period: 10, tick: 1, mode: :once) { count += 1 }
1355
+ 10.times { db.query('select 1 as a') }
1356
+ assert_equal 10, count
1357
+ end
1358
+
1359
+ def test_progress_handler_once_mode_with_batch_query
1360
+ db = Extralite::Database.new(':memory:')
1361
+
1362
+ count = 0
1363
+ db.on_progress(period: 1, mode: :once) { count += 1 }
1364
+ db.batch_query('select ?', 1..10)
1365
+ assert_equal 10, count
1366
+
1367
+ db.batch_query('select ?', 1..3)
1368
+ assert_equal 13, count
1369
+ end
1370
+
1371
+ def test_progress_handler_reset
1372
+ db = Extralite::Database.new(':memory:')
1373
+
1374
+ count = 0
1375
+ set_progress = -> {
1376
+ count = 0
1377
+ db.on_progress(mode: :once) { count += 1 }
1378
+ }
1379
+
1380
+ set_progress.()
1381
+ 10.times { db.query('select 1 as a') }
1382
+ assert_equal 10, count
1383
+
1384
+ count = 0
1385
+ db.on_progress(mode: :none)
1386
+ 10.times { db.query('select 1 as a') }
1387
+ assert_equal 0, count
1388
+
1389
+ set_progress.()
1390
+ 10.times { db.query('select 1 as a') }
1391
+ assert_equal 10, count
1392
+
1393
+ count = 0
1394
+ db.on_progress(period: 0) { foo }
1395
+ 10.times { db.query('select 1 as a') }
1396
+ assert_equal 0, count
1397
+
1398
+ set_progress.()
1399
+ 10.times { db.query('select 1 as a') }
1400
+ assert_equal 10, count
1401
+
1402
+ count = 0
1403
+ db.on_progress
1404
+ 10.times { db.query('select 1 as a') }
1405
+ assert_equal 0, count
1406
+ end
1407
+
1408
+ def test_progress_handler_invalid_arg
1409
+ db = Extralite::Database.new(':memory:')
1410
+
1411
+ assert_raises(TypeError) { db.on_progress(period: :foo) }
1412
+ assert_raises(TypeError) { db.on_progress(tick: :foo) }
1413
+ assert_raises(ArgumentError) { db.on_progress(mode: :foo) }
1414
+ end
1415
+
1416
+ def test_progress_handler_once_mode_with_prepared_query
1417
+ db = Extralite::Database.new(':memory:')
1418
+ db.execute 'create table foo (x)'
1419
+ db.batch_query('insert into foo values (?)', 1..10)
1420
+ q = db.prepare('select x from foo')
1421
+
1422
+ count = 0
1423
+ db.on_progress(period: 1, mode: :once) { count += 1 }
1424
+
1425
+ q.to_a
1426
+ assert_equal 1, count
1427
+
1428
+ q.reset
1429
+ record_count = 0
1430
+ assert_equal 2, count
1431
+ while q.next
1432
+ record_count += 1
1433
+ end
1434
+ assert_equal 10, record_count
1435
+ assert_equal 2, count
1436
+
1437
+ q.reset
1438
+ assert_equal 3, count
1439
+ while q.next
1440
+ record_count += 1
1441
+ end
1442
+ assert_equal 3, count
1443
+ end
1444
+
1445
+ LONG_QUERY = <<~SQL
1446
+ WITH RECURSIVE
1447
+ fibo (curr, next)
1448
+ AS
1449
+ ( SELECT 1,1
1450
+ UNION ALL
1451
+ SELECT next, curr + next FROM fibo
1452
+ LIMIT 10000000 )
1453
+ SELECT curr, next FROM fibo LIMIT 1 OFFSET 10000000-1;
1454
+ SQL
1455
+
1456
+ def test_progress_handler_timeout_interrupt
1457
+ db = Extralite::Database.new(':memory:')
1458
+ t0 = Time.now
1459
+ db.on_progress do
1460
+ Thread.pass
1461
+ db.interrupt if Time.now - t0 >= 0.2
1462
+ end
1463
+
1464
+ q = db.prepare(LONG_QUERY)
1465
+ result = nil
1466
+ err = nil
1467
+ begin
1468
+ result = q.next
1469
+ rescue => e
1470
+ err = e
1471
+ end
1472
+ t1 = Time.now
1473
+
1474
+ assert_nil result
1475
+ assert_equal 1, ((t1 - t0) * 5).round.to_i
1476
+ assert_kind_of Extralite::InterruptError, err
1477
+
1478
+ # try a second time, just to make sure no undefined state is left behind
1479
+ t0 = Time.now
1480
+ q = db.prepare(LONG_QUERY)
1481
+ result = nil
1482
+ err = nil
1483
+ begin
1484
+ result = q.next
1485
+ rescue => e
1486
+ err = e
1487
+ end
1488
+ t1 = Time.now
1489
+
1490
+ assert_nil result
1491
+ assert_equal 1, ((t1 - t0) * 5).round.to_i
1492
+ assert_kind_of Extralite::InterruptError, err
1493
+ end
1494
+
1495
+ class CustomTimeoutError < RuntimeError
1496
+ end
1497
+
1498
+ def test_progress_handler_timeout_raise
1499
+ db = Extralite::Database.new(':memory:')
1500
+ t0 = Time.now
1501
+ db.on_progress do
1502
+ Thread.pass
1503
+ raise CustomTimeoutError if Time.now - t0 >= 0.2
1504
+ end
1505
+
1506
+ q = db.prepare(LONG_QUERY)
1507
+ result = nil
1508
+ err = nil
1509
+ begin
1510
+ result = q.next
1511
+ rescue => e
1512
+ err = e
1513
+ end
1514
+ t1 = Time.now
1515
+
1516
+ assert_nil result
1517
+ assert_equal 1, ((t1 - t0) * 5).round.to_i
1518
+ assert_kind_of CustomTimeoutError, err
1519
+
1520
+ # try a second time, just to make sure no undefined state is left behind
1521
+ t0 = Time.now
1522
+ q = db.prepare(LONG_QUERY)
1523
+ result = nil
1524
+ err = nil
1525
+ begin
1526
+ result = q.next
1527
+ rescue => e
1528
+ err = e
1529
+ end
1530
+ t1 = Time.now
1531
+
1532
+ assert_nil result
1533
+ assert_equal 1, ((t1 - t0) * 5).round.to_i
1534
+ assert_kind_of CustomTimeoutError, err
1535
+ end
1536
+
1537
+ def test_progress_handler_busy_timeout
1538
+ fn = Tempfile.new('extralite_test_progress_handler_busy_timeout').path
1539
+ db1 = Extralite::Database.new(fn)
1540
+ db2 = Extralite::Database.new(fn)
1541
+
1542
+ db1.query('begin exclusive')
1543
+ assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
1544
+
1545
+ t0 = Time.now
1546
+ db2.on_progress do
1547
+ Thread.pass
1548
+ raise CustomTimeoutError if Time.now - t0 >= 0.2
1549
+ end
1550
+
1551
+ result = nil
1552
+ err = nil
1553
+ begin
1554
+ result = db2.execute('begin exclusive')
1555
+ rescue => e
1556
+ err = e
1557
+ end
1558
+ t1 = Time.now
1559
+
1560
+ assert_nil result
1561
+ assert_equal 1, ((t1 - t0) * 5).round.to_i
1562
+ assert_kind_of CustomTimeoutError, err
1563
+
1564
+ # Try a second time, to ensure no undefined state remains behind
1565
+ t0 = Time.now
1566
+ result = nil
1567
+ err = nil
1568
+ begin
1569
+ result = db2.execute('begin exclusive')
1570
+ rescue => e
1571
+ err = e
1572
+ end
1573
+ t1 = Time.now
1574
+
1575
+ assert_nil result
1576
+ assert_equal 1, ((t1 - t0) * 5).round.to_i
1577
+ assert_kind_of CustomTimeoutError, err
1578
+ end
1579
+
1580
+ def test_global_progress_handler
1581
+ count = 0
1582
+ Extralite.on_progress(tick: 1, period: 1) { count += 1 }
1583
+
1584
+ db = Extralite::Database.new(':memory:')
1585
+ 10.times { db.query('select 1') }
1586
+ refute_equal 0, count
1587
+
1588
+ old_count = count
1589
+ Extralite.on_progress # remove global progress handler
1590
+
1591
+ # already opened db should preserve progress handler behaviour
1592
+ 10.times { db.query('select 1') }
1593
+ refute_equal old_count, count
1594
+
1595
+ old_count = count
1596
+ db2 = Extralite::Database.new(':memory:')
1597
+ 10.times { db2.query('select 1') }
1598
+ assert_equal old_count, count
1599
+ ensure
1600
+ Extralite.on_progress(mode: :none)
1601
+ end
1151
1602
  end
1152
1603
 
1153
1604
  class RactorTest < Minitest::Test
@@ -1169,67 +1620,232 @@ class RactorTest < Minitest::Test
1169
1620
  r << 42
1170
1621
  r.take # wait for ractor to terminate
1171
1622
 
1172
- assert_equal 42, db.query_single_value('select x from foo')
1623
+ assert_equal 42, db.query_single_argv('select x from foo')
1173
1624
  end
1174
1625
 
1175
1626
  # Adapted from here: https://github.com/sparklemotion/sqlite3-ruby/pull/365/files
1176
1627
  def test_ractor_share_database
1177
- skip if SKIP_RACTOR_TESTS
1628
+ skip "skipped for now as ractors seem kinda flakey (failing sporadically)"
1178
1629
 
1179
1630
  db_receiver = Ractor.new do
1180
1631
  db = Ractor.receive
1181
1632
  Ractor.yield db.object_id
1182
1633
  begin
1183
- db.execute("create table foo (b)")
1184
- raise "Should have raised an exception in db.execute()"
1634
+ db.execute('create table foo (b)')
1635
+ raise 'Should have raised an exception in db.execute()'
1185
1636
  rescue => e
1186
1637
  Ractor.yield e
1187
1638
  end
1188
1639
  end
1189
1640
  sleep 0.1
1190
1641
  db_creator = Ractor.new(db_receiver) do |db_receiver|
1191
- db = Extralite::Database.new(":memory:")
1642
+ db = Extralite::Database.new(':memory:')
1192
1643
  Ractor.yield db.object_id
1193
1644
  db_receiver.send(db)
1194
1645
  sleep 0.1
1195
- db.execute("create table foo (a)")
1646
+ db.execute('create table foo (a)')
1196
1647
  end
1197
1648
  first_oid = db_creator.take
1198
1649
  second_oid = db_receiver.take
1199
1650
  refute_equal first_oid, second_oid
1200
1651
  ex = db_receiver.take
1201
1652
  assert_kind_of Extralite::Error, ex
1202
- assert_equal "Database is closed", ex.message
1653
+ assert_equal 'Database is closed', ex.message
1203
1654
  end
1204
1655
 
1205
- STRESS_DB_NAME = Tempfile.new('extralite_test_ractor_stress').path
1206
-
1207
1656
  # Adapted from here: https://github.com/sparklemotion/sqlite3-ruby/pull/365/files
1208
1657
  def test_ractor_stress
1209
1658
  skip if SKIP_RACTOR_TESTS
1210
-
1211
- Ractor.make_shareable(STRESS_DB_NAME)
1212
1659
 
1213
- db = Extralite::Database.new(STRESS_DB_NAME)
1214
- db.execute("PRAGMA journal_mode=WAL") # A little slow without this
1215
- db.execute("create table stress_test (a integer primary_key, b text)")
1660
+ fn = Tempfile.new('extralite_test_ractor_stress').path
1216
1661
  random = Random.new.freeze
1662
+
1217
1663
  ractors = (0..9).map do |ractor_number|
1218
- Ractor.new(random, ractor_number) do |random, ractor_number|
1219
- db_in_ractor = Extralite::Database.new(STRESS_DB_NAME)
1220
- db_in_ractor.busy_timeout = 3
1664
+ sleep 0.05
1665
+ Ractor.new(fn, random, ractor_number) do |rfn, r, n|
1666
+ rdb = Extralite::Database.new(rfn)
1667
+ rdb.busy_timeout = 3
1668
+ rdb.pragma(journal_mode: 'wal', synchronous: 1)
1669
+ rdb.execute('create table if not exists stress_test (a integer primary_key, b text)')
1670
+ changes = 0
1221
1671
  10.times do |i|
1222
- db_in_ractor.execute("insert into stress_test(a, b) values (#{ractor_number * 100 + i}, '#{random.rand}')")
1672
+ changes += rdb.execute('insert into stress_test(a, b) values (?, ?)', n * 100 + i, r.rand)
1223
1673
  end
1674
+ Ractor.yield changes
1224
1675
  end
1225
1676
  end
1226
- ractors.each { |r| r.take }
1227
- final_check = Ractor.new do
1228
- db_in_ractor = Extralite::Database.new(STRESS_DB_NAME)
1229
- count = db_in_ractor.query_single_value("select count(*) from stress_test")
1677
+
1678
+ buf = []
1679
+ ractors.each { |r| buf << r.take }
1680
+ assert_equal [10] * 10, buf
1681
+
1682
+ final_check = Ractor.new(fn) do |rfn|
1683
+ rdb = Extralite::Database.new(rfn, wal: true)
1684
+ count = rdb.query_single_argv('select count(*) from stress_test')
1230
1685
  Ractor.yield count
1231
1686
  end
1232
1687
  count = final_check.take
1233
1688
  assert_equal 100, count
1234
1689
  end
1235
1690
  end
1691
+
1692
+ class DatabaseTransformTest < Minitest::Test
1693
+ def setup
1694
+ @db = Extralite::Database.new(':memory:')
1695
+ @db.query('create table t (a, b, c)')
1696
+
1697
+ @q3 = @db.prepare('select * from t where a = ?')
1698
+ @q4 = @db.prepare('select * from t order by a')
1699
+
1700
+ @db.batch_execute('insert into t (a, b, c) values (?, ?, ?)', [
1701
+ [1, 2, { foo: 42, bar: 43 }.to_json],
1702
+ [4, 5, { foo: 45, bar: 46 }.to_json]
1703
+ ])
1704
+ end
1705
+
1706
+ class MyModel
1707
+ def initialize(h)
1708
+ @h = h
1709
+ end
1710
+
1711
+ def values
1712
+ @h
1713
+ end
1714
+ end
1715
+
1716
+ def test_query_hash_transform
1717
+ transform = ->(h) { MyModel.new(h) }
1718
+
1719
+ sql = 'select a, b from t where a = ?'
1720
+ o = @db.query(transform, sql, 1).first
1721
+ assert_kind_of MyModel, o
1722
+ assert_equal({ a: 1, b: 2 }, o.values)
1723
+
1724
+ o = @db.query(transform, sql, 4).first
1725
+ assert_kind_of MyModel, o
1726
+ assert_equal({ a: 4, b: 5 }, o.values)
1727
+
1728
+ sql = 'select a, b from t order by a'
1729
+ assert_equal [
1730
+ { a: 1, b: 2 },
1731
+ { a: 4, b: 5 }
1732
+ ], @db.query(transform, sql).map(&:values)
1733
+
1734
+ buf = []
1735
+ @db.query(transform, sql) { |r| buf << r.values }
1736
+ assert_equal [
1737
+ { a: 1, b: 2 },
1738
+ { a: 4, b: 5 }
1739
+ ], buf
1740
+ end
1741
+
1742
+ def test_query_ary_transform
1743
+ transform = ->(h) { MyModel.new(h) }
1744
+
1745
+ sql = 'select a, b from t where a = ?'
1746
+ o = @db.query_ary(transform, sql, 1).first
1747
+ assert_kind_of MyModel, o
1748
+ assert_equal([1, 2], o.values)
1749
+
1750
+ o = @db.query_ary(transform, sql, 4).first
1751
+ assert_kind_of MyModel, o
1752
+ assert_equal([4, 5], o.values)
1753
+
1754
+ sql = 'select a, b from t order by a'
1755
+ assert_equal [
1756
+ [1, 2],
1757
+ [4, 5]
1758
+ ], @db.query_ary(transform, sql).map(&:values)
1759
+
1760
+ buf = []
1761
+ @db.query_ary(transform, sql) { |r| buf << r.values }
1762
+ assert_equal [
1763
+ [1, 2],
1764
+ [4, 5]
1765
+ ], buf
1766
+ end
1767
+
1768
+ def test_query_hash_single_row_transform
1769
+ transform = ->(h) { MyModel.new(h) }
1770
+
1771
+ sql = 'select a, b from t where a = ?'
1772
+ o = @db.query_single(transform, sql, 1)
1773
+ assert_kind_of MyModel, o
1774
+ assert_equal({ a: 1, b: 2 }, o.values)
1775
+
1776
+ o = @db.query_single(transform, sql, 4)
1777
+ assert_kind_of MyModel, o
1778
+ assert_equal({ a: 4, b: 5 }, o.values)
1779
+ end
1780
+
1781
+ def test_query_ary_single_row_transform
1782
+ transform = ->(h) { MyModel.new(h) }
1783
+
1784
+ sql = 'select a, b from t where a = ?'
1785
+ o = @db.query_single_ary(transform, sql, 1)
1786
+ assert_kind_of MyModel, o
1787
+ assert_equal([1, 2], o.values)
1788
+
1789
+ o = @db.query_single_ary(transform, sql, 4)
1790
+ assert_kind_of MyModel, o
1791
+ assert_equal([4, 5], o.values)
1792
+ end
1793
+
1794
+ def test_query_argv_single_column_transform
1795
+ transform = ->(c) { JSON.parse(c, symbolize_names: true) }
1796
+ sql = 'select c from t where a = ?'
1797
+
1798
+ assert_equal([{ foo: 42, bar: 43 }], @db.query_argv(transform, sql, 1))
1799
+ assert_equal([{ foo: 45, bar: 46 }], @db.query_argv(transform, sql, 4))
1800
+
1801
+ sql = 'select c from t order by a'
1802
+ assert_equal [
1803
+ { foo: 42, bar: 43 },
1804
+ { foo: 45, bar: 46 }
1805
+ ], @db.query_argv(transform, sql)
1806
+
1807
+ buf = []
1808
+ @db.query_argv(transform, sql) { |r| buf << r }
1809
+ assert_equal [
1810
+ { foo: 42, bar: 43 },
1811
+ { foo: 45, bar: 46 }
1812
+ ], buf
1813
+ end
1814
+
1815
+ def test_query_argv_single_row_single_column
1816
+ transform = ->(c) { JSON.parse(c, symbolize_names: true) }
1817
+ sql = 'select c from t where a = ?'
1818
+
1819
+ assert_equal({ foo: 42, bar: 43 }, @db.query_single_argv(transform, sql, 1))
1820
+ assert_equal({ foo: 45, bar: 46 }, @db.query_single_argv(transform, sql, 4))
1821
+ end
1822
+
1823
+ def test_query_argv_multi_column
1824
+ transform = ->(a, b, c) { { a: a, b: b, c: JSON.parse(c, symbolize_names: true) } }
1825
+ sql = 'select * from t where a = ?'
1826
+
1827
+ assert_equal([{ a: 1, b: 2, c: { foo: 42, bar: 43 }}], @db.query_argv(transform, sql, 1))
1828
+ assert_equal([{ a: 4, b: 5, c: { foo: 45, bar: 46 }}], @db.query_argv(transform, sql, 4))
1829
+
1830
+ sql = 'select * from t order by a'
1831
+ assert_equal [
1832
+ { a: 1, b: 2, c: { foo: 42, bar: 43 }},
1833
+ { a: 4, b: 5, c: { foo: 45, bar: 46 }}
1834
+ ], @db.query_argv(transform, sql)
1835
+
1836
+ buf = []
1837
+ @db.query_argv(transform, sql) { |r| buf << r }
1838
+ assert_equal [
1839
+ { a: 1, b: 2, c: { foo: 42, bar: 43 }},
1840
+ { a: 4, b: 5, c: { foo: 45, bar: 46 }}
1841
+ ], buf
1842
+ end
1843
+
1844
+ def test_query_argv_single_row_multi_column
1845
+ transform = ->(a, b, c) { { a: a, b: b, c: JSON.parse(c, symbolize_names: true) } }
1846
+ sql = 'select * from t where a = ?'
1847
+
1848
+ assert_equal({ a: 1, b: 2, c: { foo: 42, bar: 43 }}, @db.query_single_argv(transform, sql, 1))
1849
+ assert_equal({ a: 4, b: 5, c: { foo: 45, bar: 46 }}, @db.query_single_argv(transform, sql, 4))
1850
+ end
1851
+ end