extralite 2.4 → 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/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +2 -12
- data/CHANGELOG.md +49 -10
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +876 -217
- data/TODO.md +2 -3
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +226 -19
- data/ext/extralite/database.c +339 -23
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +31 -27
- data/ext/extralite/extralite.h +25 -5
- data/ext/extralite/extralite_ext.c +10 -0
- data/ext/extralite/iterator.c +8 -3
- data/ext/extralite/query.c +222 -22
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +64 -8
- data/test/helper.rb +8 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- 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 +672 -13
- data/test/test_query.rb +367 -2
- metadata +10 -5
- data/test/perf_prepared.rb +0 -64
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
|