extralite-bundle 2.2 → 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 +16 -0
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +4 -4
- 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 +8945 -3857
- data/ext/sqlite3/sqlite3.h +293 -61
- 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: '08845d2cb3c74da55576eb9db9ada9244e2cb48d6f8cee5676550a8bcfaceeb1'
|
4
|
+
data.tar.gz: d3a8503488e7078d6c1f2112eb708777bc5a25ab8e85ac9972ae4dc3b1fe51d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80d3e967d8e13bf5568422ac45778649bc6b52adeff95cbf25d50d919fa5f0b3ac320dba063768eb2918fc6845813c3d4b895dc22a52278725b62a5520c69ad6
|
7
|
+
data.tar.gz: 37d2f0954859db30a976c4a3e255c1c607bbe70d42c7bf27772d391db9a6f1760dfb3d6b9d2fcf9a1f867f5d45dd39a8be2a870d015e993c1d34a8c25f3c9ed4
|
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,19 @@
|
|
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
|
+
|
13
|
+
# 2.3 2023-11-12
|
14
|
+
|
15
|
+
- Update bundled SQLite to version 3.44.0 (#29)
|
16
|
+
|
1
17
|
# 2.2 2023-10-14
|
2
18
|
|
3
19
|
- Set correct encoding for strings values in query results (#27)
|
data/Gemfile-bundle
ADDED
data/Gemfile.lock
CHANGED
@@ -1,15 +1,15 @@
|
|
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
|
-
rake (13.0
|
12
|
+
rake (13.1.0)
|
13
13
|
rake-compiler (1.1.6)
|
14
14
|
rake
|
15
15
|
sequel (5.51.0)
|
@@ -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.
|
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;
|