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.
@@ -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 [{journal_mode: 'memory'}], @db.pragma('journal_mode')
199
- assert_equal [{synchronous: 2}], @db.pragma('synchronous')
314
+ assert_equal 'memory', @db.pragma('journal_mode')
315
+ assert_equal 2, @db.pragma('synchronous')
200
316
 
201
- assert_equal [{schema_version: 1}], @db.pragma(:schema_version)
202
- assert_equal [{recursive_triggers: 0}], @db.pragma(:recursive_triggers)
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 [{schema_version: 33}], @db.pragma(:schema_version)
206
- assert_equal [{recursive_triggers: 1}], @db.pragma(:recursive_triggers)
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 test_execute_multi
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.execute_multi('insert into foo values (?, ?, ?)', records)
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 test_execute_multi_single_values
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.execute_multi('insert into foo values (?)', records)
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 = "/tmp/extralite-#{rand(10000)}.db"
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
- @db = Extralite::Database.new('/tmp/extralite.db')
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('/tmp/extralite.db')
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 = "/tmp/#{rand(86400)}.db"
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