extralite 2.4 → 2.5

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