extralite-bundle 2.3 → 2.4

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.
@@ -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 } }