extralite 2.3 → 2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +6 -0
- data/.github/workflows/test.yml +15 -1
- data/.gitignore +3 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +3 -3
- 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/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/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
|
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
|
@@ -118,7 +120,9 @@ files:
|
|
118
120
|
- lib/sequel/adapters/extralite.rb
|
119
121
|
- test/extensions/text.dylib
|
120
122
|
- test/extensions/text.so
|
123
|
+
- test/fixtures/image.png
|
121
124
|
- test/helper.rb
|
125
|
+
- test/issue-38.rb
|
122
126
|
- test/perf_ary.rb
|
123
127
|
- test/perf_hash.rb
|
124
128
|
- test/perf_prepared.rb
|