extralite-bundle 2.5 → 2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|