extralite 2.6 → 2.7.1

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.
@@ -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
56
62
  end
57
63
 
58
- def test_query_single_column
59
- r = @db.query_single_column('select y from t')
60
- assert_equal [2, 5], r
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
+ }
68
+ end
61
69
 
62
- r = @db.query_single_column('select y from t where x = 2')
63
- assert_equal [], 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)
73
+
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
@@ -923,29 +942,82 @@ class DatabaseTest < MiniTest::Test
923
942
  assert_equal [], db.query('select * from foo')
924
943
 
925
944
  db.execute('insert into foo values (42)')
926
- assert_equal [42], db.query_single_column('select x from foo')
945
+ assert_equal [42], db.query_argv('select x from foo')
927
946
 
928
947
  db.savepoint(:a)
929
948
 
930
949
  db.execute('insert into foo values (43)')
931
- assert_equal [42, 43], db.query_single_column('select x from foo')
950
+ assert_equal [42, 43], db.query_argv('select x from foo')
932
951
 
933
952
  db.savepoint(:b)
934
953
 
935
954
  db.execute('insert into foo values (44)')
936
- assert_equal [42, 43, 44], db.query_single_column('select x from foo')
955
+ assert_equal [42, 43, 44], db.query_argv('select x from foo')
937
956
 
938
957
  db.rollback_to(:b)
939
- assert_equal [42, 43], db.query_single_column('select x from foo')
958
+ assert_equal [42, 43], db.query_argv('select x from foo')
940
959
 
941
960
  db.release(:a)
942
961
 
943
- assert_equal [42, 43], db.query_single_column('select x from foo')
962
+ assert_equal [42, 43], db.query_argv('select x from foo')
944
963
  end
945
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
946
1018
  end
947
1019
 
948
- class ScenarioTest < MiniTest::Test
1020
+ class ScenarioTest < Minitest::Test
949
1021
  def setup
950
1022
  @fn = Tempfile.new('extralite_scenario_test').path
951
1023
  @db = Extralite::Database.new(@fn)
@@ -974,7 +1046,7 @@ class ScenarioTest < MiniTest::Test
974
1046
 
975
1047
  sleep 0.1
976
1048
  @db.query 'begin deferred'
977
- result = @db.query_single_column('select x from t')
1049
+ result = @db.query_argv('select x from t')
978
1050
  assert_equal [1, 4], result
979
1051
 
980
1052
  assert_raises(Extralite::BusyError) do
@@ -1008,8 +1080,10 @@ class ScenarioTest < MiniTest::Test
1008
1080
  @db.query('insert into t values (7, 8, 9)')
1009
1081
  @db.query('commit')
1010
1082
 
1011
- result = @db.query_single_column('select x from t')
1083
+ result = @db.query_argv('select x from t')
1012
1084
  assert_equal [1, 4, 7], result
1085
+ ensure
1086
+ t&.kill
1013
1087
  end
1014
1088
 
1015
1089
  def test_concurrent_queries
@@ -1035,7 +1109,10 @@ class ScenarioTest < MiniTest::Test
1035
1109
  t1.join
1036
1110
  t2.join
1037
1111
 
1038
- 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
1039
1116
  end
1040
1117
 
1041
1118
  def test_database_trace
@@ -1064,7 +1141,7 @@ class ScenarioTest < MiniTest::Test
1064
1141
  end
1065
1142
  end
1066
1143
 
1067
- class BackupTest < MiniTest::Test
1144
+ class BackupTest < Minitest::Test
1068
1145
  def setup
1069
1146
  @src = Extralite::Database.new(':memory:')
1070
1147
  @dst = Extralite::Database.new(':memory:')
@@ -1144,6 +1221,9 @@ class ConcurrencyTest < Minitest::Test
1144
1221
 
1145
1222
  assert delays.size > 4
1146
1223
  assert_equal 0, delays.select { |d| d > 0.15 }.size
1224
+ ensure
1225
+ t1&.kill
1226
+ t2&.kill
1147
1227
  end
1148
1228
 
1149
1229
  def test_gvl_always_hold
@@ -1178,6 +1258,9 @@ class ConcurrencyTest < Minitest::Test
1178
1258
 
1179
1259
  assert delays.size >= 1
1180
1260
  assert delays.first > 0.2
1261
+ ensure
1262
+ t1&.kill
1263
+ t2&.kill
1181
1264
  end
1182
1265
 
1183
1266
  def test_gvl_mode_get_set
@@ -1200,20 +1283,165 @@ class ConcurrencyTest < Minitest::Test
1200
1283
  db = Extralite::Database.new(':memory:')
1201
1284
 
1202
1285
  buf = []
1203
- db.on_progress(1) { buf << :progress }
1286
+ db.on_progress(period: 1) { buf << :progress }
1204
1287
 
1205
- result = db.query_single_row('select 1 as a, 2 as b, 3 as c')
1288
+ result = db.query_single('select 1 as a, 2 as b, 3 as c')
1206
1289
  assert_equal({ a: 1, b: 2, c: 3 }, result)
1207
1290
  assert_in_range 5..7, buf.size
1208
1291
 
1209
1292
  buf = []
1210
- db.on_progress(2) { buf << :progress }
1293
+ db.on_progress(period: 2) { buf << :progress }
1211
1294
 
1212
- result = db.query_single_row('select 1 as a, 2 as b, 3 as c')
1295
+ result = db.query_single('select 1 as a, 2 as b, 3 as c')
1213
1296
  assert_equal({ a: 1, b: 2, c: 3 }, result)
