extralite-bundle 2.3 → 2.4
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.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 } }
|