extralite 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/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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8ccbfbf37744a5b90d30038c3ba4e73f7d2a094a0f18f21cd8b58d66ec32b42
|
4
|
+
data.tar.gz: 5c6d3f9c967f7056f8eeb7ba471e2db50a26be334bdeec7ddf80dbd521efff77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19f10ce33c25e3b0837ec452b8eff0c4c433a729c79162d8b73e65411142f6df61703e0d7c254ac269fbf02ae39aa1c0cde5cb8f9fe3f262ab9ad0af503ceb99
|
7
|
+
data.tar.gz: 921aeeb63e23bfc145af2c978fb3ce5a47b643cf23e265aaf581eb067e8a76ef5c63effde30c567d7565a19fa8f844868456f996d5413a70ef2ba774a927d638
|
data/.editorconfig
ADDED
data/.github/workflows/test.yml
CHANGED
@@ -2,6 +2,10 @@ name: Tests
|
|
2
2
|
|
3
3
|
on: [push, pull_request]
|
4
4
|
|
5
|
+
concurrency:
|
6
|
+
group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
|
7
|
+
cancel-in-progress: true
|
8
|
+
|
5
9
|
jobs:
|
6
10
|
build:
|
7
11
|
strategy:
|
@@ -15,7 +19,7 @@ jobs:
|
|
15
19
|
|
16
20
|
runs-on: ${{matrix.os}}
|
17
21
|
steps:
|
18
|
-
- uses: actions/checkout@
|
22
|
+
- uses: actions/checkout@v4
|
19
23
|
- uses: ruby/setup-ruby@v1
|
20
24
|
with:
|
21
25
|
ruby-version: ${{matrix.ruby}}
|
@@ -24,3 +28,13 @@ jobs:
|
|
24
28
|
run: bundle exec rake compile
|
25
29
|
- name: Run tests
|
26
30
|
run: bundle exec rake test
|
31
|
+
- name: Build bundled gem
|
32
|
+
run: bundle exec rake build_bundled
|
33
|
+
- name: Run tests with bundled gem
|
34
|
+
run: |
|
35
|
+
echo Installing extralite-bundle...
|
36
|
+
bundle config --local frozen false
|
37
|
+
mv Gemfile-bundle Gemfile
|
38
|
+
bundle install
|
39
|
+
echo Running tests...
|
40
|
+
bundle exec rake test
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# 2.4 2023-12-24
|
2
|
+
|
3
|
+
- Implement GVL release threshold. [#46](https://github.com/digital-fabric/extralite/pull/46)
|
4
|
+
- Implement write barriers for better GC performance.
|
5
|
+
- Add support for binding large numbers and symbols. [#43](https://github.com/digital-fabric/extralite/pull/43)
|
6
|
+
- Implement Database#transaction. [#42](https://github.com/digital-fabric/extralite/pull/42)
|
7
|
+
- Add support for binding BLOBs. [#40](https://github.com/digital-fabric/extralite/pull/40)
|
8
|
+
- Minor fixes and cleanup of C-code.
|
9
|
+
- Fix `Database#inspect` for a closed database instance [#37](https://github.com/digital-fabric/extralite/issues/37)
|
10
|
+
- Add support for binding named parameters from Struct and Data classes [#30](https://github.com/digital-fabric/extralite/pull/30)
|
11
|
+
- Update bundled SQLite code to version 3.44.2 [#32](https://github.com/digital-fabric/extralite/pull/32)
|
12
|
+
|
1
13
|
# 2.3 2023-11-12
|
2
14
|
|
3
15
|
- Update bundled SQLite to version 3.44.0 (#29)
|
data/Gemfile-bundle
ADDED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
extralite (2.
|
4
|
+
extralite (2.4)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
docile (1.4.0)
|
10
|
-
json (2.
|
10
|
+
json (2.7.1)
|
11
11
|
minitest (5.15.0)
|
12
12
|
rake (13.1.0)
|
13
13
|
rake-compiler (1.1.6)
|
@@ -34,4 +34,4 @@ DEPENDENCIES
|
|
34
34
|
yard (= 0.9.27)
|
35
35
|
|
36
36
|
BUNDLED WITH
|
37
|
-
2.
|
37
|
+
2.4.22
|
data/README.md
CHANGED
@@ -14,14 +14,13 @@ with an SQLite3 database, as well as prepared queries (prepared statements).
|
|
14
14
|
Extralite comes in two flavors: the `extralite` gem which uses the
|
15
15
|
system-installed sqlite3 library, and the `extralite-bundle` gem which bundles
|
16
16
|
the latest version of SQLite
|
17
|
-
([3.44.
|
17
|
+
([3.44.2](https://sqlite.org/releaselog/3_44_2.html)), offering access to the
|
18
18
|
latest features and enhancements.
|
19
19
|
|
20
20
|
## Features
|
21
21
|
|
22
22
|
- Super fast - [up to 11x faster](#performance) than the
|
23
|
-
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem
|
24
|
-
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
23
|
+
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem.
|
25
24
|
- A variety of methods for different data access patterns: rows as hashes, rows
|
26
25
|
as arrays, single row, single column, single value.
|
27
26
|
- Prepared statements.
|
@@ -30,10 +29,12 @@ latest features and enhancements.
|
|
30
29
|
- Use system-installed sqlite3, or the [bundled latest version of
|
31
30
|
SQLite3](#installing-the-extralite-sqlite3-bundle).
|
32
31
|
- Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
|
33
|
-
released while preparing SQL statements and while iterating over
|
32
|
+
released peridically while preparing SQL statements and while iterating over
|
33
|
+
results.
|
34
34
|
- Automatically execute SQL strings containing multiple semicolon-separated
|
35
35
|
queries (handy for creating/modifying schemas).
|
36
|
-
- Execute the same query with multiple parameter lists (useful for inserting
|
36
|
+
- Execute the same query with multiple parameter lists (useful for inserting
|
37
|
+
records).
|
37
38
|
- Load extensions (loading of extensions is autmatically enabled. You can find
|
38
39
|
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
39
40
|
- Includes a [Sequel adapter](#usage-with-sequel).
|
@@ -101,12 +102,24 @@ db.query_single_value("select 'foo'") #=> "foo"
|
|
101
102
|
|
102
103
|
# parameter binding (works for all query_xxx methods)
|
103
104
|
db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
105
|
+
db.query_hash('select ?2 as foo, ?1 as bar, ?1 * ?2 as baz', 6, 7) #=> [{ :foo => 7, :bar => 6, :baz => 42 }]
|
104
106
|
|
105
107
|
# parameter binding of named parameters
|
106
108
|
db.query('select * from foo where bar = :bar', bar: 42)
|
107
109
|
db.query('select * from foo where bar = :bar', 'bar' => 42)
|
108
110
|
db.query('select * from foo where bar = :bar', ':bar' => 42)
|
109
111
|
|
112
|
+
# parameter binding of named parameters from Struct and Data
|
113
|
+
SomeStruct = Struct.new(:foo, :bar)
|
114
|
+
db.query_single_column('select :bar', SomeStruct.new(41, 42)) #=> [42]
|
115
|
+
SomeData = Data.define(:foo, :bar)
|
116
|
+
db.query_single_column('select :bar', SomeData.new(foo: 41, bar: 42)) #=> [42]
|
117
|
+
|
118
|
+
# parameter binding for binary data (BLOBs)
|
119
|
+
db.execute('insert into foo values (?)', File.binread('/path/to/file'))
|
120
|
+
db.execute('insert into foo values (?)', Extralite::Blob.new('Hello, 世界!'))
|
121
|
+
db.execute('insert into foo values (?)', 'Hello, 世界!'.force_encoding(Encoding::ASCII_8BIT))
|
122
|
+
|
110
123
|
# insert multiple rows
|
111
124
|
db.execute_multi('insert into foo values (?)', ['bar', 'baz'])
|
112
125
|
db.execute_multi('insert into foo values (?, ?)', [[1, 2], [3, 4]])
|
@@ -166,6 +179,13 @@ db.pragma(:journal_mode) #=> 'wal'
|
|
166
179
|
# load an extension
|
167
180
|
db.load_extension('/path/to/extension.so')
|
168
181
|
|
182
|
+
# run queries in a transaction
|
183
|
+
db.transaction do
|
184
|
+
db.execute('insert into foo values (?)', 1)
|
185
|
+
db.execute('insert into foo values (?)', 2)
|
186
|
+
db.execute('insert into foo values (?)', 3)
|
187
|
+
end
|
188
|
+
|
169
189
|
# close database
|
170
190
|
db.close
|
171
191
|
db.closed? #=> true
|
@@ -194,6 +214,23 @@ ensure
|
|
194
214
|
end
|
195
215
|
```
|
196
216
|
|
217
|
+
### Running transactions
|
218
|
+
|
219
|
+
In order to run multiple queries in a single transaction, use
|
220
|
+
`Database#transaction`, passing a block that runs the queries. You can specify
|
221
|
+
the [transaction
|
222
|
+
mode](https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions).
|
223
|
+
The default mode is `:immediate`:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
db.transaction { ... } # Run an immediate transaction
|
227
|
+
db.transaction(:deferred) { ... } # Run a deferred transaction
|
228
|
+
db.transaction(:exclusive) { ... } # Run an exclusive transaction
|
229
|
+
```
|
230
|
+
|
231
|
+
If an exception is raised in the given block, the transaction will be rolled
|
232
|
+
back. Otherwise, it is committed.
|
233
|
+
|
197
234
|
### Creating Backups
|
198
235
|
|
199
236
|
You can use `Database#backup` to create backup copies of a database. The
|
@@ -295,11 +332,33 @@ p articles.to_a
|
|
295
332
|
|
296
333
|
## Concurrency
|
297
334
|
|
298
|
-
Extralite releases the GVL while making
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
335
|
+
Extralite releases the GVL while making calls to the sqlite3 library that might
|
336
|
+
block, such as when backing up a database, or when preparing a query. Extralite
|
337
|
+
also releases the GVL periodically when iterating over records. By default, the
|
338
|
+
GVL is released every 1000 records iterated. The GVL release threshold can be
|
339
|
+
set separately for each database:
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
db.gvl_release_threshold = 10 # release GVL every 10 records
|
343
|
+
|
344
|
+
db.gvl_release_threshold = nil # use default value (currently 1000)
|
345
|
+
```
|
346
|
+
|
347
|
+
For most applications, there's no need to tune the GVL threshold value, as it
|
348
|
+
provides [excellent](#performance) performance characteristics for both single-threaded and
|
349
|
+
multi-threaded applications.
|
350
|
+
|
351
|
+
In a heavily multi-threaded application, releasing the GVL more often (lower
|
352
|
+
threshold value) will lead to less latency (for threads not running a query),
|
353
|
+
but will also hurt the throughput (for the thread running the query). Releasing
|
354
|
+
the GVL less often (higher threshold value) will lead to better throughput for
|
355
|
+
queries, while increasing latency for threads not running a query. The following
|
356
|
+
diagram demonstrates the relationship between the GVL release threshold value,
|
357
|
+
latency and throughput:
|
358
|
+
|
359
|
+
```
|
360
|
+
less latency & throughput <<< GVL release threshold >>> more latency & throughput
|
361
|
+
```
|
303
362
|
|
304
363
|
## Performance
|
305
364
|
|
data/bin/update_sqlite_source
CHANGED
@@ -9,10 +9,10 @@ require 'date'
|
|
9
9
|
|
10
10
|
FileUtils.cd '/tmp'
|
11
11
|
|
12
|
-
version_id = version.
|
12
|
+
version_id = version.split('.').each_with_index.map { |v, i| i == 0 ? v : v.rjust(2, '0') }.join
|
13
13
|
version_id += '0' * (7 - version_id.length)
|
14
14
|
url = "https://sqlite.org/#{Date.today.year}/sqlite-amalgamation-#{version_id}.zip"
|
15
|
-
dest = File.expand_path('../ext/
|
15
|
+
dest = File.expand_path('../ext/sqlite3', __dir__)
|
16
16
|
|
17
17
|
puts "Downloading from #{url}..."
|
18
18
|
`curl #{url} > #{version_id}.zip`
|
@@ -23,4 +23,11 @@ puts "Unzipping zip file..."
|
|
23
23
|
puts "Copying source files"
|
24
24
|
`cp sqlite-amalgamation-#{version_id}/sqlite3.* #{dest}/`
|
25
25
|
|
26
|
-
puts
|
26
|
+
puts "Updating README"
|
27
|
+
readme_path = File.expand_path('../README.md', __dir__)
|
28
|
+
readme = File.read(readme_path)
|
29
|
+
readme.gsub!(/\[\d+\.\d+\.\d+\]/, "[#{version}]")
|
30
|
+
readme.gsub!(/\d+_\d+_\d+\.html/, "#{version.gsub('.', '_')}.html")
|
31
|
+
File.write(readme_path, readme)
|
32
|
+
|
33
|
+
puts 'Done updating source files'
|
data/ext/extralite/common.c
CHANGED
@@ -3,6 +3,15 @@
|
|
3
3
|
|
4
4
|
rb_encoding *UTF8_ENCODING;
|
5
5
|
|
6
|
+
inline void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data) {
|
7
|
+
switch (mode) {
|
8
|
+
case GVL_RELEASE:
|
9
|
+
return rb_thread_call_without_gvl(fn, data, RUBY_UBF_IO, 0);
|
10
|
+
default:
|
11
|
+
return fn(data);
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
6
15
|
static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
7
16
|
switch (type) {
|
8
17
|
case SQLITE_NULL:
|
@@ -24,37 +33,52 @@ static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
24
33
|
|
25
34
|
void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
|
26
35
|
|
36
|
+
static inline void bind_key_value(sqlite3_stmt *stmt, VALUE k, VALUE v) {
|
37
|
+
switch (TYPE(k)) {
|
38
|
+
case T_FIXNUM:
|
39
|
+
bind_parameter_value(stmt, FIX2INT(k), v);
|
40
|
+
break;
|
41
|
+
case T_SYMBOL:
|
42
|
+
k = rb_sym2str(k);
|
43
|
+
case T_STRING:
|
44
|
+
if (RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
|
45
|
+
int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
|
46
|
+
bind_parameter_value(stmt, pos, v);
|
47
|
+
break;
|
48
|
+
default:
|
49
|
+
rb_raise(cParameterError, "Cannot bind parameter with a key of type %"PRIsVALUE"",
|
50
|
+
rb_class_name(rb_obj_class(k)));
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
27
54
|
void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
|
28
55
|
VALUE keys = rb_funcall(hash, ID_keys, 0);
|
29
56
|
long len = RARRAY_LEN(keys);
|
30
57
|
for (long i = 0; i < len; i++) {
|
31
58
|
VALUE k = RARRAY_AREF(keys, i);
|
32
59
|
VALUE v = rb_hash_aref(hash, k);
|
33
|
-
|
34
|
-
switch (TYPE(k)) {
|
35
|
-
case T_FIXNUM:
|
36
|
-
bind_parameter_value(stmt, FIX2INT(k), v);
|
37
|
-
break;
|
38
|
-
case T_SYMBOL:
|
39
|
-
k = rb_funcall(k, ID_to_s, 0);
|
40
|
-
case T_STRING:
|
41
|
-
if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
|
42
|
-
int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
|
43
|
-
bind_parameter_value(stmt, pos, v);
|
44
|
-
break;
|
45
|
-
default:
|
46
|
-
rb_raise(cError, "Cannot bind hash key value idx %ld", i);
|
47
|
-
}
|
60
|
+
bind_key_value(stmt, k, v);
|
48
61
|
}
|
49
62
|
RB_GC_GUARD(keys);
|
50
63
|
}
|
51
64
|
|
65
|
+
void bind_struct_parameter_values(sqlite3_stmt *stmt, VALUE struct_obj) {
|
66
|
+
VALUE members = rb_struct_members(struct_obj);
|
67
|
+
for (long i = 0; i < RSTRUCT_LEN(struct_obj); i++) {
|
68
|
+
VALUE k = rb_ary_entry(members, i);
|
69
|
+
VALUE v = RSTRUCT_GET(struct_obj, i);
|
70
|
+
bind_key_value(stmt, k, v);
|
71
|
+
}
|
72
|
+
RB_GC_GUARD(members);
|
73
|
+
}
|
74
|
+
|
52
75
|
inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
53
76
|
switch (TYPE(value)) {
|
54
77
|
case T_NIL:
|
55
78
|
sqlite3_bind_null(stmt, pos);
|
56
79
|
return;
|
57
80
|
case T_FIXNUM:
|
81
|
+
case T_BIGNUM:
|
58
82
|
sqlite3_bind_int64(stmt, pos, NUM2LL(value));
|
59
83
|
return;
|
60
84
|
case T_FLOAT:
|
@@ -66,14 +90,23 @@ inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
|
66
90
|
case T_FALSE:
|
67
91
|
sqlite3_bind_int(stmt, pos, 0);
|
68
92
|
return;
|
93
|
+
case T_SYMBOL:
|
94
|
+
value = rb_sym2str(value);
|
69
95
|
case T_STRING:
|
70
|
-
|
96
|
+
if (rb_enc_get_index(value) == rb_ascii8bit_encindex() || CLASS_OF(value) == cBlob)
|
97
|
+
sqlite3_bind_blob(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
98
|
+
else
|
99
|
+
sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
71
100
|
return;
|
72
101
|
case T_HASH:
|
73
102
|
bind_hash_parameter_values(stmt, value);
|
74
103
|
return;
|
104
|
+
case T_STRUCT:
|
105
|
+
bind_struct_parameter_values(stmt, value);
|
106
|
+
return;
|
75
107
|
default:
|
76
|
-
rb_raise(
|
108
|
+
rb_raise(cParameterError, "Cannot bind parameter at position %d of type %"PRIsVALUE"",
|
109
|
+
pos, rb_class_name(rb_obj_class(value)));
|
77
110
|
}
|
78
111
|
}
|
79
112
|
|
@@ -126,7 +159,7 @@ typedef struct {
|
|
126
159
|
int rc;
|
127
160
|
} prepare_stmt_ctx;
|
128
161
|
|
129
|
-
void *
|
162
|
+
void *prepare_multi_stmt_impl(void *ptr) {
|
130
163
|
prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
|
131
164
|
const char *rest = NULL;
|
132
165
|
const char *str = ctx->str;
|
@@ -163,7 +196,7 @@ is not executed, but instead handed back to the caller for looping over results.
|
|
163
196
|
*/
|
164
197
|
void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
165
198
|
prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
166
|
-
|
199
|
+
gvl_call(GVL_RELEASE, prepare_multi_stmt_impl, (void *)&ctx);
|
167
200
|
RB_GC_GUARD(sql);
|
168
201
|
|
169
202
|
switch (ctx.rc) {
|
@@ -180,7 +213,7 @@ void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
180
213
|
|
181
214
|
#define SQLITE_MULTI_STMT -1
|
182
215
|
|
183
|
-
void *
|
216
|
+
void *prepare_single_stmt_impl(void *ptr) {
|
184
217
|
prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
|
185
218
|
const char *rest = NULL;
|
186
219
|
const char *str = ctx->str;
|
@@ -205,7 +238,7 @@ end:
|
|
205
238
|
|
206
239
|
void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
207
240
|
prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
208
|
-
|
241
|
+
gvl_call(GVL_RELEASE, prepare_single_stmt_impl, (void *)&ctx);
|
209
242
|
RB_GC_GUARD(sql);
|
210
243
|
|
211
244
|
switch (ctx.rc) {
|
@@ -227,15 +260,26 @@ struct step_ctx {
|
|
227
260
|
int rc;
|
228
261
|
};
|
229
262
|
|
230
|
-
void *
|
263
|
+
void *stmt_iterate_step(void *ptr) {
|
231
264
|
struct step_ctx *ctx = (struct step_ctx *)ptr;
|
232
265
|
ctx->rc = sqlite3_step(ctx->stmt);
|
233
266
|
return NULL;
|
234
267
|
}
|
235
268
|
|
269
|
+
inline enum gvl_mode stepwise_gvl_mode(query_ctx *ctx) {
|
270
|
+
// a negative or zero threshold means the GVL is always held during iteration.
|
271
|
+
if (ctx->gvl_release_threshold <= 0) return GVL_HOLD;
|
272
|
+
|
273
|
+
if (!sqlite3_stmt_busy(ctx->stmt)) return GVL_RELEASE;
|
274
|
+
|
275
|
+
// if positive, the GVL is normally held, and release every <threshold> steps.
|
276
|
+
return (ctx->step_count % ctx->gvl_release_threshold) ? GVL_HOLD : GVL_RELEASE;
|
277
|
+
}
|
278
|
+
|
236
279
|
inline int stmt_iterate(query_ctx *ctx) {
|
237
280
|
struct step_ctx step_ctx = {ctx->stmt, 0};
|
238
|
-
|
281
|
+
ctx->step_count += 1;
|
282
|
+
gvl_call(stepwise_gvl_mode(ctx), stmt_iterate_step, (void *)&step_ctx);
|
239
283
|
switch (step_ctx.rc) {
|
240
284
|
case SQLITE_ROW:
|
241
285
|
return 1;
|