extralite-bundle 2.3 → 2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -146,9 +146,9 @@ extern "C" {
146
146
  ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147
147
  ** [sqlite_version()] and [sqlite_source_id()].
148
148
  */
149
- #define SQLITE_VERSION "3.44.0"
150
- #define SQLITE_VERSION_NUMBER 3044000
151
- #define SQLITE_SOURCE_ID "2023-11-01 11:23:50 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301"
149
+ #define SQLITE_VERSION "3.44.2"
150
+ #define SQLITE_VERSION_NUMBER 3044002
151
+ #define SQLITE_SOURCE_ID "2023-11-24 11:41:44 ebead0e7230cd33bcec9f95d2183069565b9e709bf745c9b5db65cc0cbf92c0f"
152
152
 
153
153
  /*
154
154
  ** CAPI3REF: Run-Time Library Version Numbers
@@ -5573,13 +5573,27 @@ SQLITE_API int sqlite3_create_window_function(
5573
5573
  ** </dd>
5574
5574
  **
5575
5575
  ** [[SQLITE_SUBTYPE]] <dt>SQLITE_SUBTYPE</dt><dd>
5576
- ** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call
5576
+ ** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call
5577
5577
  ** [sqlite3_value_subtype()] to inspect the sub-types of its arguments.
5578
- ** Specifying this flag makes no difference for scalar or aggregate user
5579
- ** functions. However, if it is not specified for a user-defined window
5580
- ** function, then any sub-types belonging to arguments passed to the window
5581
- ** function may be discarded before the window function is called (i.e.
5582
- ** sqlite3_value_subtype() will always return 0).
5578
+ ** This flag instructs SQLite to omit some corner-case optimizations that
5579
+ ** might disrupt the operation of the [sqlite3_value_subtype()] function,
5580
+ ** causing it to return zero rather than the correct subtype().
5581
+ ** SQL functions that invokes [sqlite3_value_subtype()] should have this
5582
+ ** property. If the SQLITE_SUBTYPE property is omitted, then the return
5583
+ ** value from [sqlite3_value_subtype()] might sometimes be zero even though
5584
+ ** a non-zero subtype was specified by the function argument expression.
5585
+ **
5586
+ ** [[SQLITE_RESULT_SUBTYPE]] <dt>SQLITE_RESULT_SUBTYPE</dt><dd>
5587
+ ** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call
5588
+ ** [sqlite3_result_subtype()] to cause a sub-type to be associated with its
5589
+ ** result.
5590
+ ** Every function that invokes [sqlite3_result_subtype()] should have this
5591
+ ** property. If it does not, then the call to [sqlite3_result_subtype()]
5592
+ ** might become a no-op if the function is used as term in an
5593
+ ** [expression index]. On the other hand, SQL functions that never invoke
5594
+ ** [sqlite3_result_subtype()] should avoid setting this property, as the
5595
+ ** purpose of this property is to disable certain optimizations that are
5596
+ ** incompatible with subtypes.
5583
5597
  ** </dd>
5584
5598
  ** </dl>
5585
5599
  */
@@ -5587,6 +5601,7 @@ SQLITE_API int sqlite3_create_window_function(
5587
5601
  #define SQLITE_DIRECTONLY 0x000080000
5588
5602
  #define SQLITE_SUBTYPE 0x000100000
5589
5603
  #define SQLITE_INNOCUOUS 0x000200000
5604
+ #define SQLITE_RESULT_SUBTYPE 0x001000000
5590
5605
 
5591
5606
  /*
5592
5607
  ** CAPI3REF: Deprecated Functions
@@ -5783,6 +5798,12 @@ SQLITE_API int sqlite3_value_encoding(sqlite3_value*);
5783
5798
  ** information can be used to pass a limited amount of context from
5784
5799
  ** one SQL function to another. Use the [sqlite3_result_subtype()]
5785
5800
  ** routine to set the subtype for the return value of an SQL function.
5801
+ **
5802
+ ** Every [application-defined SQL function] that invoke this interface
5803
+ ** should include the [SQLITE_SUBTYPE] property in the text
5804
+ ** encoding argument when the function is [sqlite3_create_function|registered].
5805
+ ** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype()
5806
+ ** might return zero instead of the upstream subtype in some corner cases.
5786
5807
  */
5787
5808
  SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
5788
5809
 
@@ -5913,14 +5934,22 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
5913
5934
  ** <li> ^(when sqlite3_set_auxdata() is invoked again on the same
5914
5935
  ** parameter)^, or
5915
5936
  ** <li> ^(during the original sqlite3_set_auxdata() call when a memory
5916
- ** allocation error occurs.)^ </ul>
5937
+ ** allocation error occurs.)^
5938
+ ** <li> ^(during the original sqlite3_set_auxdata() call if the function
5939
+ ** is evaluated during query planning instead of during query execution,
5940
+ ** as sometimes happens with [SQLITE_ENABLE_STAT4].)^ </ul>
5917
5941
  **
5918
- ** Note the last bullet in particular. The destructor X in
5942
+ ** Note the last two bullets in particular. The destructor X in
5919
5943
  ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
5920
5944
  ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata()
5921
5945
  ** should be called near the end of the function implementation and the
5922
5946
  ** function implementation should not make any use of P after
5923
- ** sqlite3_set_auxdata() has been called.
5947
+ ** sqlite3_set_auxdata() has been called. Furthermore, a call to
5948
+ ** sqlite3_get_auxdata() that occurs immediately after a corresponding call
5949
+ ** to sqlite3_set_auxdata() might still return NULL if an out-of-memory
5950
+ ** condition occurred during the sqlite3_set_auxdata() call or if the
5951
+ ** function is being evaluated during query planning rather than during
5952
+ ** query execution.
5924
5953
  **
5925
5954
  ** ^(In practice, auxiliary data is preserved between function calls for
5926
5955
  ** function parameters that are compile-time constants, including literal
@@ -6194,6 +6223,20 @@ SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n);
6194
6223
  ** higher order bits are discarded.
6195
6224
  ** The number of subtype bytes preserved by SQLite might increase
6196
6225
  ** in future releases of SQLite.
6226
+ **
6227
+ ** Every [application-defined SQL function] that invokes this interface
6228
+ ** should include the [SQLITE_RESULT_SUBTYPE] property in its
6229
+ ** text encoding argument when the SQL function is
6230
+ ** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE]
6231
+ ** property is omitted from the function that invokes sqlite3_result_subtype(),
6232
+ ** then in some cases the sqlite3_result_subtype() might fail to set
6233
+ ** the result subtype.
6234
+ **
6235
+ ** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any
6236
+ ** SQL function that invokes the sqlite3_result_subtype() interface
6237
+ ** and that does not have the SQLITE_RESULT_SUBTYPE property will raise
6238
+ ** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1
6239
+ ** by default.
6197
6240
  */
