extralite-bundle 2.4 → 2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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/ext/sqlite3/sqlite3.c +5420 -2501
- data/ext/sqlite3/sqlite3.h +73 -18
- 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/ext/sqlite3/sqlite3.h
CHANGED
@@ -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.
|
150
|
-
#define SQLITE_VERSION_NUMBER
|
151
|
-
#define SQLITE_SOURCE_ID "
|
149
|
+
#define SQLITE_VERSION "3.45.0"
|
150
|
+
#define SQLITE_VERSION_NUMBER 3045000
|
151
|
+
#define SQLITE_SOURCE_ID "2024-01-15 17:01:13 1066602b2b1976fe58b5150777cced894af17c803e068f5918390d6915b46e1d"
|
152
152
|
|
153
153
|
/*
|
154
154
|
** CAPI3REF: Run-Time Library Version Numbers
|
@@ -3954,15 +3954,17 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename);
|
|
3954
3954
|
** </ul>
|
3955
3955
|
**
|
3956
3956
|
** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
|
3957
|
-
** text that describes the error, as either UTF-8 or UTF-16 respectively
|
3957
|
+
** text that describes the error, as either UTF-8 or UTF-16 respectively,
|
3958
|
+
** or NULL if no error message is available.
|
3958
3959
|
** (See how SQLite handles [invalid UTF] for exceptions to this rule.)
|
3959
3960
|
** ^(Memory to hold the error message string is managed internally.
|
3960
3961
|
** The application does not need to worry about freeing the result.
|
3961
3962
|
** However, the error string might be overwritten or deallocated by
|
3962
3963
|
** subsequent calls to other SQLite interface functions.)^
|
3963
3964
|
**
|
3964
|
-
** ^The sqlite3_errstr() interface returns the English-language text
|
3965
|
-
** that describes the [result code], as UTF-8
|
3965
|
+
** ^The sqlite3_errstr(E) interface returns the English-language text
|
3966
|
+
** that describes the [result code] E, as UTF-8, or NULL if E is not an
|
3967
|
+
** result code for which a text error message is available.
|
3966
3968
|
** ^(Memory to hold the error message string is managed internally
|
3967
3969
|
** and must not be freed by the application)^.
|
3968
3970
|
**
|
@@ -8037,9 +8039,11 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
|
|
8037
8039
|
**
|
8038
8040
|
** ^(Some systems (for example, Windows 95) do not support the operation
|
8039
8041
|
** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try()
|
8040
|
-
** will always return SQLITE_BUSY.
|
8041
|
-
** sqlite3_mutex_try() as an optimization so this is acceptable
|
8042
|
-
** behavior.
|
8042
|
+
** will always return SQLITE_BUSY. In most cases the SQLite core only uses
|
8043
|
+
** sqlite3_mutex_try() as an optimization, so this is acceptable
|
8044
|
+
** behavior. The exceptions are unix builds that set the
|
8045
|
+
** SQLITE_ENABLE_SETLK_TIMEOUT build option. In that case a working
|
8046
|
+
** sqlite3_mutex_try() is required.)^
|
8043
8047
|
**
|
8044
8048
|
** ^The sqlite3_mutex_leave() routine exits a mutex that was
|
8045
8049
|
** previously entered by the same thread. The behavior
|
@@ -8298,6 +8302,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
|
8298
8302
|
#define SQLITE_TESTCTRL_ASSERT 12
|
8299
8303
|
#define SQLITE_TESTCTRL_ALWAYS 13
|
8300
8304
|
#define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */
|
8305
|
+
#define SQLITE_TESTCTRL_JSON_SELFCHECK 14
|
8301
8306
|
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
|
8302
8307
|
#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
|
8303
8308
|
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
|
@@ -12811,8 +12816,11 @@ struct Fts5PhraseIter {
|
|
12811
12816
|
** created with the "columnsize=0" option.
|
12812
12817
|
**
|
12813
12818
|
** xColumnText:
|
12814
|
-
**
|
12815
|
-
**
|
12819
|
+
** If parameter iCol is less than zero, or greater than or equal to the
|
12820
|
+
** number of columns in the table, SQLITE_RANGE is returned.
|
12821
|
+
**
|
12822
|
+
** Otherwise, this function attempts to retrieve the text of column iCol of
|
12823
|
+
** the current document. If successful, (*pz) is set to point to a buffer
|
12816
12824
|
** containing the text in utf-8 encoding, (*pn) is set to the size in bytes
|
12817
12825
|
** (not characters) of the buffer and SQLITE_OK is returned. Otherwise,
|
12818
12826
|
** if an error occurs, an SQLite error code is returned and the final values
|
@@ -12822,8 +12830,10 @@ struct Fts5PhraseIter {
|
|
12822
12830
|
** Returns the number of phrases in the current query expression.
|
12823
12831
|
**
|
12824
12832
|
** xPhraseSize:
|
12825
|
-
**
|
12826
|
-
**
|
12833
|
+
** If parameter iCol is less than zero, or greater than or equal to the
|
12834
|
+
** number of phrases in the current query, as returned by xPhraseCount,
|
12835
|
+
** 0 is returned. Otherwise, this function returns the number of tokens in
|
12836
|
+
** phrase iPhrase of the query. Phrases are numbered starting from zero.
|
12827
12837
|
**
|
12828
12838
|
** xInstCount:
|
12829
12839
|
** Set *pnInst to the total number of occurrences of all phrases within
|
@@ -12839,12 +12849,13 @@ struct Fts5PhraseIter {
|
|
12839
12849
|
** Query for the details of phrase match iIdx within the current row.
|
12840
12850
|
** Phrase matches are numbered starting from zero, so the iIdx argument
|
12841
12851
|
** should be greater than or equal to zero and smaller than the value
|
12842
|
-
** output by xInstCount().
|
12852
|
+
** output by xInstCount(). If iIdx is less than zero or greater than
|
12853
|
+
** or equal to the value returned by xInstCount(), SQLITE_RANGE is returned.
|
12843
12854
|
**
|
12844
|
-
**
|
12855
|
+
** Otherwise, output parameter *piPhrase is set to the phrase number, *piCol
|
12845
12856
|
** to the column in which it occurs and *piOff the token offset of the
|
12846
|
-
** first token of the phrase.
|
12847
|
-
** code (i.e. SQLITE_NOMEM) if an error occurs.
|
12857
|
+
** first token of the phrase. SQLITE_OK is returned if successful, or an
|
12858
|
+
** error code (i.e. SQLITE_NOMEM) if an error occurs.
|
12848
12859
|
**
|
12849
12860
|
** This API can be quite slow if used with an FTS5 table created with the
|
12850
12861
|
** "detail=none" or "detail=column" option.
|
@@ -12870,6 +12881,10 @@ struct Fts5PhraseIter {
|
|
12870
12881
|
** Invoking Api.xUserData() returns a copy of the pointer passed as
|
12871
12882
|
** the third argument to pUserData.
|
12872
12883
|
**
|
12884
|
+
** If parameter iPhrase is less than zero, or greater than or equal to
|
12885
|
+
** the number of phrases in the query, as returned by xPhraseCount(),
|
12886
|
+
** this function returns SQLITE_RANGE.
|
12887
|
+
**
|
12873
12888
|
** If the callback function returns any value other than SQLITE_OK, the
|
12874
12889
|
** query is abandoned and the xQueryPhrase function returns immediately.
|
12875
12890
|
** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK.
|
@@ -12984,9 +12999,42 @@ struct Fts5PhraseIter {
|
|
12984
12999
|
**
|
12985
13000
|
** xPhraseNextColumn()
|
12986
13001
|
** See xPhraseFirstColumn above.
|
13002
|
+
**
|
13003
|
+
** xQueryToken(pFts5, iPhrase, iToken, ppToken, pnToken)
|
13004
|
+
** This is used to access token iToken of phrase iPhrase of the current
|
13005
|
+
** query. Before returning, output parameter *ppToken is set to point
|
13006
|
+
** to a buffer containing the requested token, and *pnToken to the
|
13007
|
+
** size of this buffer in bytes.
|
13008
|
+
**
|
13009
|
+
** If iPhrase or iToken are less than zero, or if iPhrase is greater than
|
13010
|
+
** or equal to the number of phrases in the query as reported by
|
13011
|
+
** xPhraseCount(), or if iToken is equal to or greater than the number of
|
13012
|
+
** tokens in the phrase, SQLITE_RANGE is returned and *ppToken and *pnToken
|
13013
|
+
are both zeroed.
|
13014
|
+
**
|
13015
|
+
** The output text is not a copy of the query text that specified the
|
13016
|
+
** token. It is the output of the tokenizer module. For tokendata=1
|
13017
|
+
** tables, this includes any embedded 0x00 and trailing data.
|
13018
|
+
**
|
13019
|
+
** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken)
|
13020
|
+
** This is used to access token iToken of phrase hit iIdx within the
|
13021
|
+
** current row. If iIdx is less than zero or greater than or equal to the
|
13022
|
+
** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
|
13023
|
+
** output variable (*ppToken) is set to point to a buffer containing the
|
13024
|
+
** matching document token, and (*pnToken) to the size of that buffer in
|
13025
|
+
** bytes. This API is not available if the specified token matches a
|
13026
|
+
** prefix query term. In that case both output variables are always set
|
13027
|
+
** to 0.
|
13028
|
+
**
|
13029
|
+
** The output text is not a copy of the document text that was tokenized.
|
13030
|
+
** It is the output of the tokenizer module. For tokendata=1 tables, this
|
13031
|
+
** includes any embedded 0x00 and trailing data.
|
13032
|
+
**
|
13033
|
+
** This API can be quite slow if used with an FTS5 table created with the
|
13034
|
+
** "detail=none" or "detail=column" option.
|
12987
13035
|
*/
|
12988
13036
|
struct Fts5ExtensionApi {
|
12989
|
-
int iVersion; /* Currently always set to
|
13037
|
+
int iVersion; /* Currently always set to 3 */
|
12990
13038
|
|
12991
13039
|
void *(*xUserData)(Fts5Context*);
|
12992
13040
|
|
@@ -13021,6 +13069,13 @@ struct Fts5ExtensionApi {
|
|
13021
13069
|
|
13022
13070
|
int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*);
|
13023
13071
|
void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol);
|
13072
|
+
|
13073
|
+
/* Below this point are iVersion>=3 only */
|
13074
|
+
int (*xQueryToken)(Fts5Context*,
|
13075
|
+
int iPhrase, int iToken,
|
13076
|
+
const char **ppToken, int *pnToken
|
13077
|
+
);
|
13078
|
+
int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*);
|
13024
13079
|
};
|
13025
13080
|
|
13026
13081
|
/*
|
data/gemspec.rb
CHANGED
@@ -16,7 +16,7 @@ def common_spec(s)
|
|
16
16
|
s.rdoc_options = ["--title", "extralite", "--main", "README.md"]
|
17
17
|
s.extra_rdoc_files = ["README.md"]
|
18
18
|
s.require_paths = ["lib"]
|
19
|
-
s.required_ruby_version = '>=
|
19
|
+
s.required_ruby_version = '>= 3.0'
|
20
20
|
|
21
21
|
s.add_development_dependency 'rake-compiler', '1.1.6'
|
22
22
|
s.add_development_dependency 'minitest', '5.15.0'
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -33,16 +33,20 @@ module Extralite
|
|
33
33
|
class Database
|
34
34
|
# @!visibility private
|
35
35
|
TABLES_SQL = <<~SQL
|
36
|
-
SELECT name FROM sqlite_master
|
36
|
+
SELECT name FROM %<db>s.sqlite_master
|
37
37
|
WHERE type ='table'
|
38
|
-
AND name NOT LIKE 'sqlite_
|
38
|
+
AND name NOT LIKE 'sqlite_%%';
|
39
39
|
SQL
|
40
40
|
|
41
|
-
|
41
|
+
alias_method :execute_multi, :batch_execute
|
42
|
+
|
43
|
+
# Returns the list of currently defined tables. If a database name is given,
|
44
|
+
# returns the list of tables for the relevant attached database.
|
42
45
|
#
|
46
|
+
# @param db [String] name of attached database
|
43
47
|
# @return [Array] list of tables
|
44
|
-
def tables
|
45
|
-
query_single_column(TABLES_SQL)
|
48
|
+
def tables(db = 'main')
|
49
|
+
query_single_column(format(TABLES_SQL, db: db))
|
46
50
|
end
|
47
51
|
|
48
52
|
# Gets or sets one or more pragmas:
|
@@ -56,6 +60,11 @@ module Extralite
|
|
56
60
|
value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
|
57
61
|
end
|
58
62
|
|
63
|
+
# Error class used to roll back a transaction without propagating an
|
64
|
+
# exception.
|
65
|
+
class Rollback < Error
|
66
|
+
end
|
67
|
+
|
59
68
|
# Starts a transaction and runs the given block. If an exception is raised
|
60
69
|
# in the block, the transaction is rolled back. Otherwise, the transaction
|
61
70
|
# is commited after running the block.
|
@@ -72,13 +81,56 @@ module Extralite
|
|
72
81
|
|
73
82
|
abort = false
|
74
83
|
yield self
|
75
|
-
rescue
|
84
|
+
rescue => e
|
76
85
|
abort = true
|
77
|
-
raise
|
86
|
+
raise unless e.is_a?(Rollback)
|
78
87
|
ensure
|
79
88
|
execute(abort ? 'rollback' : 'commit')
|
80
89
|
end
|
81
90
|
|
91
|
+
# Creates a savepoint with the given name.
|
92
|
+
#
|
93
|
+
# @param name [String, Symbol] savepoint name
|
94
|
+
# @return [Extralite::Database] database
|
95
|
+
def savepoint(name)
|
96
|
+
execute "savepoint #{name}"
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
# Release a savepoint with the given name.
|
101
|
+
#
|
102
|
+
# @param name [String, Symbol] savepoint name
|
103
|
+
# @return [Extralite::Database] database
|
104
|
+
def release(name)
|
105
|
+
execute "release #{name}"
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# Rolls back changes to a savepoint with the given name.
|
110
|
+
#
|
111
|
+
# @param name [String, Symbol] savepoint name
|
112
|
+
# @return [Extralite::Database] database
|
113
|
+
def rollback_to(name)
|
114
|
+
execute "rollback to #{name}"
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# Rolls back the currently active transaction. This method should only be
|
119
|
+
# called from within a block passed to Database#transaction. This method
|
120
|
+
# raises a Extralite::Rollback exception, which will stop execution of the
|
121
|
+
# transaction block without propagating the exception.
|
122
|
+
#
|
123
|
+
# db.transaction do
|
124
|
+
# db.execute('insert into foo (42)')
|
125
|
+
# db.rollback!
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# @param name [String, Symbol] savepoint name
|
129
|
+
# @return [Extralite::Database] database
|
130
|
+
def rollback!
|
131
|
+
raise Rollback
|
132
|
+
end
|
133
|
+
|
82
134
|
private
|
83
135
|
|
84
136
|
def pragma_set(values)
|
@@ -87,7 +139,11 @@ module Extralite
|
|
87
139
|
end
|
88
140
|
|
89
141
|
def pragma_get(key)
|
90
|
-
|
142
|
+
query_single_value("pragma #{key}")
|
91
143
|
end
|
92
144
|
end
|
145
|
+
|
146
|
+
class Query
|
147
|
+
alias_method :execute_multi, :batch_execute
|
148
|
+
end
|
93
149
|
end
|
data/test/helper.rb
CHANGED
@@ -7,3 +7,11 @@ require 'minitest/autorun'
|
|
7
7
|
puts "sqlite3 version: #{Extralite.sqlite3_version}"
|
8
8
|
|
9
9
|
IS_LINUX = RUBY_PLATFORM =~ /linux/
|
10
|
+
SKIP_RACTOR_TESTS = !IS_LINUX || (RUBY_VERSION =~ /^3\.[01]/)
|
11
|
+
|
12
|
+
module Minitest::Assertions
|
13
|
+
def assert_in_range exp_range, act
|
14
|
+
msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
|
15
|
+
assert exp_range.include?(act), msg
|
16
|
+
end
|
17
|
+
end
|
data/test/issue-54.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "./lib/extralite"
|
4
|
+
|
5
|
+
puts 'Connecting to database...'
|
6
|
+
|
7
|
+
connection_1 = Extralite::Database.new("test.sqlite3")
|
8
|
+
puts "#{connection_1} connected"
|
9
|
+
connection_2 = Extralite::Database.new("test.sqlite3")
|
10
|
+
connection_2.busy_timeout = 0
|
11
|
+
puts "#{connection_2} connected"
|
12
|
+
|
13
|
+
[connection_1, connection_2].each do |connection|
|
14
|
+
puts "#{connection} beginning transaction..."
|
15
|
+
connection.execute "begin immediate transaction"
|
16
|
+
end
|
17
|
+
|
18
|
+
[connection_1, connection_2].each do |connection|
|
19
|
+
puts "#{connection} rolling back transaction..."
|
20
|
+
connection.execute "rollback transaction"
|
21
|
+
end
|
data/test/issue-59.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "./lib/extralite"
|
4
|
+
require "benchmark"
|
5
|
+
require "tempfile"
|
6
|
+
require "fileutils"
|
7
|
+
|
8
|
+
p sqlite_version: Extralite.sqlite3_version
|
9
|
+
|
10
|
+
N = (ENV['N'] || 1000).to_i
|
11
|
+
p N: N
|
12
|
+
|
13
|
+
fn1 = '/tmp/db1'
|
14
|
+
fn2 = '/tmp/db2'
|
15
|
+
|
16
|
+
FileUtils.rm(fn1) rescue nil
|
17
|
+
FileUtils.rm(fn2) rescue nil
|
18
|
+
|
19
|
+
p fn1: fn1
|
20
|
+
p fn2: fn2
|
21
|
+
|
22
|
+
db1 = Extralite::Database.new fn1
|
23
|
+
db1.execute "pragma journal_mode = wal;"
|
24
|
+
db1.transaction do
|
25
|
+
db1.execute "create table t1 ( a integer primary key, b text );"
|
26
|
+
values = N.times.map { |i| "#{i}-#{rand(1000)}" }
|
27
|
+
db1.execute_multi "insert into t1 ( b ) values ( ? );", values
|
28
|
+
|
29
|
+
p count: db1.query_single_value("select count(*) from t1")
|
30
|
+
p some_rows: db1.query("select * from t1 limit 5")
|
31
|
+
end
|
32
|
+
|
33
|
+
db2 = Extralite::Database.new fn2
|
34
|
+
db2.execute "pragma journal_mode = wal;"
|
35
|
+
db2.execute "attach '#{fn1}' as db1;"
|
36
|
+
db2.execute "create table t2 ( a integer primary key, b text );"
|
37
|
+
|
38
|
+
p main_tables: db2.tables
|
39
|
+
p db1_tables: db2.tables('db1')
|
40
|
+
|
41
|
+
overall = Benchmark.realtime do
|
42
|
+
t1 = Thread.new do
|
43
|
+
time1 = Benchmark.realtime do
|
44
|
+
db2.execute "create unique index db1.t1_b_unique on t1 (b);"
|
45
|
+
end
|
46
|
+
p({ indexing: time1 })
|
47
|
+
end
|
48
|
+
|
49
|
+
t2 = Thread.new do
|
50
|
+
time2 = Benchmark.realtime do
|
51
|
+
(N / 10000).times do |i|
|
52
|
+
values = 10000.times.map { |i| "#{i}-#{rand(1000)}" }
|
53
|
+
db2.transaction do
|
54
|
+
db2.execute_multi "insert into main.t2 ( b ) values ( ? );", values
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
p({ inserting: time2 })
|
59
|
+
p count_t2: db2.query_single_value("select count(*) from main.t2")
|
60
|
+
p some_rows_t2: db2.query("select * from main.t2 limit 5")
|
61
|
+
end
|
62
|
+
|
63
|
+
t1.join
|
64
|
+
t2.join
|
65
|
+
end
|
66
|
+
|
67
|
+
p({ overall: overall })
|
68
|
+
|
69
|
+
db1.close
|
70
|
+
db2.close
|
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
|