extralite 2.4 → 2.6
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/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +2 -12
- data/CHANGELOG.md +49 -10
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +876 -217
- data/TODO.md +2 -3
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +226 -19
- data/ext/extralite/database.c +339 -23
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +31 -27
- data/ext/extralite/extralite.h +25 -5
- data/ext/extralite/extralite_ext.c +10 -0
- data/ext/extralite/iterator.c +8 -3
- data/ext/extralite/query.c +222 -22
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +64 -8
- data/test/helper.rb +8 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +672 -13
- data/test/test_query.rb +367 -2
- metadata +10 -5
- data/test/perf_prepared.rb +0 -64
data/test/test_database.rb
CHANGED
@@ -61,7 +61,7 @@ class DatabaseTest < MiniTest::Test
|
|
61
61
|
|
62
62
|
r = @db.query_single_column('select y from t where x = 2')
|
63
63
|
assert_equal [], r
|
64
|
-
end
|
64
|
+
end
|
65
65
|
|
66
66
|
def test_query_single_value
|
67
67
|
r = @db.query_single_value('select z from t order by Z desc limit 1')
|
@@ -288,16 +288,38 @@ end
|
|
288
288
|
assert_equal [], @db.tables
|
289
289
|
end
|
290
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
|
+
|
291
313
|
def test_pragma
|
292
|
-
assert_equal
|
293
|
-
assert_equal
|
314
|
+
assert_equal 'memory', @db.pragma('journal_mode')
|
315
|
+
assert_equal 2, @db.pragma('synchronous')
|
294
316
|
|
295
|
-
assert_equal
|
296
|
-
assert_equal
|
317
|
+
assert_equal 1, @db.pragma(:schema_version)
|
318
|
+
assert_equal 0, @db.pragma(:recursive_triggers)
|
297
319
|
|
298
320
|
assert_equal [], @db.pragma(schema_version: 33, recursive_triggers: 1)
|
299
|
-
assert_equal
|
300
|
-
assert_equal
|
321
|
+
assert_equal 33, @db.pragma(:schema_version)
|
322
|
+
assert_equal 1, @db.pragma(:recursive_triggers)
|
301
323
|
end
|
302
324
|
|
303
325
|
def test_execute
|
@@ -311,7 +333,13 @@ end
|
|
311
333
|
assert_equal [[1, 2, 3], [42, 5, 6]], @db.query_ary('select * from t order by x')
|
312
334
|
end
|
313
335
|
|
314
|
-
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
|
315
343
|
@db.query('create table foo (a, b, c)')
|
316
344
|
assert_equal [], @db.query('select * from foo')
|
317
345
|
|
@@ -320,7 +348,7 @@ end
|
|
320
348
|
['4', 5, 6]
|
321
349
|
]
|
322
350
|
|
323
|
-
changes = @db.
|
351
|
+
changes = @db.batch_execute('insert into foo values (?, ?, ?)', records)
|
324
352
|
|
325
353
|
assert_equal 2, changes
|
326
354
|
assert_equal [
|
@@ -329,7 +357,7 @@ end
|
|
329
357
|
], @db.query('select * from foo')
|
330
358
|
end
|
331
359
|
|
332
|
-
def
|
360
|
+
def test_batch_execute_single_values
|
333
361
|
@db.query('create table foo (bar)')
|
334
362
|
assert_equal [], @db.query('select * from foo')
|
335
363
|
|
@@ -338,7 +366,7 @@ end
|
|
338
366
|
'bye'
|
339
367
|
]
|
340
368
|
|
341
|
-
changes = @db.
|
369
|
+
changes = @db.batch_execute('insert into foo values (?)', records)
|
342
370
|
|
343
371
|
assert_equal 2, changes
|
344
372
|
assert_equal [
|
@@ -347,6 +375,314 @@ end
|
|
347
375
|
], @db.query('select * from foo')
|
348
376
|
end
|
349
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
|
+
|
350
686
|
def test_interrupt
|
351
687
|
t = Thread.new do
|
352
688
|
sleep 0.5
|
@@ -477,6 +813,19 @@ end
|
|
477
813
|
assert_equal true, db.read_only?
|
478
814
|
end
|
479
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
|
+
|
480
829
|
def test_database_inspect
|
481
830
|
db = Extralite::Database.new(':memory:')
|
482
831
|
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
|
@@ -547,6 +896,53 @@ end
|
|
547
896
|
assert_kind_of RuntimeError, exception
|
548
897
|
assert_equal 'bar', exception.message
|
549
898
|
end
|
899
|
+
|
900
|
+
def test_database_transaction_rollback!
|
901
|
+
db = Extralite::Database.new(':memory:')
|
902
|
+
db.execute('create table foo(x)')
|
903
|
+
|
904
|
+
exception = nil
|
905
|
+
begin
|
906
|
+
db.transaction do
|
907
|
+
db.execute('insert into foo values (42)')
|
908
|
+
db.rollback!
|
909
|
+
end
|
910
|
+
rescue => e
|
911
|
+
exception = e
|
912
|
+
end
|
913
|
+
|
914
|
+
assert_equal [], db.query('select * from foo')
|
915
|
+
assert_nil exception
|
916
|
+
end
|
917
|
+
|
918
|
+
def test_database_savepoint
|
919
|
+
db = Extralite::Database.new(':memory:')
|
920
|
+
db.execute('create table foo(x)')
|
921
|
+
|
922
|
+
db.transaction do
|
923
|
+
assert_equal [], db.query('select * from foo')
|
924
|
+
|
925
|
+
db.execute('insert into foo values (42)')
|
926
|
+
assert_equal [42], db.query_single_column('select x from foo')
|
927
|
+
|
928
|
+
db.savepoint(:a)
|
929
|
+
|
930
|
+
db.execute('insert into foo values (43)')
|
931
|
+
assert_equal [42, 43], db.query_single_column('select x from foo')
|
932
|
+
|
933
|
+
db.savepoint(:b)
|
934
|
+
|
935
|
+
db.execute('insert into foo values (44)')
|
936
|
+
assert_equal [42, 43, 44], db.query_single_column('select x from foo')
|
937
|
+
|
938
|
+
db.rollback_to(:b)
|
939
|
+
assert_equal [42, 43], db.query_single_column('select x from foo')
|
940
|
+
|
941
|
+
db.release(:a)
|
942
|
+
|
943
|
+
assert_equal [42, 43], db.query_single_column('select x from foo')
|
944
|
+
end
|
945
|
+
end
|
550
946
|
end
|
551
947
|
|
552
948
|
class ScenarioTest < MiniTest::Test
|
@@ -616,6 +1012,32 @@ class ScenarioTest < MiniTest::Test
|
|
616
1012
|
assert_equal [1, 4, 7], result
|
617
1013
|
end
|
618
1014
|
|
1015
|
+
def test_concurrent_queries
|
1016
|
+
@db.query('delete from t')
|
1017
|
+
@db.gvl_release_threshold = 1
|
1018
|
+
q1 = @db.prepare('insert into t values (?, ?, ?)')
|
1019
|
+
q2 = @db.prepare('insert into t values (?, ?, ?)')
|
1020
|
+
|
1021
|
+
t1 = Thread.new do
|
1022
|
+
data = (1..50).each_slice(10).map { |a| a.map { |i| [i, i + 1, i + 2] } }
|
1023
|
+
data.each do |params|
|
1024
|
+
q1.batch_execute(params)
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
t2 = Thread.new do
|
1029
|
+
data = (51..100).each_slice(10).map { |a| a.map { |i| [i, i + 1, i + 2] } }
|
1030
|
+
data.each do |params|
|
1031
|
+
q2.batch_execute(params)
|
1032
|
+
end
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
t1.join
|
1036
|
+
t2.join
|
1037
|
+
|
1038
|
+
assert_equal (1..100).to_a, @db.query_single_column('select x from t order by x')
|
1039
|
+
end
|
1040
|
+
|
619
1041
|
def test_database_trace
|
620
1042
|
sqls = []
|
621
1043
|
@db.trace { |sql| sqls << sql }
|
@@ -678,7 +1100,7 @@ class BackupTest < MiniTest::Test
|
|
678
1100
|
end
|
679
1101
|
end
|
680
1102
|
|
681
|
-
class
|
1103
|
+
class ConcurrencyTest < Minitest::Test
|
682
1104
|
def setup
|
683
1105
|
@sql = <<~SQL
|
684
1106
|
WITH RECURSIVE r(i) AS (
|
@@ -773,4 +1195,241 @@ class GVLReleaseThresholdTest < Minitest::Test
|
|
773
1195
|
db.gvl_release_threshold = nil
|
774
1196
|
assert_equal 1000, db.gvl_release_threshold
|
775
1197
|
end
|
776
|
-
|
1198
|
+
|
1199
|
+
def test_progress_handler_simple
|
1200
|
+
db = Extralite::Database.new(':memory:')
|
1201
|
+
|
1202
|
+
buf = []
|
1203
|
+
db.on_progress(1) { buf << :progress }
|
1204
|
+
|
1205
|
+
result = db.query_single_row('select 1 as a, 2 as b, 3 as c')
|
1206
|
+
assert_equal({ a: 1, b: 2, c: 3 }, result)
|
1207
|
+
assert_in_range 5..7, buf.size
|
1208
|
+
|
1209
|
+
buf = []
|
1210
|
+
db.on_progress(2) { buf << :progress }
|
1211
|
+
|
1212
|
+
result = db.query_single_row('select 1 as a, 2 as b, 3 as c')
|
1213
|
+
assert_equal({ a: 1, b: 2, c: 3 }, result)
|
1214
|
+
assert_in_range 2..4, buf.size
|
1215
|
+
end
|
1216
|
+
|
1217
|
+
LONG_QUERY = <<~SQL
|
1218
|
+
WITH RECURSIVE
|
1219
|
+
fibo (curr, next)
|
1220
|
+
AS
|
1221
|
+
( SELECT 1,1
|
1222
|
+
UNION ALL
|
1223
|
+
SELECT next, curr + next FROM fibo
|
1224
|
+
LIMIT 10000000 )
|
1225
|
+
SELECT curr, next FROM fibo LIMIT 1 OFFSET 10000000-1;
|
1226
|
+
SQL
|
1227
|
+
|
1228
|
+
def test_progress_handler_timeout_interrupt
|
1229
|
+
db = Extralite::Database.new(':memory:')
|
1230
|
+
t0 = Time.now
|
1231
|
+
db.on_progress(1000) do
|
1232
|
+
Thread.pass
|
1233
|
+
db.interrupt if Time.now - t0 >= 0.2
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
q = db.prepare(LONG_QUERY)
|
1237
|
+
result = nil
|
1238
|
+
err = nil
|
1239
|
+
begin
|
1240
|
+
result = q.next
|
1241
|
+
rescue => e
|
1242
|
+
err = e
|
1243
|
+
end
|
1244
|
+
t1 = Time.now
|
1245
|
+
|
1246
|
+
assert_nil result
|
1247
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1248
|
+
assert_kind_of Extralite::InterruptError, err
|
1249
|
+
|
1250
|
+
# try a second time, just to make sure no undefined state is left behind
|
1251
|
+
t0 = Time.now
|
1252
|
+
q = db.prepare(LONG_QUERY)
|
1253
|
+
result = nil
|
1254
|
+
err = nil
|
1255
|
+
begin
|
1256
|
+
result = q.next
|
1257
|
+
rescue => e
|
1258
|
+
err = e
|
1259
|
+
end
|
1260
|
+
t1 = Time.now
|
1261
|
+
|
1262
|
+
assert_nil result
|
1263
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1264
|
+
assert_kind_of Extralite::InterruptError, err
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
class CustomTimeoutError < RuntimeError
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
def test_progress_handler_timeout_raise
|
1271
|
+
db = Extralite::Database.new(':memory:')
|
1272
|
+
t0 = Time.now
|
1273
|
+
db.on_progress(1000) do
|
1274
|
+
Thread.pass
|
1275
|
+
raise CustomTimeoutError if Time.now - t0 >= 0.2
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
q = db.prepare(LONG_QUERY)
|
1279
|
+
result = nil
|
1280
|
+
err = nil
|
1281
|
+
begin
|
1282
|
+
result = q.next
|
1283
|
+
rescue => e
|
1284
|
+
err = e
|
1285
|
+
end
|
1286
|
+
t1 = Time.now
|
1287
|
+
|
1288
|
+
assert_nil result
|
1289
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1290
|
+
assert_kind_of CustomTimeoutError, err
|
1291
|
+
|
1292
|
+
# try a second time, just to make sure no undefined state is left behind
|
1293
|
+
t0 = Time.now
|
1294
|
+
q = db.prepare(LONG_QUERY)
|
1295
|
+
result = nil
|
1296
|
+
err = nil
|
1297
|
+
begin
|
1298
|
+
result = q.next
|
1299
|
+
rescue => e
|
1300
|
+
err = e
|
1301
|
+
end
|
1302
|
+
t1 = Time.now
|
1303
|
+
|
1304
|
+
assert_nil result
|
1305
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1306
|
+
assert_kind_of CustomTimeoutError, err
|
1307
|
+
end
|
1308
|
+
|
1309
|
+
def test_progress_handler_busy_timeout
|
1310
|
+
fn = Tempfile.new('extralite_test_progress_handler_busy_timeout').path
|
1311
|
+
db1 = Extralite::Database.new(fn)
|
1312
|
+
db2 = Extralite::Database.new(fn)
|
1313
|
+
|
1314
|
+
db1.query('begin exclusive')
|
1315
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
1316
|
+
|
1317
|
+
t0 = Time.now
|
1318
|
+
db2.on_progress(1000) do
|
1319
|
+
Thread.pass
|
1320
|
+
raise CustomTimeoutError if Time.now - t0 >= 0.2
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
result = nil
|
1324
|
+
err = nil
|
1325
|
+
begin
|
1326
|
+
result = db2.execute('begin exclusive')
|
1327
|
+
rescue => e
|
1328
|
+
err = e
|
1329
|
+
end
|
1330
|
+
t1 = Time.now
|
1331
|
+
|
1332
|
+
assert_nil result
|
1333
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1334
|
+
assert_kind_of CustomTimeoutError, err
|
1335
|
+
|
1336
|
+
# Try a second time, to ensure no undefined state remains behind
|
1337
|
+
t0 = Time.now
|
1338
|
+
result = nil
|
1339
|
+
err = nil
|
1340
|
+
begin
|
1341
|
+
result = db2.execute('begin exclusive')
|
1342
|
+
rescue => e
|
1343
|
+
err = e
|
1344
|
+
end
|
1345
|
+
t1 = Time.now
|
1346
|
+
|
1347
|
+
assert_nil result
|
1348
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1349
|
+
assert_kind_of CustomTimeoutError, err
|
1350
|
+
end
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
class RactorTest < Minitest::Test
|
1354
|
+
def test_ractor_simple
|
1355
|
+
skip if SKIP_RACTOR_TESTS
|
1356
|
+
|
1357
|
+
fn = Tempfile.new('extralite_test_database_in_ractor').path
|
1358
|
+
|
1359
|
+
r = Ractor.new do
|
1360
|
+
path = receive
|
1361
|
+
db = Extralite::Database.new(path)
|
1362
|
+
i = receive
|
1363
|
+
db.execute 'insert into foo values (?)', i
|
1364
|
+
end
|
1365
|
+
|
1366
|
+
r << fn
|
1367
|
+
db = Extralite::Database.new(fn)
|
1368
|
+
db.execute 'create table foo (x)'
|
1369
|
+
r << 42
|
1370
|
+
r.take # wait for ractor to terminate
|
1371
|
+
|
1372
|
+
assert_equal 42, db.query_single_value('select x from foo')
|
1373
|
+
end
|
1374
|
+
|
1375
|
+
# Adapted from here: https://github.com/sparklemotion/sqlite3-ruby/pull/365/files
|
1376
|
+
def test_ractor_share_database
|
1377
|
+
skip if SKIP_RACTOR_TESTS
|
1378
|
+
|
1379
|
+
db_receiver = Ractor.new do
|
1380
|
+
db = Ractor.receive
|
1381
|
+
Ractor.yield db.object_id
|
1382
|
+
begin
|
1383
|
+
db.execute("create table foo (b)")
|
1384
|
+
raise "Should have raised an exception in db.execute()"
|
1385
|
+
rescue => e
|
1386
|
+
Ractor.yield e
|
1387
|
+
end
|
1388
|
+
end
|
1389
|
+
sleep 0.1
|
1390
|
+
db_creator = Ractor.new(db_receiver) do |db_receiver|
|
1391
|
+
db = Extralite::Database.new(":memory:")
|
1392
|
+
Ractor.yield db.object_id
|
1393
|
+
db_receiver.send(db)
|
1394
|
+
sleep 0.1
|
1395
|
+
db.execute("create table foo (a)")
|
1396
|
+
end
|
1397
|
+
first_oid = db_creator.take
|
1398
|
+
second_oid = db_receiver.take
|
1399
|
+
refute_equal first_oid, second_oid
|
1400
|
+
ex = db_receiver.take
|
1401
|
+
assert_kind_of Extralite::Error, ex
|
1402
|
+
assert_equal "Database is closed", ex.message
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
STRESS_DB_NAME = Tempfile.new('extralite_test_ractor_stress').path
|
1406
|
+
|
1407
|
+
# Adapted from here: https://github.com/sparklemotion/sqlite3-ruby/pull/365/files
|
1408
|
+
def test_ractor_stress
|
1409
|
+
skip if SKIP_RACTOR_TESTS
|
1410
|
+
|
1411
|
+
Ractor.make_shareable(STRESS_DB_NAME)
|
1412
|
+
|
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)")
|
1416
|
+
random = Random.new.freeze
|
1417
|
+
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
|
1421
|
+
10.times do |i|
|
1422
|
+
db_in_ractor.execute("insert into stress_test(a, b) values (#{ractor_number * 100 + i}, '#{random.rand}')")
|
1423
|
+
end
|
1424
|
+
end
|
1425
|
+
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")
|
1430
|
+
Ractor.yield count
|
1431
|
+
end
|
1432
|
+
count = final_check.take
|
1433
|
+
assert_equal 100, count
|
1434
|
+
end
|
1435
|
+
end
|