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