extralite 1.19 → 1.20

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2fbd9028327105949e5f4fcc225ccb4ec89cdb2f324d949c17c4690f8b4653f
4
- data.tar.gz: 226c07bfa8e8848dc322399ee386e8f15e6bd9e41c6912850709b1cc8fd24ad3
3
+ metadata.gz: f9dba60a33188d5c3c7cfb09c7c746f64bcd723d53a20da4b110037ca395a50b
4
+ data.tar.gz: 2b5d8de6e19fe38800fbd30b2465ef6788cf50ac73f79be7e07d8d1f2165ad8b
5
5
  SHA512:
6
- metadata.gz: ca3c24e4be2cedee6d20101ce56018ed9f037cbe29d4e5c6fb63116a77c988390992b770586a1973acc0c42a521389369ed79cf1553464e5f775fe420c9bf421
7
- data.tar.gz: f9ffb7dbe2a5d00ea2c15cca1407462897590f299acecf88de6a040f463f4f28715ae98ae34a6a0539f97b1cf2f12902803217517014661208b6238618013287
6
+ metadata.gz: 4723dd3278f61a7dc95aa48260a5d974f3da7383463f538f3e0d647f1591f6759f239e633b6d3efe4e17d81c7841f0670c2847d318b1fdd78e7f1365fe509aad
7
+ data.tar.gz: 5fface9ad5152b0a11bb259267a1db2d6a214a02ca3a6ad0207f707e6de7aa674a28180fd6e109d55745bb3453d17b4e3244aaeba89bf75b40ec472dda1789bc
@@ -15,7 +15,7 @@ jobs:
15
15
 
16
16
  runs-on: ${{matrix.os}}
17
17
  steps:
18
- - uses: actions/checkout@v1
18
+ - uses: actions/checkout@v3
19
19
  - uses: ruby/setup-ruby@v1
20
20
  with:
