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 +4 -4
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -0
- data/ext/extralite/common.c +2 -0
- data/ext/extralite/database.c +185 -14
- data/ext/extralite/extralite.h +7 -0
- data/ext/extralite/extralite_ext.c +1 -1
- data/ext/extralite/prepared_statement.c +26 -10
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +60 -1
- data/test/test_database.rb +62 -0
- data/test/test_extralite.rb +24 -0
- data/test/test_prepared_statement.rb +11 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9dba60a33188d5c3c7cfb09c7c746f64bcd723d53a20da4b110037ca395a50b
|
4
|
+
data.tar.gz: 2b5d8de6e19fe38800fbd30b2465ef6788cf50ac73f79be7e07d8d1f2165ad8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4723dd3278f61a7dc95aa48260a5d974f3da7383463f538f3e0d647f1591f6759f239e633b6d3efe4e17d81c7841f0670c2847d318b1fdd78e7f1365fe509aad
|
7
|
+
data.tar.gz: 5fface9ad5152b0a11bb259267a1db2d6a214a02ca3a6ad0207f707e6de7aa674a28180fd6e109d55745bb3453d17b4e3244aaeba89bf75b40ec472dda1789bc
|
data/.github/workflows/test.yml
CHANGED
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
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
|
|
data/ext/extralite/common.c
CHANGED
@@ -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:
|
data/ext/extralite/database.c
CHANGED
@@ -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
|
-
|
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, "
|
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, "
|
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, "
|
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
|
-
|
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");
|
data/ext/extralite/extralite.h
CHANGED
@@ -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);
|
@@ -283,27 +283,43 @@ VALUE PreparedStatement_closed_p(VALUE self) {
|
|
283
283
|
return stmt->stmt ? Qfalse : Qtrue;
|
284
284
|
}
|
285
285
|
|
286
|
-
|
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, "
|
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, "
|
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, "
|
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
|
}
|
data/lib/extralite/version.rb
CHANGED
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 <
|
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
|
data/test/test_database.rb
CHANGED
@@ -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
|
data/test/test_extralite.rb
CHANGED
@@ -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.
|
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:
|
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.
|
156
|
+
rubygems_version: 3.4.1
|
157
157
|
signing_key:
|
158
158
|
specification_version: 4
|
159
159
|
summary: Extra-lightweight SQLite3 wrapper for Ruby
|