extralite-bundle 2.4 → 2.6
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 +49 -10
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +876 -217
- data/TODO.md +2 -3
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +226 -19
- data/ext/extralite/database.c +339 -23
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +31 -27
- data/ext/extralite/extralite.h +25 -5
- data/ext/extralite/extralite_ext.c +10 -0
- data/ext/extralite/iterator.c +8 -3
- data/ext/extralite/query.c +222 -22
- data/ext/sqlite3/sqlite3.c +5420 -2501
- data/ext/sqlite3/sqlite3.h +73 -18
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +64 -8
- data/test/helper.rb +8 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +672 -13
- data/test/test_query.rb +367 -2
- metadata +10 -5
- data/test/perf_prepared.rb +0 -64
@@ -1,9 +1,19 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
1
3
|
void Init_ExtraliteDatabase();
|
2
4
|
void Init_ExtraliteQuery();
|
3
5
|
void Init_ExtraliteIterator();
|
6
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
7
|
+
void Init_ExtraliteChangeset();
|
8
|
+
#endif
|
4
9
|
|
5
10
|
void Init_extralite_ext(void) {
|
11
|
+
rb_ext_ractor_safe(true);
|
12
|
+
|
6
13
|
Init_ExtraliteDatabase();
|
7
14
|
Init_ExtraliteQuery();
|
8
15
|
Init_ExtraliteIterator();
|
16
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
17
|
+
Init_ExtraliteChangeset();
|
18
|
+
#endif
|
9
19
|
}
|
data/ext/extralite/iterator.c
CHANGED
@@ -19,13 +19,18 @@ static size_t Iterator_size(const void *ptr) {
|
|
19
19
|
|
20
20
|
static void Iterator_mark(void *ptr) {
|
21
21
|
Iterator_t *iterator = ptr;
|
22
|
-
|
22
|
+
rb_gc_mark_movable(iterator->query);
|
23
|
+
}
|
24
|
+
|
25
|
+
static void Iterator_compact(void *ptr) {
|
26
|
+
Iterator_t *iterator = ptr;
|
27
|
+
iterator->query = rb_gc_location(iterator->query);
|
23
28
|
}
|
24
29
|
|
25
30
|
static const rb_data_type_t Iterator_type = {
|
26
31
|
"Iterator",
|
27
|
-
{Iterator_mark, free, Iterator_size,},
|
28
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
32
|
+
{Iterator_mark, free, Iterator_size, Iterator_compact},
|
33
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
29
34
|
};
|
30
35
|
|
31
36
|
static VALUE Iterator_allocate(VALUE klass) {
|
data/ext/extralite/query.c
CHANGED
@@ -14,14 +14,22 @@ VALUE cQuery;
|
|
14
14
|
ID ID_inspect;
|
15
15
|
ID ID_slice;
|
16
16
|
|
17
|
+
#define DB_GVL_MODE(query) Database_prepare_gvl_mode(query->db_struct)
|
18
|
+
|
17
19
|
static size_t Query_size(const void *ptr) {
|
18
20
|
return sizeof(Query_t);
|
19
21
|
}
|
20
22
|
|
21
23
|
static void Query_mark(void *ptr) {
|
22
24
|
Query_t *query = ptr;
|
23
|
-
|
24
|
-
|
25
|
+
rb_gc_mark_movable(query->db);
|
26
|
+
rb_gc_mark_movable(query->sql);
|
27
|
+
}
|
28
|
+
|
29
|
+
static void Query_compact(void *ptr) {
|
30
|
+
Query_t *query = ptr;
|
31
|
+
query->db = rb_gc_location(query->db);
|
32
|
+
query->sql = rb_gc_location(query->sql);
|
25
33
|
}
|
26
34
|
|
27
35
|
static void Query_free(void *ptr) {
|
@@ -32,7 +40,7 @@ static void Query_free(void *ptr) {
|
|
32
40
|
|
33
41
|
static const rb_data_type_t Query_type = {
|
34
42
|
"Query",
|
35
|
-
{Query_mark, Query_free, Query_size,},
|
43
|
+
{Query_mark, Query_free, Query_size, Query_compact},
|
36
44
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
37
45
|
};
|
38
46
|
|
@@ -82,19 +90,17 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
|
|
82
90
|
|
83
91
|
static inline void query_reset(Query_t *query) {
|
84
92
|
if (!query->stmt)
|
85
|
-
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
86
|
-
|
87
|
-
rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
|
93
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
94
|
+
TRACE_SQL(query->db_struct, query->sql);
|
88
95
|
sqlite3_reset(query->stmt);
|
89
96
|
query->eof = 0;
|
90
97
|
}
|
91
98
|
|
92
99
|
static inline void query_reset_and_bind(Query_t *query, int argc, VALUE * argv) {
|
93
100
|
if (!query->stmt)
|
94
|
-
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
101
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
95
102
|
|
96
|
-
|
97
|
-
rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
|
103
|
+
TRACE_SQL(query->db_struct, query->sql);
|
98
104
|
|
99
105
|
sqlite3_reset(query->stmt);
|
100
106
|
query->eof = 0;
|
@@ -122,8 +128,7 @@ VALUE Query_reset(VALUE self) {
|
|
122
128
|
if (query->closed) rb_raise(cError, "Query is closed");
|
123
129
|
|
124
130
|
query_reset(query);
|
125
|
-
|
126
|
-
rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
|
131
|
+
TRACE_SQL(query->db_struct, query->sql);
|
127
132
|
|
128
133
|
return self;
|
129
134
|
}
|
@@ -367,27 +372,115 @@ VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
|
|
367
372
|
return Query_perform_next(self, ALL_ROWS, safe_query_changes);
|
368
373
|
}
|
369
374
|
|
370
|
-
/*
|
371
|
-
*
|
372
|
-
*
|
373
|
-
*
|
375
|
+
/* call-seq:
|
376
|
+
* query << [...] -> query
|
377
|
+
* query << { ... } -> query
|
378
|
+
*
|
379
|
+
* Runs the with the given parameters, returning the total changes effected.
|
380
|
+
* This method should be used for data- or schema-manipulation queries.
|
381
|
+
*
|
382
|
+
* Query parameters to be bound to placeholders in the query can be specified as
|
383
|
+
* a list of values or as a hash mapping parameter names to values. When
|
384
|
+
* parameters are given as an array, the query should specify parameters using
|
385
|
+
* `?`:
|
386
|
+
*
|
387
|
+
* query = db.prepare('update foo set x = ? where y = ?')
|
388
|
+
* query << [42, 43]
|
389
|
+
*
|
390
|
+
* Named placeholders are specified using `:`. The placeholder values are
|
391
|
+
* specified using a hash, where keys are either strings are symbols. String
|
392
|
+
* keys can include or omit the `:` prefix. The following are equivalent:
|
393
|
+
*
|
394
|
+
* query = db.prepare('update foo set x = :bar')
|
395
|
+
* query << { bar: 42 }
|
396
|
+
* query << { 'bar' => 42 }
|
397
|
+
* query << { ':bar' => 42 }
|
398
|
+
*/
|
399
|
+
VALUE Query_execute_chevrons(VALUE self, VALUE params) {
|
400
|
+
Query_execute(1, ¶ms, self);
|
401
|
+
return self;
|
402
|
+
}
|
403
|
+
|
404
|
+
/* call-seq:
|
405
|
+
* query.batch_execute(params_array) -> changes
|
406
|
+
* query.batch_execute(enumerable) -> changes
|
407
|
+
* query.batch_execute(callable) -> changes
|
408
|
+
*
|
409
|
+
* Executes the query for each set of parameters in the paramter source. If an
|
410
|
+
* enumerable is given, it is iterated and each of its values is used as the
|
411
|
+
* parameters for running the query. If a callable is given, it is called
|
412
|
+
* repeatedly and each of its return values is used as the parameters, until nil
|
413
|
+
* is returned.
|
414
|
+
*
|
415
|
+
* Returns the number of changes effected. This method is designed for inserting
|
416
|
+
* multiple records.
|
374
417
|
*
|
375
418
|
* query = db.prepare('insert into foo values (?, ?, ?)')
|
376
419
|
* records = [
|
377
420
|
* [1, 2, 3],
|
378
421
|
* [4, 5, 6]
|
379
422
|
* ]
|
380
|
-
* query.
|
423
|
+
* query.batch_execute(records)
|
424
|
+
*
|
425
|
+
* source = [
|
426
|
+
* [1, 2, 3],
|
427
|
+
* [4, 5, 6]
|
428
|
+
* ]
|
429
|
+
* query.batch_execute { records.shift }
|
381
430
|
*
|
382
|
-
* @param parameters [Array<Array, Hash
|
431
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] array of parameters to run query with
|
383
432
|
* @return [Integer] number of changes effected
|
384
433
|
*/
|
385
|
-
VALUE
|
434
|
+
VALUE Query_batch_execute(VALUE self, VALUE parameters) {
|
435
|
+
Query_t *query = self_to_query(self);
|
436
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
437
|
+
|
438
|
+
if (!query->stmt)
|
439
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
440
|
+
|
441
|
+
query_ctx ctx = QUERY_CTX(
|
442
|
+
self,
|
443
|
+
query->db_struct,
|
444
|
+
query->stmt,
|
445
|
+
parameters,
|
446
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
447
|
+
ALL_ROWS
|
448
|
+
);
|
449
|
+
return safe_batch_execute(&ctx);
|
450
|
+
}
|
451
|
+
|
452
|
+
/* call-seq:
|
453
|
+
* query.batch_query(sql, params_array) -> rows
|
454
|
+
* query.batch_query(sql, enumerable) -> rows
|
455
|
+
* query.batch_query(sql, callable) -> rows
|
456
|
+
* query.batch_query(sql, params_array) { |rows| ... } -> changes
|
457
|
+
* query.batch_query(sql, enumerable) { |rows| ... } -> changes
|
458
|
+
* query.batch_query(sql, callable) { |rows| ... } -> changes
|
459
|
+
*
|
460
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
461
|
+
* source. If a block is given, it is called with the resulting rows for each
|
462
|
+
* invocation of the query, and the total number of changes is returned.
|
463
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
464
|
+
* returned.
|
465
|
+
*
|
466
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
467
|
+
* records = [
|
468
|
+
* [1, 2],
|
469
|
+
* [3, 4]
|
470
|
+
* ]
|
471
|
+
* q.batch_query(records)
|
472
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
473
|
+
* *
|
474
|
+
* @param sql [String] query SQL
|
475
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
476
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
477
|
+
*/
|
478
|
+
VALUE Query_batch_query(VALUE self, VALUE parameters) {
|
386
479
|
Query_t *query = self_to_query(self);
|
387
480
|
if (query->closed) rb_raise(cError, "Query is closed");
|
388
481
|
|
389
482
|
if (!query->stmt)
|
390
|
-
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
483
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
391
484
|
|
392
485
|
query_ctx ctx = QUERY_CTX(
|
393
486
|
self,
|
@@ -397,7 +490,95 @@ VALUE Query_execute_multi(VALUE self, VALUE parameters) {
|
|
397
490
|
QUERY_MODE(QUERY_MULTI_ROW),
|
398
491
|
ALL_ROWS
|
399
492
|
);
|
400
|
-
return
|
493
|
+
return safe_batch_query(&ctx);
|
494
|
+
}
|
495
|
+
|
496
|
+
/* call-seq:
|
497
|
+
* query.batch_query_ary(sql, params_array) -> rows
|
498
|
+
* query.batch_query_ary(sql, enumerable) -> rows
|
499
|
+
* query.batch_query_ary(sql, callable) -> rows
|
500
|
+
* query.batch_query_ary(sql, params_array) { |rows| ... } -> changes
|
501
|
+
* query.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
|
502
|
+
* query.batch_query_ary(sql, callable) { |rows| ... } -> changes
|
503
|
+
*
|
504
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
505
|
+
* source. If a block is given, it is called with the resulting rows for each
|
506
|
+
* invocation of the query, and the total number of changes is returned.
|
507
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
508
|
+
* returned. Rows are represented as arrays.
|
509
|
+
*
|
510
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
511
|
+
* records = [
|
512
|
+
* [1, 2],
|
513
|
+
* [3, 4]
|
514
|
+
* ]
|
515
|
+
* q.batch_query_ary(records)
|
516
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
517
|
+
* *
|
518
|
+
* @param sql [String] query SQL
|
519
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
520
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
521
|
+
*/
|
522
|
+
VALUE Query_batch_query_ary(VALUE self, VALUE parameters) {
|
523
|
+
Query_t *query = self_to_query(self);
|
524
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
525
|
+
|
526
|
+
if (!query->stmt)
|
527
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
528
|
+
|
529
|
+
query_ctx ctx = QUERY_CTX(
|
530
|
+
self,
|
531
|
+
query->db_struct,
|
532
|
+
query->stmt,
|
533
|
+
parameters,
|
534
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
535
|
+
ALL_ROWS
|
536
|
+
);
|
537
|
+
return safe_batch_query_ary(&ctx);
|
538
|
+
}
|
539
|
+
|
540
|
+
/* call-seq:
|
541
|
+
* query.batch_query_single_column(sql, params_array) -> rows
|
542
|
+
* query.batch_query_single_column(sql, enumerable) -> rows
|
543
|
+
* query.batch_query_single_column(sql, callable) -> rows
|
544
|
+
* query.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
|
545
|
+
* query.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
|
546
|
+
* query.batch_query_single_column(sql, callable) { |rows| ... } -> changes
|
547
|
+
*
|
548
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
549
|
+
* source. If a block is given, it is called with the resulting rows for each
|
550
|
+
* invocation of the query, and the total number of changes is returned.
|
551
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
552
|
+
* returned. Rows are represented as single values.
|
553
|
+
*
|
554
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
555
|
+
* records = [
|
556
|
+
* [1, 2],
|
557
|
+
* [3, 4]
|
558
|
+
* ]
|
559
|
+
* q.batch_query_single_column(records)
|
560
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
561
|
+
* *
|
562
|
+
* @param sql [String] query SQL
|
563
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
564
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
565
|
+
*/
|
566
|
+
VALUE Query_batch_query_single_column(VALUE self, VALUE parameters) {
|
567
|
+
Query_t *query = self_to_query(self);
|
568
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
569
|
+
|
570
|
+
if (!query->stmt)
|
571
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
572
|
+
|
573
|
+
query_ctx ctx = QUERY_CTX(
|
574
|
+
self,
|
575
|
+
query->db_struct,
|
576
|
+
query->stmt,
|
577
|
+
parameters,
|
578
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
579
|
+
ALL_ROWS
|
580
|
+
);
|
581
|
+
return safe_batch_query_single_column(&ctx);
|
401
582
|
}
|
402
583
|
|
403
584
|
/* Returns the database associated with the query.
|
@@ -431,6 +612,19 @@ VALUE Query_columns(VALUE self) {
|
|
431
612
|
return Query_perform_next(self, ALL_ROWS, safe_query_columns);
|
432
613
|
}
|
433
614
|
|
615
|
+
/* call-seq:
|
616
|
+
* query.clone -> copy
|
617
|
+
* query.dup -> copy
|
618
|
+
*
|
619
|
+
* Returns a new query instance for the same SQL as the original query.
|
620
|
+
*
|
621
|
+
* @return [Extralite::Query] copy of query
|
622
|
+
*/
|
623
|
+
VALUE Query_clone(VALUE self) {
|
624
|
+
Query_t *query = self_to_query(self);
|
625
|
+
return rb_funcall(cQuery, ID_new, 2, query->db, query->sql);
|
626
|
+
}
|
627
|
+
|
434
628
|
/* Closes the query. Attempting to run a closed query will raise an error.
|
435
629
|
*
|
436
630
|
* @return [Extralite::Query] self
|
@@ -475,7 +669,7 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
|
|
475
669
|
if (query->closed) rb_raise(cError, "Query is closed");
|
476
670
|
|
477
671
|
if (!query->stmt)
|
478
|
-
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
672
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
479
673
|
|
480
674
|
int value = sqlite3_stmt_status(query->stmt, NUM2INT(op), RTEST(reset) ? 1 : 0);
|
481
675
|
return INT2NUM(value);
|
@@ -509,8 +703,10 @@ void Init_ExtraliteQuery(void) {
|
|
509
703
|
rb_define_method(cQuery, "close", Query_close, 0);
|
510
704
|
rb_define_method(cQuery, "closed?", Query_closed_p, 0);
|
511
705
|
rb_define_method(cQuery, "columns", Query_columns, 0);
|
706
|
+
rb_define_method(cQuery, "clone", Query_clone, 0);
|
512
707
|
rb_define_method(cQuery, "database", Query_database, 0);
|
513
708
|
rb_define_method(cQuery, "db", Query_database, 0);
|
709
|
+
rb_define_method(cQuery, "dup", Query_clone, 0);
|
514
710
|
|
515
711
|
rb_define_method(cQuery, "each", Query_each_hash, 0);
|
516
712
|
rb_define_method(cQuery, "each_ary", Query_each_ary, 0);
|
@@ -519,7 +715,11 @@ void Init_ExtraliteQuery(void) {
|
|
519
715
|
|
520
716
|
rb_define_method(cQuery, "eof?", Query_eof_p, 0);
|
521
717
|
rb_define_method(cQuery, "execute", Query_execute, -1);
|
522
|
-
rb_define_method(cQuery, "
|
718
|
+
rb_define_method(cQuery, "<<", Query_execute_chevrons, 1);
|
719
|
+
rb_define_method(cQuery, "batch_execute", Query_batch_execute, 1);
|
720
|
+
rb_define_method(cQuery, "batch_query", Query_batch_query, 1);
|
721
|
+
rb_define_method(cQuery, "batch_query_ary", Query_batch_query_ary, 1);
|
722
|
+
rb_define_method(cQuery, "batch_query_single_column", Query_batch_query_single_column, 1);
|
523
723
|
rb_define_method(cQuery, "initialize", Query_initialize, 2);
|
524
724
|
rb_define_method(cQuery, "inspect", Query_inspect, 0);
|
525
725
|
|