extralite 1.19 → 1.21

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: 9b705a2d1c970cb3e73c784a6322ea92e8f7618531b4d8b9c2d4e036bf135b0f
4
+ data.tar.gz: fceaaf8976cac3d5e5328d64e8e76d07fe5979e4897ad7f20bb6295f697659b7
5
5
  SHA512:
6
- metadata.gz: ca3c24e4be2cedee6d20101ce56018ed9f037cbe29d4e5c6fb63116a77c988390992b770586a1973acc0c42a521389369ed79cf1553464e5f775fe420c9bf421
7
- data.tar.gz: f9ffb7dbe2a5d00ea2c15cca1407462897590f299acecf88de6a040f463f4f28715ae98ae34a6a0539f97b1cf2f12902803217517014661208b6238618013287
6
+ metadata.gz: c1b1bc996aa69b8f4abd4888a23a3aeae0aa15069b5d7c13027389e6165529186949ed8679571b55c00f3b76bd5360f396e874dd4c73f3d88cd8443c8931fca9
7
+ data.tar.gz: aca5d4470770f5c635d2d3b42a1897c45535efd29a38137c3164c07629f3f68a3cc1f74f95cef180efe06b9cb5423bb8fe95e183c7001c98a05baa0f3532718e
@@ -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,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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.19)
4
+ extralite (1.21)
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
 
data/Rakefile CHANGED
@@ -44,3 +44,8 @@ task :release do
44
44
  puts "Cleaning up..."
45
45
  `rm *.gem`
46
46
  end
47
+
48
+ task :build_bundled do
49
+ puts 'Building extralite-bundle...'
50
+ `gem build extralite-bundle.gemspec`
51
+ end
@@ -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
  }