1214
1297
  assert_in_range 2..4, buf.size
1215
1298
  end
1216
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
+
1217
1445
  LONG_QUERY = <<~SQL
1218
1446
  WITH RECURSIVE
1219
1447
  fibo (curr, next)
@@ -1228,7 +1456,7 @@ class ConcurrencyTest < Minitest::Test
1228
1456
  def test_progress_handler_timeout_interrupt
1229
1457
  db = Extralite::Database.new(':memory:')
1230
1458
  t0 = Time.now
1231
- db.on_progress(1000) do
1459
+ db.on_progress do
1232
1460
  Thread.pass
1233
1461
  db.interrupt if Time.now - t0 >= 0.2
1234
1462
  end
@@ -1270,7 +1498,7 @@ class ConcurrencyTest < Minitest::Test
1270
1498
  def test_progress_handler_timeout_raise
1271
1499
  db = Extralite::Database.new(':memory:')
1272
1500
  t0 = Time.now
1273
- db.on_progress(1000) do
1501
+ db.on_progress do
1274
1502
  Thread.pass
1275
1503
  raise CustomTimeoutError if Time.now - t0 >= 0.2
1276
1504
  end
@@ -1315,7 +1543,7 @@ class ConcurrencyTest < Minitest::Test
1315
1543
  assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
1316
1544
 
1317
1545
  t0 = Time.now
1318
- db2.on_progress(1000) do
1546
+ db2.on_progress do
1319
1547
  Thread.pass
1320
1548
  raise CustomTimeoutError if Time.now - t0 >= 0.2
1321
1549
  end
@@ -1348,6 +1576,29 @@ class ConcurrencyTest < Minitest::Test
1348
1576
  assert_equal 1, ((t1 - t0) * 5).round.to_i
1349
1577
  assert_kind_of CustomTimeoutError, err
1350
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
1351
1602
  end
1352
1603
 
1353
1604
  class RactorTest < Minitest::Test
@@ -1369,67 +1620,232 @@ class RactorTest < Minitest::Test
1369
1620
  r << 42
1370
1621
  r.take # wait for ractor to terminate
1371
1622
 
1372
- assert_equal 42, db.query_single_value('select x from foo')
1623
+ assert_equal 42, db.query_single_argv('select x from foo')
1373
1624
  end
1374
1625
 
1375
1626
  # Adapted from here: https://github.com/sparklemotion/sqlite3-ruby/pull/365/files
1376
1627
  def test_ractor_share_database
1377
- skip if SKIP_RACTOR_TESTS
1628
+ skip "skipped for now as ractors seem kinda flakey (failing sporadically)"
1378
1629
 
1379
1630
  db_receiver = Ractor.new do
1380
1631
  db = Ractor.receive
1381
1632
  Ractor.yield db.object_id
1382
1633
  begin
1383
- db.execute("create table foo (b)")
1384
- 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()'
1385
1636
  rescue => e
1386
1637
  Ractor.yield e
1387
1638
  end
1388
1639
  end
1389
1640
  sleep 0.1
1390
1641
  db_creator = Ractor.new(db_receiver) do |db_receiver|
1391
- db = Extralite::Database.new(":memory:")
1642
+ db = Extralite::Database.new(':memory:')
1392
1643
  Ractor.yield db.object_id
1393
1644
  db_receiver.send(db)
1394
1645
  sleep 0.1
1395
- db.execute("create table foo (a)")
1646
+ db.execute('create table foo (a)')
1396
1647
  end
1397
1648
  first_oid = db_creator.take
1398
1649
  second_oid = db_receiver.take
1399
1650
  refute_equal first_oid, second_oid
1400
1651
  ex = db_receiver.take
1401
1652
  assert_kind_of Extralite::Error, ex
1402
- assert_equal "Database is closed", ex.message
1653
+ assert_equal 'Database is closed', ex.message
1403
1654
  end
1404
1655
 
1405
- STRESS_DB_NAME = Tempfile.new('extralite_test_ractor_stress').path
1406
-
1407
1656
  # Adapted from here: https://github.com/sparklemotion/sqlite3-ruby/pull/365/files
1408
1657
  def test_ractor_stress
1409
1658
  skip if SKIP_RACTOR_TESTS
1410
-
1411
- Ractor.make_shareable(STRESS_DB_NAME)
1412
1659
 
1413
- db = Extralite::Database.new(STRESS_DB_NAME)
1414
- db.execute("PRAGMA journal_mode=WAL") # A little slow without this
1415
- db.execute("create table stress_test (a integer primary_key, b text)")
1660
+ fn = Tempfile.new('extralite_test_ractor_stress').path
1416
1661
  random = Random.new.freeze
1662
+
1417
1663
  ractors = (0..9).map do |ractor_number|
1418
- Ractor.new(random, ractor_number) do |random, ractor_number|
1419
- db_in_ractor = Extralite::Database.new(STRESS_DB_NAME)
1420
- 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
1421
1671
  10.times do |i|
1422
- 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)
1423
1673
  end
1674
+ Ractor.yield changes
1424
1675
  end
1425
1676
  end
1426
- ractors.each { |r| r.take }
1427
- final_check = Ractor.new do
1428
- db_in_ractor = Extralite::Database.new(STRESS_DB_NAME)
1429
- 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')
1430
1685
  Ractor.yield count
1431
1686
  end
1432
1687
  count = final_check.take
1433
1688
  assert_equal 100, count
1434
1689
  end
1435
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