extralite 2.4 → 2.5
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/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +2 -12
- data/CHANGELOG.md +39 -10
- data/Gemfile.lock +1 -1
- data/README.md +45 -11
- data/TODO.md +0 -3
- data/ext/extralite/common.c +222 -15
- data/ext/extralite/database.c +185 -16
- data/ext/extralite/extralite.h +5 -1
- data/ext/extralite/extralite_ext.c +4 -0
- data/ext/extralite/query.c +213 -12
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +14 -6
- data/test/helper.rb +1 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/test_database.rb +471 -12
- data/test/test_query.rb +362 -2
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd2ac51ceb97cc6476f169da43fd11d6dad00ac39d5e9fd5eb0d4842ce670f0e
|
4
|
+
data.tar.gz: f58478324015a1f1827c55ca8b2170b177b270cad0ab8102be225a414da34148
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3051d9b5d2a2543be9bdafd5b46aab5574acefa8ca946eaf34f5b1a8097154748f90ab6ba1b7fd76d5bb2b92d97599b35748be0d815258b4b3a499c6c5cefef7
|
7
|
+
data.tar.gz: 148d29ccfff66b0f1531000d0773b8de98b98169594c08ed032fccd7e92ffe1333357aa3da2e168d2b629d4c7b41b14af373e705601bf384b9d4a4f39af9c4f6
|
@@ -0,0 +1,30 @@
|
|
1
|
+
name: Tests (extralite-bundle)
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
concurrency:
|
6
|
+
group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
|
7
|
+
cancel-in-progress: true
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
build:
|
11
|
+
strategy:
|
12
|
+
fail-fast: false
|
13
|
+
matrix:
|
14
|
+
os: [ubuntu-latest, macos-latest]
|
15
|
+
ruby: ['3.0', '3.1', '3.2', '3.3']
|
16
|
+
|
17
|
+
name: >-
|
18
|
+
${{matrix.os}}, ${{matrix.ruby}}
|
19
|
+
|
20
|
+
runs-on: ${{matrix.os}}
|
21
|
+
steps:
|
22
|
+
- uses: actions/checkout@v4
|
23
|
+
- uses: ruby/setup-ruby@v1
|
24
|
+
with:
|
25
|
+
ruby-version: ${{matrix.ruby}}
|
26
|
+
bundler-cache: true # 'bundle install' and cache
|
27
|
+
- name: Compile C-extension
|
28
|
+
run: EXTRALITE_BUNDLE=1 bundle exec rake compile
|
29
|
+
- name: Run tests
|
30
|
+
run: bundle exec rake test
|
data/.github/workflows/test.yml
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
name: Tests
|
1
|
+
name: Tests (extralite)
|
2
2
|
|
3
3
|
on: [push, pull_request]
|
4
4
|
|
@@ -12,7 +12,7 @@ jobs:
|
|
12
12
|
fail-fast: false
|
13
13
|
matrix:
|
14
14
|
os: [ubuntu-latest, macos-latest]
|
15
|
-
ruby: ['
|
15
|
+
ruby: ['3.0', '3.1', '3.2', '3.3']
|
16
16
|
|
17
17
|
name: >-
|
18
18
|
${{matrix.os}}, ${{matrix.ruby}}
|
@@ -28,13 +28,3 @@ jobs:
|
|
28
28
|
run: bundle exec rake compile
|
29
29
|
- name: Run tests
|
30
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/CHANGELOG.md
CHANGED
@@ -1,14 +1,42 @@
|
|
1
|
+
# 2.5 2024-01-16
|
2
|
+
|
3
|
+
- Update bundled sqlite to version 3.45.0
|
4
|
+
- Implement `Database#batch_query` and related methods
|
5
|
+
[53](https://github.com/digital-fabric/extralite/issues/53)
|
6
|
+
- Accept more options in `Database#initialize`
|
7
|
+
[48](https://github.com/digital-fabric/extralite/issues/48)
|
8
|
+
- Fix `Database#pragma` to return single value when reading pragma value
|
9
|
+
- Accept database name in `Database#tables` method
|
10
|
+
- Improve `Database#batch_execute` - now accepts Enumerable and Callable
|
11
|
+
parameters [52](https://github.com/digital-fabric/extralite/issues/52)
|
12
|
+
- Rename `Database#execute_multi` to `Database#batch_execute`
|
13
|
+
- Implement Query#clone
|
14
|
+
[51](https://github.com/digital-fabric/extralite/issues/51)
|
15
|
+
- Add support for GC compaction
|
16
|
+
- Remove support for Ruby 2.7
|
17
|
+
- Implement Query#<< [49](https://github.com/digital-fabric/extralite/issues/49)
|
18
|
+
- Allow passing parameters in array
|
19
|
+
- Add support for ractors
|
20
|
+
[#50](https://github.com/digital-fabric/extralite/issues/50)
|
21
|
+
|
1
22
|
# 2.4 2023-12-24
|
2
23
|
|
3
|
-
- Implement GVL release threshold.
|
24
|
+
- Implement GVL release threshold.
|
25
|
+
[#46](https://github.com/digital-fabric/extralite/pull/46)
|
4
26
|
- Implement write barriers for better GC performance.
|
5
|
-
- Add support for binding large numbers and symbols.
|
6
|
-
|
7
|
-
-
|
27
|
+
- Add support for binding large numbers and symbols.
|
28
|
+
[#43](https://github.com/digital-fabric/extralite/pull/43)
|
29
|
+
- Implement Database#transaction.
|
30
|
+
[#42](https://github.com/digital-fabric/extralite/pull/42)
|
31
|
+
- Add support for binding BLOBs.
|
32
|
+
[#40](https://github.com/digital-fabric/extralite/pull/40)
|
8
33
|
- Minor fixes and cleanup of C-code.
|
9
|
-
- Fix `Database#inspect` for a closed database instance
|
10
|
-
|
11
|
-
-
|
34
|
+
- Fix `Database#inspect` for a closed database instance
|
35
|
+
[#37](https://github.com/digital-fabric/extralite/issues/37)
|
36
|
+
- Add support for binding named parameters from Struct and Data classes
|
37
|
+
[#30](https://github.com/digital-fabric/extralite/pull/30)
|
38
|
+
- Update bundled SQLite code to version 3.44.2
|
39
|
+
[#32](https://github.com/digital-fabric/extralite/pull/32)
|
12
40
|
|
13
41
|
# 2.3 2023-11-12
|
14
42
|
|
@@ -28,7 +56,8 @@
|
|
28
56
|
|
29
57
|
- Fix Sequel migrations (#8)
|
30
58
|
- Redesign prepared statement functionality (#24)
|
31
|
-
- Rewrite `Extralite::PreparedStatement` into `Extralite::Query` with breaking
|
59
|
+
- Rewrite `Extralite::PreparedStatement` into `Extralite::Query` with breaking
|
60
|
+
API changes
|
32
61
|
- Add `Extralite::Iterator` class for external iteration
|
33
62
|
- Add `Query#each_xxx`, `Query#to_a_xxx` method
|
34
63
|
- Add `Query#eof?` method
|
@@ -77,7 +106,8 @@
|
|
77
106
|
# 1.20 2023-01-21
|
78
107
|
|
79
108
|
- Fix compilation error (#15 @sitano)
|
80
|
-
- Add status methods `Extralite.runtime_status`, `Database#status`,
|
109
|
+
- Add status methods `Extralite.runtime_status`, `Database#status`,
|
110
|
+
`PreparedStatement#status` (#14 @sitano)
|
81
111
|
- Add `Database#interrupt` (#13 @sitano)
|
82
112
|
- Add `Database#backup` (#11 @sitano)
|
83
113
|
- Derive `Extralite::Error` from `StandardError` (#10 @sitano)
|
@@ -199,4 +229,3 @@
|
|
199
229
|
## 0.1 2021-05-21
|
200
230
|
|
201
231
|
- First release
|
202
|
-
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -49,6 +49,8 @@ gem 'extralite'
|
|
49
49
|
|
50
50
|
You can also run `gem install extralite` if you just want to check it out.
|
51
51
|
|
52
|
+
__Note__: Extralite supports Ruby 3.0 and higher.
|
53
|
+
|
52
54
|
### Installing the Extralite-SQLite3 Bundle
|
53
55
|
|
54
56
|
If you don't have sqlite3 installed on your system, do not want to use the
|
@@ -102,9 +104,12 @@ db.query_single_value("select 'foo'") #=> "foo"
|
|
102
104
|
|
103
105
|
# parameter binding (works for all query_xxx methods)
|
104
106
|
db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
105
|
-
db.query_hash('select ?
|
107
|
+
db.query_hash('select ? as foo, ? as bar', [1, 2]) #=> [{ :foo => 1, :bar => 2 }]
|
108
|
+
|
109
|
+
# explicit parameter indexes
|
110
|
+
db.query_hash('select ?2 as foo, ?1 as bar, ?1 * ?2 as baz', 6, 7) #=> [{ :foo => 7, :bar => 6, :baz => 42 }
|
106
111
|
|
107
|
-
#
|
112
|
+
# named parameters
|
108
113
|
db.query('select * from foo where bar = :bar', bar: 42)
|
109
114
|
db.query('select * from foo where bar = :bar', 'bar' => 42)
|
110
115
|
db.query('select * from foo where bar = :bar', ':bar' => 42)
|
@@ -121,8 +126,15 @@ db.execute('insert into foo values (?)', Extralite::Blob.new('Hello, 世界!'))
|
|
121
126
|
db.execute('insert into foo values (?)', 'Hello, 世界!'.force_encoding(Encoding::ASCII_8BIT))
|
122
127
|
|
123
128
|
# insert multiple rows
|
124
|
-
db.
|
125
|
-
db.
|
129
|
+
db.batch_execute('insert into foo values (?)', ['bar', 'baz'])
|
130
|
+
db.batch_execute('insert into foo values (?, ?)', [[1, 2], [3, 4]])
|
131
|
+
|
132
|
+
# batch execute from enumerable
|
133
|
+
db.batch_execute('insert into foo values (?)', 1..10)
|
134
|
+
|
135
|
+
# batch execute from block
|
136
|
+
source = [[1, 2], [2, 3], [3, 4]]
|
137
|
+
db.batch_execute('insert into foo values (?, ?)') { source.shift }
|
126
138
|
|
127
139
|
# prepared queries
|
128
140
|
query = db.prepare('select ? as foo, ? as bar') #=> Extralite::Query
|
@@ -332,11 +344,18 @@ p articles.to_a
|
|
332
344
|
|
333
345
|
## Concurrency
|
334
346
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
GVL
|
339
|
-
|
347
|
+
### The Ruby GVL
|
348
|
+
|
349
|
+
Extralite releases the [Ruby
|
350
|
+
GVL](https://www.speedshop.co/2020/05/11/the-ruby-gvl-and-scaling.html) while
|
351
|
+
making calls to the sqlite3 library that might block, such as when backing up a
|
352
|
+
database, or when preparing a query. This allows other threads to run while the
|
353
|
+
underlying sqlite3 library is busy preparing queries, fetching records and
|
354
|
+
backing up databases.
|
355
|
+
|
356
|
+
Extralite also releases the GVL periodically when iterating over records. By
|
357
|
+
default, the GVL is released every 1000 records iterated. The GVL release
|
358
|
+
threshold can be set separately for each database:
|
340
359
|
|
341
360
|
```ruby
|
342
361
|
db.gvl_release_threshold = 10 # release GVL every 10 records
|
@@ -345,8 +364,8 @@ db.gvl_release_threshold = nil # use default value (currently 1000)
|
|
345
364
|
```
|
346
365
|
|
347
366
|
For most applications, there's no need to tune the GVL threshold value, as it
|
348
|
-
provides [excellent](#performance) performance characteristics for both
|
349
|
-
multi-threaded applications.
|
367
|
+
provides [excellent](#performance) performance characteristics for both
|
368
|
+
single-threaded and multi-threaded applications.
|
350
369
|
|
351
370
|
In a heavily multi-threaded application, releasing the GVL more often (lower
|
352
371
|
threshold value) will lead to less latency (for threads not running a query),
|
@@ -360,6 +379,21 @@ latency and throughput:
|
|
360
379
|
less latency & throughput <<< GVL release threshold >>> more latency & throughput
|
361
380
|
```
|
362
381
|
|
382
|
+
### Thread Safety
|
383
|
+
|
384
|
+
A single database instance can be safely used in multiple threads simultaneously
|
385
|
+
as long as the following conditions are met:
|
386
|
+
|
387
|
+
- No explicit transactions are used.
|
388
|
+
- Each thread issues queries by calling `Database#query_xxx`, or uses a separate
|
389
|
+
`Query` instance.
|
390
|
+
- The GVL release threshold is not `0` (i.e. the GVL is released periodically
|
391
|
+
while running queries.)
|
392
|
+
|
393
|
+
### Use with Ractors
|
394
|
+
|
395
|
+
Extralite databases can be used inside ractors
|
396
|
+
|
363
397
|
## Performance
|
364
398
|
|
365
399
|
A benchmark script is included, creating a table of various row counts, then
|
data/TODO.md
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
- Improve tracing
|
2
1
|
- Transactions and savepoints:
|
3
2
|
|
4
|
-
- `DB#transaction {}` - does a `BEGIN..COMMIT` - non-reentrant!
|
5
3
|
- `DB#savepoint(name)` - creates a savepoint
|
6
4
|
- `DB#release(name)` - releases a savepoint
|
7
5
|
- `DB#rollback` - raises `Extralite::Rollback`, which is rescued by `DB#transaction`
|
@@ -10,7 +8,6 @@
|
|
10
8
|
- More database methods:
|
11
9
|
|
12
10
|
- `Database#quote`
|
13
|
-
- `Database#busy_timeout=` https://sqlite.org/c3ref/busy_timeout.html
|
14
11
|
- `Database#cache_flush` https://sqlite.org/c3ref/db_cacheflush.html
|
15
12
|
- `Database#release_memory` https://sqlite.org/c3ref/db_release_memory.html
|
16
13
|
|
data/ext/extralite/common.c
CHANGED
@@ -31,7 +31,7 @@ static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
31
31
|
return Qnil;
|
32
32
|
}
|
33
33
|
|
34
|
-
|
34
|
+
int bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
|
35
35
|
|
36
36
|
static inline void bind_key_value(sqlite3_stmt *stmt, VALUE k, VALUE v) {
|
37
37
|
switch (TYPE(k)) {
|
@@ -72,24 +72,24 @@ void bind_struct_parameter_values(sqlite3_stmt *stmt, VALUE struct_obj) {
|
|
72
72
|
RB_GC_GUARD(members);
|
73
73
|
}
|
74
74
|
|
75
|
-
inline
|
75
|
+
inline int bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
76
76
|
switch (TYPE(value)) {
|
77
77
|
case T_NIL:
|
78
78
|
sqlite3_bind_null(stmt, pos);
|
79
|
-
return;
|
79
|
+
return 1;
|
80
80
|
case T_FIXNUM:
|
81
81
|
case T_BIGNUM:
|
82
82
|
sqlite3_bind_int64(stmt, pos, NUM2LL(value));
|
83
|
-
return;
|
83
|
+
return 1;
|
84
84
|
case T_FLOAT:
|
85
85
|
sqlite3_bind_double(stmt, pos, NUM2DBL(value));
|
86
|
-
return;
|
86
|
+
return 1;
|
87
87
|
case T_TRUE:
|
88
88
|
sqlite3_bind_int(stmt, pos, 1);
|
89
|
-
return;
|
89
|
+
return 1;
|
90
90
|
case T_FALSE:
|
91
91
|
sqlite3_bind_int(stmt, pos, 0);
|
92
|
-
return;
|
92
|
+
return 1;
|
93
93
|
case T_SYMBOL:
|
94
94
|
value = rb_sym2str(value);
|
95
95
|
case T_STRING:
|
@@ -97,13 +97,20 @@ inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
|
97
97
|
sqlite3_bind_blob(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
98
98
|
else
|
99
99
|
sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
100
|
-
return;
|
100
|
+
return 1;
|
101
|
+
case T_ARRAY:
|
102
|
+
{
|
103
|
+
int count = RARRAY_LEN(value);
|
104
|
+
for (int i = 0; i < count; i++)
|
105
|
+
bind_parameter_value(stmt, pos + i, RARRAY_AREF(value, i));
|
106
|
+
return count;
|
107
|
+
}
|
101
108
|
case T_HASH:
|
102
109
|
bind_hash_parameter_values(stmt, value);
|
103
|
-
return;
|
110
|
+
return 0;
|
104
111
|
case T_STRUCT:
|
105
112
|
bind_struct_parameter_values(stmt, value);
|
106
|
-
return;
|
113
|
+
return 0;
|
107
114
|
default:
|
108
115
|
rb_raise(cParameterError, "Cannot bind parameter at position %d of type %"PRIsVALUE"",
|
109
116
|
pos, rb_class_name(rb_obj_class(value)));
|
@@ -111,14 +118,18 @@ inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
|
111
118
|
}
|
112
119
|
|
113
120
|
inline void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv) {
|
114
|
-
|
121
|
+
int pos = 1;
|
122
|
+
for (int i = 0; i < argc; i++) {
|
123
|
+
pos += bind_parameter_value(stmt, pos, argv[i]);
|
124
|
+
}
|
115
125
|
}
|
116
126
|
|
117
127
|
inline void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj) {
|
118
128
|
if (TYPE(obj) == T_ARRAY) {
|
129
|
+
int pos = 1;
|
119
130
|
int count = RARRAY_LEN(obj);
|
120
131
|
for (int i = 0; i < count; i++)
|
121
|
-
bind_parameter_value(stmt,
|
132
|
+
pos += bind_parameter_value(stmt, pos, RARRAY_AREF(obj, i));
|
122
133
|
}
|
123
134
|
else
|
124
135
|
bind_parameter_value(stmt, 1, obj);
|
@@ -423,8 +434,80 @@ VALUE safe_query_single_value(query_ctx *ctx) {
|
|
423
434
|
return value;
|
424
435
|
}
|
425
436
|
|
426
|
-
|
437
|
+
enum batch_mode {
|
438
|
+
BATCH_EXECUTE,
|
439
|
+
BATCH_QUERY_ARY,
|
440
|
+
BATCH_QUERY_HASH,
|
441
|
+
BATCH_QUERY_SINGLE_COLUMN
|
442
|
+
};
|
443
|
+
|
444
|
+
static inline VALUE batch_iterate_hash(query_ctx *ctx) {
|
445
|
+
VALUE rows = rb_ary_new();
|
446
|
+
VALUE row = Qnil;
|
447
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
448
|
+
VALUE column_names = get_column_names(ctx->stmt, column_count);
|
449
|
+
|
450
|
+
while (stmt_iterate(ctx)) {
|
451
|
+
row = row_to_hash(ctx->stmt, column_count, column_names);
|
452
|
+
rb_ary_push(rows, row);
|
453
|
+
}
|
454
|
+
|
455
|
+
RB_GC_GUARD(column_names);
|
456
|
+
RB_GC_GUARD(rows);
|
457
|
+
return rows;
|
458
|
+
}
|
459
|
+
|
460
|
+
static inline VALUE batch_iterate_ary(query_ctx *ctx) {
|
461
|
+
VALUE rows = rb_ary_new();
|
462
|
+
VALUE row = Qnil;
|
463
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
464
|
+
|
465
|
+
while (stmt_iterate(ctx)) {
|
466
|
+
row = row_to_ary(ctx->stmt, column_count);
|
467
|
+
rb_ary_push(rows, row);
|
468
|
+
}
|
469
|
+
|
470
|
+
RB_GC_GUARD(rows);
|
471
|
+
return rows;
|
472
|
+
}
|
473
|
+
|
474
|
+
static inline VALUE batch_iterate_single_column(query_ctx *ctx) {
|
475
|
+
VALUE rows = rb_ary_new();
|
476
|
+
VALUE value = Qnil;
|
477
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
478
|
+
if (column_count != 1) rb_raise(cError, "Expected query result to have 1 column");
|
479
|
+
|
480
|
+
while (stmt_iterate(ctx)) {
|
481
|
+
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
482
|
+
rb_ary_push(rows, value);
|
483
|
+
}
|
484
|
+
|
485
|
+
RB_GC_GUARD(rows);
|
486
|
+
return rows;
|
487
|
+
}
|
488
|
+
|
489
|
+
static inline void batch_iterate(query_ctx *ctx, enum batch_mode mode, VALUE *rows) {
|
490
|
+
switch (mode) {
|
491
|
+
case BATCH_EXECUTE:
|
492
|
+
while (stmt_iterate(ctx));
|
493
|
+
break;
|
494
|
+
case BATCH_QUERY_ARY:
|
495
|
+
*rows = batch_iterate_ary(ctx);
|
496
|
+
break;
|
497
|
+
case BATCH_QUERY_HASH:
|
498
|
+
*rows = batch_iterate_hash(ctx);
|
499
|
+
break;
|
500
|
+
case BATCH_QUERY_SINGLE_COLUMN:
|
501
|
+
*rows = batch_iterate_single_column(ctx);
|
502
|
+
break;
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
static inline VALUE batch_run_array(query_ctx *ctx, enum batch_mode mode) {
|
427
507
|
int count = RARRAY_LEN(ctx->params);
|
508
|
+
int block_given = rb_block_given_p();
|
509
|
+
VALUE results = (mode != BATCH_EXECUTE) && !block_given ? rb_ary_new() : Qnil;
|
510
|
+
VALUE rows = Qnil;
|
428
511
|
int changes = 0;
|
429
512
|
|
430
513
|
for (int i = 0; i < count; i++) {
|
@@ -432,11 +515,135 @@ VALUE safe_execute_multi(query_ctx *ctx) {
|
|
432
515
|
sqlite3_clear_bindings(ctx->stmt);
|
433
516
|
bind_all_parameters_from_object(ctx->stmt, RARRAY_AREF(ctx->params, i));
|
434
517
|
|
435
|
-
|
518
|
+
batch_iterate(ctx, mode, &rows);
|
436
519
|
changes += sqlite3_changes(ctx->sqlite3_db);
|
520
|
+
|
521
|
+
if (mode != BATCH_EXECUTE) {
|
522
|
+
if (block_given)
|
523
|
+
rb_yield(rows);
|
524
|
+
else
|
525
|
+
rb_ary_push(results, rows);
|
526
|
+
}
|
527
|
+
}
|
528
|
+
|
529
|
+
RB_GC_GUARD(rows);
|
530
|
+
RB_GC_GUARD(results);
|
531
|
+
|
532
|
+
if (mode == BATCH_EXECUTE || block_given)
|
533
|
+
return INT2FIX(changes);
|
534
|
+
else
|
535
|
+
return results;
|
536
|
+
}
|
537
|
+
|
538
|
+
struct batch_execute_each_ctx {
|
539
|
+
query_ctx *ctx;
|
540
|
+
enum batch_mode mode;
|
541
|
+
int block_given;
|
542
|
+
VALUE results;
|
543
|
+
int changes;
|
544
|
+
};
|
545
|
+
|
546
|
+
static VALUE batch_run_each_iter(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, vctx)) {
|
547
|
+
struct batch_execute_each_ctx *each_ctx = (struct batch_execute_each_ctx*)vctx;
|
548
|
+
VALUE rows = Qnil;
|
549
|
+
|
550
|
+
sqlite3_reset(each_ctx->ctx->stmt);
|
551
|
+
sqlite3_clear_bindings(each_ctx->ctx->stmt);
|
552
|
+
bind_all_parameters_from_object(each_ctx->ctx->stmt, yield_value);
|
553
|
+
|
554
|
+
batch_iterate(each_ctx->ctx, each_ctx->mode, &rows);
|
555
|
+
each_ctx->changes += sqlite3_changes(each_ctx->ctx->sqlite3_db);
|
556
|
+
|
557
|
+
if (each_ctx->mode != BATCH_EXECUTE) {
|
558
|
+
if (each_ctx->block_given)
|
559
|
+
rb_yield(rows);
|
560
|
+
else
|
561
|
+
rb_ary_push(each_ctx->results, rows);
|
562
|
+
}
|
563
|
+
RB_GC_GUARD(rows);
|
564
|
+
|
565
|
+
return Qnil;
|
566
|
+
}
|
567
|
+
|
568
|
+
static inline VALUE batch_run_each(query_ctx *ctx, enum batch_mode mode) {
|
569
|
+
struct batch_execute_each_ctx each_ctx = {
|
570
|
+
.ctx = ctx,
|
571
|
+
.mode = mode,
|
572
|
+
.block_given = rb_block_given_p(),
|
573
|
+
.results = ((mode != BATCH_EXECUTE) && !rb_block_given_p() ? rb_ary_new() : Qnil),
|
574
|
+
.changes = 0
|
575
|
+
};
|
576
|
+
rb_block_call(ctx->params, ID_each, 0, 0, batch_run_each_iter, (VALUE)&each_ctx);
|
577
|
+
|
578
|
+
if (mode == BATCH_EXECUTE || each_ctx.block_given)
|
579
|
+
return INT2FIX(each_ctx.changes);
|
580
|
+
else
|
581
|
+
return each_ctx.results;
|
582
|
+
}
|
583
|
+
|
584
|
+
static inline VALUE batch_run_proc(query_ctx *ctx, enum batch_mode mode) {
|
585
|
+
VALUE params = Qnil;
|
586
|
+
int block_given = rb_block_given_p();
|
587
|
+
VALUE results = (mode != BATCH_EXECUTE) && !block_given ? rb_ary_new() : Qnil;
|
588
|
+
VALUE rows = Qnil;
|
589
|
+
int changes = 0;
|
590
|
+
|
591
|
+
while (1) {
|
592
|
+
params = rb_funcall(ctx->params, ID_call, 0);
|
593
|
+
if (NIL_P(params)) break;
|
594
|
+
|
595
|
+
sqlite3_reset(ctx->stmt);
|
596
|
+
sqlite3_clear_bindings(ctx->stmt);
|
597
|
+
bind_all_parameters_from_object(ctx->stmt, params);
|
598
|
+
|
599
|
+
batch_iterate(ctx, mode, &rows);
|
600
|
+
changes += sqlite3_changes(ctx->sqlite3_db);
|
601
|
+
|
602
|
+
if (mode != BATCH_EXECUTE) {
|
603
|
+
if (block_given)
|
604
|
+
rb_yield(rows);
|
605
|
+
else
|
606
|
+
rb_ary_push(results, rows);
|
607
|
+
}
|
437
608
|
}
|
438
609
|
|
439
|
-
|
610
|
+
RB_GC_GUARD(rows);
|
611
|
+
RB_GC_GUARD(results);
|
612
|
+
RB_GC_GUARD(params);
|
613
|
+
|
614
|
+
if (mode == BATCH_EXECUTE || block_given)
|
615
|
+
return INT2FIX(changes);
|
616
|
+
else
|
617
|
+
return results;
|
618
|
+
}
|
619
|
+
|
620
|
+
static inline VALUE batch_run(query_ctx *ctx, enum batch_mode mode) {
|
621
|
+
if (TYPE(ctx->params) == T_ARRAY)
|
622
|
+
return batch_run_array(ctx, mode);
|
623
|
+
|
624
|
+
if (rb_respond_to(ctx->params, ID_each))
|
625
|
+
return batch_run_each(ctx, mode);
|
626
|
+
|
627
|
+
if (rb_respond_to(ctx->params, ID_call))
|
628
|
+
return batch_run_proc(ctx, mode);
|
629
|
+
|
630
|
+
rb_raise(cParameterError, "Invalid parameter source supplied to #batch_execute");
|
631
|
+
}
|
632
|
+
|
633
|
+
VALUE safe_batch_execute(query_ctx *ctx) {
|
634
|
+
return batch_run(ctx, BATCH_EXECUTE);
|
635
|
+
}
|
636
|
+
|
637
|
+
VALUE safe_batch_query(query_ctx *ctx) {
|
638
|
+
return batch_run(ctx, BATCH_QUERY_HASH);
|
639
|
+
}
|
640
|
+
|
641
|
+
VALUE safe_batch_query_ary(query_ctx *ctx) {
|
642
|
+
return batch_run(ctx, BATCH_QUERY_ARY);
|
643
|
+
}
|
644
|
+
|
645
|
+
VALUE safe_batch_query_single_column(query_ctx *ctx) {
|
646
|
+
return batch_run(ctx, BATCH_QUERY_SINGLE_COLUMN);
|
440
647
|
}
|
441
648
|
|
442
649
|
VALUE safe_query_columns(query_ctx *ctx) {
|