extralite-bundle 2.4 → 2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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/ext/sqlite3/sqlite3.c +5420 -2501
- data/ext/sqlite3/sqlite3.h +73 -18
- 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: 4b3eeea4f7340013a751b39ce75005f85c17004d092aa49b1bb345c30e0db0e2
|
4
|
+
data.tar.gz: 7179f952c683aed063ad4f4f7613009a12cdefc02af77514fd0995dc21bba5fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eefd07e68fc4e2744a66d2941a48a85f603b667d9dd48504556f120d3077d4fbc95cc0913f85047b1fe917c11b0f0d8b60cfc1b41cbecc8e8b8d83f62cce4fb5
|
7
|
+
data.tar.gz: 1269b6eaed626d299b89ed634cc77d38b72e74913864ca18d046fd6ecae030dc6071d23152f7213ed15b1eef40effae88fe72508f4b2d6e1c46dc0a0580c783c
|
@@ -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) {
|