extralite 1.19 → 1.21
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 +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -0
- data/Rakefile +5 -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/ext/sqlite3/sqlite3.c +9358 -4988
- data/ext/sqlite3/sqlite3.h +131 -39
- 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: 9b705a2d1c970cb3e73c784a6322ea92e8f7618531b4d8b9c2d4e036bf135b0f
|
4
|
+
data.tar.gz: fceaaf8976cac3d5e5328d64e8e76d07fe5979e4897ad7f20bb6295f697659b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1b1bc996aa69b8f4abd4888a23a3aeae0aa15069b5d7c13027389e6165529186949ed8679571b55c00f3b76bd5360f396e874dd4c73f3d88cd8443c8931fca9
|
7
|
+
data.tar.gz: aca5d4470770f5c635d2d3b42a1897c45535efd29a38137c3164c07629f3f68a3cc1f74f95cef180efe06b9cb5423bb8fe95e183c7001c98a05baa0f3532718e
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# 1.21 2023-01-23
|
2
|
+
|
3
|
+
- Update bundled sqlite to version 3.40.1 (#18)
|
4
|
+
|
5
|
+
# 1.20 2023-01-21
|
6
|
+
|
7
|
+
- Fix compilation error (#15 @sitano)
|
8
|
+
- Add status methods `Extralite.runtime_status`, `Database#status`, `PreparedStatement#status` (#14 @sitano)
|
9
|
+
- Add `Database#interrupt` (#13 @sitano)
|
10
|
+
- Add `Database#backup` (#11 @sitano)
|
11
|
+
- Derive `Extralite::Error` from `StandardError` (#10 @sitano)
|
12
|
+
|
1
13
|
## 1.19 2022-12-01
|
2
14
|
|
3
15
|
- 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/Rakefile
CHANGED
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
|
}
|