extralite-bundle 2.3 → 2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +6 -0
- data/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +7 -3
- data/.gitignore +3 -0
- data/CHANGELOG.md +44 -3
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +3 -3
- data/README.md +106 -13
- data/TODO.md +0 -3
- data/bin/update_sqlite_source +10 -3
- data/ext/extralite/common.c +288 -37
- data/ext/extralite/database.c +256 -39
- data/ext/extralite/extralite.h +20 -9
- data/ext/extralite/extralite_ext.c +4 -0
- data/ext/extralite/iterator.c +5 -5
- data/ext/extralite/query.c +250 -41
- data/ext/sqlite3/sqlite3.c +5556 -2531
- data/ext/sqlite3/sqlite3.h +125 -27
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +41 -6
- data/test/fixtures/image.png +0 -0
- data/test/helper.rb +3 -0
- data/test/issue-38.rb +80 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +6 -3
- data/test/perf_hash.rb +7 -4
- data/test/test_database.rb +726 -15
- data/test/test_iterator.rb +2 -1
- data/test/test_query.rb +402 -6
- metadata +10 -3
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
|
**
|
@@ -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
|
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
|
-
**
|
5579
|
-
**
|
5580
|
-
**
|
5581
|
-
**
|
5582
|
-
**
|
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.)^
|
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
|
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.
|
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
|
-
**
|
12772
|
-
**
|
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
|
-
**
|
12783
|
-
**
|
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
|
-
**
|
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.
|
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
|
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 = '>=
|
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
@@ -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
|
-
|
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
|
-
|
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
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 =
|
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 =
|
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
|
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
|