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 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) {