extralite-bundle 2.4 → 2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.44.2"
150
- #define SQLITE_VERSION_NUMBER 3044002
151
- #define SQLITE_SOURCE_ID "2023-11-24 11:41:44 ebead0e7230cd33bcec9f95d2183069565b9e709bf745c9b5db65cc0cbf92c0f"
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. The SQLite core only ever uses
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
- ** This function attempts to retrieve the text of column iCol of the
12815
- ** current document. If successful, (*pz) is set to point to a buffer
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
- ** Returns the number of tokens in phrase iPhrase of the query. Phrases
12826
- ** are numbered starting from zero.
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
- ** Usually, output parameter *piPhrase is set to the phrase number, *piCol
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. Returns SQLITE_OK if successful, or an error
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 2 */
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 = '>= 2.7'
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'
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.4'
3
+ VERSION = '2.6'
4
4
  end
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
- # Returns the list of currently defined tables.
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
- query("pragma #{key}")
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
- db.query('create table if not exists foo ( a integer primary key, b text )')
22
- db.query('delete from foo')
23
- db.query('begin')
24
- count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
25
- db.query('commit')
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
- db = SQLite3::Database.new(DB_PATH)
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
- db = Extralite::Database.new(DB_PATH)
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 => 3, :warmup => 1)
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
- db = Extralite::Database.new(DB_PATH)
20
- db.query('create table if not exists foo ( a integer primary key, b text )')
21
- db.query('delete from foo')
22
- db.query('begin')
23
- count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
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
- db = SQLite3::Database.new(DB_PATH, :results_as_hash => true)
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
- db = Extralite::Database.new(DB_PATH)
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 => 3, :warmup => 1)
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; 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