extralite-bundle 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08845d2cb3c74da55576eb9db9ada9244e2cb48d6f8cee5676550a8bcfaceeb1'
4
- data.tar.gz: d3a8503488e7078d6c1f2112eb708777bc5a25ab8e85ac9972ae4dc3b1fe51d0
3
+ metadata.gz: 4b3eeea4f7340013a751b39ce75005f85c17004d092aa49b1bb345c30e0db0e2
4
+ data.tar.gz: 7179f952c683aed063ad4f4f7613009a12cdefc02af77514fd0995dc21bba5fa
5
5
  SHA512:
6
- metadata.gz: 80d3e967d8e13bf5568422ac45778649bc6b52adeff95cbf25d50d919fa5f0b3ac320dba063768eb2918fc6845813c3d4b895dc22a52278725b62a5520c69ad6
7
- data.tar.gz: 37d2f0954859db30a976c4a3e255c1c607bbe70d42c7bf27772d391db9a6f1760dfb3d6b9d2fcf9a1f867f5d45dd39a8be2a870d015e993c1d34a8c25f3c9ed4
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
@@ -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: ['2.7', '3.0', '3.1', '3.2', '3.3']
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. [#46](https://github.com/digital-fabric/extralite/pull/46)
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. [#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)
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 [#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)
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 API changes
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`, `PreparedStatement#status` (#14 @sitano)
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (2.4)
4
+ extralite (2.5)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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 ?2 as foo, ?1 as bar, ?1 * ?2 as baz', 6, 7) #=> [{ :foo => 7, :bar => 6, :baz => 42 }]
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
- # parameter binding of named parameters
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.execute_multi('insert into foo values (?)', ['bar', 'baz'])
125
- db.execute_multi('insert into foo values (?, ?)', [[1, 2], [3, 4]])
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
- 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:
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 single-threaded and
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
 
@@ -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
- void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
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 void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
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
- for (int i = 0; i < argc; i++) bind_parameter_value(stmt, i + 1, argv[i]);
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, i + 1, RARRAY_AREF(obj, i));
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
- VALUE safe_execute_multi(query_ctx *ctx) {
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
- while (stmt_iterate(ctx));
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
- return INT2FIX(changes);
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) {