extralite 1.19 → 1.20

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