extralite-bundle 2.3 → 2.4
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.
- checksums.yaml +4 -4
- data/.editorconfig +6 -0
- data/.github/workflows/test.yml +15 -1
- data/.gitignore +3 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +3 -3
- data/README.md +69 -10
- data/bin/update_sqlite_source +10 -3
- data/ext/extralite/common.c +67 -23
- data/ext/extralite/database.c +74 -26
- data/ext/extralite/extralite.h +15 -8
- data/ext/extralite/iterator.c +5 -5
- data/ext/extralite/query.c +37 -29
- data/ext/sqlite3/sqlite3.c +196 -90
- data/ext/sqlite3/sqlite3.h +55 -12
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +27 -0
- data/test/fixtures/image.png +0 -0
- data/test/helper.rb +2 -0
- data/test/issue-38.rb +80 -0
- data/test/perf_ary.rb +6 -3
- data/test/perf_hash.rb +7 -4
- data/test/test_database.rb +256 -4
- data/test/test_iterator.rb +2 -1
- data/test/test_query.rb +40 -4
- metadata +6 -2
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.44.
|
150
|
-
#define SQLITE_VERSION_NUMBER
|
151
|
-
#define SQLITE_SOURCE_ID "2023-11-
|
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"
|
152
152
|
|
153
153
|
/*
|
154
154
|
** CAPI3REF: Run-Time Library Version Numbers
|
@@ -5573,13 +5573,27 @@ SQLITE_API int sqlite3_create_window_function(
|
|
5573
5573
|
** </dd>
|
5574
5574
|
**
|
5575
5575
|
** [[SQLITE_SUBTYPE]] <dt>SQLITE_SUBTYPE</dt><dd>
|
5576
|
-
** The SQLITE_SUBTYPE flag indicates to SQLite that a function
|
5576
|
+
** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call
|
5577
5577
|
** [sqlite3_value_subtype()] to inspect the sub-types of its arguments.
|
5578
|
-
**
|
5579
|
-
**
|
5580
|
-
**
|
5581
|
-
**
|
5582
|
-
**
|
5578
|
+
** This flag instructs SQLite to omit some corner-case optimizations that
|
5579
|
+
** might disrupt the operation of the [sqlite3_value_subtype()] function,
|
5580
|
+
** causing it to return zero rather than the correct subtype().
|
5581
|
+
** SQL functions that invokes [sqlite3_value_subtype()] should have this
|
5582
|
+
** property. If the SQLITE_SUBTYPE property is omitted, then the return
|
5583
|
+
** value from [sqlite3_value_subtype()] might sometimes be zero even though
|
5584
|
+
** a non-zero subtype was specified by the function argument expression.
|
5585
|
+
**
|
5586
|
+
** [[SQLITE_RESULT_SUBTYPE]] <dt>SQLITE_RESULT_SUBTYPE</dt><dd>
|
5587
|
+
** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call
|
5588
|
+
** [sqlite3_result_subtype()] to cause a sub-type to be associated with its
|
5589
|
+
** result.
|
5590
|
+
** Every function that invokes [sqlite3_result_subtype()] should have this
|
5591
|
+
** property. If it does not, then the call to [sqlite3_result_subtype()]
|
5592
|
+
** might become a no-op if the function is used as term in an
|
5593
|
+
** [expression index]. On the other hand, SQL functions that never invoke
|
5594
|
+
** [sqlite3_result_subtype()] should avoid setting this property, as the
|
5595
|
+
** purpose of this property is to disable certain optimizations that are
|
5596
|
+
** incompatible with subtypes.
|
5583
5597
|
** </dd>
|
5584
5598
|
** </dl>
|
5585
5599
|
*/
|
@@ -5587,6 +5601,7 @@ SQLITE_API int sqlite3_create_window_function(
|
|
5587
5601
|
#define SQLITE_DIRECTONLY 0x000080000
|
5588
5602
|
#define SQLITE_SUBTYPE 0x000100000
|
5589
5603
|
#define SQLITE_INNOCUOUS 0x000200000
|
5604
|
+
#define SQLITE_RESULT_SUBTYPE 0x001000000
|
5590
5605
|
|
5591
5606
|
/*
|
5592
5607
|
** CAPI3REF: Deprecated Functions
|
@@ -5783,6 +5798,12 @@ SQLITE_API int sqlite3_value_encoding(sqlite3_value*);
|
|
5783
5798
|
** information can be used to pass a limited amount of context from
|
5784
5799
|
** one SQL function to another. Use the [sqlite3_result_subtype()]
|
5785
5800
|
** routine to set the subtype for the return value of an SQL function.
|
5801
|
+
**
|
5802
|
+
** Every [application-defined SQL function] that invoke this interface
|
5803
|
+
** should include the [SQLITE_SUBTYPE] property in the text
|
5804
|
+
** encoding argument when the function is [sqlite3_create_function|registered].
|
5805
|
+
** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype()
|
5806
|
+
** might return zero instead of the upstream subtype in some corner cases.
|
5786
5807
|
*/
|
5787
5808
|
SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
|
5788
5809
|
|
@@ -5913,14 +5934,22 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
|
|
5913
5934
|
** <li> ^(when sqlite3_set_auxdata() is invoked again on the same
|
5914
5935
|
** parameter)^, or
|
5915
5936
|
** <li> ^(during the original sqlite3_set_auxdata() call when a memory
|
5916
|
-
** allocation error occurs.)^
|
5937
|
+
** allocation error occurs.)^
|
5938
|
+
** <li> ^(during the original sqlite3_set_auxdata() call if the function
|
5939
|
+
** is evaluated during query planning instead of during query execution,
|
5940
|
+
** as sometimes happens with [SQLITE_ENABLE_STAT4].)^ </ul>
|
5917
5941
|
**
|
5918
|
-
** Note the last
|
5942
|
+
** Note the last two bullets in particular. The destructor X in
|
5919
5943
|
** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
|
5920
5944
|
** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata()
|
5921
5945
|
** should be called near the end of the function implementation and the
|
5922
5946
|
** function implementation should not make any use of P after
|
5923
|
-
** sqlite3_set_auxdata() has been called.
|
5947
|
+
** sqlite3_set_auxdata() has been called. Furthermore, a call to
|
5948
|
+
** sqlite3_get_auxdata() that occurs immediately after a corresponding call
|
5949
|
+
** to sqlite3_set_auxdata() might still return NULL if an out-of-memory
|
5950
|
+
** condition occurred during the sqlite3_set_auxdata() call or if the
|
5951
|
+
** function is being evaluated during query planning rather than during
|
5952
|
+
** query execution.
|
5924
5953
|
**
|
5925
5954
|
** ^(In practice, auxiliary data is preserved between function calls for
|
5926
5955
|
** function parameters that are compile-time constants, including literal
|
@@ -6194,6 +6223,20 @@ SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n);
|
|
6194
6223
|
** higher order bits are discarded.
|
6195
6224
|
** The number of subtype bytes preserved by SQLite might increase
|
6196
6225
|
** in future releases of SQLite.
|
6226
|
+
**
|
6227
|
+
** Every [application-defined SQL function] that invokes this interface
|
6228
|
+
** should include the [SQLITE_RESULT_SUBTYPE] property in its
|
6229
|
+
** text encoding argument when the SQL function is
|
6230
|
+
** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE]
|
6231
|
+
** property is omitted from the function that invokes sqlite3_result_subtype(),
|
6232
|
+
** then in some cases the sqlite3_result_subtype() might fail to set
|
6233
|
+
** the result subtype.
|
6234
|
+
**
|
6235
|
+
** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any
|
6236
|
+
** SQL function that invokes the sqlite3_result_subtype() interface
|
6237
|
+
** and that does not have the SQLITE_RESULT_SUBTYPE property will raise
|
6238
|
+
** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1
|
6239
|
+
** by default.
|
6197
6240
|
*/
|
6198
6241
|
SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
|
6199
6242
|
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -25,6 +25,10 @@ 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
|
@@ -52,6 +56,29 @@ module Extralite
|
|
52
56
|
value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
|
53
57
|
end
|
54
58
|
|
59
|
+
# Starts a transaction and runs the given block. If an exception is raised
|
60
|
+
# in the block, the transaction is rolled back. Otherwise, the transaction
|
61
|
+
# is commited after running the block.
|
62
|
+
#
|
63
|
+
# db.transaction do
|
64
|
+
# db.execute('insert into foo values (1, 2, 3)')
|
65
|
+
# raise if db.query_single_value('select x from bar') > 42
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive). Defaults to immediate.
|
69
|
+
# @return [Any] the given block's return value
|
70
|
+
def transaction(mode = :immediate)
|
71
|
+
execute "begin #{mode} transaction"
|
72
|
+
|
73
|
+
abort = false
|
74
|
+
yield self
|
75
|
+
rescue
|
76
|
+
abort = true
|
77
|
+
raise
|
78
|
+
ensure
|
79
|
+
execute(abort ? 'rollback' : 'commit')
|
80
|
+
end
|
81
|
+
|
55
82
|
private
|
56
83
|
|
57
84
|
def pragma_set(values)
|
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/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
|
data/test/test_database.rb
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
|
3
3
|
require_relative 'helper'
|
4
4
|
|
5
|
+
require 'date'
|
6
|
+
require 'tempfile'
|
7
|
+
|
5
8
|
class DatabaseTest < MiniTest::Test
|
6
9
|
def setup
|
7
10
|
@db = Extralite::Database.new(':memory:')
|
@@ -123,6 +126,9 @@ end
|
|
123
126
|
|
124
127
|
r = @db.query('select x, y, z from t where z = ?', 6)
|
125
128
|
assert_equal [{ x: 4, y: 5, z: 6 }], r
|
129
|
+
|
130
|
+
error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', Date.today) }
|
131
|
+
assert_equal error.message, 'Cannot bind parameter at position 1 of type Date'
|
126
132
|
end
|
127
133
|
|
128
134
|
def test_parameter_binding_with_index
|
@@ -152,6 +158,94 @@ end
|
|
152
158
|
assert_equal [{ x: 4, y: 5, z: 6 }], r
|
153
159
|
end
|
154
160
|
|
161
|
+
class Foo; end
|
162
|
+
|
163
|
+
def test_parameter_binding_from_hash
|
164
|
+
assert_equal 42, @db.query_single_value('select :bar', foo: 41, bar: 42)
|
165
|
+
assert_equal 42, @db.query_single_value('select :bar', 'foo' => 41, 'bar' => 42)
|
166
|
+
assert_equal 42, @db.query_single_value('select ?8', 7 => 41, 8 => 42)
|
167
|
+
assert_nil @db.query_single_value('select :bar', foo: 41)
|
168
|
+
|
169
|
+
error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', Foo.new => 42) }
|
170
|
+
assert_equal error.message, 'Cannot bind parameter with a key of type DatabaseTest::Foo'
|
171
|
+
|
172
|
+
error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', %w[a b] => 42) }
|
173
|
+
assert_equal error.message, 'Cannot bind parameter with a key of type Array'
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_parameter_binding_from_struct
|
177
|
+
foo_bar = Struct.new(:":foo", :bar)
|
178
|
+
value = foo_bar.new(41, 42)
|
179
|
+
assert_equal 41, @db.query_single_value('select :foo', value)
|
180
|
+
assert_equal 42, @db.query_single_value('select :bar', value)
|
181
|
+
assert_nil @db.query_single_value('select :baz', value)
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_parameter_binding_from_data_class
|
185
|
+
skip "Data isn't supported in Ruby < 3.2" if RUBY_VERSION < '3.2'
|
186
|
+
|
187
|
+
foo_bar = Data.define(:":foo", :bar)
|
188
|
+
value = foo_bar.new(":foo": 41, bar: 42)
|
189
|
+
assert_equal 42, @db.query_single_value('select :bar', value)
|
190
|
+
assert_nil @db.query_single_value('select :baz', value)
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_parameter_binding_for_blobs
|
194
|
+
sql = 'SELECT typeof(data) AS type, data FROM blobs WHERE ROWID = ?'
|
195
|
+
blob_path = File.expand_path('fixtures/image.png', __dir__)
|
196
|
+
@db.execute('CREATE TABLE blobs (data BLOB)')
|
197
|
+
|
198
|
+
# it's a string, not a blob
|
199
|
+
@db.execute('INSERT INTO blobs VALUES (?)', 'Hello, 世界!')
|
200
|
+
result = @db.query_single_row(sql, @db.last_insert_rowid)
|
201
|
+
assert_equal 'text', result[:type]
|
202
|
+
assert_equal Encoding::UTF_8, result[:data].encoding
|
203
|
+
|
204
|
+
data = File.binread(blob_path)
|
205
|
+
@db.execute('INSERT INTO blobs VALUES (?)', data)
|
206
|
+
result = @db.query_single_row(sql, @db.last_insert_rowid)
|
207
|
+
assert_equal 'blob', result[:type]
|
208
|
+
assert_equal data, result[:data]
|
209
|
+
|
210
|
+
data = (+'Hello, 世界!').force_encoding(Encoding::ASCII_8BIT)
|
211
|
+
@db.execute('INSERT INTO blobs VALUES (?)', data)
|
212
|
+
result = @db.query_single_row(sql, @db.last_insert_rowid)
|
213
|
+
assert_equal 'blob', result[:type]
|
214
|
+
assert_equal Encoding::ASCII_8BIT, result[:data].encoding
|
215
|
+
assert_equal 'Hello, 世界!', result[:data].force_encoding(Encoding::UTF_8)
|
216
|
+
|
217
|
+
data = Extralite::Blob.new('Hello, 世界!')
|
218
|
+
@db.execute('INSERT INTO blobs VALUES (?)', data)
|
219
|
+
result = @db.query_single_row(sql, @db.last_insert_rowid)
|
220
|
+
assert_equal 'blob', result[:type]
|
221
|
+
assert_equal Encoding::ASCII_8BIT, result[:data].encoding
|
222
|
+
assert_equal 'Hello, 世界!', result[:data].force_encoding(Encoding::UTF_8)
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_parameter_binding_for_simple_types
|
226
|
+
assert_nil @db.query_single_value('select ?', nil)
|
227
|
+
|
228
|
+
# 32-bit integers
|
229
|
+
assert_equal -2** 31, @db.query_single_value('select ?', -2**31)
|
230
|
+
assert_equal 2**31 - 1, @db.query_single_value('select ?', 2**31 - 1)
|
231
|
+
|
232
|
+
# 64-bit integers
|
233
|
+
assert_equal -2 ** 63, @db.query_single_value('select ?', -2 ** 63)
|
234
|
+
assert_equal 2**63 - 1, @db.query_single_value('select ?', 2**63 - 1)
|
235
|
+
|
236
|
+
# floats
|
237
|
+
assert_equal Float::MIN, @db.query_single_value('select ?', Float::MIN)
|
238
|
+
assert_equal Float::MAX, @db.query_single_value('select ?', Float::MAX)
|
239
|
+
|
240
|
+
# boolean
|
241
|
+
assert_equal 1, @db.query_single_value('select ?', true)
|
242
|
+
assert_equal 0, @db.query_single_value('select ?', false)
|
243
|
+
|
244
|
+
# strings and symbols
|
245
|
+
assert_equal 'foo', @db.query_single_value('select ?', 'foo')
|
246
|
+
assert_equal 'foo', @db.query_single_value('select ?', :foo)
|
247
|
+
end
|
248
|
+
|
155
249
|
def test_value_casting
|
156
250
|
r = @db.query_single_value("select 'abc'")
|
157
251
|
assert_equal 'abc', r
|
@@ -293,7 +387,7 @@ end
|
|
293
387
|
end
|
294
388
|
|
295
389
|
def test_database_busy_timeout
|
296
|
-
fn =
|
390
|
+
fn = Tempfile.new('extralite_test_database_busy_timeout').path
|
297
391
|
db1 = Extralite::Database.new(fn)
|
298
392
|
db2 = Extralite::Database.new(fn)
|
299
393
|
|
@@ -388,17 +482,77 @@ end
|
|
388
482
|
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
|
389
483
|
end
|
390
484
|
|
485
|
+
def test_database_inspect_on_closed_database
|
486
|
+
db = Extralite::Database.new(':memory:')
|
487
|
+
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
|
488
|
+
db.close
|
489
|
+
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ \(closed\)\>$/, db.inspect
|
490
|
+
end
|
491
|
+
|
391
492
|
def test_string_encoding
|
392
493
|
db = Extralite::Database.new(':memory:')
|
393
494
|
v = db.query_single_value("select 'foo'")
|
394
495
|
assert_equal 'foo', v
|
395
496
|
assert_equal 'UTF-8', v.encoding.name
|
396
497
|
end
|
498
|
+
|
499
|
+
def test_database_transaction_commit
|
500
|
+
path = Tempfile.new('extralite_test_database_transaction_commit').path
|
501
|
+
db1 = Extralite::Database.new(path)
|
502
|
+
db2 = Extralite::Database.new(path)
|
503
|
+
|
504
|
+
db1.execute('create table foo(x)')
|
505
|
+
assert_equal ['foo'], db1.tables
|
506
|
+
assert_equal ['foo'], db2.tables
|
507
|
+
|
508
|
+
q1 = Queue.new
|
509
|
+
q2 = Queue.new
|
510
|
+
th = Thread.new do
|
511
|
+
db1.transaction do
|
512
|
+
assert_equal true, db1.transaction_active?
|
513
|
+
db1.execute('insert into foo values (42)')
|
514
|
+
q1 << true
|
515
|
+
q2.pop
|
516
|
+
end
|
517
|
+
assert_equal false, db1.transaction_active?
|
518
|
+
end
|
519
|
+
q1.pop
|
520
|
+
# transaction not yet committed
|
521
|
+
assert_equal false, db2.transaction_active?
|
522
|
+
assert_equal [], db2.query('select * from foo')
|
523
|
+
|
524
|
+
q2 << true
|
525
|
+
th.join
|
526
|
+
# transaction now committed
|
527
|
+
assert_equal [{ x: 42 }], db2.query('select * from foo')
|
528
|
+
end
|
529
|
+
|
530
|
+
def test_database_transaction_rollback
|
531
|
+
db = Extralite::Database.new(':memory:')
|
532
|
+
db.execute('create table foo(x)')
|
533
|
+
|
534
|
+
assert_equal [], db.query('select * from foo')
|
535
|
+
|
536
|
+
exception = nil
|
537
|
+
begin
|
538
|
+
db.transaction do
|
539
|
+
db.execute('insert into foo values (42)')
|
540
|
+
raise 'bar'
|
541
|
+
end
|
542
|
+
rescue => e
|
543
|
+
exception = e
|
544
|
+
end
|
545
|
+
|
546
|
+
assert_equal [], db.query('select * from foo')
|
547
|
+
assert_kind_of RuntimeError, exception
|
548
|
+
assert_equal 'bar', exception.message
|
549
|
+
end
|
397
550
|
end
|
398
551
|
|
399
552
|
class ScenarioTest < MiniTest::Test
|
400
553
|
def setup
|
401
|
-
@
|
554
|
+
@fn = Tempfile.new('extralite_scenario_test').path
|
555
|
+
@db = Extralite::Database.new(@fn)
|
402
556
|
@db.query('create table if not exists t (x,y,z)')
|
403
557
|
@db.query('delete from t')
|
404
558
|
@db.query('insert into t values (1, 2, 3)')
|
@@ -408,7 +562,7 @@ class ScenarioTest < MiniTest::Test
|
|
408
562
|
def test_concurrent_transactions
|
409
563
|
done = false
|
410
564
|
t = Thread.new do
|
411
|
-
db = Extralite::Database.new(
|
565
|
+
db = Extralite::Database.new(@fn)
|
412
566
|
db.query 'begin immediate'
|
413
567
|
sleep 0.01 until done
|
414
568
|
|
@@ -465,6 +619,7 @@ class ScenarioTest < MiniTest::Test
|
|
465
619
|
def test_database_trace
|
466
620
|
sqls = []
|
467
621
|
@db.trace { |sql| sqls << sql }
|
622
|
+
GC.start
|
468
623
|
|
469
624
|
@db.query('select 1')
|
470
625
|
assert_equal ['select 1'], sqls
|
@@ -515,10 +670,107 @@ class BackupTest < MiniTest::Test
|
|
515
670
|
end
|
516
671
|
|
517
672
|
def test_backup_with_fn
|
518
|
-
tmp_fn =
|
673
|
+
tmp_fn = Tempfile.new('extralite_test_backup_with_fn').path
|
519
674
|
@src.backup(tmp_fn)
|
520
675
|
|
521
676
|
db = Extralite::Database.new(tmp_fn)
|
522
677
|
assert_equal [[1, 2, 3], [4, 5, 6]], db.query_ary('select * from t')
|
523
678
|
end
|
524
679
|
end
|
680
|
+
|
681
|
+
class GVLReleaseThresholdTest < Minitest::Test
|
682
|
+
def setup
|
683
|
+
@sql = <<~SQL
|
684
|
+
WITH RECURSIVE r(i) AS (
|
685
|
+
VALUES(0)
|
686
|
+
UNION ALL
|
687
|
+
SELECT i FROM r
|
688
|
+
LIMIT 3000000
|
689
|
+
)
|
690
|
+
SELECT i FROM r WHERE i = 1;
|
691
|
+
SQL
|
692
|
+
end
|
693
|
+
|
694
|
+
def test_default_gvl_release_threshold
|
695
|
+
db = Extralite::Database.new(':memory:')
|
696
|
+
assert_equal 1000, db.gvl_release_threshold
|
697
|
+
end
|
698
|
+
|
699
|
+
def test_gvl_always_release
|
700
|
+
skip if !IS_LINUX
|
701
|
+
|
702
|
+
delays = []
|
703
|
+
running = true
|
704
|
+
t1 = Thread.new do
|
705
|
+
last = Time.now
|
706
|
+
while running
|
707
|
+
sleep 0.1
|
708
|
+
now = Time.now
|
709
|
+
delays << (now - last)
|
710
|
+
last = now
|
711
|
+
end
|
712
|
+
end
|
713
|
+
t2 = Thread.new do
|
714
|
+
db = Extralite::Database.new(':memory:')
|
715
|
+
db.gvl_release_threshold = 1
|
716
|
+
db.query(@sql)
|
717
|
+
ensure
|
718
|
+
running = false
|
719
|
+
end
|
720
|
+
t2.join
|
721
|
+
t1.join
|
722
|
+
|
723
|
+
assert delays.size > 4
|
724
|
+
assert_equal 0, delays.select { |d| d > 0.15 }.size
|
725
|
+
end
|
726
|
+
|
727
|
+
def test_gvl_always_hold
|
728
|
+
skip if !IS_LINUX
|
729
|
+
|
730
|
+
delays = []
|
731
|
+
running = true
|
732
|
+
|
733
|
+
signal = Queue.new
|
734
|
+
db = Extralite::Database.new(':memory:')
|
735
|
+
db.gvl_release_threshold = 0
|
736
|
+
|
737
|
+
t1 = Thread.new do
|
738
|
+
last = Time.now
|
739
|
+
while running
|
740
|
+
signal << true
|
741
|
+
sleep 0.1
|
742
|
+
now = Time.now
|
743
|
+
delays << (now - last)
|
744
|
+
last = now
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
t2 = Thread.new do
|
749
|
+
signal.pop
|
750
|
+
db.query(@sql)
|
751
|
+
ensure
|
752
|
+
running = false
|
753
|
+
end
|
754
|
+
t2.join
|
755
|
+
t1.join
|
756
|
+
|
757
|
+
assert delays.size >= 1
|
758
|
+
assert delays.first > 0.2
|
759
|
+
end
|
760
|
+
|
761
|
+
def test_gvl_mode_get_set
|
762
|
+
db = Extralite::Database.new(':memory:')
|
763
|
+
assert_equal 1000, db.gvl_release_threshold
|
764
|
+
|
765
|
+
db.gvl_release_threshold = 42
|
766
|
+
assert_equal 42, db.gvl_release_threshold
|
767
|
+
|
768
|
+
db.gvl_release_threshold = 0
|
769
|
+
assert_equal 0, db.gvl_release_threshold
|
770
|
+
|
771
|
+
assert_raises(ArgumentError) { db.gvl_release_threshold = :foo }
|
772
|
+
|
773
|
+
db.gvl_release_threshold = nil
|
774
|
+
assert_equal 1000, db.gvl_release_threshold
|
775
|
+
end
|
776
|
+
end
|
data/test/test_iterator.rb
CHANGED
@@ -103,7 +103,8 @@ class IteratorTest < MiniTest::Test
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def test_return_from_block_issue_26
|
106
|
-
|
106
|
+
fn = Tempfile.new('extralite_test_return_from_block_issue_26').path
|
107
|
+
db = Extralite::Database.new(fn)
|
107
108
|
|
108
109
|
λ = ->(sql) {
|
109
110
|
db.prepare(sql).each { |r| r.each { |_, v| return v } }
|