extralite 2.4 → 2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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/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
data/ext/extralite/database.c
CHANGED
@@ -13,11 +13,19 @@ 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;
|
20
|
+
ID ID_to_s;
|
21
|
+
ID ID_track;
|
19
22
|
|
23
|
+
VALUE SYM_gvl_release_threshold;
|
20
24
|
VALUE SYM_read_only;
|
25
|
+
VALUE SYM_synchronous;
|
26
|
+
VALUE SYM_wal_journal_mode;
|
27
|
+
|
28
|
+
#define DB_GVL_MODE(db) Database_prepare_gvl_mode(db)
|
21
29
|
|
22
30
|
static size_t Database_size(const void *ptr) {
|
23
31
|
return sizeof(Database_t);
|
@@ -25,7 +33,14 @@ static size_t Database_size(const void *ptr) {
|
|
25
33
|
|
26
34
|
static void Database_mark(void *ptr) {
|
27
35
|
Database_t *db = ptr;
|
28
|
-
|
36
|
+
rb_gc_mark_movable(db->trace_proc);
|
37
|
+
rb_gc_mark_movable(db->progress_handler_proc);
|
38
|
+
}
|
39
|
+
|
40
|
+
static void Database_compact(void *ptr) {
|
41
|
+
Database_t *db = ptr;
|
42
|
+
db->trace_proc = rb_gc_location(db->trace_proc);
|
43
|
+
db->progress_handler_proc = rb_gc_location(db->progress_handler_proc);
|
29
44
|
}
|
30
45
|
|
31
46
|
static void Database_free(void *ptr) {
|
@@ -36,7 +51,7 @@ static void Database_free(void *ptr) {
|
|
36
51
|
|
37
52
|
static const rb_data_type_t Database_type = {
|
38
53
|
"Database",
|
39
|
-
{Database_mark, Database_free, Database_size,},
|
54
|
+
{Database_mark, Database_free, Database_size, Database_compact},
|
40
55
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
41
56
|
};
|
42
57
|
|
@@ -85,14 +100,40 @@ default_flags:
|
|
85
100
|
return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
86
101
|
}
|
87
102
|
|
103
|
+
VALUE Database_execute(int argc, VALUE *argv, VALUE self);
|
104
|
+
|
105
|
+
void Database_apply_opts(VALUE self, Database_t *db, VALUE opts) {
|
106
|
+
VALUE value = Qnil;
|
107
|
+
|
108
|
+
value = rb_hash_aref(opts, SYM_gvl_release_threshold);
|
109
|
+
if (!NIL_P(value)) db->gvl_release_threshold = NUM2INT(value);
|
110
|
+
|
111
|
+
value = rb_hash_aref(opts, SYM_wal_journal_mode);
|
112
|
+
if (RTEST(value)) {
|
113
|
+
value = rb_str_new_literal("PRAGMA journal_mode=wal");
|
114
|
+
Database_execute(1, &value, self);
|
115
|
+
}
|
116
|
+
|
117
|
+
value = rb_hash_aref(opts, SYM_synchronous);
|
118
|
+
if (RTEST(value)) {
|
119
|
+
value = rb_str_new_literal("PRAGMA synchronous=1");
|
120
|
+
Database_execute(1, &value, self);
|
121
|
+
}
|
122
|
+
|
123
|
+
RB_GC_GUARD(value);
|
124
|
+
}
|
125
|
+
|
88
126
|
/* Initializes a new SQLite database with the given path and options.
|
89
127
|
*
|
90
128
|
* @overload initialize(path)
|
91
129
|
* @param path [String] file path (or ':memory:' for memory database)
|
92
130
|
* @return [void]
|
93
|
-
* @overload initialize(path, read_only:
|
131
|
+
* @overload initialize(path, gvl_release_threshold: , read_only: , synchronous: , wal_journal_mode: )
|
94
132
|
* @param path [String] file path (or ':memory:' for memory database)
|
133
|
+
* @param gvl_release_threshold [Integer] GVL release threshold
|
95
134
|
* @param read_only [boolean] true for opening the database for reading only
|
135
|
+
* @param synchronous [boolean] true to set PRAGMA synchronous=1
|
136
|
+
* @param wal_journal_mode [boolean] true to set PRAGMA journal_mode=wal
|
96
137
|
* @return [void]
|
97
138
|
*/
|
98
139
|
VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
@@ -124,9 +165,12 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
|
124
165
|
}
|
125
166
|
#endif
|
126
167
|
|
127
|
-
db->
|
168
|
+
db->trace_proc = Qnil;
|
169
|
+
db->progress_handler_proc = Qnil;
|
128
170
|
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
129
171
|
|
172
|
+
if (!NIL_P(opts)) Database_apply_opts(self, db, opts);
|
173
|
+
|
130
174
|
return Qnil;
|
131
175
|
}
|
132
176
|
|
@@ -170,6 +214,10 @@ VALUE Database_closed_p(VALUE self) {
|
|
170
214
|
return db->sqlite3_db ? Qfalse : Qtrue;
|
171
215
|
}
|
172
216
|
|
217
|
+
inline enum gvl_mode Database_prepare_gvl_mode(Database_t *db) {
|
218
|
+
return db->gvl_release_threshold < 0 ? GVL_HOLD : GVL_RELEASE;
|
219
|
+
}
|
220
|
+
|
173
221
|
static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VALUE (*call)(query_ctx *)) {
|
174
222
|
Database_t *db = self_to_open_database(self);
|
175
223
|
sqlite3_stmt *stmt;
|
@@ -180,9 +228,8 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
180
228
|
sql = rb_funcall(argv[0], ID_strip, 0);
|
181
229
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
182
230
|
|
183
|
-
|
184
|
-
|
185
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
231
|
+
TRACE_SQL(db, sql);
|
232
|
+
prepare_multi_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
186
233
|
RB_GC_GUARD(sql);
|
187
234
|
|
188
235
|
bind_all_parameters(stmt, argc - 1, argv + 1);
|
@@ -342,30 +389,150 @@ VALUE Database_execute(int argc, VALUE *argv, VALUE self) {
|
|
342
389
|
}
|
343
390
|
|
344
391
|
/* call-seq:
|
345
|
-
* db.
|
392
|
+
* db.batch_execute(sql, params_array) -> changes
|
393
|
+
* db.batch_execute(sql, enumerable) -> changes
|
394
|
+
* db.batch_execute(sql, callable) -> changes
|
395
|
+
*
|
396
|
+
* Executes the given query for each list of parameters in the paramter source.
|
397
|
+
* If an enumerable is given, it is iterated and each of its values is used as
|
398
|
+
* the parameters for running the query. If a callable is given, it is called
|
399
|
+
* repeatedly and each of its return values is used as the parameters, until nil
|
400
|
+
* is returned.
|
346
401
|
*
|
347
|
-
*
|
348
|
-
*
|
349
|
-
* multiple records.
|
402
|
+
* Returns the number of changes effected. This method is designed for inserting
|
403
|
+
* multiple records or performing other mass operations.
|
350
404
|
*
|
351
405
|
* records = [
|
352
406
|
* [1, 2, 3],
|
353
407
|
* [4, 5, 6]
|
354
408
|
* ]
|
355
|
-
* db.
|
409
|
+
* db.batch_execute('insert into foo values (?, ?, ?)', records)
|
356
410
|
*
|
411
|
+
* source = [
|
412
|
+
* [1, 2, 3],
|
413
|
+
* [4, 5, 6]
|
414
|
+
* ]
|
415
|
+
* db.batch_execute('insert into foo values (?, ?, ?)', -> { records.shift })
|
416
|
+
*
|
417
|
+
* @param sql [String] query SQL
|
418
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
419
|
+
* @return [Integer] Total number of changes effected
|
357
420
|
*/
|
358
|
-
VALUE
|
421
|
+
VALUE Database_batch_execute(VALUE self, VALUE sql, VALUE parameters) {
|
359
422
|
Database_t *db = self_to_open_database(self);
|
360
423
|
sqlite3_stmt *stmt;
|
361
424
|
|
362
425
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
363
426
|
|
364
|
-
|
365
|
-
|
366
|
-
|
427
|
+
prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
428
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
429
|
+
|
430
|
+
return rb_ensure(SAFE(safe_batch_execute), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
431
|
+
}
|
432
|
+
|
433
|
+
/* call-seq:
|
434
|
+
* db.batch_query(sql, params_array) -> rows
|
435
|
+
* db.batch_query(sql, enumerable) -> rows
|
436
|
+
* db.batch_query(sql, callable) -> rows
|
437
|
+
* db.batch_query(sql, params_array) { |rows| ... } -> changes
|
438
|
+
* db.batch_query(sql, enumerable) { |rows| ... } -> changes
|
439
|
+
* db.batch_query(sql, callable) { |rows| ... } -> changes
|
440
|
+
*
|
441
|
+
* Executes the given query for each list of parameters in the given paramter
|
442
|
+
* source. If a block is given, it is called with the resulting rows for each
|
443
|
+
* invocation of the query, and the total number of changes is returned.
|
444
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
445
|
+
* returned.
|
446
|
+
*
|
447
|
+
* records = [
|
448
|
+
* [1, 2],
|
449
|
+
* [3, 4]
|
450
|
+
* ]
|
451
|
+
* db.batch_query('insert into foo values (?, ?) returning bar, baz', records)
|
452
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
453
|
+
* *
|
454
|
+
* @param sql [String] query SQL
|
455
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
456
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
457
|
+
*/
|
458
|
+
VALUE Database_batch_query(VALUE self, VALUE sql, VALUE parameters) {
|
459
|
+
Database_t *db = self_to_open_database(self);
|
460
|
+
sqlite3_stmt *stmt;
|
461
|
+
|
462
|
+
prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
463
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
464
|
+
|
465
|
+
return rb_ensure(SAFE(safe_batch_query), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
466
|
+
}
|
467
|
+
|
468
|
+
/* call-seq:
|
469
|
+
* db.batch_query_ary(sql, params_array) -> rows
|
470
|
+
* db.batch_query_ary(sql, enumerable) -> rows
|
471
|
+
* db.batch_query_ary(sql, callable) -> rows
|
472
|
+
* db.batch_query_ary(sql, params_array) { |rows| ... } -> changes
|
473
|
+
* db.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
|
474
|
+
* db.batch_query_ary(sql, callable) { |rows| ... } -> changes
|
475
|
+
*
|
476
|
+
* Executes the given query for each list of parameters in the given paramter
|
477
|
+
* source. If a block is given, it is called with the resulting rows for each
|
478
|
+
* invocation of the query, and the total number of changes is returned.
|
479
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
480
|
+
* returned. Rows are represented as arrays.
|
481
|
+
*
|
482
|
+
* records = [
|
483
|
+
* [1, 2],
|
484
|
+
* [3, 4]
|
485
|
+
* ]
|
486
|
+
* db.batch_query_ary('insert into foo values (?, ?) returning bar, baz', records)
|
487
|
+
* #=> [[1, 2], [3, 4]]
|
488
|
+
* *
|
489
|
+
* @param sql [String] query SQL
|
490
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
491
|
+
* @return [Array<Array>, Integer] Total number of changes effected
|
492
|
+
*/
|
493
|
+
VALUE Database_batch_query_ary(VALUE self, VALUE sql, VALUE parameters) {
|
494
|
+
Database_t *db = self_to_open_database(self);
|
495
|
+
sqlite3_stmt *stmt;
|
496
|
+
|
497
|
+
prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
498
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
499
|
+
|
500
|
+
return rb_ensure(SAFE(safe_batch_query_ary), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
501
|
+
}
|
502
|
+
|
503
|
+
/* call-seq:
|
504
|
+
* db.batch_query_single_column(sql, params_array) -> rows
|
505
|
+
* db.batch_query_single_column(sql, enumerable) -> rows
|
506
|
+
* db.batch_query_single_column(sql, callable) -> rows
|
507
|
+
* db.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
|
508
|
+
* db.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
|
509
|
+
* db.batch_query_single_column(sql, callable) { |rows| ... } -> changes
|
510
|
+
*
|
511
|
+
* Executes the given query for each list of parameters in the given paramter
|
512
|
+
* source. If a block is given, it is called with the resulting rows for each
|
513
|
+
* invocation of the query, and the total number of changes is returned.
|
514
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
515
|
+
* returned. Rows are single values.
|
516
|
+
*
|
517
|
+
* records = [
|
518
|
+
* [1, 2],
|
519
|
+
* [3, 4]
|
520
|
+
* ]
|
521
|
+
* db.batch_query_ary('insert into foo values (?, ?) returning baz', records)
|
522
|
+
* #=> [2, 4]
|
523
|
+
* *
|
524
|
+
* @param sql [String] query SQL
|
525
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
526
|
+
* @return [Array<any>, Integer] Total number of changes effected
|
527
|
+
*/
|
528
|
+
VALUE Database_batch_query_single_column(VALUE self, VALUE sql, VALUE parameters) {
|
529
|
+
Database_t *db = self_to_open_database(self);
|
530
|
+
sqlite3_stmt *stmt;
|
531
|
+
|
532
|
+
prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
533
|
+
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
367
534
|
|
368
|
-
return rb_ensure(SAFE(
|
535
|
+
return rb_ensure(SAFE(safe_batch_query_single_column), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
369
536
|
}
|
370
537
|
|
371
538
|
/* call-seq:
|
@@ -449,8 +616,10 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
|
|
449
616
|
|
450
617
|
/* call-seq:
|
451
618
|
* db.prepare(sql) -> Extralite::Query
|
619
|
+
* db.prepare(sql, ...) -> Extralite::Query
|
452
620
|
*
|
453
|
-
* Creates a prepared statement with the given SQL query.
|
621
|
+
* Creates a prepared statement with the given SQL query. If query parameters
|
622
|
+
* are given, they are bound to the query.
|
454
623
|
*/
|
455
624
|
VALUE Database_prepare(int argc, VALUE *argv, VALUE self) {
|
456
625
|
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
@@ -697,7 +866,127 @@ VALUE Database_total_changes(VALUE self) {
|
|
697
866
|
VALUE Database_trace(VALUE self) {
|
698
867
|
Database_t *db = self_to_open_database(self);
|
699
868
|
|
700
|
-
RB_OBJ_WRITE(self, &db->
|
869
|
+
RB_OBJ_WRITE(self, &db->trace_proc, rb_block_given_p() ? rb_block_proc() : Qnil);
|
870
|
+
return self;
|
871
|
+
}
|
872
|
+
|
873
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
874
|
+
/* Tracks changes to the database and returns a changeset. The changeset can
|
875
|
+
* then be used to store the changes to a file, apply them to another database,
|
876
|
+
* or undo the changes. The given table names specify which tables should be
|
877
|
+
* tracked for changes. Passing a value of nil causes all tables to be tracked.
|
878
|
+
*
|
879
|
+
* changeset = db.track_changes(:foo, :bar) do
|
880
|
+
* perform_a_bunch_of_queries
|
881
|
+
* end
|
882
|
+
*
|
883
|
+
* File.open('my.changes', 'w+') { |f| f << changeset.to_blob }
|
884
|
+
*
|
885
|
+
* @param table [String, Symbol] table to track
|
886
|
+
* @return [Extralite::Changeset] changeset
|
887
|
+
*/
|
888
|
+
VALUE Database_track_changes(int argc, VALUE *argv, VALUE self) {
|
889
|
+
self_to_open_database(self);
|
890
|
+
|
891
|
+
VALUE changeset = rb_funcall(cChangeset, ID_new, 0);
|
892
|
+
VALUE tables = rb_ary_new_from_values(argc, argv);
|
893
|
+
|
894
|
+
rb_funcall(changeset, ID_track, 2, self, tables);
|
895
|
+
|
896
|
+
RB_GC_GUARD(changeset);
|
897
|
+
RB_GC_GUARD(tables);
|
898
|
+
return changeset;
|
899
|
+
}
|
900
|
+
#endif
|
901
|
+
|
902
|
+
int Database_progress_handler(void *ptr) {
|
903
|
+
Database_t *db = (Database_t *)ptr;
|
904
|
+
rb_funcall(db->progress_handler_proc, ID_call, 0);
|
905
|
+
return 0;
|
906
|
+
}
|
907
|
+
|
908
|
+
int Database_busy_handler(void *ptr, int v) {
|
909
|
+
Database_t *db = (Database_t *)ptr;
|
910
|
+
rb_funcall(db->progress_handler_proc, ID_call, 0);
|
911
|
+
return 1;
|
912
|
+
}
|
913
|
+
|
914
|
+
void Database_reset_progress_handler(VALUE self, Database_t *db) {
|
915
|
+
RB_OBJ_WRITE(self, &db->progress_handler_proc, Qnil);
|
916
|
+
sqlite3_progress_handler(db->sqlite3_db, 0, NULL, NULL);
|
917
|
+
sqlite3_busy_handler(db->sqlite3_db, NULL, NULL);
|
918
|
+
}
|
919
|
+
|
920
|
+
/* call-seq:
|
921
|
+
* db.on_progress(period) { } -> db
|
922
|
+
* db.on_progress(0) -> db
|
923
|
+
*
|
924
|
+
* Installs or removes a progress handler that will be executed periodically
|
925
|
+
* while a query is running. This method can be used to support switching
|
926
|
+
* between fibers and threads or implementing timeouts for running queries.
|
927
|
+
*
|
928
|
+
* The given period parameter specifies the approximate number of SQLite virtual
|
929
|
+
* machine instructions that are evaluated between successive invocations of the
|
930
|
+
* progress handler. A period of less than 1 removes the progress handler.
|
931
|
+
*
|
932
|
+
* The progress handler is called also when the database is busy. This lets the
|
933
|
+
* application perform work while waiting for the database to become unlocked,
|
934
|
+
* or implement a timeout. Note that setting the database's busy_timeout _after_
|
935
|
+
* setting a progress handler may lead to undefined behaviour in a concurrent
|
936
|
+
* application.
|
937
|
+
*
|
938
|
+
* When the progress handler is set, the gvl release threshold value is set to
|
939
|
+
* -1, which means that the GVL will not be released at all when preparing or
|
940
|
+
* running queries. It is the application's responsibility to let other threads
|
941
|
+
* or fibers run by calling e.g. Thread.pass:
|
942
|
+
*
|
943
|
+
* db.on_progress(1000) do
|
944
|
+
* do_something_interesting
|
945
|
+
* Thread.pass # let other threads run
|
946
|
+
* end
|
947
|
+
*
|
948
|
+
* Note that the progress handler is set globally for the database and that
|
949
|
+
* Extralite does provide any hooks for telling which queries are currently
|
950
|
+
* running or at what time they were started. This means that you'll need
|
951
|
+
* to wrap the stock #query_xxx and #execute methods with your own code that
|
952
|
+
* calculates timeouts, for example:
|
953
|
+
*
|
954
|
+
* def setup_progress_handler
|
955
|
+
* @db.on_progress(1000) do
|
956
|
+
* raise TimeoutError if Time.now - @t0 >= @timeout
|
957
|
+
* Thread.pass
|
958
|
+
* end
|
959
|
+
* end
|
960
|
+
*
|
961
|
+
* def query(sql, *)
|
962
|
+
* @t0 = Time.now
|
963
|
+
* @db.query(sql, *)
|
964
|
+
* end
|
965
|
+
*
|
966
|
+
* If the gvl release threshold is set to a value equal to or larger than 0
|
967
|
+
* after setting the progress handler, the progress handler will be reset.
|
968
|
+
*
|
969
|
+
* @param period [Integer] progress handler period
|
970
|
+
* @returns [Extralite::Database] database
|
971
|
+
*/
|
972
|
+
VALUE Database_on_progress(VALUE self, VALUE period) {
|
973
|
+
Database_t *db = self_to_open_database(self);
|
974
|
+
int period_int = NUM2INT(period);
|
975
|
+
|
976
|
+
if (period_int > 0 && rb_block_given_p()) {
|
977
|
+
RB_OBJ_WRITE(self, &db->progress_handler_proc, rb_block_proc());
|
978
|
+
db->gvl_release_threshold = -1;
|
979
|
+
|
980
|
+
sqlite3_progress_handler(db->sqlite3_db, period_int, &Database_progress_handler, db);
|
981
|
+
sqlite3_busy_handler(db->sqlite3_db, &Database_busy_handler, db);
|
982
|
+
}
|
983
|
+
else {
|
984
|
+
RB_OBJ_WRITE(self, &db->progress_handler_proc, Qnil);
|
985
|
+
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
986
|
+
sqlite3_progress_handler(db->sqlite3_db, 0, NULL, NULL);
|
987
|
+
sqlite3_busy_handler(db->sqlite3_db, NULL, NULL);
|
988
|
+
}
|
989
|
+
|
701
990
|
return self;
|
702
991
|
}
|
703
992
|
|
@@ -777,8 +1066,16 @@ VALUE Database_gvl_release_threshold_set(VALUE self, VALUE value) {
|
|
777
1066
|
|
778
1067
|
switch (TYPE(value)) {
|
779
1068
|
case T_FIXNUM:
|
780
|
-
|
781
|
-
|
1069
|
+
{
|
1070
|
+
int value_int = NUM2INT(value);
|
1071
|
+
if (value_int < -1)
|
1072
|
+
rb_raise(eArgumentError, "Invalid GVL release threshold value (expect integer >= -1)");
|
1073
|
+
|
1074
|
+
if (value_int > -1 && !NIL_P(db->progress_handler_proc))
|
1075
|
+
Database_reset_progress_handler(self, db);
|
1076
|
+
db->gvl_release_threshold = value_int;
|
1077
|
+
break;
|
1078
|
+
}
|
782
1079
|
case T_NIL:
|
783
1080
|
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
784
1081
|
break;
|
@@ -811,7 +1108,10 @@ void Init_ExtraliteDatabase(void) {
|
|
811
1108
|
#endif
|
812
1109
|
|
813
1110
|
rb_define_method(cDatabase, "execute", Database_execute, -1);
|
814
|
-
rb_define_method(cDatabase, "
|
1111
|
+
rb_define_method(cDatabase, "batch_execute", Database_batch_execute, 2);
|
1112
|
+
rb_define_method(cDatabase, "batch_query", Database_batch_query, 2);
|
1113
|
+
rb_define_method(cDatabase, "batch_query_ary", Database_batch_query_ary, 2);
|
1114
|
+
rb_define_method(cDatabase, "batch_query_single_column", Database_batch_query_single_column, 2);
|
815
1115
|
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
816
1116
|
rb_define_method(cDatabase, "gvl_release_threshold", Database_gvl_release_threshold_get, 0);
|
817
1117
|
rb_define_method(cDatabase, "gvl_release_threshold=", Database_gvl_release_threshold_set, 1);
|
@@ -820,6 +1120,7 @@ void Init_ExtraliteDatabase(void) {
|
|
820
1120
|
rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
|
821
1121
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
822
1122
|
rb_define_method(cDatabase, "limit", Database_limit, -1);
|
1123
|
+
rb_define_method(cDatabase, "on_progress", Database_on_progress, 1);
|
823
1124
|
rb_define_method(cDatabase, "prepare", Database_prepare, -1);
|
824
1125
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
825
1126
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
@@ -831,6 +1132,11 @@ void Init_ExtraliteDatabase(void) {
|
|
831
1132
|
rb_define_method(cDatabase, "status", Database_status, -1);
|
832
1133
|
rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
|
833
1134
|
rb_define_method(cDatabase, "trace", Database_trace, 0);
|
1135
|
+
|
1136
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
1137
|
+
rb_define_method(cDatabase, "track_changes", Database_track_changes, -1);
|
1138
|
+
#endif
|
1139
|
+
|
834
1140
|
rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
|
835
1141
|
|
836
1142
|
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
@@ -849,12 +1155,22 @@ void Init_ExtraliteDatabase(void) {
|
|
849
1155
|
|
850
1156
|
ID_bind = rb_intern("bind");
|
851
1157
|
ID_call = rb_intern("call");
|
1158
|
+
ID_each = rb_intern("each");
|
852
1159
|
ID_keys = rb_intern("keys");
|
853
1160
|
ID_new = rb_intern("new");
|
854
1161
|
ID_strip = rb_intern("strip");
|
1162
|
+
ID_to_s = rb_intern("to_s");
|
1163
|
+
ID_track = rb_intern("track");
|
1164
|
+
|
1165
|
+
SYM_gvl_release_threshold = ID2SYM(rb_intern("gvl_release_threshold"));
|
1166
|
+
SYM_read_only = ID2SYM(rb_intern("read_only"));
|
1167
|
+
SYM_synchronous = ID2SYM(rb_intern("synchronous"));
|
1168
|
+
SYM_wal_journal_mode = ID2SYM(rb_intern("wal_journal_mode"));
|
855
1169
|
|
856
|
-
|
1170
|
+
rb_gc_register_mark_object(SYM_gvl_release_threshold);
|
857
1171
|
rb_gc_register_mark_object(SYM_read_only);
|
1172
|
+
rb_gc_register_mark_object(SYM_synchronous);
|
1173
|
+
rb_gc_register_mark_object(SYM_wal_journal_mode);
|
858
1174
|
|
859
1175
|
UTF8_ENCODING = rb_utf8_encoding();
|
860
1176
|
}
|
@@ -6,11 +6,17 @@ $CFLAGS << ' -Wno-undef'
|
|
6
6
|
$CFLAGS << ' -Wno-discarded-qualifiers'
|
7
7
|
$CFLAGS << ' -Wno-unused-function'
|
8
8
|
|
9
|
-
|
10
|
-
$defs <<
|
11
|
-
$defs <<
|
9
|
+
# enable the session extension
|
10
|
+
$defs << '-DSQLITE_ENABLE_SESSION'
|
11
|
+
$defs << '-DSQLITE_ENABLE_PREUPDATE_HOOK'
|
12
|
+
$defs << '-DEXTRALITE_ENABLE_CHANGESET'
|
12
13
|
|
13
|
-
|
14
|
+
$defs << '-DHAVE_SQLITE3_ENABLE_LOAD_EXTENSION'
|
15
|
+
$defs << '-DHAVE_SQLITE3_LOAD_EXTENSION'
|
16
|
+
$defs << '-DHAVE_SQLITE3_PREPARE_V2'
|
17
|
+
$defs << '-DHAVE_SQLITE3_ERROR_OFFSET'
|
18
|
+
$defs << '-DHAVE_SQLITE3SESSION_CHANGESET'
|
14
19
|
|
20
|
+
have_func('usleep')
|
15
21
|
dir_config('extralite_ext')
|
16
22
|
create_makefile('extralite_ext')
|
data/ext/extralite/extconf.rb
CHANGED
@@ -52,43 +52,47 @@ else
|
|
52
52
|
$CFLAGS << ' -W3'
|
53
53
|
end
|
54
54
|
|
55
|
-
if RUBY_VERSION < '2.7'
|
56
|
-
$CFLAGS << ' -DTAINTING_SUPPORT'
|
57
|
-
end
|
58
|
-
|
59
55
|
# @!visibility private
|
60
56
|
def asplode missing
|
61
57
|
if RUBY_PLATFORM =~ /mingw|mswin/
|
62
58
|
abort "#{missing} is missing. Install SQLite3 from " +
|
63
59
|
"http://www.sqlite.org/ first."
|
64
60
|
else
|
65
|
-
abort
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
61
|
+
abort <<~error
|
62
|
+
#{missing} is missing. Try 'brew install sqlite3',
|
63
|
+
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
|
64
|
+
and check your shared library search path (the location where
|
65
|
+
your sqlite3 shared library is located).
|
66
|
+
error
|
72
67
|
end
|
68
|
+
end
|
73
69
|
|
74
|
-
|
75
|
-
|
70
|
+
asplode('sqlite3.h') unless find_header('sqlite3.h')
|
71
|
+
# find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
|
76
72
|
|
77
|
-
|
73
|
+
have_library 'dl' # for static builds
|
78
74
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
75
|
+
if with_config('sqlcipher')
|
76
|
+
asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
|
77
|
+
else
|
78
|
+
asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
|
79
|
+
end
|
80
|
+
|
81
|
+
have_func('sqlite3_enable_load_extension')
|
82
|
+
have_func('sqlite3_load_extension')
|
83
|
+
have_func('sqlite3_prepare_v2')
|
84
|
+
have_func('sqlite3_error_offset')
|
85
|
+
have_func('sqlite3session_changeset')
|
86
|
+
|
87
|
+
if have_type('sqlite3_session', 'sqlite.h')
|
88
|
+
$defs << '-DEXTRALITE_ENABLE_CHANGESET'
|
89
|
+
end
|
90
|
+
# have_macro('__SQLITESESSION_H_')
|
91
|
+
# have_macro('SQLITE3_H')
|
84
92
|
|
85
|
-
have_func('sqlite3_enable_load_extension')
|
86
|
-
have_func('sqlite3_load_extension')
|
87
|
-
have_func('sqlite3_prepare_v2')
|
88
|
-
have_func('sqlite3_error_offset')
|
89
93
|
|
90
|
-
|
94
|
+
$defs << "-DEXTRALITE_NO_BUNDLE"
|
91
95
|
|
92
|
-
|
93
|
-
|
94
|
-
|
96
|
+
dir_config('extralite_ext')
|
97
|
+
create_makefile('extralite_ext')
|
98
|
+
end
|
data/ext/extralite/extralite.h
CHANGED
@@ -23,6 +23,7 @@
|
|
23
23
|
extern VALUE cDatabase;
|
24
24
|
extern VALUE cQuery;
|
25
25
|
extern VALUE cIterator;
|
26
|
+
extern VALUE cChangeset;
|
26
27
|
extern VALUE cBlob;
|
27
28
|
|
28
29
|
extern VALUE cError;
|
@@ -32,17 +33,21 @@ extern VALUE cInterruptError;
|
|
32
33
|
extern VALUE cParameterError;
|
33
34
|
|
34
35
|
extern ID ID_call;
|
36
|
+
extern ID ID_each;
|
35
37
|
extern ID ID_keys;
|
36
38
|
extern ID ID_new;
|
37
39
|
extern ID ID_strip;
|
40
|
+
extern ID ID_to_s;
|
41
|
+
extern ID ID_track;
|
38
42
|
|
39
|
-
extern VALUE SYM_hash;
|
40
43
|
extern VALUE SYM_ary;
|
44
|
+
extern VALUE SYM_hash;
|
41
45
|
extern VALUE SYM_single_column;
|
42
46
|
|
43
47
|
typedef struct {
|
44
48
|
sqlite3 *sqlite3_db;
|
45
|
-
VALUE
|
49
|
+
VALUE trace_proc;
|
50
|
+
VALUE progress_handler_proc;
|
46
51
|
int gvl_release_threshold;
|
47
52
|
} Database_t;
|
48
53
|
|
@@ -67,6 +72,13 @@ typedef struct {
|
|
67
72
|
enum iterator_mode mode;
|
68
73
|
} Iterator_t;
|
69
74
|
|
75
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
76
|
+
typedef struct {
|
77
|
+
int changeset_len;
|
78
|
+
void *changeset_ptr;
|
79
|
+
} Changeset_t;
|
80
|
+
#endif
|
81
|
+
|
70
82
|
enum query_mode {
|
71
83
|
QUERY_YIELD,
|
72
84
|
QUERY_MULTI_ROW,
|
@@ -96,11 +108,18 @@ enum gvl_mode {
|
|
96
108
|
#define MULTI_ROW_P(mode) (mode == QUERY_MULTI_ROW)
|
97
109
|
#define QUERY_CTX(self, db, stmt, params, mode, max_rows) \
|
98
110
|
{ self, db->sqlite3_db, stmt, params, mode, max_rows, 0, db->gvl_release_threshold, 0 }
|
111
|
+
#define TRACE_SQL(db, sql) \
|
112
|
+
if (db->trace_proc != Qnil) rb_funcall(db->trace_proc, ID_call, 1, sql);
|
113
|
+
|
99
114
|
#define DEFAULT_GVL_RELEASE_THRESHOLD 1000
|
100
115
|
|
116
|
+
|
101
117
|
extern rb_encoding *UTF8_ENCODING;
|
102
118
|
|
103
|
-
VALUE
|
119
|
+
VALUE safe_batch_execute(query_ctx *ctx);
|
120
|
+
VALUE safe_batch_query(query_ctx *ctx);
|
121
|
+
VALUE safe_batch_query_ary(query_ctx *ctx);
|
122
|
+
VALUE safe_batch_query_single_column(query_ctx *ctx);
|
104
123
|
VALUE safe_query_ary(query_ctx *ctx);
|
105
124
|
VALUE safe_query_changes(query_ctx *ctx);
|
106
125
|
VALUE safe_query_columns(query_ctx *ctx);
|
@@ -121,14 +140,15 @@ VALUE Query_to_a_hash(VALUE self);
|
|
121
140
|
VALUE Query_to_a_ary(VALUE self);
|
122
141
|
VALUE Query_to_a_single_column(VALUE self);
|
123
142
|
|
124
|
-
void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
125
|
-
void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
143
|
+
void prepare_single_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
144
|
+
void prepare_multi_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
126
145
|
void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv);
|
127
146
|
void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj);
|
128
147
|
int stmt_iterate(query_ctx *ctx);
|
129
148
|
VALUE cleanup_stmt(query_ctx *ctx);
|
130
149
|
|
131
150
|
sqlite3 *Database_sqlite3_db(VALUE self);
|
151
|
+
enum gvl_mode Database_prepare_gvl_mode(Database_t *db);
|
132
152
|
Database_t *self_to_database(VALUE self);
|
133
153
|
|
134
154
|
void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data);
|