6198
6241
  SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
6199
6242
 
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.3'
3
+ VERSION = '2.4'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -25,6 +25,10 @@ module Extralite
25
25
  class InterruptError < Error
26
26
  end
27
27
 
28
+ # An exception raised when an Extralite doesn't know how to bind a parameter to a query
29
+ class ParameterError < Error
30
+ end
31
+
28
32
  # An SQLite database
29
33
  class Database
30
34
  # @!visibility private
@@ -52,6 +56,29 @@ module Extralite
52
56
  value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
53
57
  end
54
58
 
59
+ # Starts a transaction and runs the given block. If an exception is raised
60
+ # in the block, the transaction is rolled back. Otherwise, the transaction
61
+ # is commited after running the block.
62
+ #
63
+ # db.transaction do
64
+ # db.execute('insert into foo values (1, 2, 3)')
65
+ # raise if db.query_single_value('select x from bar') > 42
66
+ # end
67
+ #
68
+ # @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive). Defaults to immediate.
69
+ # @return [Any] the given block's return value
70
+ def transaction(mode = :immediate)
71
+ execute "begin #{mode} transaction"
72
+
73
+ abort = false
74
+ yield self
75
+ rescue
76
+ abort = true
77
+ raise
78
+ ensure
79
+ execute(abort ? 'rollback' : 'commit')
80
+ end
81
+
55
82
  private
56
83
 
57
84
  def pragma_set(values)
Binary file
data/test/helper.rb CHANGED
@@ -5,3 +5,5 @@ require 'extralite'
5
5
  require 'minitest/autorun'
6
6
 
7
7
  puts "sqlite3 version: #{Extralite.sqlite3_version}"
