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.
@@ -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->trace_block);
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->trace_block = rb_gc_location(db->trace_block);
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->trace_block = Qnil;
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
- if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_call, 1, sql);
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->trace_block, rb_block_given_p() ? rb_block_proc() : Qnil);
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
- db->gvl_release_threshold = NUM2INT(value);
939
- break;
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
- $defs << "-DHAVE_SQLITE3_ENABLE_LOAD_EXTENSION"
10
- $defs << "-DHAVE_SQLITE3_LOAD_EXTENSION"
11
- $defs << "-DHAVE_SQLITE3_ERROR_OFFSET"
9
+ # enable the session extension
10
+ $defs << '-DSQLITE_ENABLE_SESSION'
11
+ $defs << '-DSQLITE_ENABLE_PREUPDATE_HOOK'
12
+ $defs << '-DEXTRALITE_ENABLE_CHANGESET'
12
13
 
13
- have_func('usleep')
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')
@@ -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 <<-error
66
- #{missing} is missing. Try 'brew install sqlite3',
67
- 'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
68
- and check your shared library search path (the
69
- location where your sqlite3 shared library is located).
70
- error
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
- asplode('sqlite3.h') unless find_header 'sqlite3.h'
75
- find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
70
+ asplode('sqlite3.h') unless find_header('sqlite3.h')
71
+ # find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
76
72
 
77
- have_library 'dl' # for static builds
73
+ have_library 'dl' # for static builds
78
74
 
79
- if with_config('sqlcipher')
80
- asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
81
- else
82
- asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
83
- end
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
- $defs << "-DEXTRALITE_NO_BUNDLE"
94
+ $defs << "-DEXTRALITE_NO_BUNDLE"
91
95
 
92
- dir_config('extralite_ext')
93
- create_makefile('extralite_ext')
94
- end
96
+ dir_config('extralite_ext')
97
+ create_makefile('extralite_ext')
98
+ end
@@ -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 trace_block;
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
  }
@@ -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
- rb_gc_mark(iterator->query);
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) {
@@ -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
- if (query->db_struct->trace_block != Qnil)
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
- if (query->db_struct->trace_block != Qnil)
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
- if (query->db_struct->trace_block != Qnil)
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);
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.5'
3
+ VERSION = '2.6'
4
4
  end
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\.0/)
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