extralite 2.3 → 2.5
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.
- checksums.yaml +4 -4
- data/.editorconfig +6 -0
- data/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +7 -3
- data/.gitignore +3 -0
- data/CHANGELOG.md +44 -3
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +3 -3
- data/README.md +106 -13
- data/TODO.md +0 -3
- data/bin/update_sqlite_source +10 -3
- data/ext/extralite/common.c +288 -37
- data/ext/extralite/database.c +256 -39
- data/ext/extralite/extralite.h +20 -9
- data/ext/extralite/extralite_ext.c +4 -0
- data/ext/extralite/iterator.c +5 -5
- data/ext/extralite/query.c +250 -41
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +41 -6
- data/test/fixtures/image.png +0 -0
- data/test/helper.rb +3 -0
- data/test/issue-38.rb +80 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +6 -3
- data/test/perf_hash.rb +7 -4
- data/test/test_database.rb +726 -15
- data/test/test_iterator.rb +2 -1
- data/test/test_query.rb +402 -6
- metadata +10 -3
data/test/test_database.rb
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
require_relative 'helper'
|
4
4
|
|
5
|
+
require 'date'
|
6
|
+
require 'tempfile'
|
7
|
+
|
5
8
|
class DatabaseTest < MiniTest::Test
|
6
9
|
def setup
|
7
10
|
@db = Extralite::Database.new(':memory:')
|
@@ -58,7 +61,7 @@ class DatabaseTest < MiniTest::Test
|
|
58
61
|
|
59
62
|
r = @db.query_single_column('select y from t where x = 2')
|
60
63
|
assert_equal [], r
|
61
|
-
end
|
64
|
+
end
|
62
65
|
|
63
66
|
def test_query_single_value
|
64
67
|
r = @db.query_single_value('select z from t order by Z desc limit 1')
|
@@ -123,6 +126,9 @@ end
|
|
123
126
|
|
124
127
|
r = @db.query('select x, y, z from t where z = ?', 6)
|
125
128
|
assert_equal [{ x: 4, y: 5, z: 6 }], r
|
129
|
+
|
130
|
+
error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', Date.today) }
|
131
|
+
assert_equal error.message, 'Cannot bind parameter at position 1 of type Date'
|
126
132
|
end
|
127
133
|
|
128
134
|
def test_parameter_binding_with_index
|
@@ -152,6 +158,94 @@ end
|
|
152
158
|
assert_equal [{ x: 4, y: 5, z: 6 }], r
|
153
159
|
end
|
154
160
|
|
161
|
+
class Foo; end
|
162
|
+
|
163
|
+
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)
|
168
|
+
|
169
|
+
error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', Foo.new => 42) }
|
170
|
+
assert_equal error.message, 'Cannot bind parameter with a key of type DatabaseTest::Foo'
|
171
|
+
|
172
|
+
error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', %w[a b] => 42) }
|
173
|
+
assert_equal error.message, 'Cannot bind parameter with a key of type Array'
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_parameter_binding_from_struct
|
177
|
+
foo_bar = Struct.new(:":foo", :bar)
|
178
|
+
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)
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_parameter_binding_from_data_class
|
185
|
+
skip "Data isn't supported in Ruby < 3.2" if RUBY_VERSION < '3.2'
|
186
|
+
|
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)
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_parameter_binding_for_blobs
|
194
|
+
sql = 'SELECT typeof(data) AS type, data FROM blobs WHERE ROWID = ?'
|
195
|
+
blob_path = File.expand_path('fixtures/image.png', __dir__)
|
196
|
+
@db.execute('CREATE TABLE blobs (data BLOB)')
|
197
|
+
|
198
|
+
# it's a string, not a blob
|
199
|
+
@db.execute('INSERT INTO blobs VALUES (?)', 'Hello, 世界!')
|
200
|
+
result = @db.query_single_row(sql, @db.last_insert_rowid)
|
201
|
+
assert_equal 'text', result[:type]
|
202
|
+
assert_equal Encoding::UTF_8, result[:data].encoding
|
203
|
+
|
204
|
+
data = File.binread(blob_path)
|
205
|
+
@db.execute('INSERT INTO blobs VALUES (?)', data)
|
206
|
+
result = @db.query_single_row(sql, @db.last_insert_rowid)
|
207
|
+
assert_equal 'blob', result[:type]
|
208
|
+
assert_equal data, result[:data]
|
209
|
+
|
210
|
+
data = (+'Hello, 世界!').force_encoding(Encoding::ASCII_8BIT)
|
211
|
+
@db.execute('INSERT INTO blobs VALUES (?)', data)
|
212
|
+
result = @db.query_single_row(sql, @db.last_insert_rowid)
|
213
|
+
assert_equal 'blob', result[:type]
|
214
|
+
assert_equal Encoding::ASCII_8BIT, result[:data].encoding
|
215
|
+
assert_equal 'Hello, 世界!', result[:data].force_encoding(Encoding::UTF_8)
|
216
|
+
|
217
|
+
data = Extralite::Blob.new('Hello, 世界!')
|
218
|
+
@db.execute('INSERT INTO blobs VALUES (?)', data)
|
219
|
+
result = @db.query_single_row(sql, @db.last_insert_rowid)
|
220
|
+
assert_equal 'blob', result[:type]
|
221
|
+
assert_equal Encoding::ASCII_8BIT, result[:data].encoding
|
222
|
+
assert_equal 'Hello, 世界!', result[:data].force_encoding(Encoding::UTF_8)
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_parameter_binding_for_simple_types
|
226
|
+
assert_nil @db.query_single_value('select ?', nil)
|
227
|
+
|
228
|
+
# 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)
|
231
|
+
|
232
|
+
# 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)
|
235
|
+
|
236
|
+
# 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)
|
239
|
+
|
240
|
+
# boolean
|
241
|
+
assert_equal 1, @db.query_single_value('select ?', true)
|
242
|
+
assert_equal 0, @db.query_single_value('select ?', false)
|
243
|
+
|
244
|
+
# strings and symbols
|
245
|
+
assert_equal 'foo', @db.query_single_value('select ?', 'foo')
|
246
|
+
assert_equal 'foo', @db.query_single_value('select ?', :foo)
|
247
|
+
end
|
248
|
+
|
155
249
|
def test_value_casting
|
156
250
|
r = @db.query_single_value("select 'abc'")
|
157
251
|
assert_equal 'abc', r
|
@@ -194,16 +288,38 @@ end
|
|
194
288
|
assert_equal [], @db.tables
|
195
289
|
end
|
196
290
|
|
291
|
+
def test_tables_with_db_name
|
292
|
+
assert_equal ['t'], @db.tables('main')
|
293
|
+
|
294
|
+
@db.query('create table foo (bar text)')
|
295
|
+
assert_equal ['t', 'foo'], @db.tables('main')
|
296
|
+
|
297
|
+
@db.query('drop table t')
|
298
|
+
assert_equal ['foo'], @db.tables('main')
|
299
|
+
|
300
|
+
@db.query('drop table foo')
|
301
|
+
assert_equal [], @db.tables('main')
|
302
|
+
|
303
|
+
assert_raises { @db.tables('foo') }
|
304
|
+
|
305
|
+
fn = Tempfile.new('extralite_test_tables_with_db_name').path
|
306
|
+
@db.execute "attach database '#{fn}' as foo"
|
307
|
+
|
308
|
+
assert_equal [], @db.tables('foo')
|
309
|
+
@db.execute 'create table foo.bar (x, y)'
|
310
|
+
assert_equal ['bar'], @db.tables('foo')
|
311
|
+
end
|
312
|
+
|
197
313
|
def test_pragma
|
198
|
-
assert_equal
|
199
|
-
assert_equal
|
314
|
+
assert_equal 'memory', @db.pragma('journal_mode')
|
315
|
+
assert_equal 2, @db.pragma('synchronous')
|
200
316
|
|
201
|
-
assert_equal
|
202
|
-
assert_equal
|
317
|
+
assert_equal 1, @db.pragma(:schema_version)
|
318
|
+
assert_equal 0, @db.pragma(:recursive_triggers)
|
203
319
|
|
204
320
|
assert_equal [], @db.pragma(schema_version: 33, recursive_triggers: 1)
|
205
|
-
assert_equal
|
206
|
-
assert_equal
|
321
|
+
assert_equal 33, @db.pragma(:schema_version)
|
322
|
+
assert_equal 1, @db.pragma(:recursive_triggers)
|
207
323
|
end
|
208
324
|
|
209
325
|
def test_execute
|
@@ -217,7 +333,13 @@ end
|
|
217
333
|
assert_equal [[1, 2, 3], [42, 5, 6]], @db.query_ary('select * from t order by x')
|
218
334
|
end
|
219
335
|
|
220
|
-
def
|
336
|
+
def test_execute_with_params_array
|
337
|
+
changes = @db.execute('update t set x = ? where z = ?', [42, 6])
|
338
|
+
assert_equal 1, changes
|
339
|
+
assert_equal [[1, 2, 3], [42, 5, 6]], @db.query_ary('select * from t order by x')
|
340
|
+
end
|
341
|
+
|
342
|
+
def test_batch_execute
|
221
343
|
@db.query('create table foo (a, b, c)')
|
222
344
|
assert_equal [], @db.query('select * from foo')
|
223
345
|
|
@@ -226,7 +348,7 @@ end
|
|
226
348
|
['4', 5, 6]
|
227
349
|
]
|
228
350
|
|
229
|
-
changes = @db.
|
351
|
+
changes = @db.batch_execute('insert into foo values (?, ?, ?)', records)
|
230
352
|
|
231
353
|
assert_equal 2, changes
|
232
354
|
assert_equal [
|
@@ -235,7 +357,7 @@ end
|
|
235
357
|
], @db.query('select * from foo')
|
236
358
|
end
|
237
359
|
|
238
|
-
def
|
360
|
+
def test_batch_execute_single_values
|
239
361
|
@db.query('create table foo (bar)')
|
240
362
|
assert_equal [], @db.query('select * from foo')
|
241
363
|
|
@@ -244,7 +366,7 @@ end
|
|
244
366
|
'bye'
|
245
367
|
]
|
246
368
|
|
247
|
-
changes = @db.
|
369
|
+
changes = @db.batch_execute('insert into foo values (?)', records)
|
248
370
|
|
249
371
|
assert_equal 2, changes
|
250
372
|
assert_equal [
|
@@ -253,6 +375,314 @@ end
|
|
253
375
|
], @db.query('select * from foo')
|
254
376
|
end
|
255
377
|
|
378
|
+
def test_batch_execute_with_each_interface
|
379
|
+
@db.query('create table foo (bar)')
|
380
|
+
assert_equal [], @db.query('select * from foo')
|
381
|
+
|
382
|
+
changes = @db.batch_execute('insert into foo values (?)', 1..3)
|
383
|
+
|
384
|
+
assert_equal 3, changes
|
385
|
+
assert_equal [
|
386
|
+
{ bar: 1 },
|
387
|
+
{ bar: 2 },
|
388
|
+
{ bar: 3 }
|
389
|
+
], @db.query('select * from foo')
|
390
|
+
end
|
391
|
+
|
392
|
+
def test_batch_execute_with_proc
|
393
|
+
source = [42, 43, 44]
|
394
|
+
|
395
|
+
@db.query('create table foo (a)')
|
396
|
+
assert_equal [], @db.query('select * from foo')
|
397
|
+
|
398
|
+
pr = proc { source.shift }
|
399
|
+
changes = @db.batch_execute('insert into foo values (?)', pr)
|
400
|
+
|
401
|
+
assert_equal 3, changes
|
402
|
+
assert_equal [
|
403
|
+
{ a: 42 },
|
404
|
+
{ a: 43 },
|
405
|
+
{ a: 44 }
|
406
|
+
], @db.query('select * from foo')
|
407
|
+
end
|
408
|
+
|
409
|
+
def test_batch_query_with_array
|
410
|
+
@db.query('create table foo (a, b, c)')
|
411
|
+
assert_equal [], @db.query('select * from foo')
|
412
|
+
|
413
|
+
data = [
|
414
|
+
[1, '2', 3],
|
415
|
+
['4', 5, 6]
|
416
|
+
]
|
417
|
+
results = @db.batch_query('insert into foo values (?, ?, ?) returning *', data)
|
418
|
+
assert_equal [
|
419
|
+
[{ a: 1, b: '2', c: 3 }],
|
420
|
+
[{ a: '4', b: 5, c: 6 }]
|
421
|
+
], results
|
422
|
+
|
423
|
+
results = @db.batch_query('update foo set c = ? returning *', [42, 43])
|
424
|
+
assert_equal [
|
425
|
+
[{ a: 1, b: '2', c: 42 }, { a: '4', b: 5, c: 42 }],
|
426
|
+
[{ a: 1, b: '2', c: 43 }, { a: '4', b: 5, c: 43 }]
|
427
|
+
], results
|
428
|
+
|
429
|
+
array = []
|
430
|
+
changes = @db.batch_query('update foo set c = ? returning *', [44, 45]) do |rows|
|
431
|
+
array << rows
|
432
|
+
end
|
433
|
+
assert_equal 4, changes
|
434
|
+
assert_equal [
|
435
|
+
[{ a: 1, b: '2', c: 44 }, { a: '4', b: 5, c: 44 }],
|
436
|
+
[{ a: 1, b: '2', c: 45 }, { a: '4', b: 5, c: 45 }]
|
437
|
+
], array
|
438
|
+
end
|
439
|
+
|
440
|
+
def test_batch_query_with_enumerable
|
441
|
+
@db.query('create table foo (a integer primary key, b)')
|
442
|
+
assert_equal [], @db.query('select * from foo')
|
443
|
+
|
444
|
+
results = @db.batch_query('insert into foo (b) values (?) returning *', 11..13)
|
445
|
+
assert_equal [
|
446
|
+
[{ a: 1, b: 11 }],
|
447
|
+
[{ a: 2, b: 12 }],
|
448
|
+
[{ a: 3, b: 13 }]
|
449
|
+
], results
|
450
|
+
|
451
|
+
results = @db.batch_query('update foo set b = ? returning *', [42, 43])
|
452
|
+
assert_equal [
|
453
|
+
[{ a: 1, b: 42 }, { a: 2, b: 42 }, { a: 3, b: 42 }],
|
454
|
+
[{ a: 1, b: 43 }, { a: 2, b: 43 }, { a: 3, b: 43 }]
|
455
|
+
], results
|
456
|
+
|
457
|
+
array = []
|
458
|
+
changes = @db.batch_query('update foo set b = ? returning *', [44, 45]) do |rows|
|
459
|
+
array << rows
|
460
|
+
end
|
461
|
+
assert_equal 6, changes
|
462
|
+
assert_equal [
|
463
|
+
[{ a: 1, b: 44 }, { a: 2, b: 44 }, { a: 3, b: 44 }],
|
464
|
+
[{ a: 1, b: 45 }, { a: 2, b: 45 }, { a: 3, b: 45 }]
|
465
|
+
], array
|
466
|
+
end
|
467
|
+
|
468
|
+
def parameter_source_proc(values)
|
469
|
+
proc { values.shift }
|
470
|
+
end
|
471
|
+
|
472
|
+
def test_batch_query_with_proc
|
473
|
+
@db.query('create table foo (a integer primary key, b)')
|
474
|
+
assert_equal [], @db.query('select * from foo')
|
475
|
+
|
476
|
+
pr = parameter_source_proc([5, 4, 3])
|
477
|
+
assert_kind_of Proc, pr
|
478
|
+
results = @db.batch_query('insert into foo (b) values (?) returning *', pr)
|
479
|
+
assert_equal [
|
480
|
+
[{ a: 1, b: 5 }],
|
481
|
+
[{ a: 2, b: 4 }],
|
482
|
+
[{ a: 3, b: 3 }]
|
483
|
+
], results
|
484
|
+
|
485
|
+
pr = parameter_source_proc([42, 43])
|
486
|
+
results = @db.batch_query('update foo set b = ? returning *', pr)
|
487
|
+
assert_equal [
|
488
|
+
[{ a: 1, b: 42 }, { a: 2, b: 42 }, { a: 3, b: 42 }],
|
489
|
+
[{ a: 1, b: 43 }, { a: 2, b: 43 }, { a: 3, b: 43 }]
|
490
|
+
], results
|
491
|
+
|
492
|
+
array = []
|
493
|
+
pr = parameter_source_proc([44, 45])
|
494
|
+
changes = @db.batch_query('update foo set b = ? returning *', pr) do |rows|
|
495
|
+
array << rows
|
496
|
+
end
|
497
|
+
assert_equal 6, changes
|
498
|
+
assert_equal [
|
499
|
+
[{ a: 1, b: 44 }, { a: 2, b: 44 }, { a: 3, b: 44 }],
|
500
|
+
[{ a: 1, b: 45 }, { a: 2, b: 45 }, { a: 3, b: 45 }]
|
501
|
+
], array
|
502
|
+
end
|
503
|
+
|
504
|
+
def test_batch_query_ary_with_array
|
505
|
+
@db.query('create table foo (a, b, c)')
|
506
|
+
assert_equal [], @db.query('select * from foo')
|
507
|
+
|
508
|
+
data = [
|
509
|
+
[1, '2', 3],
|
510
|
+
['4', 5, 6]
|
511
|
+
]
|
512
|
+
results = @db.batch_query_ary('insert into foo values (?, ?, ?) returning *', data)
|
513
|
+
assert_equal [
|
514
|
+
[[1, '2', 3]],
|
515
|
+
[['4', 5, 6]]
|
516
|
+
], results
|
517
|
+
|
518
|
+
results = @db.batch_query_ary('update foo set c = ? returning *', [42, 43])
|
519
|
+
assert_equal [
|
520
|
+
[[1, '2', 42], ['4', 5, 42]],
|
521
|
+
[[1, '2', 43], ['4', 5, 43]]
|
522
|
+
], results
|
523
|
+
|
524
|
+
array = []
|
525
|
+
changes = @db.batch_query_ary('update foo set c = ? returning *', [44, 45]) do |rows|
|
526
|
+
array << rows
|
527
|
+
end
|
528
|
+
assert_equal 4, changes
|
529
|
+
assert_equal [
|
530
|
+
[[1, '2', 44], ['4', 5, 44]],
|
531
|
+
[[1, '2', 45], ['4', 5, 45]]
|
532
|
+
], array
|
533
|
+
end
|
534
|
+
|
535
|
+
def test_batch_query_ary_with_enumerable
|
536
|
+
@db.query('create table foo (a integer primary key, b)')
|
537
|
+
assert_equal [], @db.query('select * from foo')
|
538
|
+
|
539
|
+
results = @db.batch_query_ary('insert into foo (b) values (?) returning *', 11..13)
|
540
|
+
assert_equal [
|
541
|
+
[[1, 11]],
|
542
|
+
[[2, 12]],
|
543
|
+
[[3, 13]]
|
544
|
+
], results
|
545
|
+
|
546
|
+
results = @db.batch_query_ary('update foo set b = ? returning *', [42, 43])
|
547
|
+
assert_equal [
|
548
|
+
[[1, 42], [2, 42], [3, 42]],
|
549
|
+
[[1, 43], [2, 43], [3, 43]]
|
550
|
+
], results
|
551
|
+
|
552
|
+
array = []
|
553
|
+
changes = @db.batch_query_ary('update foo set b = ? returning *', [44, 45]) do |rows|
|
554
|
+
array << rows
|
555
|
+
end
|
556
|
+
assert_equal 6, changes
|
557
|
+
assert_equal [
|
558
|
+
[[1, 44], [2, 44], [3, 44]],
|
559
|
+
[[1, 45], [2, 45], [3, 45]]
|
560
|
+
], array
|
561
|
+
end
|
562
|
+
|
563
|
+
def test_batch_query_ary_with_proc
|
564
|
+
@db.query('create table foo (a integer primary key, b)')
|
565
|
+
assert_equal [], @db.query('select * from foo')
|
566
|
+
|
567
|
+
pr = parameter_source_proc([5, 4, 3])
|
568
|
+
assert_kind_of Proc, pr
|
569
|
+
results = @db.batch_query_ary('insert into foo (b) values (?) returning *', pr)
|
570
|
+
assert_equal [
|
571
|
+
[[1, 5]],
|
572
|
+
[[2, 4]],
|
573
|
+
[[3, 3]]
|
574
|
+
], results
|
575
|
+
|
576
|
+
pr = parameter_source_proc([42, 43])
|
577
|
+
results = @db.batch_query_ary('update foo set b = ? returning *', pr)
|
578
|
+
assert_equal [
|
579
|
+
[[1, 42], [2, 42], [3, 42]],
|
580
|
+
[[1, 43], [2, 43], [3, 43]]
|
581
|
+
], results
|
582
|
+
|
583
|
+
array = []
|
584
|
+
pr = parameter_source_proc([44, 45])
|
585
|
+
changes = @db.batch_query_ary('update foo set b = ? returning *', pr) do |rows|
|
586
|
+
array << rows
|
587
|
+
end
|
588
|
+
assert_equal 6, changes
|
589
|
+
assert_equal [
|
590
|
+
[[1, 44], [2, 44], [3, 44]],
|
591
|
+
[[1, 45], [2, 45], [3, 45]]
|
592
|
+
], array
|
593
|
+
end
|
594
|
+
|
595
|
+
def test_batch_query_single_column_with_array
|
596
|
+
@db.query('create table foo (a, b, c)')
|
597
|
+
assert_equal [], @db.query('select * from foo')
|
598
|
+
|
599
|
+
data = [
|
600
|
+
[1, '2', 3],
|
601
|
+
['4', 5, 6]
|
602
|
+
]
|
603
|
+
results = @db.batch_query_single_column('insert into foo values (?, ?, ?) returning c', data)
|
604
|
+
assert_equal [
|
605
|
+
[3],
|
606
|
+
[6]
|
607
|
+
], results
|
608
|
+
|
609
|
+
results = @db.batch_query_single_column('update foo set c = ? returning c * 10 + cast(b as integer)', [42, 43])
|
610
|
+
assert_equal [
|
611
|
+
[422, 425],
|
612
|
+
[432, 435]
|
613
|
+
], results
|
614
|
+
|
615
|
+
array = []
|
616
|
+
changes = @db.batch_query_single_column('update foo set c = ? returning c * 10 + cast(b as integer)', [44, 45]) do |rows|
|
617
|
+
array << rows
|
618
|
+
end
|
619
|
+
assert_equal 4, changes
|
620
|
+
assert_equal [
|
621
|
+
[442, 445],
|
622
|
+
[452, 455]
|
623
|
+
], array
|
624
|
+
end
|
625
|
+
|
626
|
+
def test_batch_query_single_column_with_enumerable
|
627
|
+
@db.query('create table foo (a integer primary key, b)')
|
628
|
+
assert_equal [], @db.query('select * from foo')
|
629
|
+
|
630
|
+
results = @db.batch_query_single_column('insert into foo (b) values (?) returning b * 10 + a', 11..13)
|
631
|
+
assert_equal [
|
632
|
+
[111],
|
633
|
+
[122],
|
634
|
+
[133]
|
635
|
+
], results
|
636
|
+
|
637
|
+
results = @db.batch_query_single_column('update foo set b = ? returning b * 10 + a', 42..43)
|
638
|
+
assert_equal [
|
639
|
+
[421, 422, 423],
|
640
|
+
[431, 432, 433]
|
641
|
+
], results
|
642
|
+
|
643
|
+
array = []
|
644
|
+
changes = @db.batch_query_single_column('update foo set b = ? returning b * 10 + a', 44..45) do |rows|
|
645
|
+
array << rows
|
646
|
+
end
|
647
|
+
assert_equal 6, changes
|
648
|
+
assert_equal [
|
649
|
+
[441, 442, 443],
|
650
|
+
[451, 452, 453]
|
651
|
+
], array
|
652
|
+
end
|
653
|
+
|
654
|
+
def test_batch_query_single_column_with_proc
|
655
|
+
@db.query('create table foo (a integer primary key, b)')
|
656
|
+
assert_equal [], @db.query('select * from foo')
|
657
|
+
|
658
|
+
pr = parameter_source_proc([5, 4, 3])
|
659
|
+
assert_kind_of Proc, pr
|
660
|
+
results = @db.batch_query_single_column('insert into foo (b) values (?) returning b', pr)
|
661
|
+
assert_equal [
|
662
|
+
[5],
|
663
|
+
[4],
|
664
|
+
[3]
|
665
|
+
], results
|
666
|
+
|
667
|
+
pr = parameter_source_proc([42, 43])
|
668
|
+
results = @db.batch_query_single_column('update foo set b = ? returning b * 10 + a', pr)
|
669
|
+
assert_equal [
|
670
|
+
[421, 422, 423],
|
671
|
+
[431, 432, 433]
|
672
|
+
], results
|
673
|
+
|
674
|
+
array = []
|
675
|
+
pr = parameter_source_proc([44, 45])
|
676
|
+
changes = @db.batch_query_single_column('update foo set b = ? returning b * 10 + a', pr) do |rows|
|
677
|
+
array << rows
|
678
|
+
end
|
679
|
+
assert_equal 6, changes
|
680
|
+
assert_equal [
|
681
|
+
[441, 442, 443],
|
682
|
+
[451, 452, 453]
|
683
|
+
], array
|
684
|
+
end
|
685
|
+
|
256
686
|
def test_interrupt
|
257
687
|
t = Thread.new do
|
258
688
|
sleep 0.5
|
@@ -293,7 +723,7 @@ end
|
|
293
723
|
end
|
294
724
|
|
295
725
|
def test_database_busy_timeout
|
296
|
-
fn =
|
726
|
+
fn = Tempfile.new('extralite_test_database_busy_timeout').path
|
297
727
|
db1 = Extralite::Database.new(fn)
|
298
728
|
db2 = Extralite::Database.new(fn)
|
299
729
|
|
@@ -383,22 +813,95 @@ end
|
|
383
813
|
assert_equal true, db.read_only?
|
384
814
|
end
|
385
815
|
|
816
|
+
def test_database_initialize_options
|
817
|
+
db = Extralite::Database.new(':memory:', gvl_release_threshold: 23)
|
818
|
+
assert_equal 23, db.gvl_release_threshold
|
819
|
+
|
820
|
+
fn = Tempfile.new('extralite_test_database_initialize_options_1').path
|
821
|
+
db = Extralite::Database.new(fn, wal_journal_mode: true)
|
822
|
+
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
|
+
assert_equal 1, db.pragma(:synchronous)
|
827
|
+
end
|
828
|
+
|
386
829
|
def test_database_inspect
|
387
830
|
db = Extralite::Database.new(':memory:')
|
388
831
|
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
|
389
832
|
end
|
390
833
|
|
834
|
+
def test_database_inspect_on_closed_database
|
835
|
+
db = Extralite::Database.new(':memory:')
|
836
|
+
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
|
837
|
+
db.close
|
838
|
+
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ \(closed\)\>$/, db.inspect
|
839
|
+
end
|
840
|
+
|
391
841
|
def test_string_encoding
|
392
842
|
db = Extralite::Database.new(':memory:')
|
393
843
|
v = db.query_single_value("select 'foo'")
|
394
844
|
assert_equal 'foo', v
|
395
845
|
assert_equal 'UTF-8', v.encoding.name
|
396
846
|
end
|
847
|
+
|
848
|
+
def test_database_transaction_commit
|
849
|
+
path = Tempfile.new('extralite_test_database_transaction_commit').path
|
850
|
+
db1 = Extralite::Database.new(path)
|
851
|
+
db2 = Extralite::Database.new(path)
|
852
|
+
|
853
|
+
db1.execute('create table foo(x)')
|
854
|
+
assert_equal ['foo'], db1.tables
|
855
|
+
assert_equal ['foo'], db2.tables
|
856
|
+
|
857
|
+
q1 = Queue.new
|
858
|
+
q2 = Queue.new
|
859
|
+
th = Thread.new do
|
860
|
+
db1.transaction do
|
861
|
+
assert_equal true, db1.transaction_active?
|
862
|
+
db1.execute('insert into foo values (42)')
|
863
|
+
q1 << true
|
864
|
+
q2.pop
|
865
|
+
end
|
866
|
+
assert_equal false, db1.transaction_active?
|
867
|
+
end
|
868
|
+
q1.pop
|
869
|
+
# transaction not yet committed
|
870
|
+
assert_equal false, db2.transaction_active?
|
871
|
+
assert_equal [], db2.query('select * from foo')
|
872
|
+
|
873
|
+
q2 << true
|
874
|
+
th.join
|
875
|
+
# transaction now committed
|
876
|
+
assert_equal [{ x: 42 }], db2.query('select * from foo')
|
877
|
+
end
|
878
|
+
|
879
|
+
def test_database_transaction_rollback
|
880
|
+
db = Extralite::Database.new(':memory:')
|
881
|
+
db.execute('create table foo(x)')
|
882
|
+
|
883
|
+
assert_equal [], db.query('select * from foo')
|
884
|
+
|
885
|
+
exception = nil
|
886
|
+
begin
|
887
|
+
db.transaction do
|
888
|
+
db.execute('insert into foo values (42)')
|
889
|
+
raise 'bar'
|
890
|
+
end
|
891
|
+
rescue => e
|
892
|
+
exception = e
|
893
|
+
end
|
894
|
+
|
895
|
+
assert_equal [], db.query('select * from foo')
|
896
|
+
assert_kind_of RuntimeError, exception
|
897
|
+
assert_equal 'bar', exception.message
|
898
|
+
end
|
397
899
|
end
|
398
900
|
|
399
901
|
class ScenarioTest < MiniTest::Test
|
400
902
|
def setup
|
401
|
-
@
|
903
|
+
@fn = Tempfile.new('extralite_scenario_test').path
|
904
|
+
@db = Extralite::Database.new(@fn)
|
402
905
|
@db.query('create table if not exists t (x,y,z)')
|
403
906
|
@db.query('delete from t')
|
404
907
|
@db.query('insert into t values (1, 2, 3)')
|
@@ -408,7 +911,7 @@ class ScenarioTest < MiniTest::Test
|
|
408
911
|
def test_concurrent_transactions
|
409
912
|
done = false
|
410
913
|
t = Thread.new do
|
411
|
-
db = Extralite::Database.new(
|
914
|
+
db = Extralite::Database.new(@fn)
|
412
915
|
db.query 'begin immediate'
|
413
916
|
sleep 0.01 until done
|
414
917
|
|
@@ -462,9 +965,36 @@ class ScenarioTest < MiniTest::Test
|
|
462
965
|
assert_equal [1, 4, 7], result
|
463
966
|
end
|
464
967
|
|
968
|
+
def test_concurrent_queries
|
969
|
+
@db.query('delete from t')
|
970
|
+
@db.gvl_release_threshold = 1
|
971
|
+
q1 = @db.prepare('insert into t values (?, ?, ?)')
|
972
|
+
q2 = @db.prepare('insert into t values (?, ?, ?)')
|
973
|
+
|
974
|
+
t1 = Thread.new do
|
975
|
+
data = (1..50).each_slice(10).map { |a| a.map { |i| [i, i + 1, i + 2] } }
|
976
|
+
data.each do |params|
|
977
|
+
q1.batch_execute(params)
|
978
|
+
end
|
979
|
+
end
|
980
|
+
|
981
|
+
t2 = Thread.new do
|
982
|
+
data = (51..100).each_slice(10).map { |a| a.map { |i| [i, i + 1, i + 2] } }
|
983
|
+
data.each do |params|
|
984
|
+
q2.batch_execute(params)
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
t1.join
|
989
|
+
t2.join
|
990
|
+
|
991
|
+
assert_equal (1..100).to_a, @db.query_single_column('select x from t order by x')
|
992
|
+
end
|
993
|
+
|
465
994
|
def test_database_trace
|
466
995
|
sqls = []
|
467
996
|
@db.trace { |sql| sqls << sql }
|
997
|
+
GC.start
|
468
998
|
|
469
999
|
@db.query('select 1')
|
470
1000
|
assert_equal ['select 1'], sqls
|
@@ -515,10 +1045,191 @@ class BackupTest < MiniTest::Test
|
|
515
1045
|
end
|
516
1046
|
|
517
1047
|
def test_backup_with_fn
|
518
|
-
tmp_fn =
|
1048
|
+
tmp_fn = Tempfile.new('extralite_test_backup_with_fn').path
|
519
1049
|
@src.backup(tmp_fn)
|
520
1050
|
|
521
1051
|
db = Extralite::Database.new(tmp_fn)
|
522
1052
|
assert_equal [[1, 2, 3], [4, 5, 6]], db.query_ary('select * from t')
|
523
1053
|
end
|
524
1054
|
end
|
1055
|
+
|
1056
|
+
class GVLReleaseThresholdTest < Minitest::Test
|
1057
|
+
def setup
|
1058
|
+
@sql = <<~SQL
|
1059
|
+
WITH RECURSIVE r(i) AS (
|
1060
|
+
VALUES(0)
|
1061
|
+
UNION ALL
|
1062
|
+
SELECT i FROM r
|
1063
|
+
LIMIT 3000000
|
1064
|
+
)
|
1065
|
+
SELECT i FROM r WHERE i = 1;
|
1066
|
+
SQL
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
def test_default_gvl_release_threshold
|
1070
|
+
db = Extralite::Database.new(':memory:')
|
1071
|
+
assert_equal 1000, db.gvl_release_threshold
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
def test_gvl_always_release
|
1075
|
+
skip if !IS_LINUX
|
1076
|
+
|
1077
|
+
delays = []
|
1078
|
+
running = true
|
1079
|
+
t1 = Thread.new do
|
1080
|
+
last = Time.now
|
1081
|
+
while running
|
1082
|
+
sleep 0.1
|
1083
|
+
now = Time.now
|
1084
|
+
delays << (now - last)
|
1085
|
+
last = now
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
t2 = Thread.new do
|
1089
|
+
db = Extralite::Database.new(':memory:')
|
1090
|
+
db.gvl_release_threshold = 1
|
1091
|
+
db.query(@sql)
|
1092
|
+
ensure
|
1093
|
+
running = false
|
1094
|
+
end
|
1095
|
+
t2.join
|
1096
|
+
t1.join
|
1097
|
+
|
1098
|
+
assert delays.size > 4
|
1099
|
+
assert_equal 0, delays.select { |d| d > 0.15 }.size
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
def test_gvl_always_hold
|
1103
|
+
skip if !IS_LINUX
|
1104
|
+
|
1105
|
+
delays = []
|
1106
|
+
running = true
|
1107
|
+
|
1108
|
+
signal = Queue.new
|
1109
|
+
db = Extralite::Database.new(':memory:')
|
1110
|
+
db.gvl_release_threshold = 0
|
1111
|
+
|
1112
|
+
t1 = Thread.new do
|
1113
|
+
last = Time.now
|
1114
|
+
while running
|
1115
|
+
signal << true
|
1116
|
+
sleep 0.1
|
1117
|
+
now = Time.now
|
1118
|
+
delays << (now - last)
|
1119
|
+
last = now
|
1120
|
+
end
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
t2 = Thread.new do
|
1124
|
+
signal.pop
|
1125
|
+
db.query(@sql)
|
1126
|
+
ensure
|
1127
|
+
running = false
|
1128
|
+
end
|
1129
|
+
t2.join
|
1130
|
+
t1.join
|
1131
|
+
|
1132
|
+
assert delays.size >= 1
|
1133
|
+
assert delays.first > 0.2
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
def test_gvl_mode_get_set
|
1137
|
+
db = Extralite::Database.new(':memory:')
|
1138
|
+
assert_equal 1000, db.gvl_release_threshold
|
1139
|
+
|
1140
|
+
db.gvl_release_threshold = 42
|
1141
|
+
assert_equal 42, db.gvl_release_threshold
|
1142
|
+
|
1143
|
+
db.gvl_release_threshold = 0
|
1144
|
+
assert_equal 0, db.gvl_release_threshold
|
1145
|
+
|
1146
|
+
assert_raises(ArgumentError) { db.gvl_release_threshold = :foo }
|
1147
|
+
|
1148
|
+
db.gvl_release_threshold = nil
|
1149
|
+
assert_equal 1000, db.gvl_release_threshold
|
1150
|
+
end
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
class RactorTest < Minitest::Test
|
1154
|
+
def test_ractor_simple
|
1155
|
+
skip if SKIP_RACTOR_TESTS
|
1156
|
+
|
1157
|
+
fn = Tempfile.new('extralite_test_database_in_ractor').path
|
1158
|
+
|
1159
|
+
r = Ractor.new do
|
1160
|
+
path = receive
|
1161
|
+
db = Extralite::Database.new(path)
|
1162
|
+
i = receive
|
1163
|
+
db.execute 'insert into foo values (?)', i
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
r << fn
|
1167
|
+
db = Extralite::Database.new(fn)
|
1168
|
+
db.execute 'create table foo (x)'
|
1169
|
+
r << 42
|
1170
|
+
r.take # wait for ractor to terminate
|
1171
|
+
|
1172
|
+
assert_equal 42, db.query_single_value('select x from foo')
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
# Adapted from here: https://github.com/sparklemotion/sqlite3-ruby/pull/365/files
|
1176
|
+
def test_ractor_share_database
|
1177
|
+
skip if SKIP_RACTOR_TESTS
|
1178
|
+
|
1179
|
+
db_receiver = Ractor.new do
|
1180
|
+
db = Ractor.receive
|
1181
|
+
Ractor.yield db.object_id
|
1182
|
+
begin
|
1183
|
+
db.execute("create table foo (b)")
|
1184
|
+
raise "Should have raised an exception in db.execute()"
|
1185
|
+
rescue => e
|
1186
|
+
Ractor.yield e
|
1187
|
+
end
|
1188
|
+
end
|
1189
|
+
sleep 0.1
|
1190
|
+
db_creator = Ractor.new(db_receiver) do |db_receiver|
|
1191
|
+
db = Extralite::Database.new(":memory:")
|
1192
|
+
Ractor.yield db.object_id
|
1193
|
+
db_receiver.send(db)
|
1194
|
+
sleep 0.1
|
1195
|
+
db.execute("create table foo (a)")
|
1196
|
+
end
|
1197
|
+
first_oid = db_creator.take
|
1198
|
+
second_oid = db_receiver.take
|
1199
|
+
refute_equal first_oid, second_oid
|
1200
|
+
ex = db_receiver.take
|
1201
|
+
assert_kind_of Extralite::Error, ex
|
1202
|
+
assert_equal "Database is closed", ex.message
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
STRESS_DB_NAME = Tempfile.new('extralite_test_ractor_stress').path
|
1206
|
+
|
1207
|
+
# Adapted from here: https://github.com/sparklemotion/sqlite3-ruby/pull/365/files
|
1208
|
+
def test_ractor_stress
|
1209
|
+
skip if SKIP_RACTOR_TESTS
|
1210
|
+
|
1211
|
+
Ractor.make_shareable(STRESS_DB_NAME)
|
1212
|
+
|
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)")
|
1216
|
+
random = Random.new.freeze
|
1217
|
+
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
|
1221
|
+
10.times do |i|
|
1222
|
+
db_in_ractor.execute("insert into stress_test(a, b) values (#{ractor_number * 100 + i}, '#{random.rand}')")
|
1223
|
+
end
|
1224
|
+
end
|
1225
|
+
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")
|
1230
|
+
Ractor.yield count
|
1231
|
+
end
|
1232
|
+
count = final_check.take
|
1233
|
+
assert_equal 100, count
|
1234
|
+
end
|
1235
|
+
end
|