extralite 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 +4 -4
- data/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +2 -12
- data/CHANGELOG.md +39 -10
- data/Gemfile.lock +1 -1
- data/README.md +45 -11
- data/TODO.md +0 -3
- data/ext/extralite/common.c +222 -15
- data/ext/extralite/database.c +185 -16
- data/ext/extralite/extralite.h +5 -1
- data/ext/extralite/extralite_ext.c +4 -0
- data/ext/extralite/query.c +213 -12
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +14 -6
- data/test/helper.rb +1 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/test_database.rb +471 -12
- data/test/test_query.rb +362 -2
- metadata +6 -3
data/ext/extralite/database.c
CHANGED
@@ -13,11 +13,15 @@ VALUE eArgumentError;
|
|
13
13
|
|
14
14
|
ID ID_bind;
|
15
15
|
ID ID_call;
|
16
|
+
ID ID_each;
|
16
17
|
ID ID_keys;
|
17
18
|
ID ID_new;
|
18
19
|
ID ID_strip;
|
19
20
|
|
21
|
+
VALUE SYM_gvl_release_threshold;
|
20
22
|
VALUE SYM_read_only;
|
23
|
+
VALUE SYM_synchronous;
|
24
|
+
VALUE SYM_wal_journal_mode;
|
21
25
|
|
22
26
|
static size_t Database_size(const void *ptr) {
|
23
27
|
return sizeof(Database_t);
|
@@ -25,7 +29,12 @@ static size_t Database_size(const void *ptr) {
|
|
25
29
|
|
26
30
|
static void Database_mark(void *ptr) {
|
27
31
|
Database_t *db = ptr;
|
28
|
-
|
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);
|
29
38
|
}
|
30
39
|
|
31
40
|
static void Database_free(void *ptr) {
|
@@ -36,7 +45,7 @@ static void Database_free(void *ptr) {
|
|
36
45
|
|
37
46
|
static const rb_data_type_t Database_type = {
|
38
47
|
"Database",
|
39
|
-
{Database_mark, Database_free, Database_size,},
|
48
|
+
{Database_mark, Database_free, Database_size, Database_compact},
|
40
49
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
41
50
|
};
|
42
51
|
|
@@ -85,14 +94,40 @@ default_flags:
|
|
85
94
|
return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
86
95
|
}
|
87
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
|
+
|
88
120
|
/* Initializes a new SQLite database with the given path and options.
|
89
121
|
*
|
90
122
|
* @overload initialize(path)
|
91
123
|
* @param path [String] file path (or ':memory:' for memory database)
|
92
124
|
* @return [void]
|
93
|
-
* @overload initialize(path, read_only:
|
125
|
+
* @overload initialize(path, gvl_release_threshold: , read_only: , synchronous: , wal_journal_mode: )
|
94
126
|
* @param path [String] file path (or ':memory:' for memory database)
|
127
|
+
* @param gvl_release_threshold [Integer] GVL release threshold
|
95
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
|
96
131
|
* @return [void]
|
97
132
|
*/
|
98
133
|
VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
@@ -127,6 +162,8 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
|
127
162
|
db->trace_block = Qnil;
|
128
163
|
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
129
164
|
|
165
|
+
if (!NIL_P(opts)) Database_apply_opts(self, db, opts);
|
166
|
+
|
130
167
|
return Qnil;
|
131
168
|
}
|
132
169
|
|
@@ -180,7 +217,6 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
180
217
|
sql = rb_funcall(argv[0], ID_strip, 0);
|
181
218
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
182
219
|
|
183
|
-
// prepare query ctx
|
184
220
|
if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_call, 1, sql);
|
185
221
|
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
186
222
|
RB_GC_GUARD(sql);
|
@@ -342,30 +378,150 @@ VALUE Database_execute(int argc, VALUE *argv, VALUE self) {
|
|
342
378
|
}
|
343
379
|
|
344
380
|
/* call-seq:
|
345
|
-
* 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.
|
346
390
|
*
|
347
|
-
*
|
348
|
-
*
|
349
|
-
* multiple records.
|
391
|
+
* Returns the number of changes effected. This method is designed for inserting
|
392
|
+
* multiple records or performing other mass operations.
|
350
393
|
*
|
351
394
|
* records = [
|
352
395
|
* [1, 2, 3],
|
353
396
|
* [4, 5, 6]
|
354
397
|
* ]
|
355
|
-
* db.
|
398
|
+
* db.batch_execute('insert into foo values (?, ?, ?)', records)
|
356
399
|
*
|
400
|
+
* source = [
|
401
|
+
* [1, 2, 3],
|
402
|
+
* [4, 5, 6]
|
403
|
+
* ]
|
404
|
+
* db.batch_execute('insert into foo values (?, ?, ?)', -> { records.shift })
|
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
|
357
409
|
*/
|
358
|
-
VALUE
|
410
|
+
VALUE Database_batch_execute(VALUE self, VALUE sql, VALUE parameters) {
|
359
411
|
Database_t *db = self_to_open_database(self);
|
360
412
|
sqlite3_stmt *stmt;
|
361
413
|
|
362
414
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
363
415
|
|
364
|
-
// prepare query ctx
|
365
416
|
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
366
|
-
query_ctx ctx = QUERY_CTX(self, db, stmt,
|
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);
|
367
488
|
|
368
|
-
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);
|
369
525
|
}
|
370
526
|
|
371
527
|
/* call-seq:
|
@@ -449,8 +605,10 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
|
|
449
605
|
|
450
606
|
/* call-seq:
|
451
607
|
* db.prepare(sql) -> Extralite::Query
|
608
|
+
* db.prepare(sql, ...) -> Extralite::Query
|
452
609
|
*
|
453
|
-
* 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.
|
454
612
|
*/
|
455
613
|
VALUE Database_prepare(int argc, VALUE *argv, VALUE self) {
|
456
614
|
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
@@ -811,7 +969,10 @@ void Init_ExtraliteDatabase(void) {
|
|
811
969
|
#endif
|
812
970
|
|
813
971
|
rb_define_method(cDatabase, "execute", Database_execute, -1);
|
814
|
-
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);
|
815
976
|
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
816
977
|
rb_define_method(cDatabase, "gvl_release_threshold", Database_gvl_release_threshold_get, 0);
|
817
978
|
rb_define_method(cDatabase, "gvl_release_threshold=", Database_gvl_release_threshold_set, 1);
|
@@ -849,12 +1010,20 @@ void Init_ExtraliteDatabase(void) {
|
|
849
1010
|
|
850
1011
|
ID_bind = rb_intern("bind");
|
851
1012
|
ID_call = rb_intern("call");
|
1013
|
+
ID_each = rb_intern("each");
|
852
1014
|
ID_keys = rb_intern("keys");
|
853
1015
|
ID_new = rb_intern("new");
|
854
1016
|
ID_strip = rb_intern("strip");
|
855
1017
|
|
856
|
-
|
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);
|
857
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);
|
858
1027
|
|
859
1028
|
UTF8_ENCODING = rb_utf8_encoding();
|
860
1029
|
}
|
data/ext/extralite/extralite.h
CHANGED
@@ -32,6 +32,7 @@ extern VALUE cInterruptError;
|
|
32
32
|
extern VALUE cParameterError;
|
33
33
|
|
34
34
|
extern ID ID_call;
|
35
|
+
extern ID ID_each;
|
35
36
|
extern ID ID_keys;
|
36
37
|
extern ID ID_new;
|
37
38
|
extern ID ID_strip;
|
@@ -100,7 +101,10 @@ enum gvl_mode {
|
|
100
101
|
|
101
102
|
extern rb_encoding *UTF8_ENCODING;
|
102
103
|
|
103
|
-
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);
|
104
108
|
VALUE safe_query_ary(query_ctx *ctx);
|
105
109
|
VALUE safe_query_changes(query_ctx *ctx);
|
106
110
|
VALUE safe_query_columns(query_ctx *ctx);
|
@@ -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/query.c
CHANGED
@@ -20,8 +20,14 @@ static size_t Query_size(const void *ptr) {
|
|
20
20
|
|
21
21
|
static void Query_mark(void *ptr) {
|
22
22
|
Query_t *query = ptr;
|
23
|
-
|
24
|
-
|
23
|
+
rb_gc_mark_movable(query->db);
|
24
|
+
rb_gc_mark_movable(query->sql);
|
25
|
+
}
|
26
|
+
|
27
|
+
static void Query_compact(void *ptr) {
|
28
|
+
Query_t *query = ptr;
|
29
|
+
query->db = rb_gc_location(query->db);
|
30
|
+
query->sql = rb_gc_location(query->sql);
|
25
31
|
}
|
26
32
|
|
27
33
|
static void Query_free(void *ptr) {
|
@@ -32,7 +38,7 @@ static void Query_free(void *ptr) {
|
|
32
38
|
|
33
39
|
static const rb_data_type_t Query_type = {
|
34
40
|
"Query",
|
35
|
-
{Query_mark, Query_free, Query_size,},
|
41
|
+
{Query_mark, Query_free, Query_size, Query_compact},
|
36
42
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
37
43
|
};
|
38
44
|
|
@@ -367,22 +373,66 @@ VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
|
|
367
373
|
return Query_perform_next(self, ALL_ROWS, safe_query_changes);
|
368
374
|
}
|
369
375
|
|
370
|
-
/*
|
371
|
-
*
|
372
|
-
*
|
373
|
-
*
|
376
|
+
/* call-seq:
|
377
|
+
* query << [...] -> query
|
378
|
+
* query << { ... } -> query
|
379
|
+
*
|
380
|
+
* Runs the with the given parameters, returning the total changes effected.
|
381
|
+
* This method should be used for data- or schema-manipulation queries.
|
382
|
+
*
|
383
|
+
* Query parameters to be bound to placeholders in the query can be specified as
|
384
|
+
* a list of values or as a hash mapping parameter names to values. When
|
385
|
+
* parameters are given as an array, the query should specify parameters using
|
386
|
+
* `?`:
|
387
|
+
*
|
388
|
+
* query = db.prepare('update foo set x = ? where y = ?')
|
389
|
+
* query << [42, 43]
|
390
|
+
*
|
391
|
+
* Named placeholders are specified using `:`. The placeholder values are
|
392
|
+
* specified using a hash, where keys are either strings are symbols. String
|
393
|
+
* keys can include or omit the `:` prefix. The following are equivalent:
|
394
|
+
*
|
395
|
+
* query = db.prepare('update foo set x = :bar')
|
396
|
+
* query << { bar: 42 }
|
397
|
+
* query << { 'bar' => 42 }
|
398
|
+
* query << { ':bar' => 42 }
|
399
|
+
*/
|
400
|
+
VALUE Query_execute_chevrons(VALUE self, VALUE params) {
|
401
|
+
Query_execute(1, ¶ms, self);
|
402
|
+
return self;
|
403
|
+
}
|
404
|
+
|
405
|
+
/* call-seq:
|
406
|
+
* query.batch_execute(params_array) -> changes
|
407
|
+
* query.batch_execute(enumerable) -> changes
|
408
|
+
* query.batch_execute(callable) -> changes
|
409
|
+
*
|
410
|
+
* Executes the query for each set of parameters in the paramter source. If an
|
411
|
+
* enumerable is given, it is iterated and each of its values is used as the
|
412
|
+
* parameters for running the query. If a callable is given, it is called
|
413
|
+
* repeatedly and each of its return values is used as the parameters, until nil
|
414
|
+
* is returned.
|
415
|
+
*
|
416
|
+
* Returns the number of changes effected. This method is designed for inserting
|
417
|
+
* multiple records.
|
374
418
|
*
|
375
419
|
* query = db.prepare('insert into foo values (?, ?, ?)')
|
376
420
|
* records = [
|
377
421
|
* [1, 2, 3],
|
378
422
|
* [4, 5, 6]
|
379
423
|
* ]
|
380
|
-
* query.
|
424
|
+
* query.batch_execute(records)
|
425
|
+
*
|
426
|
+
* source = [
|
427
|
+
* [1, 2, 3],
|
428
|
+
* [4, 5, 6]
|
429
|
+
* ]
|
430
|
+
* query.batch_execute { records.shift }
|
381
431
|
*
|
382
|
-
* @param parameters [Array<Array, Hash
|
432
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] array of parameters to run query with
|
383
433
|
* @return [Integer] number of changes effected
|
384
434
|
*/
|
385
|
-
VALUE
|
435
|
+
VALUE Query_batch_execute(VALUE self, VALUE parameters) {
|
386
436
|
Query_t *query = self_to_query(self);
|
387
437
|
if (query->closed) rb_raise(cError, "Query is closed");
|
388
438
|
|
@@ -397,7 +447,139 @@ VALUE Query_execute_multi(VALUE self, VALUE parameters) {
|
|
397
447
|
QUERY_MODE(QUERY_MULTI_ROW),
|
398
448
|
ALL_ROWS
|
399
449
|
);
|
400
|
-
return
|
450
|
+
return safe_batch_execute(&ctx);
|
451
|
+
}
|
452
|
+
|
453
|
+
/* call-seq:
|
454
|
+
* query.batch_query(sql, params_array) -> rows
|
455
|
+
* query.batch_query(sql, enumerable) -> rows
|
456
|
+
* query.batch_query(sql, callable) -> rows
|
457
|
+
* query.batch_query(sql, params_array) { |rows| ... } -> changes
|
458
|
+
* query.batch_query(sql, enumerable) { |rows| ... } -> changes
|
459
|
+
* query.batch_query(sql, callable) { |rows| ... } -> changes
|
460
|
+
*
|
461
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
462
|
+
* source. If a block is given, it is called with the resulting rows for each
|
463
|
+
* invocation of the query, and the total number of changes is returned.
|
464
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
465
|
+
* returned.
|
466
|
+
*
|
467
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
468
|
+
* records = [
|
469
|
+
* [1, 2],
|
470
|
+
* [3, 4]
|
471
|
+
* ]
|
472
|
+
* q.batch_query(records)
|
473
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
474
|
+
* *
|
475
|
+
* @param sql [String] query SQL
|
476
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
477
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
478
|
+
*/
|
479
|
+
VALUE Query_batch_query(VALUE self, VALUE parameters) {
|
480
|
+
Query_t *query = self_to_query(self);
|
481
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
482
|
+
|
483
|
+
if (!query->stmt)
|
484
|
+
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
485
|
+
|
486
|
+
query_ctx ctx = QUERY_CTX(
|
487
|
+
self,
|
488
|
+
query->db_struct,
|
489
|
+
query->stmt,
|
490
|
+
parameters,
|
491
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
492
|
+
ALL_ROWS
|
493
|
+
);
|
494
|
+
return safe_batch_query(&ctx);
|
495
|
+
}
|
496
|
+
|
497
|
+
/* call-seq:
|
498
|
+
* query.batch_query_ary(sql, params_array) -> rows
|
499
|
+
* query.batch_query_ary(sql, enumerable) -> rows
|
500
|
+
* query.batch_query_ary(sql, callable) -> rows
|
501
|
+
* query.batch_query_ary(sql, params_array) { |rows| ... } -> changes
|
502
|
+
* query.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
|
503
|
+
* query.batch_query_ary(sql, callable) { |rows| ... } -> changes
|
504
|
+
*
|
505
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
506
|
+
* source. If a block is given, it is called with the resulting rows for each
|
507
|
+
* invocation of the query, and the total number of changes is returned.
|
508
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
509
|
+
* returned. Rows are represented as arrays.
|
510
|
+
*
|
511
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
512
|
+
* records = [
|
513
|
+
* [1, 2],
|
514
|
+
* [3, 4]
|
515
|
+
* ]
|
516
|
+
* q.batch_query_ary(records)
|
517
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
518
|
+
* *
|
519
|
+
* @param sql [String] query SQL
|
520
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
521
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
522
|
+
*/
|
523
|
+
VALUE Query_batch_query_ary(VALUE self, VALUE parameters) {
|
524
|
+
Query_t *query = self_to_query(self);
|
525
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
526
|
+
|
527
|
+
if (!query->stmt)
|
528
|
+
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
529
|
+
|
530
|
+
query_ctx ctx = QUERY_CTX(
|
531
|
+
self,
|
532
|
+
query->db_struct,
|
533
|
+
query->stmt,
|
534
|
+
parameters,
|
535
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
536
|
+
ALL_ROWS
|
537
|
+
);
|
538
|
+
return safe_batch_query_ary(&ctx);
|
539
|
+
}
|
540
|
+
|
541
|
+
/* call-seq:
|
542
|
+
* query.batch_query_single_column(sql, params_array) -> rows
|
543
|
+
* query.batch_query_single_column(sql, enumerable) -> rows
|
544
|
+
* query.batch_query_single_column(sql, callable) -> rows
|
545
|
+
* query.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
|
546
|
+
* query.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
|
547
|
+
* query.batch_query_single_column(sql, callable) { |rows| ... } -> changes
|
548
|
+
*
|
549
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
550
|
+
* source. If a block is given, it is called with the resulting rows for each
|
551
|
+
* invocation of the query, and the total number of changes is returned.
|
552
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
553
|
+
* returned. Rows are represented as single values.
|
554
|
+
*
|
555
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
556
|
+
* records = [
|
557
|
+
* [1, 2],
|
558
|
+
* [3, 4]
|
559
|
+
* ]
|
560
|
+
* q.batch_query_single_column(records)
|
561
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
562
|
+
* *
|
563
|
+
* @param sql [String] query SQL
|
564
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
565
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
566
|
+
*/
|
567
|
+
VALUE Query_batch_query_single_column(VALUE self, VALUE parameters) {
|
568
|
+
Query_t *query = self_to_query(self);
|
569
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
570
|
+
|
571
|
+
if (!query->stmt)
|
572
|
+
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
573
|
+
|
574
|
+
query_ctx ctx = QUERY_CTX(
|
575
|
+
self,
|
576
|
+
query->db_struct,
|
577
|
+
query->stmt,
|
578
|
+
parameters,
|
579
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
580
|
+
ALL_ROWS
|
581
|
+
);
|
582
|
+
return safe_batch_query_single_column(&ctx);
|
401
583
|
}
|
402
584
|
|
403
585
|
/* Returns the database associated with the query.
|
@@ -431,6 +613,19 @@ VALUE Query_columns(VALUE self) {
|
|
431
613
|
return Query_perform_next(self, ALL_ROWS, safe_query_columns);
|
432
614
|
}
|
433
615
|
|
616
|
+
/* call-seq:
|
617
|
+
* query.clone -> copy
|
618
|
+
* query.dup -> copy
|
619
|
+
*
|
620
|
+
* Returns a new query instance for the same SQL as the original query.
|
621
|
+
*
|
622
|
+
* @return [Extralite::Query] copy of query
|
623
|
+
*/
|
624
|
+
VALUE Query_clone(VALUE self) {
|
625
|
+
Query_t *query = self_to_query(self);
|
626
|
+
return rb_funcall(cQuery, ID_new, 2, query->db, query->sql);
|
627
|
+
}
|
628
|
+
|
434
629
|
/* Closes the query. Attempting to run a closed query will raise an error.
|
435
630
|
*
|
436
631
|
* @return [Extralite::Query] self
|
@@ -509,8 +704,10 @@ void Init_ExtraliteQuery(void) {
|
|
509
704
|
rb_define_method(cQuery, "close", Query_close, 0);
|
510
705
|
rb_define_method(cQuery, "closed?", Query_closed_p, 0);
|
511
706
|
rb_define_method(cQuery, "columns", Query_columns, 0);
|
707
|
+
rb_define_method(cQuery, "clone", Query_clone, 0);
|
512
708
|
rb_define_method(cQuery, "database", Query_database, 0);
|
513
709
|
rb_define_method(cQuery, "db", Query_database, 0);
|
710
|
+
rb_define_method(cQuery, "dup", Query_clone, 0);
|
514
711
|
|
515
712
|
rb_define_method(cQuery, "each", Query_each_hash, 0);
|
516
713
|
rb_define_method(cQuery, "each_ary", Query_each_ary, 0);
|
@@ -519,7 +716,11 @@ void Init_ExtraliteQuery(void) {
|
|
519
716
|
|
520
717
|
rb_define_method(cQuery, "eof?", Query_eof_p, 0);
|
521
718
|
rb_define_method(cQuery, "execute", Query_execute, -1);
|
522
|
-
rb_define_method(cQuery, "
|
719
|
+
rb_define_method(cQuery, "<<", Query_execute_chevrons, 1);
|
720
|
+
rb_define_method(cQuery, "batch_execute", Query_batch_execute, 1);
|
721
|
+
rb_define_method(cQuery, "batch_query", Query_batch_query, 1);
|
722
|
+
rb_define_method(cQuery, "batch_query_ary", Query_batch_query_ary, 1);
|
723
|
+
rb_define_method(cQuery, "batch_query_single_column", Query_batch_query_single_column, 1);
|
523
724
|
rb_define_method(cQuery, "initialize", Query_initialize, 2);
|
524
725
|
rb_define_method(cQuery, "inspect", Query_inspect, 0);
|
525
726
|
|
data/gemspec.rb
CHANGED
@@ -16,7 +16,7 @@ def common_spec(s)
|
|
16
16
|
s.rdoc_options = ["--title", "extralite", "--main", "README.md"]
|
17
17
|
s.extra_rdoc_files = ["README.md"]
|
18
18
|
s.require_paths = ["lib"]
|
19
|
-
s.required_ruby_version = '>=
|
19
|
+
s.required_ruby_version = '>= 3.0'
|
20
20
|
|
21
21
|
s.add_development_dependency 'rake-compiler', '1.1.6'
|
22
22
|
s.add_development_dependency 'minitest', '5.15.0'
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -33,16 +33,20 @@ module Extralite
|
|
33
33
|
class Database
|
34
34
|
# @!visibility private
|
35
35
|
TABLES_SQL = <<~SQL
|
36
|
-
SELECT name FROM sqlite_master
|
36
|
+
SELECT name FROM %<db>s.sqlite_master
|
37
37
|
WHERE type ='table'
|
38
|
-
AND name NOT LIKE 'sqlite_
|
38
|
+
AND name NOT LIKE 'sqlite_%%';
|
39
39
|
SQL
|
40
40
|
|
41
|
-
|
41
|
+
alias_method :execute_multi, :batch_execute
|
42
|
+
|
43
|
+
# Returns the list of currently defined tables. If a database name is given,
|
44
|
+
# returns the list of tables for the relevant attached database.
|
42
45
|
#
|
46
|
+
# @param db [String] name of attached database
|
43
47
|
# @return [Array] list of tables
|
44
|
-
def tables
|
45
|
-
query_single_column(TABLES_SQL)
|
48
|
+
def tables(db = 'main')
|
49
|
+
query_single_column(format(TABLES_SQL, db: db))
|
46
50
|
end
|
47
51
|
|
48
52
|
# Gets or sets one or more pragmas:
|
@@ -87,7 +91,11 @@ module Extralite
|
|
87
91
|
end
|
88
92
|
|
89
93
|
def pragma_get(key)
|
90
|
-
|
94
|
+
query_single_value("pragma #{key}")
|
91
95
|
end
|
92
96
|
end
|
97
|
+
|
98
|
+
class Query
|
99
|
+
alias_method :execute_multi, :batch_execute
|
100
|
+
end
|
93
101
|
end
|
data/test/helper.rb
CHANGED