8
+
9
+ IS_LINUX = RUBY_PLATFORM =~ /linux/
data/test/issue-38.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+ require "./lib/extralite"
5
+ require "benchmark"
6
+
7
+ # Setup
8
+
9
+ File.delete("benchmark.sqlite3") if File.exist?("benchmark.sqlite3")
10
+
11
+ POOL_SIZE = 10
12
+
13
+ EXTRALITE_CONNECTIONS = POOL_SIZE.times.map do
14
+ db = Extralite::Database.new("benchmark.sqlite3")
15
+ db.execute("PRAGMA journal_mode = WAL")
16
+ db.execute("PRAGMA synchronous = NORMAL")
17
+ db.execute("PRAGMA journal_size_limit = 64000000")
18
+ db.execute("PRAGMA mmap_size = 128000000")
19
+ db.execute("PRAGMA cache_size = 2000")
20
+ db.execute("PRAGMA busy_timeout = 5000")
21
+ db
22
+ end
23
+
24
+ SQLITE3_CONNECTIONS = POOL_SIZE.times.map do
25
+ db = SQLite3::Database.new("benchmark.sqlite3")
26
+ db.execute("PRAGMA journal_mode = WAL")
27
+ db.execute("PRAGMA synchronous = NORMAL")
28
+ db.execute("PRAGMA journal_size_limit = 64000000")
29
+ db.execute("PRAGMA mmap_size = 128000000")
30
+ db.execute("PRAGMA cache_size = 2000")
31
+ db.execute("PRAGMA busy_timeout = 5000")
32
+ db
33
+ end
34
+
35
+ EXTRALITE_CONNECTIONS[0].execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at TEXT, updated_at TEXT) STRICT")
36
+ insert_statement = EXTRALITE_CONNECTIONS[0].prepare("INSERT INTO users (name, created_at, updated_at) VALUES (?, ?, ?)")
37
+ 1000.times do
38
+ insert_statement.execute("John Doe", Time.now.iso8601, Time.now.iso8601)
39
+ end
40
+
41
+ # Benchmark variations
42
+
43
+ THREAD_COUNTS = [1, 2, 4, 8]
44
+ LIMITS = [10, 100, 1000]
45
+ CLIENTS = %w[extralite sqlite3]
46
+
47
+ # Benchmark
48
+
49
+ GC.disable
50
+ Benchmark.bm do |x|
51
+ LIMITS.each do |limit|
52
+ THREAD_COUNTS.each do |thread_count|
53
+ CLIENTS.each do |client|
54
+ GC.start
55
+
56
+ x.report("#{client.rjust('extralite'.length)} - limit: #{limit}, threads: #{thread_count}") do
57
+ threads = thread_count.times.map do |thread_number|
58
+ Thread.new do
59
+ start = Time.now
60
+ if client == "extralite"
61
+ 1_000.times do
62
+ records = EXTRALITE_CONNECTIONS[thread_number].query_ary("SELECT * FROM users LIMIT #{limit}")
63
+ raise "Expected #{limit} but got #{length}" unless records.length == limit
64
+ end
65
+ else
66
+ 1_000.times do
67
+ records = SQLITE3_CONNECTIONS[thread_number].query("SELECT * FROM users LIMIT #{limit}").entries
68
+ raise "Expected #{limit} but got #{length}" unless records.length == limit
69
+ end
70
+ end
71
+ end
72
+ end
73
+ threads.each(&:join)
74
+ end
75
+ end
76
+ puts
77
+ end
78
+ end
79
+ end
80
+ GC.enable
data/test/perf_ary.rb CHANGED
@@ -12,15 +12,18 @@ end
12
12
  require 'benchmark/ips'
13
13
  require 'fileutils'
14
14
 
15
- DB_PATH = '/tmp/extralite_sqlite3_perf.db'
15
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
16
+ puts "DB_PATH = #{DB_PATH.inspect}"
17
+
16
18
 
17
19
  def prepare_database(count)
18
- FileUtils.rm(DB_PATH) rescue nil
19
20
  db = Extralite::Database.new(DB_PATH)
20
- db.query('create table foo ( a integer primary key, b text )')
21
+ db.query('create table if not exists foo ( a integer primary key, b text )')
22
+ db.query('delete from foo')
21
23
  db.query('begin')
22
24
  count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
23
25
  db.query('commit')
26
+ db.close
24
27
  end
25
28
 
26
29
  def sqlite3_run(count)
data/test/perf_hash.rb CHANGED
@@ -12,15 +12,17 @@ end
12
12
  require 'benchmark/ips'
13
13
  require 'fileutils'
14
14
 
15
- DB_PATH = '/tmp/extralite_sqlite3_perf.db'
15
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
16
+ puts "DB_PATH = #{DB_PATH.inspect}"
16
17
 
17
18
  def prepare_database(count)
