extralite-bundle 2.2 → 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.
- checksums.yaml +4 -4
- data/.editorconfig +6 -0
- data/.github/workflows/test.yml +15 -1
- data/.gitignore +3 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +4 -4
- data/README.md +69 -10
- data/bin/update_sqlite_source +10 -3
- data/ext/extralite/common.c +67 -23
- data/ext/extralite/database.c +74 -26
- data/ext/extralite/extralite.h +15 -8
- data/ext/extralite/iterator.c +5 -5
- data/ext/extralite/query.c +37 -29
- data/ext/sqlite3/sqlite3.c +8945 -3857
- data/ext/sqlite3/sqlite3.h +293 -61
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +27 -0
- data/test/fixtures/image.png +0 -0
- data/test/helper.rb +2 -0
- data/test/issue-38.rb +80 -0
- data/test/perf_ary.rb +6 -3
- data/test/perf_hash.rb +7 -4
- data/test/test_database.rb +256 -4
- data/test/test_iterator.rb +2 -1
- data/test/test_query.rb +40 -4
- metadata +6 -2
data/lib/extralite/version.rb
CHANGED
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
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 =
|
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 =
|
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
|
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
|
data/test/test_database.rb
CHANGED
@@ -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 =
|
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
|
-
@
|
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(
|
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 =
|
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
|
data/test/test_iterator.rb
CHANGED
@@ -103,7 +103,8 @@ class IteratorTest < MiniTest::Test
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def test_return_from_block_issue_26
|
106
|
-
|
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 } }
|
data/test/test_query.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'helper'
|
4
|
+
require 'date'
|
4
5
|
|
5
6
|
class QueryTest < MiniTest::Test
|
6
7
|
def setup
|
@@ -267,7 +268,7 @@ class QueryTest < MiniTest::Test
|
|
267
268
|
end
|
268
269
|
|
269
270
|
def test_query_each_without_block
|
270
|
-
query = @db.prepare('select * from t')
|
271
|
+
query = @db.prepare('select * from t')
|
271
272
|
iter = query.each
|
272
273
|
assert_kind_of Extralite::Iterator, iter
|
273
274
|
|
@@ -294,7 +295,7 @@ class QueryTest < MiniTest::Test
|
|
294
295
|
end
|
295
296
|
|
296
297
|
def test_query_each_ary_without_block
|
297
|
-
query = @db.prepare('select * from t')
|
298
|
+
query = @db.prepare('select * from t')
|
298
299
|
iter = query.each_ary
|
299
300
|
assert_kind_of Extralite::Iterator, iter
|
300
301
|
|
@@ -322,7 +323,7 @@ class QueryTest < MiniTest::Test
|
|
322
323
|
end
|
323
324
|
|
324
325
|
def test_query_each_single_column_without_block
|
325
|
-
query = @db.prepare('select x from t')
|
326
|
+
query = @db.prepare('select x from t')
|
326
327
|
iter = query.each_single_column
|
327
328
|
assert_kind_of Extralite::Iterator, iter
|
328
329
|
|
@@ -375,6 +376,9 @@ class QueryTest < MiniTest::Test
|
|
375
376
|
def test_query_parameter_binding_simple
|
376
377
|
r = @db.prepare('select x, y, z from t where x = ?').bind(1).next
|
377
378
|
assert_equal({ x: 1, y: 2, z: 3 }, r)
|
379
|
+
|
380
|
+
error = assert_raises(Extralite::ParameterError) { @db.prepare('select ?').bind(Date.today).next }
|
381
|
+
assert_equal error.message, 'Cannot bind parameter at position 1 of type Date'
|
378
382
|
end
|
379
383
|
|
380
384
|
def test_query_parameter_binding_with_index
|
@@ -404,6 +408,38 @@ class QueryTest < MiniTest::Test
|
|
404
408
|
assert_equal({ x: 4, y: 5, z: 6 }, r)
|
405
409
|
end
|
406
410
|
|
411
|
+
class Foo; end
|
412
|
+
|
413
|
+
def test_parameter_binding_from_hash
|
414
|
+
assert_equal 42, @db.prepare('select :bar').bind(foo: 41, bar: 42).next_single_column
|
415
|
+
assert_equal 42, @db.prepare('select :bar').bind('foo' => 41, 'bar' => 42).next_single_column
|
416
|
+
assert_equal 42, @db.prepare('select ?8').bind(7 => 41, 8 => 42).next_single_column
|
417
|
+
assert_nil @db.prepare('select :bar').bind(foo: 41).next_single_column
|
418
|
+
|
419
|
+
error = assert_raises(Extralite::ParameterError) { @db.prepare('select ?').bind(Foo.new => 42).next_single_column }
|
420
|
+
assert_equal error.message, 'Cannot bind parameter with a key of type QueryTest::Foo'
|
421
|
+
|
422
|
+
error = assert_raises(Extralite::ParameterError) { @db.prepare('select ?').bind(%w[a b] => 42).next_single_column }
|
423
|
+
assert_equal error.message, 'Cannot bind parameter with a key of type Array'
|
424
|
+
end
|
425
|
+
|
426
|
+
def test_parameter_binding_from_struct
|
427
|
+
foo_bar = Struct.new(:":foo", :bar)
|
428
|
+
value = foo_bar.new(41, 42)
|
429
|
+
assert_equal 41, @db.prepare('select :foo').bind(value).next_single_column
|
430
|
+
assert_equal 42, @db.prepare('select :bar').bind(value).next_single_column
|
431
|
+
assert_nil @db.prepare('select :baz').bind(value).next_single_column
|
432
|
+
end
|
433
|
+
|
434
|
+
def test_parameter_binding_from_data_class
|
435
|
+
skip "Data isn't supported in Ruby < 3.2" if RUBY_VERSION < '3.2'
|
436
|
+
|
437
|
+
foo_bar = Data.define(:":foo", :bar)
|
438
|
+
value = foo_bar.new(":foo": 41, bar: 42)
|
439
|
+
assert_equal 42, @db.prepare('select :bar').bind(value).next_single_column
|
440
|
+
assert_nil @db.prepare('select :baz').bind(value).next_single_column
|
441
|
+
end
|
442
|
+
|
407
443
|
def test_query_columns
|
408
444
|
r = @db.prepare("select 'abc' as a, 'def' as b").columns
|
409
445
|
assert_equal [:a, :b], r
|
@@ -459,7 +495,7 @@ class QueryTest < MiniTest::Test
|
|
459
495
|
{ a: 1, b: '2', c: 3 },
|
460
496
|
{ a: '4', b: 5, c: 6 }
|
461
497
|
], @db.query('select * from foo')
|
462
|
-
end
|
498
|
+
end
|
463
499
|
|
464
500
|
def test_query_status
|
465
501
|
assert_equal 0, @query.status(Extralite::SQLITE_STMTSTATUS_RUN)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extralite-bundle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '2.
|
4
|
+
version: '2.4'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -88,12 +88,14 @@ extensions:
|
|
88
88
|
extra_rdoc_files:
|
89
89
|
- README.md
|
90
90
|
files:
|
91
|
+
- ".editorconfig"
|
91
92
|
- ".github/FUNDING.yml"
|
92
93
|
- ".github/workflows/test.yml"
|
93
94
|
- ".gitignore"
|
94
95
|
- ".yardopts"
|
95
96
|
- CHANGELOG.md
|
96
97
|
- Gemfile
|
98
|
+
- Gemfile-bundle
|
97
99
|
- Gemfile.lock
|
98
100
|
- LICENSE
|
99
101
|
- README.md
|
@@ -120,7 +122,9 @@ files:
|
|
120
122
|
- lib/sequel/adapters/extralite.rb
|
121
123
|
- test/extensions/text.dylib
|
122
124
|
- test/extensions/text.so
|
125
|
+
- test/fixtures/image.png
|
123
126
|
- test/helper.rb
|
127
|
+
- test/issue-38.rb
|
124
128
|
- test/perf_ary.rb
|
125
129
|
- test/perf_hash.rb
|
126
130
|
- test/perf_prepared.rb
|