extralite 2.4 → 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.
@@ -13,11 +13,15 @@ VALUE eArgumentError;
13
13
 
14
14
  ID ID_bind;
15
15
  ID ID_call;
16
+ ID ID_each;
16
17
  ID ID_keys;
17
18
  ID ID_new;
18
19
  ID ID_strip;
19
20
 
21
+ VALUE SYM_gvl_release_threshold;
20
22
  VALUE SYM_read_only;
23
+ VALUE SYM_synchronous;
24
+ VALUE SYM_wal_journal_mode;
21
25
 
22
26
  static size_t Database_size(const void *ptr) {
23
27
  return sizeof(Database_t);
@@ -25,7 +29,12 @@ static size_t Database_size(const void *ptr) {
25
29
 
26
30
  static void Database_mark(void *ptr) {
27
31
  Database_t *db = ptr;
28
- rb_gc_mark(db->trace_block);
32
+ rb_gc_mark_movable(db->trace_block);
33
+ }
34
+
35
+ static void Database_compact(void *ptr) {
36
+ Database_t *db = ptr;
37
+ db->trace_block = rb_gc_location(db->trace_block);
29
38
  }
30
39
 
31
40
  static void Database_free(void *ptr) {
@@ -36,7 +45,7 @@ static void Database_free(void *ptr) {
36
45
 
37
46
  static const rb_data_type_t Database_type = {
38
47
  "Database",
39
- {Database_mark, Database_free, Database_size,},
48
+ {Database_mark, Database_free, Database_size, Database_compact},
40
49
  0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
41
50
  };
42
51
 
@@ -85,14 +94,40 @@ default_flags:
85
94
  return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
86
95
  }
87
96
 
97
+ VALUE Database_execute(int argc, VALUE *argv, VALUE self);
98
+
99
+ void Database_apply_opts(VALUE self, Database_t *db, VALUE opts) {
100
+ VALUE value = Qnil;
101
+
102
+ value = rb_hash_aref(opts, SYM_gvl_release_threshold);
103
+ if (!NIL_P(value)) db->gvl_release_threshold = NUM2INT(value);
104
+
105
+ value = rb_hash_aref(opts, SYM_wal_journal_mode);
106
+ if (RTEST(value)) {
107
+ value = rb_str_new_literal("PRAGMA journal_mode=wal");
108
+ Database_execute(1, &value, self);
109
+ }
110
+
111
+ value = rb_hash_aref(opts, SYM_synchronous);
112
+ if (RTEST(value)) {
113
+ value = rb_str_new_literal("PRAGMA synchronous=1");
114
+ Database_execute(1, &value, self);
115
+ }
116
+
117
+ RB_GC_GUARD(value);
118
+ }
119
+
88
120
  /* Initializes a new SQLite database with the given path and options.
89
121
  *
90
122
  * @overload initialize(path)
91
123
  * @param path [String] file path (or ':memory:' for memory database)
92
124
  * @return [void]
93
- * @overload initialize(path, read_only: false)
125
+ * @overload initialize(path, gvl_release_threshold: , read_only: , synchronous: , wal_journal_mode: )
94
126
  * @param path [String] file path (or ':memory:' for memory database)
127
+ * @param gvl_release_threshold [Integer] GVL release threshold
95
128
  * @param read_only [boolean] true for opening the database for reading only
129
+ * @param synchronous [boolean] true to set PRAGMA synchronous=1
130
+ * @param wal_journal_mode [boolean] true to set PRAGMA journal_mode=wal
96
131
  * @return [void]
97
132
  */
98
133
  VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
@@ -127,6 +162,8 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
127
162
  db->trace_block = Qnil;
128
163
  db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
129
164
 
165
+ if (!NIL_P(opts)) Database_apply_opts(self, db, opts);
166
+
130
167
  return Qnil;
131
168
  }
132
169
 
@@ -180,7 +217,6 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
180
217
  sql = rb_funcall(argv[0], ID_strip, 0);
181
218
  if (RSTRING_LEN(sql) == 0) return Qnil;
182
219
 
183
- // prepare query ctx
184
220
  if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_call, 1, sql);
185
221
  prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
186
222
  RB_GC_GUARD(sql);
@@ -342,30 +378,150 @@ VALUE Database_execute(int argc, VALUE *argv, VALUE self) {
342
378
  }
343
379
 
344
380
  /* call-seq:
345
- * db.execute_multi(sql, params_array) -> changes
381
+ * db.batch_execute(sql, params_array) -> changes
382
+ * db.batch_execute(sql, enumerable) -> changes
383
+ * db.batch_execute(sql, callable) -> changes
384
+ *
385
+ * Executes the given query for each list of parameters in the paramter source.
386
+ * If an enumerable is given, it is iterated and each of its values is used as
387
+ * the parameters for running the query. If a callable is given, it is called
388
+ * repeatedly and each of its return values is used as the parameters, until nil
389
+ * is returned.
346
390
  *
347
- * Executes the given query for each list of parameters in params_array. Returns
348
- * the number of changes effected. This method is designed for inserting
349
- * multiple records.
391
+ * Returns the number of changes effected. This method is designed for inserting
392
+ * multiple records or performing other mass operations.
350
393
  *
351
394
  * records = [
352
395
  * [1, 2, 3],
353
396
  * [4, 5, 6]
354
397
  * ]
355
- * db.execute_multi('insert into foo values (?, ?, ?)', records)
398
+ * db.batch_execute('insert into foo values (?, ?, ?)', records)
356
399
  *
400
+ * source = [
401
+ * [1, 2, 3],
402
+ * [4, 5, 6]
403
+ * ]
404
+ * db.batch_execute('insert into foo values (?, ?, ?)', -> { records.shift })
405
+ *
406
+ * @param sql [String] query SQL
407
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
408
+ * @return [Integer] Total number of changes effected
357
409
  */
358
- VALUE Database_execute_multi(VALUE self, VALUE sql, VALUE params_array) {
410
+ VALUE Database_batch_execute(VALUE self, VALUE sql, VALUE parameters) {
359
411
  Database_t *db = self_to_open_database(self);
360
412
  sqlite3_stmt *stmt;
361
413
 
362
414
  if (RSTRING_LEN(sql) == 0) return Qnil;
363
415
 
364
- // prepare query ctx
365
416
  prepare_single_stmt(db->sqlite3_db, &stmt, sql);
366
- query_ctx ctx = QUERY_CTX(self, db, stmt, params_array, QUERY_MULTI_ROW, ALL_ROWS);
417
+ query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
418
+
419
+ return rb_ensure(SAFE(safe_batch_execute), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
420
+ }
421
+
422
+ /* call-seq:
423
+ * db.batch_query(sql, params_array) -> rows
424
+ * db.batch_query(sql, enumerable) -> rows
425
+ * db.batch_query(sql, callable) -> rows
426
+ * db.batch_query(sql, params_array) { |rows| ... } -> changes
427
+ * db.batch_query(sql, enumerable) { |rows| ... } -> changes
428
+ * db.batch_query(sql, callable) { |rows| ... } -> changes
429
+ *
430
+ * Executes the given query for each list of parameters in the given paramter
431
+ * source. If a block is given, it is called with the resulting rows for each
432
+ * invocation of the query, and the total number of changes is returned.
433
+ * Otherwise, an array containing the resulting rows for each invocation is
434
+ * returned.
435
+ *
436
+ * records = [
437
+ * [1, 2],
438
+ * [3, 4]
439
+ * ]
440
+ * db.batch_query('insert into foo values (?, ?) returning bar, baz', records)
441
+ * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
442
+ * *
443
+ * @param sql [String] query SQL
444
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
445
+ * @return [Array<Hash>, Integer] Total number of changes effected
446
+ */
447
+ VALUE Database_batch_query(VALUE self, VALUE sql, VALUE parameters) {
448
+ Database_t *db = self_to_open_database(self);
449
+ sqlite3_stmt *stmt;
450
+
451
+ prepare_single_stmt(db->sqlite3_db, &stmt, sql);
452
+ query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
453
+
454
+ return rb_ensure(SAFE(safe_batch_query), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
455
+ }
456
+
457
+ /* call-seq:
458
+ * db.batch_query_ary(sql, params_array) -> rows
459
+ * db.batch_query_ary(sql, enumerable) -> rows
460
+ * db.batch_query_ary(sql, callable) -> rows
461
+ * db.batch_query_ary(sql, params_array) { |rows| ... } -> changes
462
+ * db.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
463
+ * db.batch_query_ary(sql, callable) { |rows| ... } -> changes
464
+ *
465
+ * Executes the given query for each list of parameters in the given paramter
466
+ * source. If a block is given, it is called with the resulting rows for each
467
+ * invocation of the query, and the total number of changes is returned.
468
+ * Otherwise, an array containing the resulting rows for each invocation is
469
+ * returned. Rows are represented as arrays.
470
+ *
471
+ * records = [
472
+ * [1, 2],
473
+ * [3, 4]
474
+ * ]
475
+ * db.batch_query_ary('insert into foo values (?, ?) returning bar, baz', records)
476
+ * #=> [[1, 2], [3, 4]]
477
+ * *
478
+ * @param sql [String] query SQL
479
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
480
+ * @return [Array<Array>, Integer] Total number of changes effected
481
+ */
482
+ VALUE Database_batch_query_ary(VALUE self, VALUE sql, VALUE parameters) {
483
+ Database_t *db = self_to_open_database(self);
484
+ sqlite3_stmt *stmt;
485
+
486
+ prepare_single_stmt(db->sqlite3_db, &stmt, sql);
487
+ query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
367
488
 
368
- return rb_ensure(SAFE(safe_execute_multi), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
489
+ return rb_ensure(SAFE(safe_batch_query_ary), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
490
+ }
491
+
492
+ /* call-seq:
493
+ * db.batch_query_single_column(sql, params_array) -> rows
494
+ * db.batch_query_single_column(sql, enumerable) -> rows
495
+ * db.batch_query_single_column(sql, callable) -> rows
496
+ * db.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
497
+ * db.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
498
+ * db.batch_query_single_column(sql, callable) { |rows| ... } -> changes
499
+ *
500
+ * Executes the given query for each list of parameters in the given paramter
501
+ * source. If a block is given, it is called with the resulting rows for each
502
+ * invocation of the query, and the total number of changes is returned.
503
+ * Otherwise, an array containing the resulting rows for each invocation is
504
+ * returned. Rows are single values.
505
+ *
506
+ * records = [
507
+ * [1, 2],
508
+ * [3, 4]
509
+ * ]
510
+ * db.batch_query_ary('insert into foo values (?, ?) returning baz', records)
511
+ * #=> [2, 4]
512
+ * *
513
+ * @param sql [String] query SQL
514
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
515
+ * @return [Array<any>, Integer] Total number of changes effected
516
+ */
517
+ VALUE Database_batch_query_single_column(VALUE self, VALUE sql, VALUE parameters) {
518
+ Database_t *db = self_to_open_database(self);
519
+ sqlite3_stmt *stmt;
520
+
521
+ prepare_single_stmt(db->sqlite3_db, &stmt, sql);
522
+ query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
523
+
524
+ return rb_ensure(SAFE(safe_batch_query_single_column), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
369
525
  }
370
526
 
371
527
  /* call-seq:
@@ -449,8 +605,10 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
449
605
 
450
606
  /* call-seq:
451
607
  * db.prepare(sql) -> Extralite::Query
608
+ * db.prepare(sql, ...) -> Extralite::Query
452
609
  *
453
- * Creates a prepared statement with the given SQL query.
610
+ * Creates a prepared statement with the given SQL query. If query parameters
611
+ * are given, they are bound to the query.
454
612
  */
455
613
  VALUE Database_prepare(int argc, VALUE *argv, VALUE self) {
456
614
  rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
@@ -811,7 +969,10 @@ void Init_ExtraliteDatabase(void) {
811
969
  #endif
812
970
 
813
971
  rb_define_method(cDatabase, "execute", Database_execute, -1);
814
- rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
972
+ rb_define_method(cDatabase, "batch_execute", Database_batch_execute, 2);
973
+ rb_define_method(cDatabase, "batch_query", Database_batch_query, 2);
974
+ rb_define_method(cDatabase, "batch_query_ary", Database_batch_query_ary, 2);
975
+ rb_define_method(cDatabase, "batch_query_single_column", Database_batch_query_single_column, 2);
815
976
  rb_define_method(cDatabase, "filename", Database_filename, -1);
816
977
  rb_define_method(cDatabase, "gvl_release_threshold", Database_gvl_release_threshold_get, 0);
817
978
  rb_define_method(cDatabase, "gvl_release_threshold=", Database_gvl_release_threshold_set, 1);
@@ -849,12 +1010,20 @@ void Init_ExtraliteDatabase(void) {
849
1010
 
850
1011
  ID_bind = rb_intern("bind");
851
1012
  ID_call = rb_intern("call");
1013
+ ID_each = rb_intern("each");
852
1014
  ID_keys = rb_intern("keys");
853
1015
  ID_new = rb_intern("new");
854
1016
  ID_strip = rb_intern("strip");
855
1017
 
856
- SYM_read_only = ID2SYM(rb_intern("read_only"));
1018
+ SYM_gvl_release_threshold = ID2SYM(rb_intern("gvl_release_threshold"));
1019
+ SYM_read_only = ID2SYM(rb_intern("read_only"));
1020
+ SYM_synchronous = ID2SYM(rb_intern("synchronous"));
1021
+ SYM_wal_journal_mode = ID2SYM(rb_intern("wal_journal_mode"));
1022
+
1023
+ rb_gc_register_mark_object(SYM_gvl_release_threshold);
857
1024
  rb_gc_register_mark_object(SYM_read_only);
1025
+ rb_gc_register_mark_object(SYM_synchronous);
1026
+ rb_gc_register_mark_object(SYM_wal_journal_mode);
858
1027
 
859
1028
  UTF8_ENCODING = rb_utf8_encoding();
860
1029
  }
@@ -32,6 +32,7 @@ extern VALUE cInterruptError;
32
32
  extern VALUE cParameterError;
33
33
 
34
34
  extern ID ID_call;
35
+ extern ID ID_each;
35
36
  extern ID ID_keys;
36
37
  extern ID ID_new;
37
38
  extern ID ID_strip;
@@ -100,7 +101,10 @@ enum gvl_mode {
100
101
 
101
102
  extern rb_encoding *UTF8_ENCODING;
102
103
 
103
- VALUE safe_execute_multi(query_ctx *ctx);
104
+ VALUE safe_batch_execute(query_ctx *ctx);
105
+ VALUE safe_batch_query(query_ctx *ctx);
106
+ VALUE safe_batch_query_ary(query_ctx *ctx);
107
+ VALUE safe_batch_query_single_column(query_ctx *ctx);
104
108
  VALUE safe_query_ary(query_ctx *ctx);
105
109
  VALUE safe_query_changes(query_ctx *ctx);
106
110
  VALUE safe_query_columns(query_ctx *ctx);
@@ -1,8 +1,12 @@
1
+ #include "ruby.h"
2
+
1
3
  void Init_ExtraliteDatabase();
2
4
  void Init_ExtraliteQuery();
3
5
  void Init_ExtraliteIterator();
4
6
 
5
7
  void Init_extralite_ext(void) {
8
+ rb_ext_ractor_safe(true);
9
+
6
10
  Init_ExtraliteDatabase();
7
11
  Init_ExtraliteQuery();
8
12
  Init_ExtraliteIterator();
@@ -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,7 +38,7 @@ 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,},
41
+ {Query_mark, Query_free, Query_size, Query_compact},
36
42
  0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
37
43
  };
38
44
 
@@ -367,22 +373,66 @@ VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
367
373
  return Query_perform_next(self, ALL_ROWS, safe_query_changes);
368
374
  }
369
375
 
370
- /* Executes the query for each set of parameters in the given array. Parameters
371
- * can be specified as either an array (for unnamed parameters) or a hash (for
372
- * named parameters). Returns the number of changes effected. This method is
373
- * 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.
374
418
  *
375
419
  * query = db.prepare('insert into foo values (?, ?, ?)')
376
420
  * records = [
377
421
  * [1, 2, 3],
378
422
  * [4, 5, 6]
379
423
  * ]
380
- * 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 }
381
431
  *
382
- * @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
383
433
  * @return [Integer] number of changes effected
384
434
  */
385
- VALUE Query_execute_multi(VALUE self, VALUE parameters) {
435
+ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
386
436
  Query_t *query = self_to_query(self);
387
437
  if (query->closed) rb_raise(cError, "Query is closed");
388
438
 
@@ -397,7 +447,139 @@ VALUE Query_execute_multi(VALUE self, VALUE parameters) {
397
447
  QUERY_MODE(QUERY_MULTI_ROW),
398
448
  ALL_ROWS
399
449
  );
400
- return safe_execute_multi(&ctx);
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);
401
583
  }
402
584
 
403
585
  /* Returns the database associated with the query.
@@ -431,6 +613,19 @@ VALUE Query_columns(VALUE self) {
431
613
  return Query_perform_next(self, ALL_ROWS, safe_query_columns);
432
614
  }
433
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
+
434
629
  /* Closes the query. Attempting to run a closed query will raise an error.
435
630
  *
436
631
  * @return [Extralite::Query] self
@@ -509,8 +704,10 @@ void Init_ExtraliteQuery(void) {
509
704
  rb_define_method(cQuery, "close", Query_close, 0);
510
705
  rb_define_method(cQuery, "closed?", Query_closed_p, 0);
511
706
  rb_define_method(cQuery, "columns", Query_columns, 0);
707
+ rb_define_method(cQuery, "clone", Query_clone, 0);
512
708
  rb_define_method(cQuery, "database", Query_database, 0);
513
709
  rb_define_method(cQuery, "db", Query_database, 0);
710
+ rb_define_method(cQuery, "dup", Query_clone, 0);
514
711
 
515
712
  rb_define_method(cQuery, "each", Query_each_hash, 0);
516
713
  rb_define_method(cQuery, "each_ary", Query_each_ary, 0);
@@ -519,7 +716,11 @@ void Init_ExtraliteQuery(void) {
519
716
 
520
717
  rb_define_method(cQuery, "eof?", Query_eof_p, 0);
521
718
  rb_define_method(cQuery, "execute", Query_execute, -1);
522
- 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);
523
724
  rb_define_method(cQuery, "initialize", Query_initialize, 2);
524
725
  rb_define_method(cQuery, "inspect", Query_inspect, 0);
525
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.4'
3
+ VERSION = '2.5'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -33,16 +33,20 @@ module Extralite
33
33
  class Database
34
34
  # @!visibility private
35
35
  TABLES_SQL = <<~SQL
36
- SELECT name FROM sqlite_master
36
+ SELECT name FROM %<db>s.sqlite_master
37
37
  WHERE type ='table'
38
- AND name NOT LIKE 'sqlite_%';
38
+ AND name NOT LIKE 'sqlite_%%';
39
39
  SQL
40
40
 
41
- # 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.
42
45
  #
46
+ # @param db [String] name of attached database
43
47
  # @return [Array] list of tables
44
- def tables
45
- query_single_column(TABLES_SQL)
48
+ def tables(db = 'main')
49
+ query_single_column(format(TABLES_SQL, db: db))
46
50
  end
47
51
 
48
52
  # Gets or sets one or more pragmas:
@@ -87,7 +91,11 @@ module Extralite
87
91
  end
88
92
 
89
93
  def pragma_get(key)
90
- query("pragma #{key}")
94
+ query_single_value("pragma #{key}")
91
95
  end
92
96
  end
97
+
98
+ class Query
99
+ alias_method :execute_multi, :batch_execute
100
+ end
93
101
  end
data/test/helper.rb CHANGED
@@ -7,3 +7,4 @@ 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/)