extralite-bundle 1.19 → 1.21
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 +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: 71d65f6263e6916f9f3c92eb10f35d677385b8e3e1bc09a53bba5d809450bd8f
|
4
|
+
data.tar.gz: ad2df66fb54681f8e3b0537a40973616b477b43d9a8ed992f7880daab7ea2997
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4afbf7a47d788d8045f45caf80b15eccd78913cc94587e1f15b68970bfc9ac8310ea45bd60ae9e51a923c086c50044afc6aac22c84b6d18af03fee57a6b75c7f
|
7
|
+
data.tar.gz: 986a4b84c02f84eb7bc0ebbbff537beafba3562771dc055053e9ca9ca8af2c5f53c2b4b52a65b74a881d91ade7185c40dac35d11f6641f682d266555a689f894
|
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
|
}
|