extralite-bundle 2.3 → 2.5

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.0"
150
- #define SQLITE_VERSION_NUMBER 3044000
151
- #define SQLITE_SOURCE_ID "2023-11-01 11:23:50 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301"
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
  **
@@ -5573,13 +5575,27 @@ SQLITE_API int sqlite3_create_window_function(
5573
5575
  ** </dd>
5574
5576
  **
5575
5577
  ** [[SQLITE_SUBTYPE]] <dt>SQLITE_SUBTYPE</dt><dd>
5576
- ** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call
5578
+ ** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call
5577
5579
  ** [sqlite3_value_subtype()] to inspect the sub-types of its arguments.
5578
- ** Specifying this flag makes no difference for scalar or aggregate user
5579
- ** functions. However, if it is not specified for a user-defined window
5580
- ** function, then any sub-types belonging to arguments passed to the window
5581
- ** function may be discarded before the window function is called (i.e.
5582
- ** sqlite3_value_subtype() will always return 0).
5580
+ ** This flag instructs SQLite to omit some corner-case optimizations that
5581
+ ** might disrupt the operation of the [sqlite3_value_subtype()] function,
5582
+ ** causing it to return zero rather than the correct subtype().
5583
+ ** SQL functions that invokes [sqlite3_value_subtype()] should have this
5584
+ ** property. If the SQLITE_SUBTYPE property is omitted, then the return
5585
+ ** value from [sqlite3_value_subtype()] might sometimes be zero even though
5586
+ ** a non-zero subtype was specified by the function argument expression.
5587
+ **
5588
+ ** [[SQLITE_RESULT_SUBTYPE]] <dt>SQLITE_RESULT_SUBTYPE</dt><dd>
5589
+ ** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call
5590
+ ** [sqlite3_result_subtype()] to cause a sub-type to be associated with its
5591
+ ** result.
5592
+ ** Every function that invokes [sqlite3_result_subtype()] should have this
5593
+ ** property. If it does not, then the call to [sqlite3_result_subtype()]
5594
+ ** might become a no-op if the function is used as term in an
5595
+ ** [expression index]. On the other hand, SQL functions that never invoke
5596
+ ** [sqlite3_result_subtype()] should avoid setting this property, as the
5597
+ ** purpose of this property is to disable certain optimizations that are
5598
+ ** incompatible with subtypes.
5583
5599
  ** </dd>
5584
5600
  ** </dl>
5585
5601
  */
@@ -5587,6 +5603,7 @@ SQLITE_API int sqlite3_create_window_function(
5587
5603
  #define SQLITE_DIRECTONLY 0x000080000
5588
5604
  #define SQLITE_SUBTYPE 0x000100000
5589
5605
  #define SQLITE_INNOCUOUS 0x000200000
5606
+ #define SQLITE_RESULT_SUBTYPE 0x001000000
5590
5607
 
5591
5608
  /*
5592
5609
  ** CAPI3REF: Deprecated Functions
@@ -5783,6 +5800,12 @@ SQLITE_API int sqlite3_value_encoding(sqlite3_value*);
5783
5800
  ** information can be used to pass a limited amount of context from
5784
5801
  ** one SQL function to another. Use the [sqlite3_result_subtype()]
5785
5802
  ** routine to set the subtype for the return value of an SQL function.
5803
+ **
5804
+ ** Every [application-defined SQL function] that invoke this interface
5805
+ ** should include the [SQLITE_SUBTYPE] property in the text
5806
+ ** encoding argument when the function is [sqlite3_create_function|registered].
5807
+ ** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype()
5808
+ ** might return zero instead of the upstream subtype in some corner cases.
5786
5809
  */
5787
5810
  SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
5788
5811
 
@@ -5913,14 +5936,22 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
5913
5936
  ** <li> ^(when sqlite3_set_auxdata() is invoked again on the same
5914
5937
  ** parameter)^, or
5915
5938
  ** <li> ^(during the original sqlite3_set_auxdata() call when a memory
5916
- ** allocation error occurs.)^ </ul>
5939
+ ** allocation error occurs.)^
5940
+ ** <li> ^(during the original sqlite3_set_auxdata() call if the function
5941
+ ** is evaluated during query planning instead of during query execution,
5942
+ ** as sometimes happens with [SQLITE_ENABLE_STAT4].)^ </ul>
5917
5943
  **
5918
- ** Note the last bullet in particular. The destructor X in
5944
+ ** Note the last two bullets in particular. The destructor X in
5919
5945
  ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
5920
5946
  ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata()
5921
5947
  ** should be called near the end of the function implementation and the
5922
5948
  ** function implementation should not make any use of P after
5923
- ** sqlite3_set_auxdata() has been called.
5949
+ ** sqlite3_set_auxdata() has been called. Furthermore, a call to
5950
+ ** sqlite3_get_auxdata() that occurs immediately after a corresponding call
5951
+ ** to sqlite3_set_auxdata() might still return NULL if an out-of-memory
5952
+ ** condition occurred during the sqlite3_set_auxdata() call or if the
5953
+ ** function is being evaluated during query planning rather than during
5954
+ ** query execution.
5924
5955
  **
5925
5956
  ** ^(In practice, auxiliary data is preserved between function calls for
5926
5957
  ** function parameters that are compile-time constants, including literal
@@ -6194,6 +6225,20 @@ SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n);
6194
6225
  ** higher order bits are discarded.
6195
6226
  ** The number of subtype bytes preserved by SQLite might increase
6196
6227
  ** in future releases of SQLite.
6228
+ **
6229
+ ** Every [application-defined SQL function] that invokes this interface
6230
+ ** should include the [SQLITE_RESULT_SUBTYPE] property in its
6231
+ ** text encoding argument when the SQL function is
6232
+ ** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE]
6233
+ ** property is omitted from the function that invokes sqlite3_result_subtype(),
6234
+ ** then in some cases the sqlite3_result_subtype() might fail to set
6235
+ ** the result subtype.
6236
+ **
6237
+ ** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any
6238
+ ** SQL function that invokes the sqlite3_result_subtype() interface
6239
+ ** and that does not have the SQLITE_RESULT_SUBTYPE property will raise
6240
+ ** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1
6241
+ ** by default.
6197
6242
  */
