extralite-bundle 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.
@@ -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