18
- FileUtils.rm(DB_PATH) rescue nil
19
19
  db = Extralite::Database.new(DB_PATH)
20
- db.query('create table foo ( a integer primary key, b text )')
20
+ db.query('create table if not exists foo ( a integer primary key, b text )')
21
+ db.query('delete from foo')
21
22
  db.query('begin')
22
23
  count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
23
24
  db.query('commit')
25
+ db.close
24
26
  end
25
27
 
26
28
  def sqlite3_run(count)
@@ -36,7 +38,7 @@ def extralite_run(count)
36
38
  end
37
39
 
38
40
  [10, 1000, 100000].each do |c|
39
- puts; puts; puts "Record count: #{c}"
41
+ puts "Record count: #{c}"
40
42
 
41
43
  prepare_database(c)
42
44
 
@@ -48,4 +50,5 @@ end
48
50
 
49
51
  x.compare!
50
52
  end
53
+ puts; puts;
51
54
  end
@@ -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:')
@@ -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
@@ -293,7 +387,7 @@ end
293
387
  end
294
388
 
295
389
  def test_database_busy_timeout
296
- fn = "/tmp/extralite-#{rand(10000)}.db"
390
+ fn = Tempfile.new('extralite_test_database_busy_timeout').path
297
391
  db1 = Extralite::Database.new(fn)
298
392
  db2 = Extralite::Database.new(fn)
299
393
 
@@ -388,17 +482,77 @@ end
388
482
  assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
389
483
  end
390
484
 
485
+ def test_database_inspect_on_closed_database
486
+ db = Extralite::Database.new(':memory:')
487
+ assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
488
+ db.close
489
+ assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ \(closed\)\>$/, db.inspect
490
+ end
491
+
391
492
  def test_string_encoding
392
493
  db = Extralite::Database.new(':memory:')
393
494
  v = db.query_single_value("select 'foo'")
394
495
  assert_equal 'foo', v
395
496
  assert_equal 'UTF-8', v.encoding.name
396
497
  end
498
+
499
+ def test_database_transaction_commit
500
+ path = Tempfile.new('extralite_test_database_transaction_commit').path
501
+ db1 = Extralite::Database.new(path)
502
+ db2 = Extralite::Database.new(path)
503
+
504
+ db1.execute('create table foo(x)')
505
+ assert_equal ['foo'], db1.tables
506
+ assert_equal ['foo'], db2.tables
507
+
508
+ q1 = Queue.new
509
+ q2 = Queue.new
510
+ th = Thread.new do
511
+ db1.transaction do
512
+ assert_equal true, db1.transaction_active?
513
+ db1.execute('insert into foo values (42)')
514
+ q1 << true
515
+ q2.pop
516
+ end
517
+ assert_equal false, db1.transaction_active?
518
+ end
519
+ q1.pop
520
+ # transaction not yet committed
521
+ assert_equal false, db2.transaction_active?
522
+ assert_equal [], db2.query('select * from foo')
523
+
524
+ q2 << true
525
+ th.join
526
+ # transaction now committed
527
+ assert_equal [{ x: 42 }], db2.query('select * from foo')
528
+ end
529
+
530
+ def test_database_transaction_rollback
531
+ db = Extralite::Database.new(':memory:')
532
+ db.execute('create table foo(x)')
533
+
534
+ assert_equal [], db.query('select * from foo')
535
+
536
+ exception = nil
537
+ begin
538
+ db.transaction do
539
+ db.execute('insert into foo values (42)')
540
+ raise 'bar'
541
+ end
542
+ rescue => e
543
+ exception = e
544
+ end
545
+
546
+ assert_equal [], db.query('select * from foo')
547
+ assert_kind_of RuntimeError, exception
548
+ assert_equal 'bar', exception.message
549
+ end
397
550
  end
398
551
 
399
552
  class ScenarioTest < MiniTest::Test
400
553
  def setup
401
- @db = Extralite::Database.new('/tmp/extralite.db')
554
+ @fn = Tempfile.new('extralite_scenario_test').path
555
+ @db = Extralite::Database.new(@fn)
402
556
  @db.query('create table if not exists t (x,y,z)')
403
557
  @db.query('delete from t')
404
558
  @db.query('insert into t values (1, 2, 3)')
@@ -408,7 +562,7 @@ class ScenarioTest < MiniTest::Test
408
562
  def test_concurrent_transactions
409
563
  done = false
410
564
  t = Thread.new do