6198
6243
  SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
6199
6244
 
@@ -7994,9 +8039,11 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
7994
8039
  **
7995
8040
  ** ^(Some systems (for example, Windows 95) do not support the operation
7996
8041
  ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try()
7997
- ** will always return SQLITE_BUSY. The SQLite core only ever uses
7998
- ** sqlite3_mutex_try() as an optimization so this is acceptable
7999
- ** 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.)^
8000
8047
  **
8001
8048
  ** ^The sqlite3_mutex_leave() routine exits a mutex that was
8002
8049
  ** previously entered by the same thread. The behavior
@@ -8255,6 +8302,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
8255
8302
  #define SQLITE_TESTCTRL_ASSERT 12
8256
8303
  #define SQLITE_TESTCTRL_ALWAYS 13
8257
8304
  #define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */
8305
+ #define SQLITE_TESTCTRL_JSON_SELFCHECK 14
8258
8306
  #define SQLITE_TESTCTRL_OPTIMIZATIONS 15
8259
8307
  #define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
8260
8308
  #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
@@ -12768,8 +12816,11 @@ struct Fts5PhraseIter {
12768
12816
  ** created with the "columnsize=0" option.
12769
12817
  **
12770
12818
  ** xColumnText:
12771
- ** This function attempts to retrieve the text of column iCol of the
12772
- ** 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
12773
12824
  ** containing the text in utf-8 encoding, (*pn) is set to the size in bytes
12774
12825
  ** (not characters) of the buffer and SQLITE_OK is returned. Otherwise,
12775
12826
  ** if an error occurs, an SQLite error code is returned and the final values
@@ -12779,8 +12830,10 @@ struct Fts5PhraseIter {
12779
12830
  ** Returns the number of phrases in the current query expression.
12780
12831
  **
12781
12832
  ** xPhraseSize:
12782
- ** Returns the number of tokens in phrase iPhrase of the query. Phrases
12783
- ** 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.
12784
12837
  **
12785
12838
  ** xInstCount:
12786
12839
  ** Set *pnInst to the total number of occurrences of all phrases within
@@ -12796,12 +12849,13 @@ struct Fts5PhraseIter {
12796
12849
  ** Query for the details of phrase match iIdx within the current row.
12797
12850
  ** Phrase matches are numbered starting from zero, so the iIdx argument
12798
12851
  ** should be greater than or equal to zero and smaller than the value
12799
- ** 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.
12800
12854
  **
12801
- ** Usually, output parameter *piPhrase is set to the phrase number, *piCol
12855
+ ** Otherwise, output parameter *piPhrase is set to the phrase number, *piCol
12802
12856
  ** to the column in which it occurs and *piOff the token offset of the
12803
- ** first token of the phrase. Returns SQLITE_OK if successful, or an error
12804
- ** 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.
12805
12859
  **
12806
12860
  ** This API can be quite slow if used with an FTS5 table created with the
12807
12861
  ** "detail=none" or "detail=column" option.
@@ -12827,6 +12881,10 @@ struct Fts5PhraseIter {
12827
12881
  ** Invoking Api.xUserData() returns a copy of the pointer passed as
12828
12882
  ** the third argument to pUserData.
12829
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
+ **
12830
12888
  ** If the callback function returns any value other than SQLITE_OK, the
12831
12889
  ** query is abandoned and the xQueryPhrase function returns immediately.
12832
12890
  ** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK.
@@ -12941,9 +12999,42 @@ struct Fts5PhraseIter {
12941
12999
  **
12942
13000
  ** xPhraseNextColumn()
12943
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.
12944
13035
  */
12945
13036
  struct Fts5ExtensionApi {
12946
- int iVersion; /* Currently always set to 2 */
13037
+ int iVersion; /* Currently always set to 3 */
12947
13038
 
12948
13039
  void *(*xUserData)(Fts5Context*);
12949
13040
 
@@ -12978,6 +13069,13 @@ struct Fts5ExtensionApi {
12978
13069
 
12979
13070
  int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*);
12980
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*);
12981
13079
  };
12982
13080
 
12983
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.3'
3
+ VERSION = '2.5'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -25,20 +25,28 @@ module Extralite
25
25
  class InterruptError < Error
26
26
  end
27
27
 
28
+ # An exception raised when an Extralite doesn't know how to bind a parameter to a query
29
+ class ParameterError < Error
30
+ end
31
+
28
32
  # An SQLite database
29
33
  class Database
30
34
  # @!visibility private
31
35
  TABLES_SQL = <<~SQL
32
- SELECT name FROM sqlite_master
36
+ SELECT name FROM %<db>s.sqlite_master
33
37
  WHERE type ='table'
34
- AND name NOT LIKE 'sqlite_%';
38
+ AND name NOT LIKE 'sqlite_%%';
35
39
  SQL
36
40
 
37
- # 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.
38
45
  #
46
+ # @param db [String] name of attached database
39
47
  # @return [Array] list of tables
40
- def tables
41
- query_single_column(TABLES_SQL)
48
+ def tables(db = 'main')
49
+ query_single_column(format(TABLES_SQL, db: db))
42
50
  end
43
51
 
44
52
  # Gets or sets one or more pragmas:
@@ -52,6 +60,29 @@ module Extralite
52
60
  value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
53
61
  end
54
62
 
63
+ # Starts a transaction and runs the given block. If an exception is raised
64
+ # in the block, the transaction is rolled back. Otherwise, the transaction
65
+ # is commited after running the block.
66
+ #
67
+ # db.transaction do
68
+ # db.execute('insert into foo values (1, 2, 3)')
69
+ # raise if db.query_single_value('select x from bar') > 42
70
+ # end
71
+ #
72
+ # @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive). Defaults to immediate.
73
+ # @return [Any] the given block's return value
74
+ def transaction(mode = :immediate)
75
+ execute "begin #{mode} transaction"
76
+
77
+ abort = false
78
+ yield self
79
+ rescue
80
+ abort = true
81
+ raise
82
+ ensure
83
+ execute(abort ? 'rollback' : 'commit')
84
+ end
85
+
55
86
  private
56
87
 
57
88
  def pragma_set(values)
@@ -60,7 +91,11 @@ module Extralite
60
91
  end
61
92
 
62
93
  def pragma_get(key)
63
- query("pragma #{key}")
94
+ query_single_value("pragma #{key}")
64
95
  end
65
96
  end
97
+
98
+ class Query
99
+ alias_method :execute_multi, :batch_execute
100
+ end
66
101
  end
Binary file
data/test/helper.rb CHANGED
@@ -5,3 +5,6 @@ require 'extralite'
5
5
  require 'minitest/autorun'
6
6
 
7
7
  puts "sqlite3 version: #{Extralite.sqlite3_version}"
8
+
9
+ IS_LINUX = RUBY_PLATFORM =~ /linux/
10
+ SKIP_RACTOR_TESTS = !IS_LINUX || (RUBY_VERSION =~ /^3\.0/)
data/test/issue-38.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+ require "./lib/extralite"
5
+ require "benchmark"
6
+
7
+ # Setup
8
+
9
+ File.delete("benchmark.sqlite3") if File.exist?("benchmark.sqlite3")
10
+
11
+ POOL_SIZE = 10
12
+
13
+ EXTRALITE_CONNECTIONS = POOL_SIZE.times.map do
14
+ db = Extralite::Database.new("benchmark.sqlite3")
15
+ db.execute("PRAGMA journal_mode = WAL")
16
+ db.execute("PRAGMA synchronous = NORMAL")
17
+ db.execute("PRAGMA journal_size_limit = 64000000")
18
+ db.execute("PRAGMA mmap_size = 128000000")
19
+ db.execute("PRAGMA cache_size = 2000")
20
+ db.execute("PRAGMA busy_timeout = 5000")
21
+ db
22
+ end
23
+
24
+ SQLITE3_CONNECTIONS = POOL_SIZE.times.map do
25
+ db = SQLite3::Database.new("benchmark.sqlite3")
26
+ db.execute("PRAGMA journal_mode = WAL")
27
+ db.execute("PRAGMA synchronous = NORMAL")
28
+ db.execute("PRAGMA journal_size_limit = 64000000")
29
+ db.execute("PRAGMA mmap_size = 128000000")
30
+ db.execute("PRAGMA cache_size = 2000")
31
+ db.execute("PRAGMA busy_timeout = 5000")
32
+ db
33
+ end
34
+
35
+ EXTRALITE_CONNECTIONS[0].execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at TEXT, updated_at TEXT) STRICT")
36
+ insert_statement = EXTRALITE_CONNECTIONS[0].prepare("INSERT INTO users (name, created_at, updated_at) VALUES (?, ?, ?)")
37
+ 1000.times do
38
+ insert_statement.execute("John Doe", Time.now.iso8601, Time.now.iso8601)
39
+ end
40
+
41
+ # Benchmark variations
42
+
43
+ THREAD_COUNTS = [1, 2, 4, 8]
44
+ LIMITS = [10, 100, 1000]
45
+ CLIENTS = %w[extralite sqlite3]
46
+
47
+ # Benchmark
48
+
49
+ GC.disable
50
+ Benchmark.bm do |x|
51
+ LIMITS.each do |limit|
52
+ THREAD_COUNTS.each do |thread_count|
53
+ CLIENTS.each do |client|
54
+ GC.start
55
+
56
+ x.report("#{client.rjust('extralite'.length)} - limit: #{limit}, threads: #{thread_count}") do
57
+ threads = thread_count.times.map do |thread_number|
58
+ Thread.new do
59
+ start = Time.now
60
+ if client == "extralite"
61
+ 1_000.times do
62
+ records = EXTRALITE_CONNECTIONS[thread_number].query_ary("SELECT * FROM users LIMIT #{limit}")
63
+ raise "Expected #{limit} but got #{length}" unless records.length == limit
64
+ end
65
+ else
66
+ 1_000.times do
67
+ records = SQLITE3_CONNECTIONS[thread_number].query("SELECT * FROM users LIMIT #{limit}").entries
68
+ raise "Expected #{limit} but got #{length}" unless records.length == limit
69
+ end
70
+ end
71
+ end
72
+ end
73
+ threads.each(&:join)
74
+ end
75
+ end
76
+ puts
77
+ end
78
+ end
79
+ end
80
+ GC.enable
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
@@ -12,15 +12,18 @@ end
12
12
  require 'benchmark/ips'
13
13
  require 'fileutils'
14
14
 
15
- DB_PATH = '/tmp/extralite_sqlite3_perf.db'
15
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
16
+ puts "DB_PATH = #{DB_PATH.inspect}"
17
+
16
18
 
17
19
  def prepare_database(count)
18
- FileUtils.rm(DB_PATH) rescue nil
19
20
  db = Extralite::Database.new(DB_PATH)
20
- db.query('create table foo ( a integer primary key, b text )')
21
+ db.query('create table if not exists foo ( a integer primary key, b text )')
22
+ db.query('delete from foo')
21
23
  db.query('begin')
22
24
  count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
23
25
  db.query('commit')
26
+ db.close
24
27
  end
25
28
 
26
29
  def sqlite3_run(count)
data/test/perf_hash.rb CHANGED
@@ -12,15 +12,17 @@ end
12
12
  require 'benchmark/ips'
13
13
  require 'fileutils'
14
14
 
15
- DB_PATH = '/tmp/extralite_sqlite3_perf.db'
15
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
16
+ puts "DB_PATH = #{DB_PATH.inspect}"
16
17
 
17
18
  def prepare_database(count)
18
- FileUtils.rm(DB_PATH) rescue nil
19
19
  db = Extralite::Database.new(DB_PATH)
20
- db.query('create table foo ( a integer primary key, b text )')
20
+ db.query('create table if not exists foo ( a integer primary key, b text )')
21
+ db.query('delete from foo')
21
22
  db.query('begin')
22
23
  count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
23
24
  db.query('commit')
25
+ db.close
24
26
  end
25
27
 
26
28
  def sqlite3_run(count)
@@ -36,7 +38,7 @@ def extralite_run(count)
36
38
  end
37
39
 
38
40
  [10, 1000, 100000].each do |c|
39
- puts; puts; puts "Record count: #{c}"
41
+ puts "Record count: #{c}"
40
42
 
41
43
  prepare_database(c)
42
44
 
@@ -48,4 +50,5 @@ end
48
50
 
49
51
  x.compare!
50
52
  end
53
+ puts; puts;
51
54
  end