extralite 2.3 → 2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,14 @@
1
1
  #include <stdio.h>
2
+ #include <stdlib.h>
2
3
  #include "extralite.h"
3
4
 
4
5
  VALUE cDatabase;
6
+ VALUE cBlob;
5
7
  VALUE cError;
6
8
  VALUE cSQLError;
7
9
  VALUE cBusyError;
8
10
  VALUE cInterruptError;
11
+ VALUE cParameterError;
9
12
  VALUE eArgumentError;
10
13
 
11
14
  ID ID_bind;
@@ -13,7 +16,6 @@ ID ID_call;
13
16
  ID ID_keys;
14
17
  ID ID_new;
15
18
  ID ID_strip;
16
- ID ID_to_s;
17
19
 
18
20
  VALUE SYM_read_only;
19
21
 
@@ -21,6 +23,11 @@ static size_t Database_size(const void *ptr) {
21
23
  return sizeof(Database_t);
22
24
  }
23
25
 
26
+ static void Database_mark(void *ptr) {
27
+ Database_t *db = ptr;
28
+ rb_gc_mark(db->trace_block);
29
+ }
30
+
24
31
  static void Database_free(void *ptr) {
25
32
  Database_t *db = ptr;
26
33
  if (db->sqlite3_db) sqlite3_close_v2(db->sqlite3_db);
@@ -29,8 +36,8 @@ static void Database_free(void *ptr) {
29
36
 
30
37
  static const rb_data_type_t Database_type = {
31
38
  "Database",
32
- {0, Database_free, Database_size,},
33
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
39
+ {Database_mark, Database_free, Database_size,},
40
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
34
41
  };
35
42
 
36
43
  static VALUE Database_allocate(VALUE klass) {
@@ -48,7 +55,7 @@ inline Database_t *self_to_database(VALUE self) {
48
55
  inline Database_t *self_to_open_database(VALUE self) {
49
56
  Database_t *db = self_to_database(self);
50
57
  if (!(db)->sqlite3_db) rb_raise(cError, "Database is closed");
51
-
58
+
52
59
  return db;
53
60
  }
54
61
 
@@ -118,6 +125,7 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
118
125
  #endif
119
126
 
120
127
  db->trace_block = Qnil;
128
+ db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
121
129
 
122
130
  return Qnil;
123
131
  }
@@ -178,8 +186,8 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
178
186
  RB_GC_GUARD(sql);
179
187
 
180
188
  bind_all_parameters(stmt, argc - 1, argv + 1);
181
- query_ctx ctx = { self, db->sqlite3_db, stmt, Qnil, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS };
182
-
189
+ query_ctx ctx = QUERY_CTX(self, db, stmt, Qnil, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS);
190
+
183
191
  return rb_ensure(SAFE(call), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
184
192
  }
185
193
 
@@ -190,18 +198,18 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
190
198
  * Runs a query returning rows as hashes (with symbol keys). If a block is
191
199
  * given, it will be called for each row. Otherwise, an array containing all
192
200
  * rows is returned.
193
- *
201
+ *
194
202
  * Query parameters to be bound to placeholders in the query can be specified as
195
203
  * a list of values or as a hash mapping parameter names to values. When
196
204
  * parameters are given as an array, the query should specify parameters using
197
205
  * `?`:
198
- *
206
+ *
199
207
  * db.query('select * from foo where x = ?', 42)
200
208
  *
201
209
  * Named placeholders are specified using `:`. The placeholder values are
202
210
  * specified using a hash, where keys are either strings are symbols. String
203
211
  * keys can include or omit the `:` prefix. The following are equivalent:
204
- *
212
+ *
205
213
  * db.query('select * from foo where x = :bar', bar: 42)
206
214
  * db.query('select * from foo where x = :bar', 'bar' => 42)
207
215
  * db.query('select * from foo where x = :bar', ':bar' => 42)
@@ -355,7 +363,7 @@ VALUE Database_execute_multi(VALUE self, VALUE sql, VALUE params_array) {
355
363
 
356
364
  // prepare query ctx
357
365
  prepare_single_stmt(db->sqlite3_db, &stmt, sql);
358
- query_ctx ctx = { self, db->sqlite3_db, stmt, params_array, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS };
366
+ query_ctx ctx = QUERY_CTX(self, db, stmt, params_array, QUERY_MULTI_ROW, ALL_ROWS);
359
367
 
360
368
  return rb_ensure(SAFE(safe_execute_multi), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
361
369
  }
@@ -391,10 +399,10 @@ VALUE Database_changes(VALUE self) {
391
399
  return INT2FIX(sqlite3_changes(db->sqlite3_db));
392
400
  }
393
401
 
394
- /* call-seq:
395
- * db.filename -> string
402
+ /* call-seq: db.filename -> string db.filename(db_name) -> string
396
403
  *
397
- * Returns the database filename.
404
+ * Returns the database filename. If db_name is given, returns the filename for
405
+ * the respective attached database.
398
406
  */
399
407
  VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
400
408
  const char *db_name;
@@ -480,13 +488,13 @@ typedef struct {
480
488
  #define BACKUP_STEP_MAX_PAGES 16
481
489
  #define BACKUP_SLEEP_MS 100
482
490
 
483
- void *backup_step_without_gvl(void *ptr) {
491
+ void *backup_step_impl(void *ptr) {
484
492
  backup_ctx *ctx = (backup_ctx *)ptr;
485
493
  ctx->rc = sqlite3_backup_step(ctx->backup, BACKUP_STEP_MAX_PAGES);
486
494
  return NULL;
487
495
  }
488
496
 
489
- void *backup_sleep_without_gvl(void *unused) {
497
+ void *backup_sleep_impl(void *unused) {
490
498
  sqlite3_sleep(BACKUP_SLEEP_MS);
491
499
  return NULL;
492
500
  }
@@ -496,7 +504,7 @@ VALUE backup_safe_iterate(VALUE ptr) {
496
504
  int done = 0;
497
505
 
498
506
  while (!done) {
499
- rb_thread_call_without_gvl(backup_step_without_gvl, (void *)ctx, RUBY_UBF_IO, 0);
507
+ gvl_call(GVL_RELEASE, backup_step_impl, (void *)ctx);
500
508
  switch(ctx->rc) {
501
509
  case SQLITE_DONE:
502
510
  if (ctx->block_given) {
@@ -514,7 +522,7 @@ VALUE backup_safe_iterate(VALUE ptr) {
514
522
  continue;
515
523
  case SQLITE_BUSY:
516
524
  case SQLITE_LOCKED:
517
- rb_thread_call_without_gvl(backup_sleep_without_gvl, NULL, RUBY_UBF_IO, 0);
525
+ gvl_call(GVL_RELEASE, backup_sleep_impl, NULL);
518
526
  continue;
519
527
  default:
520
528
  rb_raise(cError, "%s", sqlite3_errstr(ctx->rc));
@@ -689,7 +697,7 @@ VALUE Database_total_changes(VALUE self) {
689
697
  VALUE Database_trace(VALUE self) {
690
698
  Database_t *db = self_to_open_database(self);
691
699
 
692
- db->trace_block = rb_block_given_p() ? rb_block_proc() : Qnil;
700
+ RB_OBJ_WRITE(self, &db->trace_block, rb_block_given_p() ? rb_block_proc() : Qnil);
693
701
  return self;
694
702
  }
695
703
 
@@ -734,11 +742,51 @@ VALUE Database_error_offset(VALUE self) {
734
742
  * @return [String] string representation
735
743
  */
736
744
  VALUE Database_inspect(VALUE self) {
745
+ Database_t *db = self_to_database(self);
737
746
  VALUE cname = rb_class_name(CLASS_OF(self));
738
- VALUE filename = Database_filename(0, NULL, self);
739
- if (RSTRING_LEN(filename) == 0) filename = rb_str_new_literal(":memory:");
740
747
 
741
- return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, filename);
748
+ if (!(db)->sqlite3_db)
749
+ return rb_sprintf("#<%"PRIsVALUE":%p (closed)>", cname, (void*)self);
750
+ else {
751
+ VALUE filename = Database_filename(0, NULL, self);
752
+ if (RSTRING_LEN(filename) == 0) filename = rb_str_new_literal(":memory:");
753
+ return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, filename);
754
+ }
755
+ }
756
+
757
+ /* Returns the database's GVL release threshold.
758
+ *
759
+ * @return [Integer] GVL release threshold
760
+ */
761
+ VALUE Database_gvl_release_threshold_get(VALUE self) {
762
+ Database_t *db = self_to_open_database(self);
763
+ return INT2NUM(db->gvl_release_threshold);
764
+ }
765
+
766
+ /* Sets the database's GVL release threshold. To always hold the GVL while
767
+ * running a query, set the threshold to 0. To release the GVL on each record,
768
+ * set the threshold to 1. Larger values mean the GVL will be released less
769
+ * often, e.g. a value of 10 means the GVL will be released every 10 records
770
+ * iterated. A value of nil sets the threshold to the default value, which is
771
+ * currently 1000.
772
+ *
773
+ * @return [Integer, nil] New GVL release threshold
774
+ */
775
+ VALUE Database_gvl_release_threshold_set(VALUE self, VALUE value) {
776
+ Database_t *db = self_to_open_database(self);
777
+
778
+ switch (TYPE(value)) {
779
+ case T_FIXNUM:
780
+ db->gvl_release_threshold = NUM2INT(value);
781
+ break;
782
+ case T_NIL:
783
+ db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
784
+ break;
785
+ default:
786
+ rb_raise(eArgumentError, "Invalid GVL release threshold value (expect integer or nil)");
787
+ }
788
+
789
+ return INT2NUM(db->gvl_release_threshold);
742
790
  }
743
791
 
744
792
  void Init_ExtraliteDatabase(void) {
@@ -765,6 +813,8 @@ void Init_ExtraliteDatabase(void) {
765
813
  rb_define_method(cDatabase, "execute", Database_execute, -1);
766
814
  rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
767
815
  rb_define_method(cDatabase, "filename", Database_filename, -1);
816
+ rb_define_method(cDatabase, "gvl_release_threshold", Database_gvl_release_threshold_get, 0);
817
+ rb_define_method(cDatabase, "gvl_release_threshold=", Database_gvl_release_threshold_set, 1);
768
818
  rb_define_method(cDatabase, "initialize", Database_initialize, -1);
769
819
  rb_define_method(cDatabase, "inspect", Database_inspect, 0);
770
820
  rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
@@ -787,14 +837,13 @@ void Init_ExtraliteDatabase(void) {
787
837
  rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
788
838
  #endif
789
839
 
840
+ cBlob = rb_define_class_under(mExtralite, "Blob", rb_cString);
841
+
790
842
  cError = rb_define_class_under(mExtralite, "Error", rb_eStandardError);
791
843
  cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
792
844
  cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
793
845
  cInterruptError = rb_define_class_under(mExtralite, "InterruptError", cError);
794
- rb_gc_register_mark_object(cError);
795
- rb_gc_register_mark_object(cSQLError);
796
- rb_gc_register_mark_object(cBusyError);
797
- rb_gc_register_mark_object(cInterruptError);
846
+ cParameterError = rb_define_class_under(mExtralite, "ParameterError", cError);
798
847
 
799
848
  eArgumentError = rb_const_get(rb_cObject, rb_intern("ArgumentError"));
800
849
 
@@ -803,7 +852,6 @@ void Init_ExtraliteDatabase(void) {
803
852
  ID_keys = rb_intern("keys");
804
853
  ID_new = rb_intern("new");
805
854
  ID_strip = rb_intern("strip");
806
- ID_to_s = rb_intern("to_s");
807
855
 
808
856
  SYM_read_only = ID2SYM(rb_intern("read_only"));
809
857
  rb_gc_register_mark_object(SYM_read_only);
@@ -23,17 +23,18 @@
23
23
  extern VALUE cDatabase;
24
24
  extern VALUE cQuery;
25
25
  extern VALUE cIterator;
26
+ extern VALUE cBlob;
26
27
 
27
28
  extern VALUE cError;
28
29
  extern VALUE cSQLError;
29
30
  extern VALUE cBusyError;
30
31
  extern VALUE cInterruptError;
32
+ extern VALUE cParameterError;
31
33
 
32
34
  extern ID ID_call;
33
35
  extern ID ID_keys;
34
36
  extern ID ID_new;
35
37
  extern ID ID_strip;
36
- extern ID ID_to_s;
37
38
 
38
39
  extern VALUE SYM_hash;
39
40
  extern VALUE SYM_ary;
@@ -42,6 +43,7 @@ extern VALUE SYM_single_column;
42
43
  typedef struct {
43
44
  sqlite3 *sqlite3_db;
44
45
  VALUE trace_block;
46
+ int gvl_release_threshold;
45
47
  } Database_t;
46
48
 
47
49
  typedef struct {
@@ -79,19 +81,22 @@ typedef struct {
79
81
  enum query_mode mode;
80
82
  int max_rows;
81
83
  int eof;
84
+ int gvl_release_threshold;
85
+ int step_count;
82
86
  } query_ctx;
83
87
 
84
- typedef struct {
85
- VALUE dst;
86
- VALUE src;
87
- sqlite3_backup *p;
88
- } backup_t;
88
+ enum gvl_mode {
89
+ GVL_RELEASE,
90
+ GVL_HOLD
91
+ };
89
92
 
90
- #define TUPLE_MAX_EMBEDDED_VALUES 20
91
93
  #define ALL_ROWS -1
92
94
  #define SINGLE_ROW -2
93
95
  #define QUERY_MODE(default) (rb_block_given_p() ? QUERY_YIELD : default)
94
96
  #define MULTI_ROW_P(mode) (mode == QUERY_MULTI_ROW)
97
+ #define QUERY_CTX(self, db, stmt, params, mode, max_rows) \
98
+ { self, db->sqlite3_db, stmt, params, mode, max_rows, 0, db->gvl_release_threshold, 0 }
99
+ #define DEFAULT_GVL_RELEASE_THRESHOLD 1000
95
100
 
96
101
  extern rb_encoding *UTF8_ENCODING;
97
102
 
@@ -126,4 +131,6 @@ VALUE cleanup_stmt(query_ctx *ctx);
126
131
  sqlite3 *Database_sqlite3_db(VALUE self);
127
132
  Database_t *self_to_database(VALUE self);
128
133
 
129
- #endif /* EXTRALITE_H */
134
+ void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data);
135
+
136
+ #endif /* EXTRALITE_H */
@@ -52,7 +52,7 @@ static inline enum iterator_mode symbol_to_mode(VALUE sym) {
52
52
  * iteration mode is one of: `:hash`, `:ary`, or `:single_column`. An iterator
53
53
  * is normally returned from one of the methods `Query#each`/`Query#each_hash`,
54
54
  * `Query#each_ary` or `Query#each_single_column` when called without a block:
55
- *
55
+ *
56
56
  * iterator = query.each
57
57
  * ...
58
58
  *
@@ -115,12 +115,12 @@ inline next_method mode_to_next_method(enum iterator_mode mode) {
115
115
 
116
116
  /* Returns the next 1 or more rows from the associated query's result set
117
117
  * according to the iteration mode, i.e. as a hash, an array or a single value.
118
- *
118
+ *
119
119
  * If no row count is given, a single row is returned. If a row count is given,
120
120
  * an array containing up to the `row_count` rows is returned. If `row_count` is
121
121
  * -1, all rows are returned. If the end of the result set has been reached,
122
122
  * `nil` is returned.
123
- *
123
+ *
124
124
  * If a block is given, rows are passed to the block and self is returned.
125
125
  *
126
126
  * @overload next()
@@ -152,7 +152,7 @@ inline to_a_method mode_to_to_a_method(enum iterator_mode mode) {
152
152
 
153
153
  /* Returns all rows from the associated query's result set according to the
154
154
  * iteration mode, i.e. as a hash, an array or a single value.
155
- *
155
+ *
156
156
  * @return [Array] array of query result set rows
157
157
  */
158
158
  VALUE Iterator_to_a(VALUE self) {
@@ -180,7 +180,7 @@ inline VALUE mode_to_symbol(Iterator_t *iterator) {
180
180
  VALUE Iterator_inspect(VALUE self) {
181
181
  VALUE cname = rb_class_name(CLASS_OF(self));
182
182
  VALUE sym = mode_to_symbol(self_to_iterator(self));
183
-
183
+
184
184
  return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sym);
185
185
  }
186
186
 
@@ -33,7 +33,7 @@ static void Query_free(void *ptr) {
33
33
  static const rb_data_type_t Query_type = {
34
34
  "Query",
35
35
  {Query_mark, Query_free, Query_size,},
36
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
36
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
37
37
  };
38
38
 
39
39
  static VALUE Query_allocate(VALUE klass) {
@@ -67,10 +67,12 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
67
67
  if (!RSTRING_LEN(sql))
68
68
  rb_raise(cError, "Cannot prepare an empty SQL query");
69
69
 
70
+ RB_OBJ_WRITE(self, &query->db, db);
71
+ RB_OBJ_WRITE(self, &query->sql, sql);
72
+
70
73
  query->db = db;
71
74
  query->db_struct = self_to_database(db);
72
75
  query->sqlite3_db = Database_sqlite3_db(db);
73
- query->sql = sql;
74
76
  query->stmt = NULL;
75
77
  query->closed = 0;
76
78
  query->eof = 0;
@@ -134,14 +136,14 @@ VALUE Query_reset(VALUE self) {
134
136
  * Bound parameters can be specified as a list of values or as a hash mapping
135
137
  * parameter names to values. When parameters are given as a splatted array, the
136
138
  * query should specify parameters using `?`:
137
- *
139
+ *
138
140
  * query = db.prepare('select * from foo where x = ?')
139
141
  * query.bind(42)
140
142
  *
141
143
  * Named placeholders are specified using `:`. The placeholder values are
142
144
  * specified using a hash, where keys are either strings are symbols. String
143
145
  * keys can include or omit the `:` prefix. The following are equivalent:
144
- *
146
+ *
145
147
  * query = db.prepare('select * from foo where x = :bar')
146
148
  * query.bind(bar: 42)
147
149
  *
@@ -171,19 +173,18 @@ VALUE Query_eof_p(VALUE self) {
171
173
  static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(query_ctx *)) {
172
174
  Query_t *query = self_to_query(self);
173
175
  if (query->closed) rb_raise(cError, "Query is closed");
174
-
176
+
175
177
  if (!query->stmt) query_reset(query);
176
178
  if (query->eof) return rb_block_given_p() ? self : Qnil;
177
179
 
178
- query_ctx ctx = {
180
+ query_ctx ctx = QUERY_CTX(
179
181
  self,
180
- query->sqlite3_db,
182
+ query->db_struct,
181
183
  query->stmt,
182
184
  Qnil,
183
185
  QUERY_MODE(max_rows == SINGLE_ROW ? QUERY_SINGLE_ROW : QUERY_MULTI_ROW),
184
- MAX_ROWS(max_rows),
185
- 0
186
- };
186
+ MAX_ROWS(max_rows)
187
+ );
187
188
  VALUE result = call(&ctx);
188
189
  query->eof = ctx.eof;
189
190
  return (ctx.mode == QUERY_YIELD) ? self : result;
@@ -193,12 +194,12 @@ static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(q
193
194
 
194
195
  /* Returns the next 1 or more rows from the associated query's result set as a
195
196
  * hash.
196
- *
197
+ *
197
198
  * If no row count is given, a single row is returned. If a row count is given,
198
199
  * an array containing up to the `row_count` rows is returned. If `row_count` is
199
200
  * -1, all rows are returned. If the end of the result set has been reached,
200
201
  * `nil` is returned.
201
- *
202
+ *
202
203
  * If a block is given, rows are passed to the block and self is returned.
203
204
  *
204
205
  * @overload next()
@@ -219,12 +220,12 @@ VALUE Query_next_hash(int argc, VALUE *argv, VALUE self) {
219
220
 
220
221
  /* Returns the next 1 or more rows from the associated query's result set as an
221
222
  * array.
222
- *
223
+ *
223
224
  * If no row count is given, a single row is returned. If a row count is given,
224
225
  * an array containing up to the `row_count` rows is returned. If `row_count` is
225
226
  * -1, all rows are returned. If the end of the result set has been reached,
226
227
  * `nil` is returned.
227
- *
228
+ *
228
229
  * If a block is given, rows are passed to the block and self is returned.
229
230
  *
230
231
  * @overload next_ary()
@@ -241,12 +242,12 @@ VALUE Query_next_ary(int argc, VALUE *argv, VALUE self) {
241
242
  /* Returns the next 1 or more rows from the associated query's result set as an
242
243
  * single values. If the result set contains more than one column an error is
243
244
  * raised.
244
- *
245
+ *
245
246
  * If no row count is given, a single row is returned. If a row count is given,
246
247
  * an array containing up to the `row_count` rows is returned. If `row_count` is
247
248
  * -1, all rows are returned. If the end of the result set has been reached,
248
249
  * `nil` is returned.
249
- *
250
+ *
250
251
  * If a block is given, rows are passed to the block and self is returned.
251
252
  *
252
253
  * @overload next_ary()
@@ -261,7 +262,7 @@ VALUE Query_next_single_column(int argc, VALUE *argv, VALUE self) {
261
262
  }
262
263
 
263
264
  /* Returns all rows in the associated query's result set as hashes.
264
- *
265
+ *
265
266
  * @overload to_a()
266
267
  * @return [Array<Hash>] all rows
267
268
  * @overload to_a_hash
@@ -274,7 +275,7 @@ VALUE Query_to_a_hash(VALUE self) {
274
275
  }
275
276
 
276
277
  /* Returns all rows in the associated query's result set as arrays.
277
- *
278
+ *
278
279
  * @return [Array<Array>] all rows
279
280
  */
280
281
  VALUE Query_to_a_ary(VALUE self) {
@@ -285,7 +286,7 @@ VALUE Query_to_a_ary(VALUE self) {
285
286
 
286
287
  /* Returns all rows in the associated query's result set as single values. If
287
288
  * the result set contains more than one column an error is raised.
288
- *
289
+ *
289
290
  * @return [Array<Object>] all rows
290
291
  */
291
292
  VALUE Query_to_a_single_column(VALUE self) {
@@ -297,7 +298,7 @@ VALUE Query_to_a_single_column(VALUE self) {
297
298
  /* Iterates through the result set, passing each row to the given block as a
298
299
  * hash. If no block is given, returns a `Extralite::Iterator` instance in hash
299
300
  * mode.
300
- *
301
+ *
301
302
  * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
302
303
  */
303
304
  VALUE Query_each_hash(VALUE self) {
@@ -311,7 +312,7 @@ VALUE Query_each_hash(VALUE self) {
311
312
  /* Iterates through the result set, passing each row to the given block as an
312
313
  * array. If no block is given, returns a `Extralite::Iterator` instance in
313
314
  * array mode.
314
- *
315
+ *
315
316
  * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
316
317
  */
317
318
  VALUE Query_each_ary(VALUE self) {
@@ -326,7 +327,7 @@ VALUE Query_each_ary(VALUE self) {
326
327
  * single value. If the result set contains more than one column an error is
327
328
  * raised. If no block is given, returns a `Extralite::Iterator` instance in
328
329
  * single column mode.
329
- *
330
+ *
330
331
  * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
331
332
  */
332
333
  VALUE Query_each_single_column(VALUE self) {
@@ -388,7 +389,14 @@ VALUE Query_execute_multi(VALUE self, VALUE parameters) {
388
389
  if (!query->stmt)
389
390
  prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
390
391
 
391
- query_ctx ctx = { self, query->sqlite3_db, query->stmt, parameters, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS };
392
+ query_ctx ctx = QUERY_CTX(
393
+ self,
394
+ query->db_struct,
395
+ query->stmt,
396
+ parameters,
397
+ QUERY_MODE(QUERY_MULTI_ROW),
398
+ ALL_ROWS
399
+ );
392
400
  return safe_execute_multi(&ctx);
393
401
  }
394
402
 
@@ -405,7 +413,7 @@ VALUE Query_database(VALUE self) {
405
413
  }
406
414
 
407
415
  /* Returns the SQL string for the query.
408
- *
416
+ *
409
417
  * @return [String] SQL string
410
418
  */
411
419
  VALUE Query_sql(VALUE self) {
@@ -414,7 +422,7 @@ VALUE Query_sql(VALUE self) {
414
422
  }
415
423
 
416
424
  /* Returns the column names for the query without running it.
417
- *
425
+ *
418
426
  * @return [Array<Symbol>] column names
419
427
  */
420
428
  VALUE Query_columns(VALUE self) {
@@ -424,7 +432,7 @@ VALUE Query_columns(VALUE self) {
424
432
  }
425
433
 
426
434
  /* Closes the query. Attempting to run a closed query will raise an error.
427
- *
435
+ *
428
436
  * @return [Extralite::Query] self
429
437
  */
430
438
  VALUE Query_close(VALUE self) {
@@ -438,7 +446,7 @@ VALUE Query_close(VALUE self) {
438
446
  }
439
447
 
440
448
  /* Returns true if the query is closed.
441
- *
449
+ *
442
450
  * @return [boolean] true if query is closed
443
451
  */
444
452
  VALUE Query_closed_p(VALUE self) {
@@ -449,7 +457,7 @@ VALUE Query_closed_p(VALUE self) {
449
457
  /* Returns the current [status
450
458
  * value](https://sqlite.org/c3ref/c_stmtstatus_counter.html) for the given op.
451
459
  * To reset the value, pass true as reset.
452
- *
460
+ *
453
461
  * @overload status(op)
454
462
  * @param op [Integer] status op
455
463
  * @return [Integer] current status value for the given op
@@ -486,7 +494,7 @@ VALUE Query_inspect(VALUE self) {
486
494
  rb_str_cat2(sql, "...");
487
495
  }
488
496
  sql = rb_funcall(sql, ID_inspect, 0);
489
-
497
+
490
498
  RB_GC_GUARD(sql);
491
499
  return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sql);
492
500
  }
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.3'
3
+ VERSION = '2.4'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -25,6 +25,10 @@ module Extralite
25
25
  class InterruptError < Error
26
26
  end
27
27
 
28
+ # An exception raised when an Extralite doesn't know how to bind a parameter to a query
29
+ class ParameterError < Error
30
+ end
31
+
28
32
  # An SQLite database
29
33
  class Database
30
34
  # @!visibility private
@@ -52,6 +56,29 @@ module Extralite
52
56
  value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
53
57
  end
54
58
 
59
+ # Starts a transaction and runs the given block. If an exception is raised
60
+ # in the block, the transaction is rolled back. Otherwise, the transaction
61
+ # is commited after running the block.
62
+ #
63
+ # db.transaction do
64
+ # db.execute('insert into foo values (1, 2, 3)')
65
+ # raise if db.query_single_value('select x from bar') > 42
66
+ # end
67
+ #
68
+ # @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive). Defaults to immediate.
69
+ # @return [Any] the given block's return value
70
+ def transaction(mode = :immediate)
71
+ execute "begin #{mode} transaction"
72
+
73
+ abort = false
74
+ yield self
75
+ rescue
76
+ abort = true
77
+ raise
78
+ ensure
79
+ execute(abort ? 'rollback' : 'commit')
80
+ end
81
+
55
82
  private
56
83
 
57
84
  def pragma_set(values)
Binary file
data/test/helper.rb CHANGED
@@ -5,3 +5,5 @@ require 'extralite'
5
5
  require 'minitest/autorun'
6
6
 
7
7
  puts "sqlite3 version: #{Extralite.sqlite3_version}"
8
+
9
+ IS_LINUX = RUBY_PLATFORM =~ /linux/