extralite-bundle 2.3 → 2.5
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-bundle.yml +30 -0
- data/.github/workflows/test.yml +7 -3
- data/.gitignore +3 -0
- data/CHANGELOG.md +44 -3
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +3 -3
- data/README.md +106 -13
- data/TODO.md +0 -3
- data/bin/update_sqlite_source +10 -3
- data/ext/extralite/common.c +288 -37
- data/ext/extralite/database.c +256 -39
- data/ext/extralite/extralite.h +20 -9
- data/ext/extralite/extralite_ext.c +4 -0
- data/ext/extralite/iterator.c +5 -5
- data/ext/extralite/query.c +250 -41
- data/ext/sqlite3/sqlite3.c +5556 -2531
- data/ext/sqlite3/sqlite3.h +125 -27
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +41 -6
- data/test/fixtures/image.png +0 -0
- data/test/helper.rb +3 -0
- data/test/issue-38.rb +80 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +6 -3
- data/test/perf_hash.rb +7 -4
- data/test/test_database.rb +726 -15
- data/test/test_iterator.rb +2 -1
- data/test/test_query.rb +402 -6
- metadata +10 -3
data/ext/extralite/database.c
CHANGED
@@ -1,26 +1,42 @@
|
|
1
1
|
#include <stdio.h>
|
2
|
+
#include <stdlib.h>
|
2
3
|
#include "extralite.h"
|
3
4
|
|
4
5
|
VALUE cDatabase;
|
6
|
+
VALUE cBlob;
|
5
7
|
VALUE cError;
|
6
8
|
VALUE cSQLError;
|
7
9
|
VALUE cBusyError;
|
8
10
|
VALUE cInterruptError;
|
11
|
+
VALUE cParameterError;
|
9
12
|
VALUE eArgumentError;
|
10
13
|
|
11
14
|
ID ID_bind;
|
12
15
|
ID ID_call;
|
16
|
+
ID ID_each;
|
13
17
|
ID ID_keys;
|
14
18
|
ID ID_new;
|
15
19
|
ID ID_strip;
|
16
|
-
ID ID_to_s;
|
17
20
|
|
21
|
+
VALUE SYM_gvl_release_threshold;
|
18
22
|
VALUE SYM_read_only;
|
23
|
+
VALUE SYM_synchronous;
|
24
|
+
VALUE SYM_wal_journal_mode;
|
19
25
|
|
20
26
|
static size_t Database_size(const void *ptr) {
|
21
27
|
return sizeof(Database_t);
|
22
28
|
}
|
23
29
|
|
30
|
+
static void Database_mark(void *ptr) {
|
31
|
+
Database_t *db = ptr;
|
32
|
+
rb_gc_mark_movable(db->trace_block);
|
33
|
+
}
|
34
|
+
|
35
|
+
static void Database_compact(void *ptr) {
|
36
|
+
Database_t *db = ptr;
|
37
|
+
db->trace_block = rb_gc_location(db->trace_block);
|
38
|
+
}
|
39
|
+
|
24
40
|
static void Database_free(void *ptr) {
|
25
41
|
Database_t *db = ptr;
|
26
42
|
if (db->sqlite3_db) sqlite3_close_v2(db->sqlite3_db);
|
@@ -29,8 +45,8 @@ static void Database_free(void *ptr) {
|
|
29
45
|
|
30
46
|
static const rb_data_type_t Database_type = {
|
31
47
|
"Database",
|
32
|
-
{
|
33
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
48
|
+
{Database_mark, Database_free, Database_size, Database_compact},
|
49
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
34
50
|
};
|
35
51
|
|
36
52
|
static VALUE Database_allocate(VALUE klass) {
|
@@ -48,7 +64,7 @@ inline Database_t *self_to_database(VALUE self) {
|
|
48
64
|
inline Database_t *self_to_open_database(VALUE self) {
|
49
65
|
Database_t *db = self_to_database(self);
|
50
66
|
if (!(db)->sqlite3_db) rb_raise(cError, "Database is closed");
|
51
|
-
|
67
|
+
|
52
68
|
return db;
|
53
69
|
}
|
54
70
|
|
@@ -78,14 +94,40 @@ default_flags:
|
|
78
94
|
return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
79
95
|
}
|
80
96
|
|
97
|
+
VALUE Database_execute(int argc, VALUE *argv, VALUE self);
|
98
|
+
|
99
|
+
void Database_apply_opts(VALUE self, Database_t *db, VALUE opts) {
|
100
|
+
VALUE value = Qnil;
|
101
|
+
|
102
|
+
value = rb_hash_aref(opts, SYM_gvl_release_threshold);
|
103
|
+
if (!NIL_P(value)) db->gvl_release_threshold = NUM2INT(value);
|
104
|
+
|
105
|
+
value = rb_hash_aref(opts, SYM_wal_journal_mode);
|
106
|
+
if (RTEST(value)) {
|
107
|
+
value = rb_str_new_literal("PRAGMA journal_mode=wal");
|
108
|
+
Database_execute(1, &value, self);
|
109
|
+
}
|
110
|
+
|
111
|
+
value = rb_hash_aref(opts, SYM_synchronous);
|
112
|
+
if (RTEST(value)) {
|
113
|
+
value = rb_str_new_literal("PRAGMA synchronous=1");
|
114
|
+
Database_execute(1, &value, self);
|
115
|
+
}
|
116
|
+
|
117
|
+
RB_GC_GUARD(value);
|
118
|
+
}
|
119
|
+
|
81
120
|
/* Initializes a new SQLite database with the given path and options.
|
82
121
|
*
|
83
122
|
* @overload initialize(path)
|
84
123
|
* @param path [String] file path (or ':memory:' for memory database)
|
85
124
|
* @return [void]
|
86
|
-
* @overload initialize(path, read_only:
|
125
|
+
* @overload initialize(path, gvl_release_threshold: , read_only: , synchronous: , wal_journal_mode: )
|
87
126
|
* @param path [String] file path (or ':memory:' for memory database)
|
127
|
+
* @param gvl_release_threshold [Integer] GVL release threshold
|
88
128
|
* @param read_only [boolean] true for opening the database for reading only
|
129
|
+
* @param synchronous [boolean] true to set PRAGMA synchronous=1
|
130
|
+
* @param wal_journal_mode [boolean] true to set PRAGMA journal_mode=wal
|
89
131
|
* @return [void]
|
90
132
|
*/
|
91
133
|
VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
@@ -118,6 +160,9 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
|
118
160
|
#endif
|
119
161
|
|
120
162
|
db->trace_block = Qnil;
|
163
|
+
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
164
|
+
|
165
|
+
if (!NIL_P(opts)) Database_apply_opts(self, db, opts);
|
121
166
|
|
122
167
|
return Qnil;
|
123
168
|
}
|
@@ -172,14 +217,13 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
172
217
|
sql = rb_funcall(argv[0], ID_strip, 0);
|
173
218
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
174
219
|
|
175
|
-
// prepare query ctx
|
176
220
|
if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_call, 1, sql);
|
177
221
|
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
178
222
|
RB_GC_GUARD(sql);
|
179
223
|
|
180
224
|
bind_all_parameters(stmt, argc - 1, argv + 1);
|
181
|
-
query_ctx ctx =
|
182
|
-
|
225
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, Qnil, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS);
|
226
|
+
|
183
227
|
return rb_ensure(SAFE(call), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
184
228
|
}
|
185
229
|
|
@@ -190,18 +234,18 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
190
234
|
* Runs a query returning rows as hashes (with symbol keys). If a block is
|
191
235
|
* given, it will be called for each row. Otherwise, an array containing all
|
192
236
|
* rows is returned.
|
193
|
-
*
|
237
|
+
*
|
194
238
|
* Query parameters to be bound to placeholders in the query can be specified as
|
195
239
|
* a list of values or as a hash mapping parameter names to values. When
|
196
240
|
* parameters are given as an array, the query should specify parameters using
|
197
241
|
* `?`:
|
198
|
-
*
|
242
|
+
*
|
199
243
|
* db.query('select * from foo where x = ?', 42)
|
200
244
|
*
|
201
245
|
* Named placeholders are specified using `:`. The placeholder values are
|
202
246
|
* specified using a hash, where keys are either strings are symbols. String
|
203
247
|
* keys can include or omit the `:` prefix. The following are equivalent:
|
204
|
-
*
|
248
|
+
*
|
205
249
|
* db.query('select * from foo where x = :bar', bar: 42)
|
206
250
|
* db.query('select * from foo where x = :bar', 'bar' => 42)
|
207
251
|
* db.query('select * from foo where x = :bar', ':bar' => 42)
|
@@ -334,30 +378,150 @@ VALUE Database_execute(int argc, VALUE *argv, VALUE self) {
|
|
334
378
|
}
|
335
379
|
|
336
380
|
/* call-seq:
|
337
|
-
* db.
|
381
|
+
* db.batch_execute(sql, params_array) -> changes
|
382
|
+
* db.batch_execute(sql, enumerable) -> changes
|
383
|
+
* db.batch_execute(sql, callable) -> changes
|
384
|
+
*
|
385
|
+
* Executes the given query for each list of parameters in the paramter source.
|
386
|
+
* If an enumerable is given, it is iterated and each of its values is used as
|
387
|
+
* the parameters for running the query. If a callable is given, it is called
|
388
|
+
* repeatedly and each of its return values is used as the parameters, until nil
|
389
|
+
* is returned.
|
338
390
|
*
|
339
|
-
*
|
340
|
-
*
|
341
|
-
* multiple records.
|
391
|
+
* Returns the number of changes effected. This method is designed for inserting
|
392
|
+
* multiple records or performing other mass operations.
|
342
393
|
*
|
343
394
|
* records = [
|
344
395
|
* [1, 2, 3],
|
345
396
|
* [4, 5, 6]
|
346
397
|
* ]
|
347
|
-
* db.
|
398
|
+
* db.batch_execute('insert into foo values (?, ?, ?)', records)
|
399
|
+
*
|
400
|
+
* source = [
|
401
|
+
* [1, 2, 3],
|
402
|
+
* [4, 5, 6]
|
403
|
+
* ]
|
404
|
+
* db.batch_execute('insert into foo values (?, ?, ?)', -> { records.shift })
|
348
405
|
*
|
406
|
+
* @param sql [String] query SQL
|
407
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
408
|
+
* @return [Integer] Total number of changes effected
|
349
409
|
*/
|
350
|
-
VALUE
|
410
|
+
VALUE Database_batch_execute(VALUE self, VALUE sql, VALUE parameters) {
|
351
411
|
Database_t *db = self_to_open_database(self);
|
352
412
|
sqlite3_stmt *stmt;
|
353
413
|
|
354
414
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
355
415
|
|
356
|
-
// prepare query ctx
|
357
416
|
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
358
|
-
query_ctx ctx =
|
417
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
418
|
+
|
419
|
+
return rb_ensure(SAFE(safe_batch_execute), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
420
|
+
}
|
421
|
+
|
422
|
+
/* call-seq:
|
423
|
+
* db.batch_query(sql, params_array) -> rows
|
424
|
+
* db.batch_query(sql, enumerable) -> rows
|
425
|
+
* db.batch_query(sql, callable) -> rows
|
426
|
+
* db.batch_query(sql, params_array) { |rows| ... } -> changes
|
427
|
+
* db.batch_query(sql, enumerable) { |rows| ... } -> changes
|
428
|
+
* db.batch_query(sql, callable) { |rows| ... } -> changes
|
429
|
+
*
|
430
|
+
* Executes the given query for each list of parameters in the given paramter
|
431
|
+
* source. If a block is given, it is called with the resulting rows for each
|
432
|
+
* invocation of the query, and the total number of changes is returned.
|
433
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
434
|
+
* returned.
|
435
|
+
*
|
436
|
+
* records = [
|
437
|
+
* [1, 2],
|
438
|
+
* [3, 4]
|
439
|
+
* ]
|
440
|
+
* db.batch_query('insert into foo values (?, ?) returning bar, baz', records)
|
441
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
442
|
+
* *
|
443
|
+
* @param sql [String] query SQL
|
444
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
445
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
446
|
+
*/
|
447
|
+
VALUE Database_batch_query(VALUE self, VALUE sql, VALUE parameters) {
|
448
|
+
Database_t *db = self_to_open_database(self);
|
449
|
+
sqlite3_stmt *stmt;
|
450
|
+
|
451
|
+
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
452
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
453
|
+
|
454
|
+
return rb_ensure(SAFE(safe_batch_query), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
455
|
+
}
|
456
|
+
|
457
|
+
/* call-seq:
|
458
|
+
* db.batch_query_ary(sql, params_array) -> rows
|
459
|
+
* db.batch_query_ary(sql, enumerable) -> rows
|
460
|
+
* db.batch_query_ary(sql, callable) -> rows
|
461
|
+
* db.batch_query_ary(sql, params_array) { |rows| ... } -> changes
|
462
|
+
* db.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
|
463
|
+
* db.batch_query_ary(sql, callable) { |rows| ... } -> changes
|
464
|
+
*
|
465
|
+
* Executes the given query for each list of parameters in the given paramter
|
466
|
+
* source. If a block is given, it is called with the resulting rows for each
|
467
|
+
* invocation of the query, and the total number of changes is returned.
|
468
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
469
|
+
* returned. Rows are represented as arrays.
|
470
|
+
*
|
471
|
+
* records = [
|
472
|
+
* [1, 2],
|
473
|
+
* [3, 4]
|
474
|
+
* ]
|
475
|
+
* db.batch_query_ary('insert into foo values (?, ?) returning bar, baz', records)
|
476
|
+
* #=> [[1, 2], [3, 4]]
|
477
|
+
* *
|
478
|
+
* @param sql [String] query SQL
|
479
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
480
|
+
* @return [Array<Array>, Integer] Total number of changes effected
|
481
|
+
*/
|
482
|
+
VALUE Database_batch_query_ary(VALUE self, VALUE sql, VALUE parameters) {
|
483
|
+
Database_t *db = self_to_open_database(self);
|
484
|
+
sqlite3_stmt *stmt;
|
485
|
+
|
486
|
+
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
487
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
359
488
|
|
360
|
-
return rb_ensure(SAFE(
|
489
|
+
return rb_ensure(SAFE(safe_batch_query_ary), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
490
|
+
}
|
491
|
+
|
492
|
+
/* call-seq:
|
493
|
+
* db.batch_query_single_column(sql, params_array) -> rows
|
494
|
+
* db.batch_query_single_column(sql, enumerable) -> rows
|
495
|
+
* db.batch_query_single_column(sql, callable) -> rows
|
496
|
+
* db.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
|
497
|
+
* db.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
|
498
|
+
* db.batch_query_single_column(sql, callable) { |rows| ... } -> changes
|
499
|
+
*
|
500
|
+
* Executes the given query for each list of parameters in the given paramter
|
501
|
+
* source. If a block is given, it is called with the resulting rows for each
|
502
|
+
* invocation of the query, and the total number of changes is returned.
|
503
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
504
|
+
* returned. Rows are single values.
|
505
|
+
*
|
506
|
+
* records = [
|
507
|
+
* [1, 2],
|
508
|
+
* [3, 4]
|
509
|
+
* ]
|
510
|
+
* db.batch_query_ary('insert into foo values (?, ?) returning baz', records)
|
511
|
+
* #=> [2, 4]
|
512
|
+
* *
|
513
|
+
* @param sql [String] query SQL
|
514
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
515
|
+
* @return [Array<any>, Integer] Total number of changes effected
|
516
|
+
*/
|
517
|
+
VALUE Database_batch_query_single_column(VALUE self, VALUE sql, VALUE parameters) {
|
518
|
+
Database_t *db = self_to_open_database(self);
|
519
|
+
sqlite3_stmt *stmt;
|
520
|
+
|
521
|
+
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
522
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
523
|
+
|
524
|
+
return rb_ensure(SAFE(safe_batch_query_single_column), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
361
525
|
}
|
362
526
|
|
363
527
|
/* call-seq:
|
@@ -391,10 +555,10 @@ VALUE Database_changes(VALUE self) {
|
|
391
555
|
return INT2FIX(sqlite3_changes(db->sqlite3_db));
|
392
556
|
}
|
393
557
|
|
394
|
-
/* call-seq:
|
395
|
-
* db.filename -> string
|
558
|
+
/* call-seq: db.filename -> string db.filename(db_name) -> string
|
396
559
|
*
|
397
|
-
* Returns the database filename.
|
560
|
+
* Returns the database filename. If db_name is given, returns the filename for
|
561
|
+
* the respective attached database.
|
398
562
|
*/
|
399
563
|
VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
|
400
564
|
const char *db_name;
|
@@ -441,8 +605,10 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
|
|
441
605
|
|
442
606
|
/* call-seq:
|
443
607
|
* db.prepare(sql) -> Extralite::Query
|
608
|
+
* db.prepare(sql, ...) -> Extralite::Query
|
444
609
|
*
|
445
|
-
* Creates a prepared statement with the given SQL query.
|
610
|
+
* Creates a prepared statement with the given SQL query. If query parameters
|
611
|
+
* are given, they are bound to the query.
|
446
612
|
*/
|
447
613
|
VALUE Database_prepare(int argc, VALUE *argv, VALUE self) {
|
448
614
|
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
@@ -480,13 +646,13 @@ typedef struct {
|
|
480
646
|
#define BACKUP_STEP_MAX_PAGES 16
|
481
647
|
#define BACKUP_SLEEP_MS 100
|
482
648
|
|
483
|
-
void *
|
649
|
+
void *backup_step_impl(void *ptr) {
|
484
650
|
backup_ctx *ctx = (backup_ctx *)ptr;
|
485
651
|
ctx->rc = sqlite3_backup_step(ctx->backup, BACKUP_STEP_MAX_PAGES);
|
486
652
|
return NULL;
|
487
653
|
}
|
488
654
|
|
489
|
-
void *
|
655
|
+
void *backup_sleep_impl(void *unused) {
|
490
656
|
sqlite3_sleep(BACKUP_SLEEP_MS);
|
491
657
|
return NULL;
|
492
658
|
}
|
@@ -496,7 +662,7 @@ VALUE backup_safe_iterate(VALUE ptr) {
|
|
496
662
|
int done = 0;
|
497
663
|
|
498
664
|
while (!done) {
|
499
|
-
|
665
|
+
gvl_call(GVL_RELEASE, backup_step_impl, (void *)ctx);
|
500
666
|
switch(ctx->rc) {
|
501
667
|
case SQLITE_DONE:
|
502
668
|
if (ctx->block_given) {
|
@@ -514,7 +680,7 @@ VALUE backup_safe_iterate(VALUE ptr) {
|
|
514
680
|
continue;
|
515
681
|
case SQLITE_BUSY:
|
516
682
|
case SQLITE_LOCKED:
|
517
|
-
|
683
|
+
gvl_call(GVL_RELEASE, backup_sleep_impl, NULL);
|
518
684
|
continue;
|
519
685
|
default:
|
520
686
|
rb_raise(cError, "%s", sqlite3_errstr(ctx->rc));
|
@@ -689,7 +855,7 @@ VALUE Database_total_changes(VALUE self) {
|
|
689
855
|
VALUE Database_trace(VALUE self) {
|
690
856
|
Database_t *db = self_to_open_database(self);
|
691
857
|
|
692
|
-
db->trace_block
|
858
|
+
RB_OBJ_WRITE(self, &db->trace_block, rb_block_given_p() ? rb_block_proc() : Qnil);
|
693
859
|
return self;
|
694
860
|
}
|
695
861
|
|
@@ -734,11 +900,51 @@ VALUE Database_error_offset(VALUE self) {
|
|
734
900
|
* @return [String] string representation
|
735
901
|
*/
|
736
902
|
VALUE Database_inspect(VALUE self) {
|
903
|
+
Database_t *db = self_to_database(self);
|
737
904
|
VALUE cname = rb_class_name(CLASS_OF(self));
|
738
|
-
VALUE filename = Database_filename(0, NULL, self);
|
739
|
-
if (RSTRING_LEN(filename) == 0) filename = rb_str_new_literal(":memory:");
|
740
905
|
|
741
|
-
|
906
|
+
if (!(db)->sqlite3_db)
|
907
|
+
return rb_sprintf("#<%"PRIsVALUE":%p (closed)>", cname, (void*)self);
|
908
|
+
else {
|
909
|
+
VALUE filename = Database_filename(0, NULL, self);
|
910
|
+
if (RSTRING_LEN(filename) == 0) filename = rb_str_new_literal(":memory:");
|
911
|
+
return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, filename);
|
912
|
+
}
|
913
|
+
}
|
914
|
+
|
915
|
+
/* Returns the database's GVL release threshold.
|
916
|
+
*
|
917
|
+
* @return [Integer] GVL release threshold
|
918
|
+
*/
|
919
|
+
VALUE Database_gvl_release_threshold_get(VALUE self) {
|
920
|
+
Database_t *db = self_to_open_database(self);
|
921
|
+
return INT2NUM(db->gvl_release_threshold);
|
922
|
+
}
|
923
|
+
|
924
|
+
/* Sets the database's GVL release threshold. To always hold the GVL while
|
925
|
+
* running a query, set the threshold to 0. To release the GVL on each record,
|
926
|
+
* set the threshold to 1. Larger values mean the GVL will be released less
|
927
|
+
* often, e.g. a value of 10 means the GVL will be released every 10 records
|
928
|
+
* iterated. A value of nil sets the threshold to the default value, which is
|
929
|
+
* currently 1000.
|
930
|
+
*
|
931
|
+
* @return [Integer, nil] New GVL release threshold
|
932
|
+
*/
|
933
|
+
VALUE Database_gvl_release_threshold_set(VALUE self, VALUE value) {
|
934
|
+
Database_t *db = self_to_open_database(self);
|
935
|
+
|
936
|
+
switch (TYPE(value)) {
|
937
|
+
case T_FIXNUM:
|
938
|
+
db->gvl_release_threshold = NUM2INT(value);
|
939
|
+
break;
|
940
|
+
case T_NIL:
|
941
|
+
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
942
|
+
break;
|
943
|
+
default:
|
944
|
+
rb_raise(eArgumentError, "Invalid GVL release threshold value (expect integer or nil)");
|
945
|
+
}
|
946
|
+
|
947
|
+
return INT2NUM(db->gvl_release_threshold);
|
742
948
|
}
|
743
949
|
|
744
950
|
void Init_ExtraliteDatabase(void) {
|
@@ -763,8 +969,13 @@ void Init_ExtraliteDatabase(void) {
|
|
763
969
|
#endif
|
764
970
|
|
765
971
|
rb_define_method(cDatabase, "execute", Database_execute, -1);
|
766
|
-
rb_define_method(cDatabase, "
|
972
|
+
rb_define_method(cDatabase, "batch_execute", Database_batch_execute, 2);
|
973
|
+
rb_define_method(cDatabase, "batch_query", Database_batch_query, 2);
|
974
|
+
rb_define_method(cDatabase, "batch_query_ary", Database_batch_query_ary, 2);
|
975
|
+
rb_define_method(cDatabase, "batch_query_single_column", Database_batch_query_single_column, 2);
|
767
976
|
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
977
|
+
rb_define_method(cDatabase, "gvl_release_threshold", Database_gvl_release_threshold_get, 0);
|
978
|
+
rb_define_method(cDatabase, "gvl_release_threshold=", Database_gvl_release_threshold_set, 1);
|
768
979
|
rb_define_method(cDatabase, "initialize", Database_initialize, -1);
|
769
980
|
rb_define_method(cDatabase, "inspect", Database_inspect, 0);
|
770
981
|
rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
|
@@ -787,26 +998,32 @@ void Init_ExtraliteDatabase(void) {
|
|
787
998
|
rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
|
788
999
|
#endif
|
789
1000
|
|
1001
|
+
cBlob = rb_define_class_under(mExtralite, "Blob", rb_cString);
|
1002
|
+
|
790
1003
|
cError = rb_define_class_under(mExtralite, "Error", rb_eStandardError);
|
791
1004
|
cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
|
792
1005
|
cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
|
793
1006
|
cInterruptError = rb_define_class_under(mExtralite, "InterruptError", cError);
|
794
|
-
|
795
|
-
rb_gc_register_mark_object(cSQLError);
|
796
|
-
rb_gc_register_mark_object(cBusyError);
|
797
|
-
rb_gc_register_mark_object(cInterruptError);
|
1007
|
+
cParameterError = rb_define_class_under(mExtralite, "ParameterError", cError);
|
798
1008
|
|
799
1009
|
eArgumentError = rb_const_get(rb_cObject, rb_intern("ArgumentError"));
|
800
1010
|
|
801
1011
|
ID_bind = rb_intern("bind");
|
802
1012
|
ID_call = rb_intern("call");
|
1013
|
+
ID_each = rb_intern("each");
|
803
1014
|
ID_keys = rb_intern("keys");
|
804
1015
|
ID_new = rb_intern("new");
|
805
1016
|
ID_strip = rb_intern("strip");
|
806
|
-
ID_to_s = rb_intern("to_s");
|
807
1017
|
|
808
|
-
|
1018
|
+
SYM_gvl_release_threshold = ID2SYM(rb_intern("gvl_release_threshold"));
|
1019
|
+
SYM_read_only = ID2SYM(rb_intern("read_only"));
|
1020
|
+
SYM_synchronous = ID2SYM(rb_intern("synchronous"));
|
1021
|
+
SYM_wal_journal_mode = ID2SYM(rb_intern("wal_journal_mode"));
|
1022
|
+
|
1023
|
+
rb_gc_register_mark_object(SYM_gvl_release_threshold);
|
809
1024
|
rb_gc_register_mark_object(SYM_read_only);
|
1025
|
+
rb_gc_register_mark_object(SYM_synchronous);
|
1026
|
+
rb_gc_register_mark_object(SYM_wal_journal_mode);
|
810
1027
|
|
811
1028
|
UTF8_ENCODING = rb_utf8_encoding();
|
812
1029
|
}
|
data/ext/extralite/extralite.h
CHANGED
@@ -23,17 +23,19 @@
|
|
23
23
|
extern VALUE cDatabase;
|
24
24
|
extern VALUE cQuery;
|
25
25
|
extern VALUE cIterator;
|
26
|
+
extern VALUE cBlob;
|
26
27
|
|
27
28
|
extern VALUE cError;
|
28
29
|
extern VALUE cSQLError;
|
29
30
|
extern VALUE cBusyError;
|
30
31
|
extern VALUE cInterruptError;
|
32
|
+
extern VALUE cParameterError;
|
31
33
|
|
32
34
|
extern ID ID_call;
|
35
|
+
extern ID ID_each;
|
33
36
|
extern ID ID_keys;
|
34
37
|
extern ID ID_new;
|
35
38
|
extern ID ID_strip;
|
36
|
-
extern ID ID_to_s;
|
37
39
|
|
38
40
|
extern VALUE SYM_hash;
|
39
41
|
extern VALUE SYM_ary;
|
@@ -42,6 +44,7 @@ extern VALUE SYM_single_column;
|
|
42
44
|
typedef struct {
|
43
45
|
sqlite3 *sqlite3_db;
|
44
46
|
VALUE trace_block;
|
47
|
+
int gvl_release_threshold;
|
45
48
|
} Database_t;
|
46
49
|
|
47
50
|
typedef struct {
|
@@ -79,23 +82,29 @@ typedef struct {
|
|
79
82
|
enum query_mode mode;
|
80
83
|
int max_rows;
|
81
84
|
int eof;
|
85
|
+
int gvl_release_threshold;
|
86
|
+
int step_count;
|
82
87
|
} query_ctx;
|
83
88
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
} backup_t;
|
89
|
+
enum gvl_mode {
|
90
|
+
GVL_RELEASE,
|
91
|
+
GVL_HOLD
|
92
|
+
};
|
89
93
|
|
90
|
-
#define TUPLE_MAX_EMBEDDED_VALUES 20
|
91
94
|
#define ALL_ROWS -1
|
92
95
|
#define SINGLE_ROW -2
|
93
96
|
#define QUERY_MODE(default) (rb_block_given_p() ? QUERY_YIELD : default)
|
94
97
|
#define MULTI_ROW_P(mode) (mode == QUERY_MULTI_ROW)
|
98
|
+
#define QUERY_CTX(self, db, stmt, params, mode, max_rows) \
|
99
|
+
{ self, db->sqlite3_db, stmt, params, mode, max_rows, 0, db->gvl_release_threshold, 0 }
|
100
|
+
#define DEFAULT_GVL_RELEASE_THRESHOLD 1000
|
95
101
|
|
96
102
|
extern rb_encoding *UTF8_ENCODING;
|
97
103
|
|
98
|
-
VALUE
|
104
|
+
VALUE safe_batch_execute(query_ctx *ctx);
|
105
|
+
VALUE safe_batch_query(query_ctx *ctx);
|
106
|
+
VALUE safe_batch_query_ary(query_ctx *ctx);
|
107
|
+
VALUE safe_batch_query_single_column(query_ctx *ctx);
|
99
108
|
VALUE safe_query_ary(query_ctx *ctx);
|
100
109
|
VALUE safe_query_changes(query_ctx *ctx);
|
101
110
|
VALUE safe_query_columns(query_ctx *ctx);
|
@@ -126,4 +135,6 @@ VALUE cleanup_stmt(query_ctx *ctx);
|
|
126
135
|
sqlite3 *Database_sqlite3_db(VALUE self);
|
127
136
|
Database_t *self_to_database(VALUE self);
|
128
137
|
|
129
|
-
|
138
|
+
void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data);
|
139
|
+
|
140
|
+
#endif /* EXTRALITE_H */
|
@@ -1,8 +1,12 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
1
3
|
void Init_ExtraliteDatabase();
|
2
4
|
void Init_ExtraliteQuery();
|
3
5
|
void Init_ExtraliteIterator();
|
4
6
|
|
5
7
|
void Init_extralite_ext(void) {
|
8
|
+
rb_ext_ractor_safe(true);
|
9
|
+
|
6
10
|
Init_ExtraliteDatabase();
|
7
11
|
Init_ExtraliteQuery();
|
8
12
|
Init_ExtraliteIterator();
|
data/ext/extralite/iterator.c
CHANGED
@@ -52,7 +52,7 @@ static inline enum iterator_mode symbol_to_mode(VALUE sym) {
|
|
52
52
|
* iteration mode is one of: `:hash`, `:ary`, or `:single_column`. An iterator
|
53
53
|
* is normally returned from one of the methods `Query#each`/`Query#each_hash`,
|
54
54
|
* `Query#each_ary` or `Query#each_single_column` when called without a block:
|
55
|
-
*
|
55
|
+
*
|
56
56
|
* iterator = query.each
|
57
57
|
* ...
|
58
58
|
*
|
@@ -115,12 +115,12 @@ inline next_method mode_to_next_method(enum iterator_mode mode) {
|
|
115
115
|
|
116
116
|
/* Returns the next 1 or more rows from the associated query's result set
|
117
117
|
* according to the iteration mode, i.e. as a hash, an array or a single value.
|
118
|
-
*
|
118
|
+
*
|
119
119
|
* If no row count is given, a single row is returned. If a row count is given,
|
120
120
|
* an array containing up to the `row_count` rows is returned. If `row_count` is
|
121
121
|
* -1, all rows are returned. If the end of the result set has been reached,
|
122
122
|
* `nil` is returned.
|
123
|
-
*
|
123
|
+
*
|
124
124
|
* If a block is given, rows are passed to the block and self is returned.
|
125
125
|
*
|
126
126
|
* @overload next()
|
@@ -152,7 +152,7 @@ inline to_a_method mode_to_to_a_method(enum iterator_mode mode) {
|
|
152
152
|
|
153
153
|
/* Returns all rows from the associated query's result set according to the
|
154
154
|
* iteration mode, i.e. as a hash, an array or a single value.
|
155
|
-
*
|
155
|
+
*
|
156
156
|
* @return [Array] array of query result set rows
|
157
157
|
*/
|
158
158
|
VALUE Iterator_to_a(VALUE self) {
|
@@ -180,7 +180,7 @@ inline VALUE mode_to_symbol(Iterator_t *iterator) {
|
|
180
180
|
VALUE Iterator_inspect(VALUE self) {
|
181
181
|
VALUE cname = rb_class_name(CLASS_OF(self));
|
182
182
|
VALUE sym = mode_to_symbol(self_to_iterator(self));
|
183
|
-
|
183
|
+
|
184
184
|
return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sym);
|
185
185
|
}
|
186
186
|
|