extralite 2.3 → 2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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/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
|
|