extralite-bundle 2.5 → 2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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