411
- db = Extralite::Database.new('/tmp/extralite.db')
565
+ db = Extralite::Database.new(@fn)
412
566
  db.query 'begin immediate'
413
567
  sleep 0.01 until done
414
568
 
@@ -465,6 +619,7 @@ class ScenarioTest < MiniTest::Test
465
619
  def test_database_trace
466
620
  sqls = []
467
621
  @db.trace { |sql| sqls << sql }
622
+ GC.start
468
623
 
469
624
  @db.query('select 1')
470
625
  assert_equal ['select 1'], sqls
@@ -515,10 +670,107 @@ class BackupTest < MiniTest::Test
515
670
  end
516
671
 
517
672
  def test_backup_with_fn
518
- tmp_fn = "/tmp/#{rand(86400)}.db"
673
+ tmp_fn = Tempfile.new('extralite_test_backup_with_fn').path
519
674
  @src.backup(tmp_fn)
520
675
 
521
676
  db = Extralite::Database.new(tmp_fn)
522
677
  assert_equal [[1, 2, 3], [4, 5, 6]], db.query_ary('select * from t')
523
678
  end
524
679
  end
680
+
681
+ class GVLReleaseThresholdTest < Minitest::Test
682
+ def setup
683
+ @sql = <<~SQL
684
+ WITH RECURSIVE r(i) AS (
685
+ VALUES(0)
686
+ UNION ALL
687
+ SELECT i FROM r
688
+ LIMIT 3000000
689
+ )
690
+ SELECT i FROM r WHERE i = 1;
691
+ SQL
692
+ end
693
+
694
+ def test_default_gvl_release_threshold
695
+ db = Extralite::Database.new(':memory:')
696
+ assert_equal 1000, db.gvl_release_threshold
697
+ end
698
+
699
+ def test_gvl_always_release
700
+ skip if !IS_LINUX
701
+
702
+ delays = []
703
+ running = true
704
+ t1 = Thread.new do
705
+ last = Time.now
706
+ while running
707
+ sleep 0.1
708
+ now = Time.now
709
+ delays << (now - last)
710
+ last = now
711
+ end
712
+ end
713
+ t2 = Thread.new do
714
+ db = Extralite::Database.new(':memory:')
715
+ db.gvl_release_threshold = 1
716
+ db.query(@sql)
717
+ ensure
718
+ running = false
719
+ end
720
+ t2.join
721
+ t1.join
722
+
723
+ assert delays.size > 4
724
+ assert_equal 0, delays.select { |d| d > 0.15 }.size
725
+ end
726
+
727
+ def test_gvl_always_hold
728
+ skip if !IS_LINUX
729
+
730
+ delays = []
731
+ running = true
732
+
733
+ signal = Queue.new
734
+ db = Extralite::Database.new(':memory:')
735
+ db.gvl_release_threshold = 0
736
+
737
+ t1 = Thread.new do
738
+ last = Time.now
739
+ while running
740
+ signal << true
741
+ sleep 0.1
742
+ now = Time.now
743
+ delays << (now - last)
744
+ last = now
745
+ end
746
+ end
747
+
748
+ t2 = Thread.new do
749
+ signal.pop
750
+ db.query(@sql)
751
+ ensure
752
+ running = false
753
+ end
754
+ t2.join
755
+ t1.join
756
+
757
+ assert delays.size >= 1
758
+ assert delays.first > 0.2
759
+ end
760
+
761
+ def test_gvl_mode_get_set
762
+ db = Extralite::Database.new(':memory:')
763
+ assert_equal 1000, db.gvl_release_threshold
764
+
765
+ db.gvl_release_threshold = 42
766
+ assert_equal 42, db.gvl_release_threshold
767
+
768
+ db.gvl_release_threshold = 0
769
+ assert_equal 0, db.gvl_release_threshold
770
+
771
+ assert_raises(ArgumentError) { db.gvl_release_threshold = :foo }
772
+
773
+ db.gvl_release_threshold = nil
774
+ assert_equal 1000, db.gvl_release_threshold
775
+ end
776
+ end
@@ -103,7 +103,8 @@ class IteratorTest < MiniTest::Test
103
103
  end
104
104
 
105
105
  def test_return_from_block_issue_26
106
- db = Extralite::Database.new('/tmp/locked.db')
106
+ fn = Tempfile.new('extralite_test_return_from_block_issue_26').path
107
+ db = Extralite::Database.new(fn)
107
108
 
108
109
  λ = ->(sql) {
109
110
  db.prepare(sql).each { |r| r.each { |_, v| return v } }