extralite 2.5 → 2.6
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/CHANGELOG.md +10 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +869 -244
- data/TODO.md +2 -0
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +4 -4
- data/ext/extralite/database.c +159 -12
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +31 -27
- data/ext/extralite/extralite.h +20 -4
- data/ext/extralite/extralite_ext.c +6 -0
- data/ext/extralite/iterator.c +8 -3
- data/ext/extralite/query.c +12 -13
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +50 -2
- data/test/helper.rb +8 -1
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +201 -1
- data/test/test_query.rb +5 -0
- metadata +6 -4
- data/test/perf_prepared.rb +0 -64
data/test/perf_ary.rb
CHANGED
@@ -15,26 +15,25 @@ require 'fileutils'
|
|
15
15
|
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
16
16
|
puts "DB_PATH = #{DB_PATH.inspect}"
|
17
17
|
|
18
|
+
$sqlite3_db = SQLite3::Database.new(DB_PATH)
|
19
|
+
$extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
18
20
|
|
19
21
|
def prepare_database(count)
|
20
22
|
db = Extralite::Database.new(DB_PATH)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
count.times {
|
25
|
-
|
26
|
-
db.close
|
23
|
+
$extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
|
24
|
+
$extralite_db.query('delete from foo')
|
25
|
+
$extralite_db.query('begin')
|
26
|
+
count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
27
|
+
$extralite_db.query('commit')
|
27
28
|
end
|
28
29
|
|
29
30
|
def sqlite3_run(count)
|
30
|
-
|
31
|
-
results = db.execute('select * from foo')
|
31
|
+
results = $sqlite3_db.execute('select * from foo')
|
32
32
|
raise unless results.size == count
|
33
33
|
end
|
34
34
|
|
35
35
|
def extralite_run(count)
|
36
|
-
|
37
|
-
results = db.query_ary('select * from foo')
|
36
|
+
results = $extralite_db.query('select * from foo')
|
38
37
|
raise unless results.size == count
|
39
38
|
end
|
40
39
|
|
@@ -43,12 +42,15 @@ end
|
|
43
42
|
|
44
43
|
prepare_database(c)
|
45
44
|
|
46
|
-
Benchmark.ips do |x|
|
47
|
-
x.config(:time =>
|
45
|
+
bm = Benchmark.ips do |x|
|
46
|
+
x.config(:time => 5, :warmup => 2)
|
48
47
|
|
49
48
|
x.report("sqlite3") { sqlite3_run(c) }
|
50
49
|
x.report("extralite") { extralite_run(c) }
|
51
50
|
|
52
51
|
x.compare!
|
53
52
|
end
|
53
|
+
puts;
|
54
|
+
bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
|
55
|
+
puts;
|
54
56
|
end
|
data/test/perf_hash.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Run on Ruby 3.3 with YJIT enabled
|
4
|
+
|
3
5
|
require 'bundler/inline'
|
4
6
|
|
5
7
|
gemfile do
|
@@ -15,40 +17,40 @@ require 'fileutils'
|
|
15
17
|
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
16
18
|
puts "DB_PATH = #{DB_PATH.inspect}"
|
17
19
|
|
20
|
+
$sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
|
21
|
+
$extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
22
|
+
|
18
23
|
def prepare_database(count)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
db.query('commit')
|
25
|
-
db.close
|
24
|
+
$extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
|
25
|
+
$extralite_db.query('delete from foo')
|
26
|
+
$extralite_db.query('begin')
|
27
|
+
count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
28
|
+
$extralite_db.query('commit')
|
26
29
|
end
|
27
30
|
|
28
31
|
def sqlite3_run(count)
|
29
|
-
|
30
|
-
results = db.execute('select * from foo')
|
32
|
+
results = $sqlite3_db.execute('select * from foo')
|
31
33
|
raise unless results.size == count
|
32
34
|
end
|
33
35
|
|
34
36
|
def extralite_run(count)
|
35
|
-
|
36
|
-
results = db.query('select * from foo')
|
37
|
+
results = $extralite_db.query('select * from foo')
|
37
38
|
raise unless results.size == count
|
38
39
|
end
|
39
40
|
|
40
41
|
[10, 1000, 100000].each do |c|
|
41
42
|
puts "Record count: #{c}"
|
42
|
-
|
43
43
|
prepare_database(c)
|
44
44
|
|
45
|
-
Benchmark.ips do |x|
|
46
|
-
x.config(:time =>
|
45
|
+
bm = Benchmark.ips do |x|
|
46
|
+
x.config(:time => 5, :warmup => 2)
|
47
47
|
|
48
48
|
x.report("sqlite3") { sqlite3_run(c) }
|
49
49
|
x.report("extralite") { extralite_run(c) }
|
50
50
|
|
51
51
|
x.compare!
|
52
52
|
end
|
53
|
-
puts;
|
53
|
+
puts;
|
54
|
+
bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
|
55
|
+
puts;
|
54
56
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'extralite', path: '..'
|
8
|
+
gem 'sqlite3'
|
9
|
+
gem 'benchmark-ips'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'benchmark/ips'
|
13
|
+
require 'fileutils'
|
14
|
+
|
15
|
+
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
16
|
+
puts "DB_PATH = #{DB_PATH.inspect}"
|
17
|
+
|
18
|
+
def prepare_database(count)
|
19
|
+
$sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
|
20
|
+
$extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
21
|
+
|
22
|
+
$extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
|
23
|
+
$extralite_db.query('delete from foo')
|
24
|
+
$extralite_db.query('begin')
|
25
|
+
count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
26
|
+
$extralite_db.query('commit')
|
27
|
+
|
28
|
+
$sqlite3_stmt = $sqlite3_db.prepare('select * from foo')
|
29
|
+
$extralite_q = $extralite_db.prepare('select * from foo')
|
30
|
+
end
|
31
|
+
|
32
|
+
def sqlite3_run(count)
|
33
|
+
results = $sqlite3_stmt.execute
|
34
|
+
raise unless results.to_a.size == count
|
35
|
+
end
|
36
|
+
|
37
|
+
def extralite_run(count)
|
38
|
+
results = $extralite_q.to_a
|
39
|
+
raise unless results.size == count
|
40
|
+
end
|
41
|
+
|
42
|
+
[10, 1000, 100000].each do |c|
|
43
|
+
puts "Record count: #{c}"
|
44
|
+
|
45
|
+
prepare_database(c)
|
46
|
+
|
47
|
+
bm = Benchmark.ips do |x|
|
48
|
+
x.config(:time => 5, :warmup => 2)
|
49
|
+
|
50
|
+
x.report("sqlite3") { sqlite3_run(c) }
|
51
|
+
x.report("extralite") { extralite_run(c) }
|
52
|
+
|
53
|
+
x.compare!
|
54
|
+
end
|
55
|
+
puts;
|
56
|
+
bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
|
57
|
+
puts;
|
58
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
require 'date'
|
6
|
+
require 'tempfile'
|
7
|
+
|
8
|
+
class ChangesetTest < MiniTest::Test
|
9
|
+
def setup
|
10
|
+
@db = Extralite::Database.new(':memory:')
|
11
|
+
skip if !@db.respond_to?(:track_changes)
|
12
|
+
|
13
|
+
@db.execute('create table if not exists t (x integer primary key, y, z)')
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_each
|
17
|
+
changeset = Extralite::Changeset.new
|
18
|
+
|
19
|
+
changeset.track(@db, [nil]) do
|
20
|
+
@db.execute('insert into t values (1, 2, 3)')
|
21
|
+
end
|
22
|
+
changes = []
|
23
|
+
changeset.each { |*o| changes << o }
|
24
|
+
assert_equal [
|
25
|
+
[:insert, 't', nil, [1, 2, 3]]
|
26
|
+
], changes
|
27
|
+
|
28
|
+
|
29
|
+
changeset.track(@db, [nil]) do
|
30
|
+
@db.execute('update t set y = 22 where x = 1')
|
31
|
+
end
|
32
|
+
changes = []
|
33
|
+
changeset.each { |*o| changes << o }
|
34
|
+
assert_equal [
|
35
|
+
[:update, 't', [1, 2, nil], [nil, 22, nil]]
|
36
|
+
], changes
|
37
|
+
|
38
|
+
|
39
|
+
changeset.track(@db, [nil]) do
|
40
|
+
@db.execute('delete from t where x = 1')
|
41
|
+
end
|
42
|
+
changes = []
|
43
|
+
changeset.each { |*o| changes << o }
|
44
|
+
assert_equal [
|
45
|
+
[:delete, 't', [1, 22, 3], nil]
|
46
|
+
], changes
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_to_a
|
50
|
+
changeset = Extralite::Changeset.new
|
51
|
+
|
52
|
+
changeset.track(@db, [nil]) do
|
53
|
+
@db.execute('insert into t values (1, 2, 3)')
|
54
|
+
@db.execute('insert into t values (4, 5, 6)')
|
55
|
+
end
|
56
|
+
assert_equal [
|
57
|
+
[:insert, 't', nil, [1, 2, 3]],
|
58
|
+
[:insert, 't', nil, [4, 5, 6]]
|
59
|
+
], changeset.to_a
|
60
|
+
|
61
|
+
|
62
|
+
changeset.track(@db, [nil]) do
|
63
|
+
@db.execute('update t set y = 22.22 where z < 10')
|
64
|
+
end
|
65
|
+
assert_equal [
|
66
|
+
[:update, 't', [1, 2, nil], [nil, 22.22, nil]],
|
67
|
+
[:update, 't', [4, 5, nil], [nil, 22.22, nil]]
|
68
|
+
], changeset.to_a
|
69
|
+
|
70
|
+
|
71
|
+
changeset.track(@db, [nil]) do
|
72
|
+
@db.execute('delete from t where x = 1')
|
73
|
+
end
|
74
|
+
assert_equal [
|
75
|
+
[:delete, 't', [1, 22.22, 3], nil]
|
76
|
+
], changeset.to_a
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_apply
|
80
|
+
changeset = Extralite::Changeset.new
|
81
|
+
|
82
|
+
changeset.track(@db, [:t]) do
|
83
|
+
@db.execute('insert into t values (1, 2, 3)')
|
84
|
+
@db.execute('insert into t values (4, 5, 6)')
|
85
|
+
end
|
86
|
+
|
87
|
+
db2 = Extralite::Database.new(':memory:')
|
88
|
+
db2.execute('create table if not exists t (x integer primary key, y, z)')
|
89
|
+
|
90
|
+
changeset.apply(db2)
|
91
|
+
|
92
|
+
assert_equal [
|
93
|
+
{ x: 1, y: 2, z: 3 },
|
94
|
+
{ x: 4, y: 5, z: 6 }
|
95
|
+
], db2.query('select * from t')
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_invert
|
99
|
+
changeset = Extralite::Changeset.new
|
100
|
+
|
101
|
+
changeset.track(@db, [:t]) do
|
102
|
+
@db.execute('insert into t values (1, 2, 3)')
|
103
|
+
@db.execute('insert into t values (4, 5, 6)')
|
104
|
+
end
|
105
|
+
|
106
|
+
db2 = Extralite::Database.new(':memory:')
|
107
|
+
db2.execute('create table if not exists t (x integer primary key, y, z)')
|
108
|
+
|
109
|
+
changeset.apply(db2)
|
110
|
+
|
111
|
+
assert_equal [
|
112
|
+
{ x: 1, y: 2, z: 3 },
|
113
|
+
{ x: 4, y: 5, z: 6 }
|
114
|
+
], db2.query('select * from t')
|
115
|
+
|
116
|
+
db2.execute('insert into t values (7, 8, 9)')
|
117
|
+
inverted = changeset.invert
|
118
|
+
|
119
|
+
assert_kind_of Extralite::Changeset, inverted
|
120
|
+
refute_equal inverted, changeset
|
121
|
+
|
122
|
+
assert_equal [
|
123
|
+
[:delete, 't', [1, 2, 3], nil],
|
124
|
+
[:delete, 't', [4, 5, 6], nil],
|
125
|
+
], inverted.to_a
|
126
|
+
|
127
|
+
inverted.apply(@db)
|
128
|
+
assert_equal [], @db.query('select * from t')
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_blob
|
132
|
+
changeset = Extralite::Changeset.new
|
133
|
+
assert_equal "", changeset.to_blob
|
134
|
+
|
135
|
+
changeset.track(@db, [:t]) do
|
136
|
+
@db.execute('insert into t values (1, 2, 3)')
|
137
|
+
@db.execute('insert into t values (4, 5, 6)')
|
138
|
+
end
|
139
|
+
|
140
|
+
blob = changeset.to_blob
|
141
|
+
assert_kind_of String, blob
|
142
|
+
assert_equal Encoding::ASCII_8BIT, blob.encoding
|
143
|
+
assert !blob.empty?
|
144
|
+
|
145
|
+
c2 = Extralite::Changeset.new
|
146
|
+
c2.load(blob)
|
147
|
+
assert_equal c2.to_blob, blob
|
148
|
+
|
149
|
+
assert_equal [
|
150
|
+
[:insert, 't', nil, [1, 2, 3]],
|
151
|
+
[:insert, 't', nil, [4, 5, 6]]
|
152
|
+
], c2.to_a
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_empty_blob
|
156
|
+
changeset = Extralite::Changeset.new
|
157
|
+
changeset.load('')
|
158
|
+
|
159
|
+
assert_raises(Extralite::Error) { changeset.to_a }
|
160
|
+
end
|
161
|
+
end
|
data/test/test_database.rb
CHANGED
@@ -896,6 +896,53 @@ class DatabaseTest < MiniTest::Test
|
|
896
896
|
assert_kind_of RuntimeError, exception
|
897
897
|
assert_equal 'bar', exception.message
|
898
898
|
end
|
899
|
+
|
900
|
+
def test_database_transaction_rollback!
|
901
|
+
db = Extralite::Database.new(':memory:')
|
902
|
+
db.execute('create table foo(x)')
|
903
|
+
|
904
|
+
exception = nil
|
905
|
+
begin
|
906
|
+
db.transaction do
|
907
|
+
db.execute('insert into foo values (42)')
|
908
|
+
db.rollback!
|
909
|
+
end
|
910
|
+
rescue => e
|
911
|
+
exception = e
|
912
|
+
end
|
913
|
+
|
914
|
+
assert_equal [], db.query('select * from foo')
|
915
|
+
assert_nil exception
|
916
|
+
end
|
917
|
+
|
918
|
+
def test_database_savepoint
|
919
|
+
db = Extralite::Database.new(':memory:')
|
920
|
+
db.execute('create table foo(x)')
|
921
|
+
|
922
|
+
db.transaction do
|
923
|
+
assert_equal [], db.query('select * from foo')
|
924
|
+
|
925
|
+
db.execute('insert into foo values (42)')
|
926
|
+
assert_equal [42], db.query_single_column('select x from foo')
|
927
|
+
|
928
|
+
db.savepoint(:a)
|
929
|
+
|
930
|
+
db.execute('insert into foo values (43)')
|
931
|
+
assert_equal [42, 43], db.query_single_column('select x from foo')
|
932
|
+
|
933
|
+
db.savepoint(:b)
|
934
|
+
|
935
|
+
db.execute('insert into foo values (44)')
|
936
|
+
assert_equal [42, 43, 44], db.query_single_column('select x from foo')
|
937
|
+
|
938
|
+
db.rollback_to(:b)
|
939
|
+
assert_equal [42, 43], db.query_single_column('select x from foo')
|
940
|
+
|
941
|
+
db.release(:a)
|
942
|
+
|
943
|
+
assert_equal [42, 43], db.query_single_column('select x from foo')
|
944
|
+
end
|
945
|
+
end
|
899
946
|
end
|
900
947
|
|
901
948
|
class ScenarioTest < MiniTest::Test
|
@@ -1053,7 +1100,7 @@ class BackupTest < MiniTest::Test
|
|
1053
1100
|
end
|
1054
1101
|
end
|
1055
1102
|
|
1056
|
-
class
|
1103
|
+
class ConcurrencyTest < Minitest::Test
|
1057
1104
|
def setup
|
1058
1105
|
@sql = <<~SQL
|
1059
1106
|
WITH RECURSIVE r(i) AS (
|
@@ -1148,6 +1195,159 @@ class GVLReleaseThresholdTest < Minitest::Test
|
|
1148
1195
|
db.gvl_release_threshold = nil
|
1149
1196
|
assert_equal 1000, db.gvl_release_threshold
|
1150
1197
|
end
|
1198
|
+
|
1199
|
+
def test_progress_handler_simple
|
1200
|
+
db = Extralite::Database.new(':memory:')
|
1201
|
+
|
1202
|
+
buf = []
|
1203
|
+
db.on_progress(1) { buf << :progress }
|
1204
|
+
|
1205
|
+
result = db.query_single_row('select 1 as a, 2 as b, 3 as c')
|
1206
|
+
assert_equal({ a: 1, b: 2, c: 3 }, result)
|
1207
|
+
assert_in_range 5..7, buf.size
|
1208
|
+
|
1209
|
+
buf = []
|
1210
|
+
db.on_progress(2) { buf << :progress }
|
1211
|
+
|
1212
|
+
result = db.query_single_row('select 1 as a, 2 as b, 3 as c')
|
1213
|
+
assert_equal({ a: 1, b: 2, c: 3 }, result)
|
1214
|
+
assert_in_range 2..4, buf.size
|
1215
|
+
end
|
1216
|
+
|
1217
|
+
LONG_QUERY = <<~SQL
|
1218
|
+
WITH RECURSIVE
|
1219
|
+
fibo (curr, next)
|
1220
|
+
AS
|
1221
|
+
( SELECT 1,1
|
1222
|
+
UNION ALL
|
1223
|
+
SELECT next, curr + next FROM fibo
|
1224
|
+
LIMIT 10000000 )
|
1225
|
+
SELECT curr, next FROM fibo LIMIT 1 OFFSET 10000000-1;
|
1226
|
+
SQL
|
1227
|
+
|
1228
|
+
def test_progress_handler_timeout_interrupt
|
1229
|
+
db = Extralite::Database.new(':memory:')
|
1230
|
+
t0 = Time.now
|
1231
|
+
db.on_progress(1000) do
|
1232
|
+
Thread.pass
|
1233
|
+
db.interrupt if Time.now - t0 >= 0.2
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
q = db.prepare(LONG_QUERY)
|
1237
|
+
result = nil
|
1238
|
+
err = nil
|
1239
|
+
begin
|
1240
|
+
result = q.next
|
1241
|
+
rescue => e
|
1242
|
+
err = e
|
1243
|
+
end
|
1244
|
+
t1 = Time.now
|
1245
|
+
|
1246
|
+
assert_nil result
|
1247
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1248
|
+
assert_kind_of Extralite::InterruptError, err
|
1249
|
+
|
1250
|
+
# try a second time, just to make sure no undefined state is left behind
|
1251
|
+
t0 = Time.now
|
1252
|
+
q = db.prepare(LONG_QUERY)
|
1253
|
+
result = nil
|
1254
|
+
err = nil
|
1255
|
+
begin
|
1256
|
+
result = q.next
|
1257
|
+
rescue => e
|
1258
|
+
err = e
|
1259
|
+
end
|
1260
|
+
t1 = Time.now
|
1261
|
+
|
1262
|
+
assert_nil result
|
1263
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1264
|
+
assert_kind_of Extralite::InterruptError, err
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
class CustomTimeoutError < RuntimeError
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
def test_progress_handler_timeout_raise
|
1271
|
+
db = Extralite::Database.new(':memory:')
|
1272
|
+
t0 = Time.now
|
1273
|
+
db.on_progress(1000) do
|
1274
|
+
Thread.pass
|
1275
|
+
raise CustomTimeoutError if Time.now - t0 >= 0.2
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
q = db.prepare(LONG_QUERY)
|
1279
|
+
result = nil
|
1280
|
+
err = nil
|
1281
|
+
begin
|
1282
|
+
result = q.next
|
1283
|
+
rescue => e
|
1284
|
+
err = e
|
1285
|
+
end
|
1286
|
+
t1 = Time.now
|
1287
|
+
|
1288
|
+
assert_nil result
|
1289
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1290
|
+
assert_kind_of CustomTimeoutError, err
|
1291
|
+
|
1292
|
+
# try a second time, just to make sure no undefined state is left behind
|
1293
|
+
t0 = Time.now
|
1294
|
+
q = db.prepare(LONG_QUERY)
|
1295
|
+
result = nil
|
1296
|
+
err = nil
|
1297
|
+
begin
|
1298
|
+
result = q.next
|
1299
|
+
rescue => e
|
1300
|
+
err = e
|
1301
|
+
end
|
1302
|
+
t1 = Time.now
|
1303
|
+
|
1304
|
+
assert_nil result
|
1305
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1306
|
+
assert_kind_of CustomTimeoutError, err
|
1307
|
+
end
|
1308
|
+
|
1309
|
+
def test_progress_handler_busy_timeout
|
1310
|
+
fn = Tempfile.new('extralite_test_progress_handler_busy_timeout').path
|
1311
|
+
db1 = Extralite::Database.new(fn)
|
1312
|
+
db2 = Extralite::Database.new(fn)
|
1313
|
+
|
1314
|
+
db1.query('begin exclusive')
|
1315
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
1316
|
+
|
1317
|
+
t0 = Time.now
|
1318
|
+
db2.on_progress(1000) do
|
1319
|
+
Thread.pass
|
1320
|
+
raise CustomTimeoutError if Time.now - t0 >= 0.2
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
result = nil
|
1324
|
+
err = nil
|
1325
|
+
begin
|
1326
|
+
result = db2.execute('begin exclusive')
|
1327
|
+
rescue => e
|
1328
|
+
err = e
|
1329
|
+
end
|
1330
|
+
t1 = Time.now
|
1331
|
+
|
1332
|
+
assert_nil result
|
1333
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1334
|
+
assert_kind_of CustomTimeoutError, err
|
1335
|
+
|
1336
|
+
# Try a second time, to ensure no undefined state remains behind
|
1337
|
+
t0 = Time.now
|
1338
|
+
result = nil
|
1339
|
+
err = nil
|
1340
|
+
begin
|
1341
|
+
result = db2.execute('begin exclusive')
|
1342
|
+
rescue => e
|
1343
|
+
err = e
|
1344
|
+
end
|
1345
|
+
t1 = Time.now
|
1346
|
+
|
1347
|
+
assert_nil result
|
1348
|
+
assert_equal 1, ((t1 - t0) * 5).round.to_i
|
1349
|
+
assert_kind_of CustomTimeoutError, err
|
1350
|
+
end
|
1151
1351
|
end
|
1152
1352
|
|
1153
1353
|
class RactorTest < Minitest::Test
|
data/test/test_query.rb
CHANGED
@@ -37,6 +37,11 @@ class QueryTest < MiniTest::Test
|
|
37
37
|
results = q.bind(foo: 'c').to_a_ary
|
38
38
|
|
39
39
|
assert_equal [['a', 'b', 'c']], results
|
40
|
+
|
41
|
+
# try again with the same parameters
|
42
|
+
results = q.to_a_ary
|
43
|
+
|
44
|
+
assert_equal [['a', 'b', 'c']], results
|
40
45
|
end
|
41
46
|
|
42
47
|
def test_query_next
|
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.6'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -103,6 +103,7 @@ files:
|
|
103
103
|
- Rakefile
|
104
104
|
- TODO.md
|
105
105
|
- bin/update_sqlite_source
|
106
|
+
- ext/extralite/changeset.c
|
106
107
|
- ext/extralite/common.c
|
107
108
|
- ext/extralite/database.c
|
108
109
|
- ext/extralite/extconf-bundle.rb
|
@@ -128,8 +129,9 @@ files:
|
|
128
129
|
- test/issue-59.rb
|
129
130
|
- test/perf_ary.rb
|
130
131
|
- test/perf_hash.rb
|
131
|
-
- test/
|
132
|
+
- test/perf_hash_prepared.rb
|
132
133
|
- test/run.rb
|
134
|
+
- test/test_changeset.rb
|
133
135
|
- test/test_database.rb
|
134
136
|
- test/test_extralite.rb
|
135
137
|
- test/test_iterator.rb
|
@@ -162,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
164
|
- !ruby/object:Gem::Version
|
163
165
|
version: '0'
|
164
166
|
requirements: []
|
165
|
-
rubygems_version: 3.
|
167
|
+
rubygems_version: 3.5.3
|
166
168
|
signing_key:
|
167
169
|
specification_version: 4
|
168
170
|
summary: Extra-lightweight SQLite3 wrapper for Ruby
|
data/test/perf_prepared.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'bundler/inline'
|
4
|
-
|
5
|
-
gemfile do
|
6
|
-
source 'https://rubygems.org'
|
7
|
-
gem 'extralite', path: '..'
|
8
|
-
gem 'sqlite3'
|
9
|
-
gem 'benchmark-ips'
|
10
|
-
end
|
11
|
-
|
12
|
-
require 'benchmark/ips'
|
13
|
-
require 'fileutils'
|
14
|
-
|
15
|
-
DB_PATH = '/tmp/extralite_sqlite3_perf.db'
|
16
|
-
|
17
|
-
def prepare_database(count)
|
18
|
-
FileUtils.rm(DB_PATH) rescue nil
|
19
|
-
db = Extralite::Database.new(DB_PATH)
|
20
|
-
db.query('create table foo ( a integer primary key, b text )')
|
21
|
-
db.query('begin')
|
22
|
-
count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
23
|
-
db.query('commit')
|
24
|
-
end
|
25
|
-
|
26
|
-
def sqlite3_prepare
|
27
|
-
db = SQLite3::Database.new(DB_PATH, :results_as_hash => true)
|
28
|
-
db.prepare('select * from foo')
|
29
|
-
end
|
30
|
-
|
31
|
-
def sqlite3_run(stmt, count)
|
32
|
-
# db = SQLite3::Database.new(DB_PATH, :results_as_hash => true)
|
33
|
-
results = stmt.execute.to_a
|
34
|
-
raise unless results.size == count
|
35
|
-
end
|
36
|
-
|
37
|
-
def extralite_prepare
|
38
|
-
db = Extralite::Database.new(DB_PATH)
|
39
|
-
db.prepare('select * from foo')
|
40
|
-
end
|
41
|
-
|
42
|
-
def extralite_run(query, count)
|
43
|
-
# db = Extralite::Database.new(DB_PATH)
|
44
|
-
results = query.to_a
|
45
|
-
raise unless results.size == count
|
46
|
-
end
|
47
|
-
|
48
|
-
[10, 1000, 100000].each do |c|
|
49
|
-
puts; puts; puts "Record count: #{c}"
|
50
|
-
|
51
|
-
prepare_database(c)
|
52
|
-
|
53
|
-
sqlite3_stmt = sqlite3_prepare
|
54
|
-
extralite_stmt = extralite_prepare
|
55
|
-
|
56
|
-
Benchmark.ips do |x|
|
57
|
-
x.config(:time => 3, :warmup => 1)
|
58
|
-
|
59
|
-
x.report("sqlite3") { sqlite3_run(sqlite3_stmt, c) }
|
60
|
-
x.report("extralite") { extralite_run(extralite_stmt, c) }
|
61
|
-
|
62
|
-
x.compare!
|
63
|
-
end
|
64
|
-
end
|