extralite 2.3 → 2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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