21
21
  ruby-version: ${{matrix.ruby}}
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 1.20 2023-01-21
2
+
3
+ - Fix compilation error (#15 @sitano)
4
+ - Add status methods `Extralite.runtime_status`, `Database#status`, `PreparedStatement#status` (#14 @sitano)
5
+ - Add `Database#interrupt` (#13 @sitano)
6
+ - Add `Database#backup` (#11 @sitano)
7
+ - Derive `Extralite::Error` from `StandardError` (#10 @sitano)
8
+
1
9
  ## 1.19 2022-12-01
2
10
 
3
11
  - Add `Database#execute_multi`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.19)
4
+ extralite (1.20)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -55,6 +55,10 @@ latest features and enhancements.
55
55
  - Load extensions (loading of extensions is autmatically enabled. You can find
56
56
  some useful extensions here: https://github.com/nalgeon/sqlean.)
57
57
  - Includes a [Sequel adapter](#usage-with-sequel).
58
+ - Other features:
59
+ - Backup databases
60
+ - Interrupt long-running queries (from another thread)
61
+ - Get runtime status, database status and prepared statement status values.
58
62
 
59
63
  ## Installation
60
64
 
@@ -243,6 +243,8 @@ int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
243
243
  return 0;
244
244
  case SQLITE_BUSY:
245
245
  rb_raise(cBusyError, "Database is busy");
246
+ case SQLITE_INTERRUPT:
247
+ rb_raise(cInterruptError, "Query was interrupted");
246
248
  case SQLITE_ERROR:
247
249
  rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
248
250
  default:
@@ -5,6 +5,7 @@ VALUE cDatabase;
5
5
  VALUE cError;
6
6
  VALUE cSQLError;
7
7
  VALUE cBusyError;
8
+ VALUE cInterruptError;
8
9
 
9
10
  ID ID_KEYS;
10
11
  ID ID_NEW;
@@ -386,43 +387,213 @@ VALUE Database_prepare(VALUE self, VALUE sql) {
386
387
  return rb_funcall(cPreparedStatement, ID_NEW, 2, self, sql);
387
388
  }
388
389
 
389
- void Init_ExtraliteDatabase() {
390
+ /* call-seq:
391
+ * db.interrupt -> db
392
+ *
393
+ * Interrupts a long running query. This method is to be called from a different
394
+ * thread than the one running the query. Upon calling `#interrupt` the running
395
+ * query will stop and raise an `Extralite::InterruptError` exception.
396
+ *
397
+ * It is not safe to call `#interrupt` on a database that is about to be closed.
398
+ * For more information, consult the [sqlite3 API docs](https://sqlite.org/c3ref/interrupt.html).
399
+ */
400
+ VALUE Database_interrupt(VALUE self) {
401
+ Database_t *db;
402
+ GetOpenDatabase(self, db);
403
+
404
+ sqlite3_interrupt(db->sqlite3_db);
405
+ return self;
406
+ }
407
+
408
+ typedef struct {
409
+ sqlite3 *dst;
410
+ int close_dst_on_cleanup;
411
+ sqlite3_backup *backup;
412
+ int block_given;
413
+ int rc;
414
+ } backup_ctx;
415
+
416
+ #define BACKUP_STEP_MAX_PAGES 16
417
+ #define BACKUP_SLEEP_MS 100
418
+
419
+ void *backup_step_without_gvl(void *ptr) {
420
+ backup_ctx *ctx = (backup_ctx *)ptr;
421
+ ctx->rc = sqlite3_backup_step(ctx->backup, BACKUP_STEP_MAX_PAGES);
422
+ return NULL;
423
+ }
424
+
425
+ void *backup_sleep_without_gvl(void *unused) {
426
+ sqlite3_sleep(BACKUP_SLEEP_MS);
427
+ return NULL;
428
+ }
429
+
430
+ VALUE backup_safe_iterate(VALUE ptr) {
431
+ backup_ctx *ctx = (backup_ctx *)ptr;
432
+ int done = 0;
433
+
434
+ while (!done) {
435
+ rb_thread_call_without_gvl(backup_step_without_gvl, (void *)ctx, RUBY_UBF_IO, 0);
436
+ switch(ctx->rc) {
437
+ case SQLITE_DONE:
438
+ if (ctx->block_given) {
439
+ VALUE total = INT2FIX(sqlite3_backup_pagecount(ctx->backup));
440
+ rb_yield_values(2, total, total);
441
+ }
442
+ done = 1;
443
+ continue;
444
+ case SQLITE_OK:
445
+ if (ctx->block_given) {
446
+ VALUE remaining = INT2FIX(sqlite3_backup_remaining(ctx->backup));
447
+ VALUE total = INT2FIX(sqlite3_backup_pagecount(ctx->backup));
448
+ rb_yield_values(2, remaining, total);
449
+ }
450
+ continue;
451
+ case SQLITE_BUSY:
452
+ case SQLITE_LOCKED:
453
+ rb_thread_call_without_gvl(backup_sleep_without_gvl, NULL, RUBY_UBF_IO, 0);
454
+ continue;
455
+ default:
456
+ rb_raise(cError, "%s", sqlite3_errstr(ctx->rc));
457
+ }
458
+ };
459
+
460
+ return Qnil;
461
+ }
462
+
463
+ VALUE backup_cleanup(VALUE ptr) {
464
+ backup_ctx *ctx = (backup_ctx *)ptr;
465
+
466
+ sqlite3_backup_finish(ctx->backup);
467
+
468
+ if (ctx->close_dst_on_cleanup)
469
+ sqlite3_close(ctx->dst);
470
+ return Qnil;
471
+ }
472
+
473
+ VALUE Database_backup(int argc, VALUE *argv, VALUE self) {
474
+ VALUE dst;
475
+ VALUE src_name;
476
+ VALUE dst_name;
477
+ rb_scan_args(argc, argv, "12", &dst, &src_name, &dst_name);
478
+ if (src_name == Qnil) src_name = rb_str_new_literal("main");
479
+ if (dst_name == Qnil) dst_name = rb_str_new_literal("main");
480
+
481
+ int dst_is_fn = TYPE(dst) == T_STRING;
482
+
483
+ Database_t *src;
484
+ GetOpenDatabase(self, src);
485
+ sqlite3 *dst_db;
486
+
487
+ if (dst_is_fn) {
488
+ int rc = sqlite3_open(StringValueCStr(dst), &dst_db);
489
+ if (rc) {
490
+ sqlite3_close(dst_db);
491
+ rb_raise(cError, "%s", sqlite3_errmsg(dst_db));
492
+ }
493
+ }
494
+ else {
495
+ Database_t *dst_struct;
496
+ GetOpenDatabase(dst, dst_struct);
497
+ dst_db = dst_struct->sqlite3_db;
498
+ }
499
+
500
+ // TODO: add possibility to use different src and dest db names (main, tmp, or
501
+ // attached db's).
502
+ sqlite3_backup *backup;
503
+ backup = sqlite3_backup_init(dst_db, StringValueCStr(dst_name), src->sqlite3_db, StringValueCStr(src_name));
504
+ if (!backup) {
505
+ if (dst_is_fn)
506
+ sqlite3_close(dst_db);
507
+ rb_raise(cError, "%s", sqlite3_errmsg(dst_db));
508
+ }
509
+
510
+ backup_ctx ctx = { dst_db, dst_is_fn, backup, rb_block_given_p(), 0 };
511
+ rb_ensure(SAFE(backup_safe_iterate), (VALUE)&ctx, SAFE(backup_cleanup), (VALUE)&ctx);
512
+
513
+ return self;
514
+ }
515
+
516
+ /*
517
+ * Extralite.runtime_status(op[, reset]) -> [value, highwatermark]
518
+ *
519
+ * Returns runtime status values for the given op as an array containing the
520
+ * current value and the high water mark value. To reset the high water mark,
521
+ * pass true as reset.
522
+ */
523
+ VALUE Extralite_runtime_status(int argc, VALUE* argv, VALUE self) {
524
+ VALUE op, reset;
525
+ sqlite3_int64 cur, hwm;
526
+
527
+ rb_scan_args(argc, argv, "11", &op, &reset);
528
+
529
+ int rc = sqlite3_status64(NUM2INT(op), &cur, &hwm, RTEST(reset) ? 1 : 0);
530
+ if (rc != SQLITE_OK) rb_raise(cError, "%s", sqlite3_errstr(rc));
531
+
532
+ return rb_ary_new3(2, LONG2FIX(cur), LONG2FIX(hwm));
533
+ }
534
+
535
+ /* call-seq:
536
+ * db.status(op[, reset]) -> [value, highwatermark]
537
+ *
538
+ * Returns database status values for the given op as an array containing the
539
+ * current value and the high water mark value. To reset the high water mark,
540
+ * pass true as reset.
541
+ */
542
+ VALUE Database_status(int argc, VALUE* argv, VALUE self) {
543
+ VALUE op, reset;
544
+ int cur, hwm;
545
+
546
+ rb_scan_args(argc, argv, "11", &op, &reset);
547
+
548
+ Database_t *db;
549
+ GetOpenDatabase(self, db);
550
+
551
+ int rc = sqlite3_db_status(db->sqlite3_db, NUM2INT(op), &cur, &hwm, RTEST(reset) ? 1 : 0);
552
+ if (rc != SQLITE_OK) rb_raise(cError, "%s", sqlite3_errstr(rc));
553
+
554
+ return rb_ary_new3(2, INT2NUM(cur), INT2NUM(hwm));
555
+ }
556
+
557
+ void Init_ExtraliteDatabase(void) {
390
558
  VALUE mExtralite = rb_define_module("Extralite");
559
+ rb_define_singleton_method(mExtralite, "runtime_status", Extralite_runtime_status, -1);
391
560
  rb_define_singleton_method(mExtralite, "sqlite3_version", Extralite_sqlite3_version, 0);
392
561
 
393
562
  cDatabase = rb_define_class_under(mExtralite, "Database", rb_cObject);
394
563
  rb_define_alloc_func(cDatabase, Database_allocate);
395
564
 
396
- rb_define_method(cDatabase, "initialize", Database_initialize, 1);
565
+ rb_define_method(cDatabase, "backup", Database_backup, -1);
566
+ rb_define_method(cDatabase, "changes", Database_changes, 0);
397
567
  rb_define_method(cDatabase, "close", Database_close, 0);
398
568
  rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
399
-
569
+ rb_define_method(cDatabase, "columns", Database_columns, 1);
570
+ rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
571
+ rb_define_method(cDatabase, "filename", Database_filename, -1);
572
+ rb_define_method(cDatabase, "initialize", Database_initialize, 1);
573
+ rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
574
+ rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
575
+ rb_define_method(cDatabase, "prepare", Database_prepare, 1);
400
576
  rb_define_method(cDatabase, "query", Database_query_hash, -1);
401
- rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
402
577
  rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
403
- rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
578
+ rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
404
579
  rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
580
+ rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
405
581
  rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
406
- rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
407
- rb_define_method(cDatabase, "columns", Database_columns, 1);
408
-
409
- rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
410
- rb_define_method(cDatabase, "changes", Database_changes, 0);
411
- rb_define_method(cDatabase, "filename", Database_filename, -1);
582
+ rb_define_method(cDatabase, "status", Database_status, -1);
412
583
  rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
413
584
 
414
585
  #ifdef HAVE_SQLITE3_LOAD_EXTENSION
415
586
  rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
416
587
  #endif
417
588
 
418
- rb_define_method(cDatabase, "prepare", Database_prepare, 1);
419
-
420
- cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
589
+ cError = rb_define_class_under(mExtralite, "Error", rb_eStandardError);
421
590
  cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
422
591
  cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
592
+ cInterruptError = rb_define_class_under(mExtralite, "InterruptError", cError);
423
593
  rb_gc_register_mark_object(cError);
424
594
  rb_gc_register_mark_object(cSQLError);
425
595
  rb_gc_register_mark_object(cBusyError);
596
+ rb_gc_register_mark_object(cInterruptError);
426
597
 
427
598
  ID_KEYS = rb_intern("keys");
428
599
  ID_NEW = rb_intern("new");
@@ -25,6 +25,7 @@ extern VALUE cPreparedStatement;
25
25
  extern VALUE cError;
26
26
  extern VALUE cSQLError;
27
27
  extern VALUE cBusyError;
28
+ extern VALUE cInterruptError;
28
29
 
29
30
  extern ID ID_KEYS;
30
31
  extern ID ID_NEW;
@@ -49,6 +50,12 @@ typedef struct {
49
50
  VALUE params;
50
51
  } query_ctx;
51
52
 
53
+ typedef struct {
54
+ VALUE dst;
55
+ VALUE src;
56
+ sqlite3_backup *p;
57
+ } backup_t;
58
+
52
59
  VALUE safe_query_ary(query_ctx *ctx);
53
60
  VALUE safe_query_hash(query_ctx *ctx);
54
61
  VALUE safe_query_single_column(query_ctx *ctx);
@@ -1,7 +1,7 @@
1
1
  void Init_ExtraliteDatabase();
2
2
  void Init_ExtralitePreparedStatement();
3
3
 
4
- void Init_extralite_ext() {
4
+ void Init_extralite_ext(void) {
5
5
  Init_ExtraliteDatabase();
6
6
  Init_ExtralitePreparedStatement();
7
7
  }
@@ -283,27 +283,43 @@ VALUE PreparedStatement_closed_p(VALUE self) {
283
283
  return stmt->stmt ? Qfalse : Qtrue;
284
284
  }
285
285
 
286
- void Init_ExtralitePreparedStatement() {
286
+ /* call-seq:
287
+ * stmt.status(op[, reset]) -> value
288
+ *
289
+ * Returns the current status value for the given op. To reset the value, pass
290
+ * true as reset.
291
+ */
292
+ VALUE PreparedStatement_status(int argc, VALUE* argv, VALUE self) {
293
+ VALUE op, reset;
294
+
295
+ rb_scan_args(argc, argv, "11", &op, &reset);
296
+
297
+ PreparedStatement_t *stmt;
298
+ GetPreparedStatement(self, stmt);
299
+
300
+ int value = sqlite3_stmt_status(stmt->stmt, NUM2INT(op), RTEST(reset) ? 1 : 0);
301
+ return INT2NUM(value);
302
+ }
303
+
304
+ void Init_ExtralitePreparedStatement(void) {
287
305
  VALUE mExtralite = rb_define_module("Extralite");
288
306
 
289
307
  cPreparedStatement = rb_define_class_under(mExtralite, "PreparedStatement", rb_cObject);
290
308
  rb_define_alloc_func(cPreparedStatement, PreparedStatement_allocate);
291
309
 
292
- rb_define_method(cPreparedStatement, "initialize", PreparedStatement_initialize, 2);
310
+ rb_define_method(cPreparedStatement, "close", PreparedStatement_close, 0);
311
+ rb_define_method(cPreparedStatement, "closed?", PreparedStatement_closed_p, 0);
312
+ rb_define_method(cPreparedStatement, "columns", PreparedStatement_columns, 0);
293
313
  rb_define_method(cPreparedStatement, "database", PreparedStatement_database, 0);
294
314
  rb_define_method(cPreparedStatement, "db", PreparedStatement_database, 0);
295
- rb_define_method(cPreparedStatement, "sql", PreparedStatement_sql, 0);
296
-
315
+ rb_define_method(cPreparedStatement, "execute_multi", PreparedStatement_execute_multi, 1);
316
+ rb_define_method(cPreparedStatement, "initialize", PreparedStatement_initialize, 2);
297
317
  rb_define_method(cPreparedStatement, "query", PreparedStatement_query_hash, -1);
298
318
  rb_define_method(cPreparedStatement, "query_hash", PreparedStatement_query_hash, -1);
299
319
  rb_define_method(cPreparedStatement, "query_ary", PreparedStatement_query_ary, -1);
300
320
  rb_define_method(cPreparedStatement, "query_single_row", PreparedStatement_query_single_row, -1);
301
321
  rb_define_method(cPreparedStatement, "query_single_column", PreparedStatement_query_single_column, -1);
302
322
  rb_define_method(cPreparedStatement, "query_single_value", PreparedStatement_query_single_value, -1);
303
- rb_define_method(cPreparedStatement, "execute_multi", PreparedStatement_execute_multi, 1);
304
-
305
- rb_define_method(cPreparedStatement, "columns", PreparedStatement_columns, 0);
306
-
307
- rb_define_method(cPreparedStatement, "close", PreparedStatement_close, 0);
308
- rb_define_method(cPreparedStatement, "closed?", PreparedStatement_closed_p, 0);
323
+ rb_define_method(cPreparedStatement, "sql", PreparedStatement_sql, 0);
324
+ rb_define_method(cPreparedStatement, "status", PreparedStatement_status, -1);
309
325
  }
@@ -1,3 +1,3 @@
1
1
  module Extralite
2
- VERSION = '1.19'
2
+ VERSION = '1.20'
3
3
  end
data/lib/extralite.rb CHANGED
@@ -2,8 +2,48 @@ require_relative './extralite_ext'
2
2
 
3
3
  # Extralite is a Ruby gem for working with SQLite databases
4
4
  module Extralite
5
+
6
+ SQLITE_STATUS_MEMORY_USED = 0
7
+ SQLITE_STATUS_PAGECACHE_USED = 1
8
+ SQLITE_STATUS_PAGECACHE_OVERFLOW = 2
9
+ SQLITE_STATUS_SCRATCH_USED = 3 # NOT USED
10
+ SQLITE_STATUS_SCRATCH_OVERFLOW = 4 # NOT USED
11
+ SQLITE_STATUS_MALLOC_SIZE = 5
12
+ SQLITE_STATUS_PARSER_STACK = 6
13
+ SQLITE_STATUS_PAGECACHE_SIZE = 7
14
+ SQLITE_STATUS_SCRATCH_SIZE = 8 # NOT USED
15
+ SQLITE_STATUS_MALLOC_COUNT = 9
16
+
17
+ SQLITE_DBSTATUS_LOOKASIDE_USED = 0
18
+ SQLITE_DBSTATUS_CACHE_USED = 1
19
+ SQLITE_DBSTATUS_SCHEMA_USED = 2
20
+ SQLITE_DBSTATUS_STMT_USED = 3
21
+ SQLITE_DBSTATUS_LOOKASIDE_HIT = 4
22
+ SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5
23
+ SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6
24
+ SQLITE_DBSTATUS_CACHE_HIT = 7
25
+ SQLITE_DBSTATUS_CACHE_MISS = 8
26
+ SQLITE_DBSTATUS_CACHE_WRITE = 9
27
+ SQLITE_DBSTATUS_DEFERRED_FKS = 10
28
+ SQLITE_DBSTATUS_CACHE_USED_SHARED = 11
29
+ SQLITE_DBSTATUS_CACHE_SPILL = 12
30
+
31
+ SQLITE_STMTSTATUS_FULLSCAN_STEP = 1
32
+ SQLITE_STMTSTATUS_SORT = 2
33
+ SQLITE_STMTSTATUS_AUTOINDEX = 3
34
+ SQLITE_STMTSTATUS_VM_STEP = 4
35
+ SQLITE_STMTSTATUS_REPREPARE = 5
36
+ SQLITE_STMTSTATUS_RUN = 6
37
+ SQLITE_STMTSTATUS_FILTER_MISS = 7
38
+ SQLITE_STMTSTATUS_FILTER_HIT = 8
39
+ SQLITE_STMTSTATUS_MEMUSED = 99
40
+
41
+ # The following class definitions are not really needed, as they're already
42
+ # defined in the C extension. We put them here for the sake of generating
43
+ # docs.
44
+
5
45
  # A base class for Extralite exceptions
6
- class Error < RuntimeError
46
+ class Error < ::StandardError
7
47
  end
8
48
 
9
49
  # An exception representing an SQL error emitted by SQLite
@@ -15,6 +55,11 @@ module Extralite
15
55
  class BusyError < Error
16
56
  end
17
57
 
58
+ # An exception raised when a query is interrupted by calling
59
+ # `Database#interrupt` from another thread
60
+ class InterruptError < Error
61
+ end
62
+
18
63
  # An SQLite database
19
64
  class Database
20
65
  alias_method :execute, :query
@@ -42,4 +87,18 @@ module Extralite
42
87
  query("pragma #{key}")
43
88
  end
44
89
  end
90
+
91
+ # An SQLite backup
92
+ class Backup
93
+ # def initialize(dst, dst_name, src, src_name); end
94
+
95
+ # def dst; end
96
+ # def src; end
97
+
98
+ # def step(pages); end
99
+ # def finish; end
100
+
101
+ # def pagecount; end
102
+ # def remaining; end
103
+ end
45
104
  end
@@ -243,6 +243,32 @@ end
243
243
  { bar: 'bye' }
244
244
  ], @db.query('select * from foo')
245
245
  end
246
+
247
+ def test_interrupt
248
+ t = Thread.new do
249
+ sleep 0.5
250
+ @db.interrupt
251
+ end
252
+
253
+ n = 2**31
254
+ assert_raises(Extralite::InterruptError) {
255
+ @db.query <<-SQL
256
+ WITH RECURSIVE
257
+ fibo (curr, next)
258
+ AS
259
+ ( SELECT 1,1
260
+ UNION ALL
261
+ SELECT next, curr+next FROM fibo
262
+ LIMIT #{n} )
263
+ SELECT curr, next FROM fibo LIMIT 1 OFFSET #{n}-1;
264
+ SQL
265
+ }
266
+ t.join
267
+ end
268
+
269
+ def test_database_status
270
+ assert_operator 0, :<, @db.status(Extralite::SQLITE_DBSTATUS_SCHEMA_USED).first
271
+ end
246
272
  end
247
273
 
248
274
  class ScenarioTest < MiniTest::Test
@@ -311,3 +337,39 @@ class ScenarioTest < MiniTest::Test
311
337
  assert_equal [1, 4, 7], result
312
338
  end
313
339
  end
340
+
341
+ class BackupTest < MiniTest::Test
342
+ def setup
343
+ @src = Extralite::Database.new(':memory:')
344
+ @dst = Extralite::Database.new(':memory:')
345
+
346
+ @src.query('create table t (x,y,z)')
347
+ @src.query('insert into t values (1, 2, 3)')
348
+ @src.query('insert into t values (4, 5, 6)')
349
+ end
350
+
351
+ def test_backup
352
+ @src.backup(@dst)
353
+ assert_equal [[1, 2, 3], [4, 5, 6]], @dst.query_ary('select * from t')
354
+ end
355
+
356
+ def test_backup_with_block
357
+ progress = []
358
+ @src.backup(@dst) { |r, t| progress << [r, t] }
359
+ assert_equal [[1, 2, 3], [4, 5, 6]], @dst.query_ary('select * from t')
360
+ assert_equal [[2, 2]], progress
361
+ end
362
+
363
+ def test_backup_with_schema_names
364
+ @src.backup(@dst, 'main', 'temp')
365
+ assert_equal [[1, 2, 3], [4, 5, 6]], @dst.query_ary('select * from temp.t')
366
+ end
367
+
368
+ def test_backup_with_fn
369
+ tmp_fn = "/tmp/#{rand(86400)}.db"
370
+ @src.backup(tmp_fn)
371
+
372
+ db = Extralite::Database.new(tmp_fn)
373
+ assert_equal [[1, 2, 3], [4, 5, 6]], db.query_ary('select * from t')
374
+ end
375
+ end
@@ -6,4 +6,28 @@ class ExtraliteTest < MiniTest::Test
6
6
  def test_sqlite3_version
7
7
  assert_match /^3\.\d+\.\d+$/, Extralite.sqlite3_version
8
8
  end
9
+
10
+ def test_status
11
+ db = Extralite::Database.new(':memory:')
12
+ db.query('create table if not exists t (x,y,z)')
13
+ db.query('insert into t values (1, 2, 3)')
14
+
15
+ begin
16
+ a = Extralite::runtime_status(Extralite::SQLITE_STATUS_MEMORY_USED, false)
17
+ b = Extralite::runtime_status(Extralite::SQLITE_STATUS_MEMORY_USED)
18
+ c = Extralite::runtime_status(Extralite::SQLITE_STATUS_MEMORY_USED, true)
19
+ d = Extralite::runtime_status(Extralite::SQLITE_STATUS_MEMORY_USED, true)
20
+
21
+ assert_operator 0, :<, a[0]
22
+ assert_operator a[0], :<=, a[1]
23
+
24
+ assert_equal a, b
25
+ assert_equal a, c
26
+
27
+ assert_equal a[0], d[0]
28
+ assert_equal a[0], d[1]
29
+ ensure
30
+ db.close
31
+ end
32
+ end
9
33
  end
@@ -200,4 +200,15 @@ end
200
200
  { a: '4', b: 5, c: 6 }
201
201
  ], @db.query('select * from foo')
202
202
  end
203
+
204
+ def test_prepared_statement_status
205
+ assert_equal 0, @stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
206
+ @stmt.query
207
+ assert_equal 1, @stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
208
+ @stmt.query
209
+ assert_equal 2, @stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
210
+ @stmt.query
211
+ assert_equal 3, @stmt.status(Extralite::SQLITE_STMTSTATUS_RUN, true)
212
+ assert_equal 0, @stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
213
+ end
203
214
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extralite
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.19'
4
+ version: '1.20'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-01 00:00:00.000000000 Z
11
+ date: 2023-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -153,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
153
  - !ruby/object:Gem::Version
154
154
  version: '0'
155
155
  requirements: []
156
- rubygems_version: 3.3.7
156
+ rubygems_version: 3.4.1
157
157
  signing_key:
158
158
  specification_version: 4
159
159
  summary: Extra-lightweight SQLite3 wrapper for Ruby