extralite 2.3 → 2.5

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.
@@ -20,8 +20,14 @@ static size_t Query_size(const void *ptr) {
20
20
 
21
21
  static void Query_mark(void *ptr) {
22
22
  Query_t *query = ptr;
23
- rb_gc_mark(query->db);
24
- rb_gc_mark(query->sql);
23
+ rb_gc_mark_movable(query->db);
24
+ rb_gc_mark_movable(query->sql);
25
+ }
26
+
27
+ static void Query_compact(void *ptr) {
28
+ Query_t *query = ptr;
29
+ query->db = rb_gc_location(query->db);
30
+ query->sql = rb_gc_location(query->sql);
25
31
  }
26
32
 
27
33
  static void Query_free(void *ptr) {
@@ -32,8 +38,8 @@ static void Query_free(void *ptr) {
32
38
 
33
39
  static const rb_data_type_t Query_type = {
34
40
  "Query",
35
- {Query_mark, Query_free, Query_size,},
36
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
41
+ {Query_mark, Query_free, Query_size, Query_compact},
42
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
37
43
  };
38
44
 
39
45
  static VALUE Query_allocate(VALUE klass) {
@@ -67,10 +73,12 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
67
73
  if (!RSTRING_LEN(sql))
68
74
  rb_raise(cError, "Cannot prepare an empty SQL query");
69
75
 
76
+ RB_OBJ_WRITE(self, &query->db, db);
77
+ RB_OBJ_WRITE(self, &query->sql, sql);
78
+
70
79
  query->db = db;
71
80
  query->db_struct = self_to_database(db);
72
81
  query->sqlite3_db = Database_sqlite3_db(db);
73
- query->sql = sql;
74
82
  query->stmt = NULL;
75
83
  query->closed = 0;
76
84
  query->eof = 0;
@@ -134,14 +142,14 @@ VALUE Query_reset(VALUE self) {
134
142
  * Bound parameters can be specified as a list of values or as a hash mapping
135
143
  * parameter names to values. When parameters are given as a splatted array, the
136
144
  * query should specify parameters using `?`:
137
- *
145
+ *
138
146
  * query = db.prepare('select * from foo where x = ?')
139
147
  * query.bind(42)
140
148
  *
141
149
  * Named placeholders are specified using `:`. The placeholder values are
142
150
  * specified using a hash, where keys are either strings are symbols. String
143
151
  * keys can include or omit the `:` prefix. The following are equivalent:
144
- *
152
+ *
145
153
  * query = db.prepare('select * from foo where x = :bar')
146
154
  * query.bind(bar: 42)
147
155
  *
@@ -171,19 +179,18 @@ VALUE Query_eof_p(VALUE self) {
171
179
  static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(query_ctx *)) {
172
180
  Query_t *query = self_to_query(self);
173
181
  if (query->closed) rb_raise(cError, "Query is closed");
174
-
182
+
175
183
  if (!query->stmt) query_reset(query);
176
184
  if (query->eof) return rb_block_given_p() ? self : Qnil;
177
185
 
178
- query_ctx ctx = {
186
+ query_ctx ctx = QUERY_CTX(
179
187
  self,
180
- query->sqlite3_db,
188
+ query->db_struct,
181
189
  query->stmt,
182
190
  Qnil,
183
191
  QUERY_MODE(max_rows == SINGLE_ROW ? QUERY_SINGLE_ROW : QUERY_MULTI_ROW),
184
- MAX_ROWS(max_rows),
185
- 0
186
- };
192
+ MAX_ROWS(max_rows)
193
+ );
187
194
  VALUE result = call(&ctx);
188
195
  query->eof = ctx.eof;
189
196
  return (ctx.mode == QUERY_YIELD) ? self : result;
@@ -193,12 +200,12 @@ static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(q
193
200
 
194
201
  /* Returns the next 1 or more rows from the associated query's result set as a
195
202
  * hash.
196
- *
203
+ *
197
204
  * If no row count is given, a single row is returned. If a row count is given,
198
205
  * an array containing up to the `row_count` rows is returned. If `row_count` is
199
206
  * -1, all rows are returned. If the end of the result set has been reached,
200
207
  * `nil` is returned.
201
- *
208
+ *
202
209
  * If a block is given, rows are passed to the block and self is returned.
203
210
  *
204
211
  * @overload next()
@@ -219,12 +226,12 @@ VALUE Query_next_hash(int argc, VALUE *argv, VALUE self) {
219
226
 
220
227
  /* Returns the next 1 or more rows from the associated query's result set as an
221
228
  * array.
222
- *
229
+ *
223
230
  * If no row count is given, a single row is returned. If a row count is given,
224
231
  * an array containing up to the `row_count` rows is returned. If `row_count` is
225
232
  * -1, all rows are returned. If the end of the result set has been reached,
226
233
  * `nil` is returned.
227
- *
234
+ *
228
235
  * If a block is given, rows are passed to the block and self is returned.
229
236
  *
230
237
  * @overload next_ary()
@@ -241,12 +248,12 @@ VALUE Query_next_ary(int argc, VALUE *argv, VALUE self) {
241
248
  /* Returns the next 1 or more rows from the associated query's result set as an
242
249
  * single values. If the result set contains more than one column an error is
243
250
  * raised.
244
- *
251
+ *
245
252
  * If no row count is given, a single row is returned. If a row count is given,
246
253
  * an array containing up to the `row_count` rows is returned. If `row_count` is
247
254
  * -1, all rows are returned. If the end of the result set has been reached,
248
255
  * `nil` is returned.
249
- *
256
+ *
250
257
  * If a block is given, rows are passed to the block and self is returned.
251
258
  *
252
259
  * @overload next_ary()
@@ -261,7 +268,7 @@ VALUE Query_next_single_column(int argc, VALUE *argv, VALUE self) {
261
268
  }
262
269
 
263
270
  /* Returns all rows in the associated query's result set as hashes.
264
- *
271
+ *
265
272
  * @overload to_a()
266
273
  * @return [Array<Hash>] all rows
267
274
  * @overload to_a_hash
@@ -274,7 +281,7 @@ VALUE Query_to_a_hash(VALUE self) {
274
281
  }
275
282
 
276
283
  /* Returns all rows in the associated query's result set as arrays.
277
- *
284
+ *
278
285
  * @return [Array<Array>] all rows
279
286
  */
280
287
  VALUE Query_to_a_ary(VALUE self) {
@@ -285,7 +292,7 @@ VALUE Query_to_a_ary(VALUE self) {
285
292
 
286
293
  /* Returns all rows in the associated query's result set as single values. If
287
294
  * the result set contains more than one column an error is raised.
288
- *
295
+ *
289
296
  * @return [Array<Object>] all rows
290
297
  */
291
298
  VALUE Query_to_a_single_column(VALUE self) {
@@ -297,7 +304,7 @@ VALUE Query_to_a_single_column(VALUE self) {
297
304
  /* Iterates through the result set, passing each row to the given block as a
298
305
  * hash. If no block is given, returns a `Extralite::Iterator` instance in hash
299
306
  * mode.
300
- *
307
+ *
301
308
  * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
302
309
  */
303
310
  VALUE Query_each_hash(VALUE self) {
@@ -311,7 +318,7 @@ VALUE Query_each_hash(VALUE self) {
311
318
  /* Iterates through the result set, passing each row to the given block as an
312
319
  * array. If no block is given, returns a `Extralite::Iterator` instance in
313
320
  * array mode.
314
- *
321
+ *
315
322
  * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
316
323
  */
317
324
  VALUE Query_each_ary(VALUE self) {
@@ -326,7 +333,7 @@ VALUE Query_each_ary(VALUE self) {
326
333
  * single value. If the result set contains more than one column an error is
327
334
  * raised. If no block is given, returns a `Extralite::Iterator` instance in
328
335
  * single column mode.
329
- *
336
+ *
330
337
  * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
331
338
  */
332
339
  VALUE Query_each_single_column(VALUE self) {
@@ -366,30 +373,213 @@ VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
366
373
  return Query_perform_next(self, ALL_ROWS, safe_query_changes);
367
374
  }
368
375
 
369
- /* Executes the query for each set of parameters in the given array. Parameters
370
- * can be specified as either an array (for unnamed parameters) or a hash (for
371
- * named parameters). Returns the number of changes effected. This method is
372
- * designed for inserting multiple records.
376
+ /* call-seq:
377
+ * query << [...] -> query
378
+ * query << { ... } -> query
379
+ *
380
+ * Runs the with the given parameters, returning the total changes effected.
381
+ * This method should be used for data- or schema-manipulation queries.
382
+ *
383
+ * Query parameters to be bound to placeholders in the query can be specified as
384
+ * a list of values or as a hash mapping parameter names to values. When
385
+ * parameters are given as an array, the query should specify parameters using
386
+ * `?`:
387
+ *
388
+ * query = db.prepare('update foo set x = ? where y = ?')
389
+ * query << [42, 43]
390
+ *
391
+ * Named placeholders are specified using `:`. The placeholder values are
392
+ * specified using a hash, where keys are either strings are symbols. String
393
+ * keys can include or omit the `:` prefix. The following are equivalent:
394
+ *
395
+ * query = db.prepare('update foo set x = :bar')
396
+ * query << { bar: 42 }
397
+ * query << { 'bar' => 42 }
398
+ * query << { ':bar' => 42 }
399
+ */
400
+ VALUE Query_execute_chevrons(VALUE self, VALUE params) {
401
+ Query_execute(1, &params, self);
402
+ return self;
403
+ }
404
+
405
+ /* call-seq:
406
+ * query.batch_execute(params_array) -> changes
407
+ * query.batch_execute(enumerable) -> changes
408
+ * query.batch_execute(callable) -> changes
409
+ *
410
+ * Executes the query for each set of parameters in the paramter source. If an
411
+ * enumerable is given, it is iterated and each of its values is used as the
412
+ * parameters for running the query. If a callable is given, it is called
413
+ * repeatedly and each of its return values is used as the parameters, until nil
414
+ * is returned.
415
+ *
416
+ * Returns the number of changes effected. This method is designed for inserting
417
+ * multiple records.
373
418
  *
374
419
  * query = db.prepare('insert into foo values (?, ?, ?)')
375
420
  * records = [
376
421
  * [1, 2, 3],
377
422
  * [4, 5, 6]
378
423
  * ]
379
- * query.execute_multi(records)
424
+ * query.batch_execute(records)
425
+ *
426
+ * source = [
427
+ * [1, 2, 3],
428
+ * [4, 5, 6]
429
+ * ]
430
+ * query.batch_execute { records.shift }
380
431
  *
381
- * @param parameters [Array<Array, Hash>] array of parameters to run query with
432
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] array of parameters to run query with
382
433
  * @return [Integer] number of changes effected
383
434
  */
384
- VALUE Query_execute_multi(VALUE self, VALUE parameters) {
435
+ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
385
436
  Query_t *query = self_to_query(self);
386
437
  if (query->closed) rb_raise(cError, "Query is closed");
387
438
 
388
439
  if (!query->stmt)
389
440
  prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
390
441
 
391
- query_ctx ctx = { self, query->sqlite3_db, query->stmt, parameters, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS };
392
- return safe_execute_multi(&ctx);
442
+ query_ctx ctx = QUERY_CTX(
443
+ self,
444
+ query->db_struct,
445
+ query->stmt,
446
+ parameters,
447
+ QUERY_MODE(QUERY_MULTI_ROW),
448
+ ALL_ROWS
449
+ );
450
+ return safe_batch_execute(&ctx);
451
+ }
452
+
453
+ /* call-seq:
454
+ * query.batch_query(sql, params_array) -> rows
455
+ * query.batch_query(sql, enumerable) -> rows
456
+ * query.batch_query(sql, callable) -> rows
457
+ * query.batch_query(sql, params_array) { |rows| ... } -> changes
458
+ * query.batch_query(sql, enumerable) { |rows| ... } -> changes
459
+ * query.batch_query(sql, callable) { |rows| ... } -> changes
460
+ *
461
+ * Executes the prepared query for each list of parameters in the given paramter
462
+ * source. If a block is given, it is called with the resulting rows for each
463
+ * invocation of the query, and the total number of changes is returned.
464
+ * Otherwise, an array containing the resulting rows for each invocation is
465
+ * returned.
466
+ *
467
+ * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
468
+ * records = [
469
+ * [1, 2],
470
+ * [3, 4]
471
+ * ]
472
+ * q.batch_query(records)
473
+ * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
474
+ * *
475
+ * @param sql [String] query SQL
476
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
477
+ * @return [Array<Hash>, Integer] Total number of changes effected
478
+ */
479
+ VALUE Query_batch_query(VALUE self, VALUE parameters) {
480
+ Query_t *query = self_to_query(self);
481
+ if (query->closed) rb_raise(cError, "Query is closed");
482
+
483
+ if (!query->stmt)
484
+ prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
485
+
486
+ query_ctx ctx = QUERY_CTX(
487
+ self,
488
+ query->db_struct,
489
+ query->stmt,
490
+ parameters,
491
+ QUERY_MODE(QUERY_MULTI_ROW),
492
+ ALL_ROWS
493
+ );
494
+ return safe_batch_query(&ctx);
495
+ }
496
+
497
+ /* call-seq:
498
+ * query.batch_query_ary(sql, params_array) -> rows
499
+ * query.batch_query_ary(sql, enumerable) -> rows
500
+ * query.batch_query_ary(sql, callable) -> rows
501
+ * query.batch_query_ary(sql, params_array) { |rows| ... } -> changes
502
+ * query.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
503
+ * query.batch_query_ary(sql, callable) { |rows| ... } -> changes
504
+ *
505
+ * Executes the prepared query for each list of parameters in the given paramter
506
+ * source. If a block is given, it is called with the resulting rows for each
507
+ * invocation of the query, and the total number of changes is returned.
508
+ * Otherwise, an array containing the resulting rows for each invocation is
509
+ * returned. Rows are represented as arrays.
510
+ *
511
+ * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
512
+ * records = [
513
+ * [1, 2],
514
+ * [3, 4]
515
+ * ]
516
+ * q.batch_query_ary(records)
517
+ * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
518
+ * *
519
+ * @param sql [String] query SQL
520
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
521
+ * @return [Array<Hash>, Integer] Total number of changes effected
522
+ */
523
+ VALUE Query_batch_query_ary(VALUE self, VALUE parameters) {
524
+ Query_t *query = self_to_query(self);
525
+ if (query->closed) rb_raise(cError, "Query is closed");
526
+
527
+ if (!query->stmt)
528
+ prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
529
+
530
+ query_ctx ctx = QUERY_CTX(
531
+ self,
532
+ query->db_struct,
533
+ query->stmt,
534
+ parameters,
535
+ QUERY_MODE(QUERY_MULTI_ROW),
536
+ ALL_ROWS
537
+ );
538
+ return safe_batch_query_ary(&ctx);
539
+ }
540
+
541
+ /* call-seq:
542
+ * query.batch_query_single_column(sql, params_array) -> rows
543
+ * query.batch_query_single_column(sql, enumerable) -> rows
544
+ * query.batch_query_single_column(sql, callable) -> rows
545
+ * query.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
546
+ * query.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
547
+ * query.batch_query_single_column(sql, callable) { |rows| ... } -> changes
548
+ *
549
+ * Executes the prepared query for each list of parameters in the given paramter
550
+ * source. If a block is given, it is called with the resulting rows for each
551
+ * invocation of the query, and the total number of changes is returned.
552
+ * Otherwise, an array containing the resulting rows for each invocation is
553
+ * returned. Rows are represented as single values.
554
+ *
555
+ * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
556
+ * records = [
557
+ * [1, 2],
558
+ * [3, 4]
559
+ * ]
560
+ * q.batch_query_single_column(records)
561
+ * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
562
+ * *
563
+ * @param sql [String] query SQL
564
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
565
+ * @return [Array<Hash>, Integer] Total number of changes effected
566
+ */
567
+ VALUE Query_batch_query_single_column(VALUE self, VALUE parameters) {
568
+ Query_t *query = self_to_query(self);
569
+ if (query->closed) rb_raise(cError, "Query is closed");
570
+
571
+ if (!query->stmt)
572
+ prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
573
+
574
+ query_ctx ctx = QUERY_CTX(
575
+ self,
576
+ query->db_struct,
577
+ query->stmt,
578
+ parameters,
579
+ QUERY_MODE(QUERY_MULTI_ROW),
580
+ ALL_ROWS
581
+ );
582
+ return safe_batch_query_single_column(&ctx);
393
583
  }
394
584
 
395
585
  /* Returns the database associated with the query.
@@ -405,7 +595,7 @@ VALUE Query_database(VALUE self) {
405
595
  }
406
596
 
407
597
  /* Returns the SQL string for the query.
408
- *
598
+ *
409
599
  * @return [String] SQL string
410
600
  */
411
601
  VALUE Query_sql(VALUE self) {
@@ -414,7 +604,7 @@ VALUE Query_sql(VALUE self) {
414
604
  }
415
605
 
416
606
  /* Returns the column names for the query without running it.
417
- *
607
+ *
418
608
  * @return [Array<Symbol>] column names
419
609
  */
420
610
  VALUE Query_columns(VALUE self) {
@@ -423,8 +613,21 @@ VALUE Query_columns(VALUE self) {
423
613
  return Query_perform_next(self, ALL_ROWS, safe_query_columns);
424
614
  }
425
615
 
616
+ /* call-seq:
617
+ * query.clone -> copy
618
+ * query.dup -> copy
619
+ *
620
+ * Returns a new query instance for the same SQL as the original query.
621
+ *
622
+ * @return [Extralite::Query] copy of query
623
+ */
624
+ VALUE Query_clone(VALUE self) {
625
+ Query_t *query = self_to_query(self);
626
+ return rb_funcall(cQuery, ID_new, 2, query->db, query->sql);
627
+ }
628
+
426
629
  /* Closes the query. Attempting to run a closed query will raise an error.
427
- *
630
+ *
428
631
  * @return [Extralite::Query] self
429
632
  */
430
633
  VALUE Query_close(VALUE self) {
@@ -438,7 +641,7 @@ VALUE Query_close(VALUE self) {
438
641
  }
439
642
 
440
643
  /* Returns true if the query is closed.
441
- *
644
+ *
442
645
  * @return [boolean] true if query is closed
443
646
  */
444
647
  VALUE Query_closed_p(VALUE self) {
@@ -449,7 +652,7 @@ VALUE Query_closed_p(VALUE self) {
449
652
  /* Returns the current [status
450
653
  * value](https://sqlite.org/c3ref/c_stmtstatus_counter.html) for the given op.
451
654
  * To reset the value, pass true as reset.
452
- *
655
+ *
453
656
  * @overload status(op)
454
657
  * @param op [Integer] status op
455
658
  * @return [Integer] current status value for the given op
@@ -486,7 +689,7 @@ VALUE Query_inspect(VALUE self) {
486
689
  rb_str_cat2(sql, "...");
487
690
  }
488
691
  sql = rb_funcall(sql, ID_inspect, 0);
489
-
692
+
490
693
  RB_GC_GUARD(sql);
491
694
  return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sql);
492
695
  }
@@ -501,8 +704,10 @@ void Init_ExtraliteQuery(void) {
501
704
  rb_define_method(cQuery, "close", Query_close, 0);
502
705
  rb_define_method(cQuery, "closed?", Query_closed_p, 0);
503
706
  rb_define_method(cQuery, "columns", Query_columns, 0);
707
+ rb_define_method(cQuery, "clone", Query_clone, 0);
504
708
  rb_define_method(cQuery, "database", Query_database, 0);
505
709
  rb_define_method(cQuery, "db", Query_database, 0);
710
+ rb_define_method(cQuery, "dup", Query_clone, 0);
506
711
 
507
712
  rb_define_method(cQuery, "each", Query_each_hash, 0);
508
713
  rb_define_method(cQuery, "each_ary", Query_each_ary, 0);
@@ -511,7 +716,11 @@ void Init_ExtraliteQuery(void) {
511
716
 
512
717
  rb_define_method(cQuery, "eof?", Query_eof_p, 0);
513
718
  rb_define_method(cQuery, "execute", Query_execute, -1);
514
- rb_define_method(cQuery, "execute_multi", Query_execute_multi, 1);
719
+ rb_define_method(cQuery, "<<", Query_execute_chevrons, 1);
720
+ rb_define_method(cQuery, "batch_execute", Query_batch_execute, 1);
721
+ rb_define_method(cQuery, "batch_query", Query_batch_query, 1);
722
+ rb_define_method(cQuery, "batch_query_ary", Query_batch_query_ary, 1);
723
+ rb_define_method(cQuery, "batch_query_single_column", Query_batch_query_single_column, 1);
515
724
  rb_define_method(cQuery, "initialize", Query_initialize, 2);
516
725
  rb_define_method(cQuery, "inspect", Query_inspect, 0);
517
726
 
data/gemspec.rb CHANGED
@@ -16,7 +16,7 @@ def common_spec(s)
16
16
  s.rdoc_options = ["--title", "extralite", "--main", "README.md"]
17
17
  s.extra_rdoc_files = ["README.md"]
18
18
  s.require_paths = ["lib"]
19
- s.required_ruby_version = '>= 2.7'
19
+ s.required_ruby_version = '>= 3.0'
20
20
 
21
21
  s.add_development_dependency 'rake-compiler', '1.1.6'
22
22
  s.add_development_dependency 'minitest', '5.15.0'
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.3'
3
+ VERSION = '2.5'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -25,20 +25,28 @@ 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
31
35
  TABLES_SQL = <<~SQL
32
- SELECT name FROM sqlite_master
36
+ SELECT name FROM %<db>s.sqlite_master
33
37
  WHERE type ='table'
34
- AND name NOT LIKE 'sqlite_%';
38
+ AND name NOT LIKE 'sqlite_%%';
35
39
  SQL
36
40
 
37
- # Returns the list of currently defined tables.
41
+ alias_method :execute_multi, :batch_execute
42
+
43
+ # Returns the list of currently defined tables. If a database name is given,
44
+ # returns the list of tables for the relevant attached database.
38
45
  #
46
+ # @param db [String] name of attached database
39
47
  # @return [Array] list of tables
40
- def tables
41
- query_single_column(TABLES_SQL)
48
+ def tables(db = 'main')
49
+ query_single_column(format(TABLES_SQL, db: db))
42
50
  end
43
51
 
44
52
  # Gets or sets one or more pragmas:
@@ -52,6 +60,29 @@ module Extralite
52
60
  value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
53
61
  end
54
62
 
63
+ # Starts a transaction and runs the given block. If an exception is raised
64
+ # in the block, the transaction is rolled back. Otherwise, the transaction
65
+ # is commited after running the block.
66
+ #
67
+ # db.transaction do
68
+ # db.execute('insert into foo values (1, 2, 3)')
69
+ # raise if db.query_single_value('select x from bar') > 42
70
+ # end
71
+ #
72
+ # @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive). Defaults to immediate.
73
+ # @return [Any] the given block's return value
74
+ def transaction(mode = :immediate)
75
+ execute "begin #{mode} transaction"
76
+
77
+ abort = false
78
+ yield self
79
+ rescue
80
+ abort = true
81
+ raise
82
+ ensure
83
+ execute(abort ? 'rollback' : 'commit')
84
+ end
85
+
55
86
  private
56
87
 
57
88
  def pragma_set(values)
@@ -60,7 +91,11 @@ module Extralite
60
91
  end
61
92
 
62
93
  def pragma_get(key)
63
- query("pragma #{key}")
94
+ query_single_value("pragma #{key}")
64
95
  end
65
96
  end
97
+
98
+ class Query
99
+ alias_method :execute_multi, :batch_execute
100
+ end
66
101
  end
Binary file
data/test/helper.rb CHANGED
@@ -5,3 +5,6 @@ require 'extralite'
5
5
  require 'minitest/autorun'
6
6
 
7
7
  puts "sqlite3 version: #{Extralite.sqlite3_version}"
8
+
9
+ IS_LINUX = RUBY_PLATFORM =~ /linux/
10
+ SKIP_RACTOR_TESTS = !IS_LINUX || (RUBY_VERSION =~ /^3\.0/)
data/test/issue-38.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+ require "./lib/extralite"
5
+ require "benchmark"
6
+
7
+ # Setup
8
+
9
+ File.delete("benchmark.sqlite3") if File.exist?("benchmark.sqlite3")
10
+
11
+ POOL_SIZE = 10
12
+
13
+ EXTRALITE_CONNECTIONS = POOL_SIZE.times.map do
14
+ db = Extralite::Database.new("benchmark.sqlite3")
15
+ db.execute("PRAGMA journal_mode = WAL")
16
+ db.execute("PRAGMA synchronous = NORMAL")
17
+ db.execute("PRAGMA journal_size_limit = 64000000")
18
+ db.execute("PRAGMA mmap_size = 128000000")
19
+ db.execute("PRAGMA cache_size = 2000")
20
+ db.execute("PRAGMA busy_timeout = 5000")
21
+ db
22
+ end
23
+
24
+ SQLITE3_CONNECTIONS = POOL_SIZE.times.map do
25
+ db = SQLite3::Database.new("benchmark.sqlite3")
26
+ db.execute("PRAGMA journal_mode = WAL")
27
+ db.execute("PRAGMA synchronous = NORMAL")
28
+ db.execute("PRAGMA journal_size_limit = 64000000")
29
+ db.execute("PRAGMA mmap_size = 128000000")
30
+ db.execute("PRAGMA cache_size = 2000")
31
+ db.execute("PRAGMA busy_timeout = 5000")
32
+ db
33
+ end
34
+
35
+ EXTRALITE_CONNECTIONS[0].execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at TEXT, updated_at TEXT) STRICT")
36
+ insert_statement = EXTRALITE_CONNECTIONS[0].prepare("INSERT INTO users (name, created_at, updated_at) VALUES (?, ?, ?)")
37
+ 1000.times do
38
+ insert_statement.execute("John Doe", Time.now.iso8601, Time.now.iso8601)
39
+ end
40
+
41
+ # Benchmark variations
42
+
43
+ THREAD_COUNTS = [1, 2, 4, 8]
44
+ LIMITS = [10, 100, 1000]
45
+ CLIENTS = %w[extralite sqlite3]
46
+
47
+ # Benchmark
48
+
49
+ GC.disable
50
+ Benchmark.bm do |x|
51
+ LIMITS.each do |limit|
52
+ THREAD_COUNTS.each do |thread_count|
53
+ CLIENTS.each do |client|
54
+ GC.start
55
+
56
+ x.report("#{client.rjust('extralite'.length)} - limit: #{limit}, threads: #{thread_count}") do
57
+ threads = thread_count.times.map do |thread_number|
58
+ Thread.new do
59
+ start = Time.now
60
+ if client == "extralite"
61
+ 1_000.times do
62
+ records = EXTRALITE_CONNECTIONS[thread_number].query_ary("SELECT * FROM users LIMIT #{limit}")
63
+ raise "Expected #{limit} but got #{length}" unless records.length == limit
64
+ end
65
+ else
66
+ 1_000.times do
67
+ records = SQLITE3_CONNECTIONS[thread_number].query("SELECT * FROM users LIMIT #{limit}").entries
68
+ raise "Expected #{limit} but got #{length}" unless records.length == limit
69
+ end
70
+ end
71
+ end
72
+ end
73
+ threads.each(&:join)
74
+ end
75
+ end
76
+ puts
77
+ end
78
+ end
79
+ end
80
+ GC.enable
data/test/issue-54.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "./lib/extralite"
4
+
5
+ puts 'Connecting to database...'
6
+
7
+ connection_1 = Extralite::Database.new("test.sqlite3")
8
+ puts "#{connection_1} connected"
9
+ connection_2 = Extralite::Database.new("test.sqlite3")
10
+ connection_2.busy_timeout = 0
11
+ puts "#{connection_2} connected"
12
+
13
+ [connection_1, connection_2].each do |connection|
14
+ puts "#{connection} beginning transaction..."
15
+ connection.execute "begin immediate transaction"
16
+ end
17
+
18
+ [connection_1, connection_2].each do |connection|
19
+ puts "#{connection} rolling back transaction..."
20
+ connection.execute "rollback transaction"
21
+ end