extralite 2.5 → 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/CHANGELOG.md +10 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +869 -244
- data/TODO.md +2 -0
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +4 -4
- data/ext/extralite/database.c +159 -12
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +31 -27
- data/ext/extralite/extralite.h +20 -4
- data/ext/extralite/extralite_ext.c +6 -0
- data/ext/extralite/iterator.c +8 -3
- data/ext/extralite/query.c +12 -13
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +50 -2
- data/test/helper.rb +8 -1
- 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 +201 -1
- data/test/test_query.rb +5 -0
- metadata +6 -4
- data/test/perf_prepared.rb +0 -64
data/ext/extralite/database.c
CHANGED
@@ -17,24 +17,30 @@ ID ID_each;
|
|
17
17
|
ID ID_keys;
|
18
18
|
ID ID_new;
|
19
19
|
ID ID_strip;
|
20
|
+
ID ID_to_s;
|
21
|
+
ID ID_track;
|
20
22
|
|
21
23
|
VALUE SYM_gvl_release_threshold;
|
22
24
|
VALUE SYM_read_only;
|
23
25
|
VALUE SYM_synchronous;
|
24
26
|
VALUE SYM_wal_journal_mode;
|
25
27
|
|
28
|
+
#define DB_GVL_MODE(db) Database_prepare_gvl_mode(db)
|
29
|
+
|
26
30
|
static size_t Database_size(const void *ptr) {
|
27
31
|
return sizeof(Database_t);
|
28
32
|
}
|
29
33
|
|
30
34
|
static void Database_mark(void *ptr) {
|
31
35
|
Database_t *db = ptr;
|
32
|
-
rb_gc_mark_movable(db->
|
36
|
+
rb_gc_mark_movable(db->trace_proc);
|
37
|
+
rb_gc_mark_movable(db->progress_handler_proc);
|
33
38
|
}
|
34
39
|
|
35
40
|
static void Database_compact(void *ptr) {
|
36
41
|
Database_t *db = ptr;
|
37
|
-
db->
|
42
|
+
db->trace_proc = rb_gc_location(db->trace_proc);
|
43
|
+
db->progress_handler_proc = rb_gc_location(db->progress_handler_proc);
|
38
44
|
}
|
39
45
|
|
40
46
|
static void Database_free(void *ptr) {
|
@@ -159,7 +165,8 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
|
159
165
|
}
|
160
166
|
#endif
|
161
167
|
|
162
|
-
db->
|
168
|
+
db->trace_proc = Qnil;
|
169
|
+
db->progress_handler_proc = Qnil;
|
163
170
|
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
164
171
|
|
165
172
|
if (!NIL_P(opts)) Database_apply_opts(self, db, opts);
|
@@ -207,6 +214,10 @@ VALUE Database_closed_p(VALUE self) {
|
|
207
214
|
return db->sqlite3_db ? Qfalse : Qtrue;
|
208
215
|
}
|
209
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
|
+
|
210
221
|
static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VALUE (*call)(query_ctx *)) {
|
211
222
|
Database_t *db = self_to_open_database(self);
|
212
223
|
sqlite3_stmt *stmt;
|
@@ -217,8 +228,8 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
217
228
|
sql = rb_funcall(argv[0], ID_strip, 0);
|
218
229
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
219
230
|
|
220
|
-
|
221
|
-
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);
|
222
233
|
RB_GC_GUARD(sql);
|
223
234
|
|
224
235
|
bind_all_parameters(stmt, argc - 1, argv + 1);
|
@@ -413,7 +424,7 @@ VALUE Database_batch_execute(VALUE self, VALUE sql, VALUE parameters) {
|
|
413
424
|
|
414
425
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
415
426
|
|
416
|
-
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
427
|
+
prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
417
428
|
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
418
429
|
|
419
430
|
return rb_ensure(SAFE(safe_batch_execute), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
@@ -448,7 +459,7 @@ VALUE Database_batch_query(VALUE self, VALUE sql, VALUE parameters) {
|
|
448
459
|
Database_t *db = self_to_open_database(self);
|
449
460
|
sqlite3_stmt *stmt;
|
450
461
|
|
451
|
-
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
462
|
+
prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
452
463
|
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
453
464
|
|
454
465
|
return rb_ensure(SAFE(safe_batch_query), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
@@ -483,7 +494,7 @@ VALUE Database_batch_query_ary(VALUE self, VALUE sql, VALUE parameters) {
|
|
483
494
|
Database_t *db = self_to_open_database(self);
|
484
495
|
sqlite3_stmt *stmt;
|
485
496
|
|
486
|
-
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
497
|
+
prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
487
498
|
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
488
499
|
|
489
500
|
return rb_ensure(SAFE(safe_batch_query_ary), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
@@ -518,7 +529,7 @@ VALUE Database_batch_query_single_column(VALUE self, VALUE sql, VALUE parameters
|
|
518
529
|
Database_t *db = self_to_open_database(self);
|
519
530
|
sqlite3_stmt *stmt;
|
520
531
|
|
521
|
-
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
532
|
+
prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
|
522
533
|
query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
|
523
534
|
|
524
535
|
return rb_ensure(SAFE(safe_batch_query_single_column), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
@@ -855,7 +866,127 @@ VALUE Database_total_changes(VALUE self) {
|
|
855
866
|
VALUE Database_trace(VALUE self) {
|
856
867
|
Database_t *db = self_to_open_database(self);
|
857
868
|
|
858
|
-
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
|
+
|
859
990
|
return self;
|
860
991
|
}
|
861
992
|
|
@@ -935,8 +1066,16 @@ VALUE Database_gvl_release_threshold_set(VALUE self, VALUE value) {
|
|
935
1066
|
|
936
1067
|
switch (TYPE(value)) {
|
937
1068
|
case T_FIXNUM:
|
938
|
-
|
939
|
-
|
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
|
+
}
|
940
1079
|
case T_NIL:
|
941
1080
|
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
942
1081
|
break;
|
@@ -981,6 +1120,7 @@ void Init_ExtraliteDatabase(void) {
|
|
981
1120
|
rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
|
982
1121
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
983
1122
|
rb_define_method(cDatabase, "limit", Database_limit, -1);
|
1123
|
+
rb_define_method(cDatabase, "on_progress", Database_on_progress, 1);
|
984
1124
|
rb_define_method(cDatabase, "prepare", Database_prepare, -1);
|
985
1125
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
986
1126
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
@@ -992,6 +1132,11 @@ void Init_ExtraliteDatabase(void) {
|
|
992
1132
|
rb_define_method(cDatabase, "status", Database_status, -1);
|
993
1133
|
rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
|
994
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
|
+
|
995
1140
|
rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
|
996
1141
|
|
997
1142
|
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
@@ -1014,6 +1159,8 @@ void Init_ExtraliteDatabase(void) {
|
|
1014
1159
|
ID_keys = rb_intern("keys");
|
1015
1160
|
ID_new = rb_intern("new");
|
1016
1161
|
ID_strip = rb_intern("strip");
|
1162
|
+
ID_to_s = rb_intern("to_s");
|
1163
|
+
ID_track = rb_intern("track");
|
1017
1164
|
|
1018
1165
|
SYM_gvl_release_threshold = ID2SYM(rb_intern("gvl_release_threshold"));
|
1019
1166
|
SYM_read_only = ID2SYM(rb_intern("read_only"));
|
@@ -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;
|
@@ -36,14 +37,17 @@ extern ID ID_each;
|
|
36
37
|
extern ID ID_keys;
|
37
38
|
extern ID ID_new;
|
38
39
|
extern ID ID_strip;
|
40
|
+
extern ID ID_to_s;
|
41
|
+
extern ID ID_track;
|
39
42
|
|
40
|
-
extern VALUE SYM_hash;
|
41
43
|
extern VALUE SYM_ary;
|
44
|
+
extern VALUE SYM_hash;
|
42
45
|
extern VALUE SYM_single_column;
|
43
46
|
|
44
47
|
typedef struct {
|
45
48
|
sqlite3 *sqlite3_db;
|
46
|
-
VALUE
|
49
|
+
VALUE trace_proc;
|
50
|
+
VALUE progress_handler_proc;
|
47
51
|
int gvl_release_threshold;
|
48
52
|
} Database_t;
|
49
53
|
|
@@ -68,6 +72,13 @@ typedef struct {
|
|
68
72
|
enum iterator_mode mode;
|
69
73
|
} Iterator_t;
|
70
74
|
|
75
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
76
|
+
typedef struct {
|
77
|
+
int changeset_len;
|
78
|
+
void *changeset_ptr;
|
79
|
+
} Changeset_t;
|
80
|
+
#endif
|
81
|
+
|
71
82
|
enum query_mode {
|
72
83
|
QUERY_YIELD,
|
73
84
|
QUERY_MULTI_ROW,
|
@@ -97,8 +108,12 @@ enum gvl_mode {
|
|
97
108
|
#define MULTI_ROW_P(mode) (mode == QUERY_MULTI_ROW)
|
98
109
|
#define QUERY_CTX(self, db, stmt, params, mode, max_rows) \
|
99
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
|
+
|
100
114
|
#define DEFAULT_GVL_RELEASE_THRESHOLD 1000
|
101
115
|
|
116
|
+
|
102
117
|
extern rb_encoding *UTF8_ENCODING;
|
103
118
|
|
104
119
|
VALUE safe_batch_execute(query_ctx *ctx);
|
@@ -125,14 +140,15 @@ VALUE Query_to_a_hash(VALUE self);
|
|
125
140
|
VALUE Query_to_a_ary(VALUE self);
|
126
141
|
VALUE Query_to_a_single_column(VALUE self);
|
127
142
|
|
128
|
-
void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
129
|
-
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);
|
130
145
|
void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv);
|
131
146
|
void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj);
|
132
147
|
int stmt_iterate(query_ctx *ctx);
|
133
148
|
VALUE cleanup_stmt(query_ctx *ctx);
|
134
149
|
|
135
150
|
sqlite3 *Database_sqlite3_db(VALUE self);
|
151
|
+
enum gvl_mode Database_prepare_gvl_mode(Database_t *db);
|
136
152
|
Database_t *self_to_database(VALUE self);
|
137
153
|
|
138
154
|
void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data);
|
@@ -3,6 +3,9 @@
|
|
3
3
|
void Init_ExtraliteDatabase();
|
4
4
|
void Init_ExtraliteQuery();
|
5
5
|
void Init_ExtraliteIterator();
|
6
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
7
|
+
void Init_ExtraliteChangeset();
|
8
|
+
#endif
|
6
9
|
|
7
10
|
void Init_extralite_ext(void) {
|
8
11
|
rb_ext_ractor_safe(true);
|
@@ -10,4 +13,7 @@ void Init_extralite_ext(void) {
|
|
10
13
|
Init_ExtraliteDatabase();
|
11
14
|
Init_ExtraliteQuery();
|
12
15
|
Init_ExtraliteIterator();
|
16
|
+
#ifdef EXTRALITE_ENABLE_CHANGESET
|
17
|
+
Init_ExtraliteChangeset();
|
18
|
+
#endif
|
13
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,6 +14,8 @@ 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
|
}
|
@@ -88,19 +90,17 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
|
|
88
90
|
|
89
91
|
static inline void query_reset(Query_t *query) {
|
90
92
|
if (!query->stmt)
|
91
|
-
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
92
|
-
|
93
|
-
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);
|
94
95
|
sqlite3_reset(query->stmt);
|
95
96
|
query->eof = 0;
|
96
97
|
}
|
97
98
|
|
98
99
|
static inline void query_reset_and_bind(Query_t *query, int argc, VALUE * argv) {
|
99
100
|
if (!query->stmt)
|
100
|
-
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);
|
101
102
|
|
102
|
-
|
103
|
-
rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
|
103
|
+
TRACE_SQL(query->db_struct, query->sql);
|
104
104
|
|
105
105
|
sqlite3_reset(query->stmt);
|
106
106
|
query->eof = 0;
|
@@ -128,8 +128,7 @@ VALUE Query_reset(VALUE self) {
|
|
128
128
|
if (query->closed) rb_raise(cError, "Query is closed");
|
129
129
|
|
130
130
|
query_reset(query);
|
131
|
-
|
132
|
-
rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
|
131
|
+
TRACE_SQL(query->db_struct, query->sql);
|
133
132
|
|
134
133
|
return self;
|
135
134
|
}
|
@@ -437,7 +436,7 @@ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
|
|
437
436
|
if (query->closed) rb_raise(cError, "Query is closed");
|
438
437
|
|
439
438
|
if (!query->stmt)
|
440
|
-
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
439
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
441
440
|
|
442
441
|
query_ctx ctx = QUERY_CTX(
|
443
442
|
self,
|
@@ -481,7 +480,7 @@ VALUE Query_batch_query(VALUE self, VALUE parameters) {
|
|
481
480
|
if (query->closed) rb_raise(cError, "Query is closed");
|
482
481
|
|
483
482
|
if (!query->stmt)
|
484
|
-
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);
|
485
484
|
|
486
485
|
query_ctx ctx = QUERY_CTX(
|
487
486
|
self,
|
@@ -525,7 +524,7 @@ VALUE Query_batch_query_ary(VALUE self, VALUE parameters) {
|
|
525
524
|
if (query->closed) rb_raise(cError, "Query is closed");
|
526
525
|
|
527
526
|
if (!query->stmt)
|
528
|
-
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
527
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
529
528
|
|
530
529
|
query_ctx ctx = QUERY_CTX(
|
531
530
|
self,
|
@@ -569,7 +568,7 @@ VALUE Query_batch_query_single_column(VALUE self, VALUE parameters) {
|
|
569
568
|
if (query->closed) rb_raise(cError, "Query is closed");
|
570
569
|
|
571
570
|
if (!query->stmt)
|
572
|
-
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
571
|
+
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
573
572
|
|
574
573
|
query_ctx ctx = QUERY_CTX(
|
575
574
|
self,
|
@@ -670,7 +669,7 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
|
|
670
669
|
if (query->closed) rb_raise(cError, "Query is closed");
|
671
670
|
|
672
671
|
if (!query->stmt)
|
673
|
-
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);
|
674
673
|
|
675
674
|
int value = sqlite3_stmt_status(query->stmt, NUM2INT(op), RTEST(reset) ? 1 : 0);
|
676
675
|
return INT2NUM(value);
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -60,6 +60,11 @@ module Extralite
|
|
60
60
|
value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
|
61
61
|
end
|
62
62
|
|
63
|
+
# Error class used to roll back a transaction without propagating an
|
64
|
+
# exception.
|
65
|
+
class Rollback < Error
|
66
|
+
end
|
67
|
+
|
63
68
|
# Starts a transaction and runs the given block. If an exception is raised
|
64
69
|
# in the block, the transaction is rolled back. Otherwise, the transaction
|
65
70
|
# is commited after running the block.
|
@@ -76,13 +81,56 @@ module Extralite
|
|
76
81
|
|
77
82
|
abort = false
|
78
83
|
yield self
|
79
|
-
rescue
|
84
|
+
rescue => e
|
80
85
|
abort = true
|
81
|
-
raise
|
86
|
+
raise unless e.is_a?(Rollback)
|
82
87
|
ensure
|
83
88
|
execute(abort ? 'rollback' : 'commit')
|
84
89
|
end
|
85
90
|
|
91
|
+
# Creates a savepoint with the given name.
|
92
|
+
#
|
93
|
+
# @param name [String, Symbol] savepoint name
|
94
|
+
# @return [Extralite::Database] database
|
95
|
+
def savepoint(name)
|
96
|
+
execute "savepoint #{name}"
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
# Release a savepoint with the given name.
|
101
|
+
#
|
102
|
+
# @param name [String, Symbol] savepoint name
|
103
|
+
# @return [Extralite::Database] database
|
104
|
+
def release(name)
|
105
|
+
execute "release #{name}"
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# Rolls back changes to a savepoint with the given name.
|
110
|
+
#
|
111
|
+
# @param name [String, Symbol] savepoint name
|
112
|
+
# @return [Extralite::Database] database
|
113
|
+
def rollback_to(name)
|
114
|
+
execute "rollback to #{name}"
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# Rolls back the currently active transaction. This method should only be
|
119
|
+
# called from within a block passed to Database#transaction. This method
|
120
|
+
# raises a Extralite::Rollback exception, which will stop execution of the
|
121
|
+
# transaction block without propagating the exception.
|
122
|
+
#
|
123
|
+
# db.transaction do
|
124
|
+
# db.execute('insert into foo (42)')
|
125
|
+
# db.rollback!
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# @param name [String, Symbol] savepoint name
|
129
|
+
# @return [Extralite::Database] database
|
130
|
+
def rollback!
|
131
|
+
raise Rollback
|
132
|
+
end
|
133
|
+
|
86
134
|
private
|
87
135
|
|
88
136
|
def pragma_set(values)
|
data/test/helper.rb
CHANGED
@@ -7,4 +7,11 @@ require 'minitest/autorun'
|
|
7
7
|
puts "sqlite3 version: #{Extralite.sqlite3_version}"
|
8
8
|
|
9
9
|
IS_LINUX = RUBY_PLATFORM =~ /linux/
|
10
|
-
SKIP_RACTOR_TESTS = !IS_LINUX || (RUBY_VERSION =~ /^3\.
|
10
|
+
SKIP_RACTOR_TESTS = !IS_LINUX || (RUBY_VERSION =~ /^3\.[01]/)
|
11
|
+
|
12
|
+
module Minitest::Assertions
|
13
|
+
def assert_in_range exp_range, act
|
14
|
+
msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
|
15
|
+
assert exp_range.include?(act), msg
|
16
|
+
end
|
17
|
+
end
|