AtlatestRepositorysigil-sqlite
sigil-sqlite / tree / nativesqlite.c
1
/*2
* Sigil SQLite Bindings3
*4
* Provides SQLite database access for Sigil applications.5
* Uses SQLite amalgamation for easy embedding.6
*/8
#include <sigil/sigil.h>9
#include "../vendor/sqlite3.h"10
#include <stdio.h>11
#include <stdlib.h>12
#include <string.h>14
/*15
* Database handle structure16
*/17
typedef struct {18
sqlite3 *db;19
int closed;20
/* Cooperative connection lock. A SQLite connection is a SERIAL resource:21
* its implicit read transaction ends only when no statement is active, so22
* two fibers interleaving on one connection can pin a snapshot. The23
* scheme layer holds this across a whole primitive, yields included.24
* Single-threaded cooperative runtime, so a plain int is atomic enough:25
* only a yield can interleave, and this is never held across one without26
* the holder intending it. */27
int locked;28
} SqliteDB;30
/*31
* Statement handle structure32
*/33
typedef struct {34
sqlite3_stmt *stmt;35
sqlite3 *db; /* Reference to parent database */36
int finalized;37
} SqliteStmt;39
/* Type tags for foreign objects */40
static Value db_type_tag = SIGIL_UNDEFINED;41
static Value stmt_type_tag = SIGIL_UNDEFINED;43
/*44
* Finalizer for database handles45
*/46
static void db_finalizer(void *data)47
{48
SqliteDB *handle = (SqliteDB *)data;49
if (handle && !handle->closed && handle->db) {50
sqlite3_close(handle->db);51
}52
free(handle);53
}55
/*56
* Finalizer for statement handles57
*/58
static void stmt_finalizer(void *data)59
{60
SqliteStmt *handle = (SqliteStmt *)data;61
if (handle && !handle->finalized && handle->stmt) {62
sqlite3_finalize(handle->stmt);63
}64
free(handle);65
}67
/*68
* Helper: Check if value is a database handle69
*/70
static int is_db(Value v)71
{72
if (!sigil_is_foreign(v)) return 0;73
return sigil_foreign_type(v) == db_type_tag;74
}76
/*77
* Helper: Check if value is a statement handle78
*/79
static int is_stmt(Value v)80
{81
if (!sigil_is_foreign(v)) return 0;82
return sigil_foreign_type(v) == stmt_type_tag;83
}85
/*86
* Helper: Get database data from value87
*/88
static SqliteDB *as_db(Value v)89
{90
return (SqliteDB *)sigil_foreign_data(v);91
}93
/*94
* Helper: Get statement data from value95
*/96
static SqliteStmt *as_stmt(Value v)97
{98
return (SqliteStmt *)sigil_foreign_data(v);99
}101
/*102
* Helper: Extract null-terminated string from Sigil string103
* Caller must free the returned string.104
*/105
static char *extract_string(Value v)106
{107
SigilString *s = (SigilString *)sigil_as_ptr(v);108
char *result = malloc(s->byte_length + 1);109
if (!result) return NULL;110
memcpy(result, s->data, s->byte_length);111
result[s->byte_length] = '\0';112
return result;113
}115
/*116
* sqlite-open path -> db | #f117
* Open a database file. Creates if it doesn't exist.118
*/119
static Value native_sqlite_open(SigilVM *vm, int argc, Value *args)120
{121
(void)argc;123
if (!sigil_is_string(args[0])) {124
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-open: expected string for path");125
return SIGIL_UNDEFINED;126
}128
char *path = extract_string(args[0]);129
if (!path) return SIGIL_FALSE;131
sqlite3 *db;132
int rc = sqlite3_open(path, &db);133
free(path);135
if (rc != SQLITE_OK) {136
if (db) sqlite3_close(db);137
return SIGIL_FALSE;138
}140
SqliteDB *handle = malloc(sizeof(SqliteDB));141
if (!handle) {142
sqlite3_close(db);143
return SIGIL_FALSE;144
}146
handle->db = db;147
handle->closed = 0;148
handle->locked = 0;150
return sigil_make_foreign(vm, db_type_tag, handle, db_finalizer, sizeof(SqliteDB));151
}153
/*154
* sqlite-close db -> boolean155
* Close a database connection.156
*/157
static Value native_sqlite_close(SigilVM *vm, int argc, Value *args)158
{159
(void)argc;161
if (!is_db(args[0])) {162
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-close: expected database handle");163
return SIGIL_UNDEFINED;164
}166
SqliteDB *handle = as_db(args[0]);167
if (handle->closed) {168
return SIGIL_TRUE; /* Already closed */169
}171
int rc = sqlite3_close(handle->db);172
if (rc != SQLITE_OK) {173
return SIGIL_FALSE;174
}176
handle->db = NULL;177
handle->closed = 1;178
return SIGIL_TRUE;179
}181
/*182
* sqlite-db? value -> boolean183
* Check if value is a database handle.184
*/185
static Value native_sqlite_dbp(SigilVM *vm, int argc, Value *args)186
{187
(void)vm;188
(void)argc;189
return sigil_bool(is_db(args[0]));190
}192
/*193
* sqlite-stmt? value -> boolean194
* Check if value is a statement handle.195
*/196
static Value native_sqlite_stmtp(SigilVM *vm, int argc, Value *args)197
{198
(void)vm;199
(void)argc;200
return sigil_bool(is_stmt(args[0]));201
}203
/*204
* sqlite-exec db sql -> boolean205
* Execute SQL that doesn't return rows (CREATE, INSERT, UPDATE, DELETE).206
*/207
static Value native_sqlite_exec(SigilVM *vm, int argc, Value *args)208
{209
(void)argc;211
if (!is_db(args[0])) {212
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-exec: expected database handle");213
return SIGIL_UNDEFINED;214
}215
if (!sigil_is_string(args[1])) {216
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-exec: expected string for SQL");217
return SIGIL_UNDEFINED;218
}220
SqliteDB *handle = as_db(args[0]);221
if (handle->closed) {222
sigil__vm_error(vm, SIGIL_ERR_IO, "sqlite-exec: database is closed");223
return SIGIL_UNDEFINED;224
}226
char *sql = extract_string(args[1]);227
if (!sql) return SIGIL_FALSE;229
/* Force non-blocking: never let sqlite3_exec block the single cooperative230
* scheduler thread waiting on a lock. A contended lock returns SQLITE_BUSY231
* immediately, which we surface as the symbol 'busy so the Scheme wrapper232
* can yield to the scheduler + retry cooperatively (letting the lock holder233
* run and commit). This overrides any PRAGMA busy_timeout the caller set —234
* that intent is honored by the wrapper's retry budget instead. */235
sqlite3_busy_timeout(handle->db, 0);237
char *errmsg = NULL;238
int rc = sqlite3_exec(handle->db, sql, NULL, NULL, &errmsg);239
free(sql);240
if (errmsg) sqlite3_free(errmsg);242
if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {243
return sigil_intern_symbol(vm, "busy", 4);244
}245
if (rc != SQLITE_OK) {246
return SIGIL_FALSE;247
}248
return SIGIL_TRUE;249
}251
/*252
* sqlite-prepare db sql -> stmt | #f253
* Prepare a SQL statement for execution.254
*/255
static Value native_sqlite_prepare(SigilVM *vm, int argc, Value *args)256
{257
(void)argc;259
if (!is_db(args[0])) {260
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-prepare: expected database handle");261
return SIGIL_UNDEFINED;262
}263
if (!sigil_is_string(args[1])) {264
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-prepare: expected string for SQL");265
return SIGIL_UNDEFINED;266
}268
SqliteDB *db_handle = as_db(args[0]);269
if (db_handle->closed) {270
sigil__vm_error(vm, SIGIL_ERR_IO, "sqlite-prepare: database is closed");271
return SIGIL_UNDEFINED;272
}274
SigilString *sql_str = (SigilString *)sigil_as_ptr(args[1]);276
sqlite3_stmt *stmt;277
int rc = sqlite3_prepare_v2(db_handle->db, sql_str->data, sql_str->byte_length,278
&stmt, NULL);280
if (rc != SQLITE_OK || !stmt) {281
return SIGIL_FALSE;282
}284
SqliteStmt *handle = malloc(sizeof(SqliteStmt));285
if (!handle) {286
sqlite3_finalize(stmt);287
return SIGIL_FALSE;288
}290
handle->stmt = stmt;291
handle->db = db_handle->db;292
handle->finalized = 0;294
return sigil_make_foreign(vm, stmt_type_tag, handle, stmt_finalizer, sizeof(SqliteStmt));295
}297
/*298
* sqlite-bind stmt index value -> boolean299
* Bind a value to a parameter in a prepared statement.300
* Index is 1-based.301
*/302
static Value native_sqlite_bind(SigilVM *vm, int argc, Value *args)303
{304
(void)argc;306
if (!is_stmt(args[0])) {307
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-bind: expected statement handle");308
return SIGIL_UNDEFINED;309
}310
if (!sigil_is_fixnum(args[1])) {311
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-bind: expected integer for index");312
return SIGIL_UNDEFINED;313
}315
SqliteStmt *handle = as_stmt(args[0]);316
if (handle->finalized) {317
sigil__vm_error(vm, SIGIL_ERR_IO, "sqlite-bind: statement is finalized");318
return SIGIL_UNDEFINED;319
}321
int index = (int)sigil_as_fixnum(args[1]);322
Value value = args[2];323
int rc;325
if (sigil_is_null(value) || sigil_is_false(value)) {326
/* Bind NULL */327
rc = sqlite3_bind_null(handle->stmt, index);328
} else if (sigil_is_fixnum(value)) {329
/* Bind integer */330
rc = sqlite3_bind_int64(handle->stmt, index, sigil_as_fixnum(value));331
} else if (sigil_is_flonum(value)) {332
/* Bind double */333
rc = sqlite3_bind_double(handle->stmt, index, sigil_as_flonum(value));334
} else if (sigil_is_string(value)) {335
/* Bind text */336
SigilString *s = (SigilString *)sigil_as_ptr(value);337
rc = sqlite3_bind_text(handle->stmt, index, s->data, s->byte_length,338
SQLITE_TRANSIENT);339
} else if (sigil_is_bytevector(value)) {340
/* Bind blob */341
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(value);342
rc = sqlite3_bind_blob(handle->stmt, index, bv->data, bv->length,343
SQLITE_TRANSIENT);344
} else {345
sigil__vm_error(vm, SIGIL_ERR_TYPE,346
"sqlite-bind: value must be null, boolean, integer, real, string, or bytevector");347
return SIGIL_UNDEFINED;348
}350
return sigil_bool(rc == SQLITE_OK);351
}353
/*354
* sqlite-step stmt -> 'row | 'done | #f355
* Execute one step of a prepared statement.356
* Returns 'row if a row is available, 'done if finished, #f on error.357
*/358
static Value native_sqlite_step(SigilVM *vm, int argc, Value *args)359
{360
(void)argc;362
if (!is_stmt(args[0])) {363
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-step: expected statement handle");364
return SIGIL_UNDEFINED;365
}367
SqliteStmt *handle = as_stmt(args[0]);368
if (handle->finalized) {369
sigil__vm_error(vm, SIGIL_ERR_IO, "sqlite-step: statement is finalized");370
return SIGIL_UNDEFINED;371
}373
/* Force non-blocking (see native_sqlite_exec): a contended lock surfaces as374
* 'busy for the Scheme wrapper to yield + retry, rather than blocking the375
* scheduler thread inside sqlite3_step. */376
sqlite3_busy_timeout(sqlite3_db_handle(handle->stmt), 0);378
int rc = sqlite3_step(handle->stmt);380
if (rc == SQLITE_ROW) {381
return sigil_intern_symbol(vm, "row", 3);382
} else if (rc == SQLITE_DONE) {383
return sigil_intern_symbol(vm, "done", 4);384
} else if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {385
return sigil_intern_symbol(vm, "busy", 4);386
} else {387
return SIGIL_FALSE;388
}389
}391
/*392
* sqlite-column-count stmt -> integer393
* Get the number of columns in the result set.394
*/395
static Value native_sqlite_column_count(SigilVM *vm, int argc, Value *args)396
{397
(void)argc;399
if (!is_stmt(args[0])) {400
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column-count: expected statement handle");401
return SIGIL_UNDEFINED;402
}404
SqliteStmt *handle = as_stmt(args[0]);405
if (handle->finalized) {406
return sigil_fixnum(0);407
}409
return sigil_fixnum(sqlite3_column_count(handle->stmt));410
}412
/*413
* sqlite-column-name stmt index -> string414
* Get the name of a column (0-based index).415
*/416
static Value native_sqlite_column_name(SigilVM *vm, int argc, Value *args)417
{418
(void)argc;420
if (!is_stmt(args[0])) {421
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column-name: expected statement handle");422
return SIGIL_UNDEFINED;423
}424
if (!sigil_is_fixnum(args[1])) {425
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column-name: expected integer for index");426
return SIGIL_UNDEFINED;427
}429
SqliteStmt *handle = as_stmt(args[0]);430
if (handle->finalized) {431
return SIGIL_FALSE;432
}434
int index = (int)sigil_as_fixnum(args[1]);435
const char *name = sqlite3_column_name(handle->stmt, index);437
if (!name) return SIGIL_FALSE;438
return sigil_make_string(vm, name, strlen(name));439
}441
/*442
* sqlite-column stmt index -> value443
* Get the value of a column in the current row (0-based index).444
* Returns appropriate Sigil type based on SQLite column type.445
*/446
static Value native_sqlite_column(SigilVM *vm, int argc, Value *args)447
{448
(void)argc;450
if (!is_stmt(args[0])) {451
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column: expected statement handle");452
return SIGIL_UNDEFINED;453
}454
if (!sigil_is_fixnum(args[1])) {455
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column: expected integer for index");456
return SIGIL_UNDEFINED;457
}459
SqliteStmt *handle = as_stmt(args[0]);460
if (handle->finalized) {461
return SIGIL_FALSE;462
}464
int index = (int)sigil_as_fixnum(args[1]);465
int type = sqlite3_column_type(handle->stmt, index);467
switch (type) {468
case SQLITE_NULL:469
return SIGIL_FALSE; /* Use #f for NULL */471
case SQLITE_INTEGER:472
return sigil_fixnum(sqlite3_column_int64(handle->stmt, index));474
case SQLITE_FLOAT:475
return sigil_flonum(sqlite3_column_double(handle->stmt, index));477
case SQLITE_TEXT: {478
const char *text = (const char *)sqlite3_column_text(handle->stmt, index);479
int len = sqlite3_column_bytes(handle->stmt, index);480
return sigil_make_string(vm, text, len);481
}483
case SQLITE_BLOB: {484
const void *data = sqlite3_column_blob(handle->stmt, index);485
int len = sqlite3_column_bytes(handle->stmt, index);486
Value bv = sigil_make_bytevector(vm, (size_t)len);487
if (sigil_is_bytevector(bv)) {488
memcpy(sigil_bytevector_data(bv), data, len);489
}490
return bv;491
}493
default:494
return SIGIL_FALSE;495
}496
}498
/*499
* sqlite-reset stmt -> boolean500
* Reset a prepared statement to its initial state.501
*/502
static Value native_sqlite_reset(SigilVM *vm, int argc, Value *args)503
{504
(void)argc;506
if (!is_stmt(args[0])) {507
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-reset: expected statement handle");508
return SIGIL_UNDEFINED;509
}511
SqliteStmt *handle = as_stmt(args[0]);512
if (handle->finalized) {513
return SIGIL_FALSE;514
}516
int rc = sqlite3_reset(handle->stmt);517
return sigil_bool(rc == SQLITE_OK);518
}520
/*521
* sqlite-finalize stmt -> boolean522
* Finalize a prepared statement, freeing resources.523
*/524
static Value native_sqlite_finalize(SigilVM *vm, int argc, Value *args)525
{526
(void)argc;528
if (!is_stmt(args[0])) {529
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-finalize: expected statement handle");530
return SIGIL_UNDEFINED;531
}533
SqliteStmt *handle = as_stmt(args[0]);534
if (handle->finalized) {535
return SIGIL_TRUE; /* Already finalized */536
}538
int rc = sqlite3_finalize(handle->stmt);539
handle->stmt = NULL;540
handle->finalized = 1;542
return sigil_bool(rc == SQLITE_OK);543
}545
/*546
* sqlite-last-insert-rowid db -> integer547
* Get the rowid of the last inserted row.548
*/549
static Value native_sqlite_last_insert_rowid(SigilVM *vm, int argc, Value *args)550
{551
(void)argc;553
if (!is_db(args[0])) {554
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-last-insert-rowid: expected database handle");555
return SIGIL_UNDEFINED;556
}558
SqliteDB *handle = as_db(args[0]);559
if (handle->closed) {560
return sigil_fixnum(0);561
}563
return sigil_fixnum(sqlite3_last_insert_rowid(handle->db));564
}566
/*567
* sqlite-changes db -> integer568
* Get the number of rows changed by the last statement.569
*/570
static Value native_sqlite_changes(SigilVM *vm, int argc, Value *args)571
{572
(void)argc;574
if (!is_db(args[0])) {575
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-changes: expected database handle");576
return SIGIL_UNDEFINED;577
}579
SqliteDB *handle = as_db(args[0]);580
if (handle->closed) {581
return sigil_fixnum(0);582
}584
return sigil_fixnum(sqlite3_changes(handle->db));585
}587
/*588
* sqlite-errmsg db -> string589
* Get the error message for the most recent error.590
*/591
static Value native_sqlite_errmsg(SigilVM *vm, int argc, Value *args)592
{593
(void)argc;595
if (!is_db(args[0])) {596
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-errmsg: expected database handle");597
return SIGIL_UNDEFINED;598
}600
SqliteDB *handle = as_db(args[0]);601
if (handle->closed) {602
return sigil_make_string(vm, "database is closed", 18);603
}605
const char *msg = sqlite3_errmsg(handle->db);606
return sigil_make_string(vm, msg, strlen(msg));607
}609
/*610
* %sqlite-try-acquire! db -> boolean611
* %sqlite-release! db -> unspecified612
*613
* A cooperative, per-connection mutual-exclusion flag. NOT a thread lock: the614
* runtime is single-threaded, so a plain int cannot be torn. Its whole job is615
* to stop two FIBERS interleaving inside one connection.616
*617
* Why a connection needs one at all: SQLite ends a connection's implicit read618
* transaction only when no statement on it is active. If fiber A has a619
* statement open (parked in the busy-retry, or simply mid-result-set) and620
* fiber B runs a query on the same connection, B's read transaction cannot be621
* released when B finalizes -- A is still using the btree. The connection is622
* then pinned to B's snapshot until A completes, and every later read on it623
* misses any commit made in between. See docs/concurrency.md.624
*/625
static Value native_sqlite_try_acquire(SigilVM *vm, int argc, Value *args)626
{627
(void)argc;628
if (!is_db(args[0])) {629
sigil__vm_error(vm, SIGIL_ERR_TYPE, "%sqlite-try-acquire!: expected database handle");630
return SIGIL_UNDEFINED;631
}632
SqliteDB *handle = as_db(args[0]);633
if (handle->locked) return SIGIL_FALSE;634
handle->locked = 1;635
return SIGIL_TRUE;636
}638
static Value native_sqlite_release(SigilVM *vm, int argc, Value *args)639
{640
(void)argc;641
if (!is_db(args[0])) {642
sigil__vm_error(vm, SIGIL_ERR_TYPE, "%sqlite-release!: expected database handle");643
return SIGIL_UNDEFINED;644
}645
as_db(args[0])->locked = 0;646
return SIGIL_TRUE;647
}649
/*650
* sqlite-connection-locked? db -> boolean651
* Diagnostic only: is a fiber currently holding this connection?652
*/653
static Value native_sqlite_connection_locked(SigilVM *vm, int argc, Value *args)654
{655
(void)argc;656
if (!is_db(args[0])) {657
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-connection-locked?: expected database handle");658
return SIGIL_UNDEFINED;659
}660
return sigil_bool(as_db(args[0])->locked != 0);661
}663
/*664
* Helper macro for module-scoped registration with export665
*/666
#define REGISTER_AND_EXPORT(name, func, arity, doc) \667
sigil_module_register_native(vm, name, func, arity, doc); \668
sigil_module_export(vm, name)670
/*671
* Initialize the (sigil sqlite) module.672
*/673
void sigil__init_sigil_sqlite_module(SigilVM *vm)674
{675
/* Initialize type tags */676
db_type_tag = sigil_intern_symbol(vm, "sqlite-db", 9);677
stmt_type_tag = sigil_intern_symbol(vm, "sqlite-stmt", 11);679
SigilModule *module = sigil_begin_module(vm, "(sigil sqlite)");680
if (!module) return;682
/* Type predicates */683
REGISTER_AND_EXPORT("sqlite-db?", native_sqlite_dbp,684
SIGIL_ARITY_EXACT(1), "Check if value is a database handle");685
REGISTER_AND_EXPORT("sqlite-stmt?", native_sqlite_stmtp,686
SIGIL_ARITY_EXACT(1), "Check if value is a statement handle");688
/* Database operations */689
REGISTER_AND_EXPORT("sqlite-open", native_sqlite_open,690
SIGIL_ARITY_EXACT(1), "Open database file");691
REGISTER_AND_EXPORT("sqlite-close", native_sqlite_close,692
SIGIL_ARITY_EXACT(1), "Close database connection");693
/* Internal: returns 'busy on a contended lock (never blocks). The public694
* sqlite-exec (Scheme, in sqlite.sgl) wraps this with cooperative retry. */695
REGISTER_AND_EXPORT("%sqlite-exec-native", native_sqlite_exec,696
SIGIL_ARITY_EXACT(2), "Execute SQL without results (non-blocking)");698
/* Prepared statements */699
REGISTER_AND_EXPORT("sqlite-prepare", native_sqlite_prepare,700
SIGIL_ARITY_EXACT(2), "Prepare SQL statement");701
REGISTER_AND_EXPORT("sqlite-bind", native_sqlite_bind,702
SIGIL_ARITY_EXACT(3), "Bind parameter value");703
/* Internal: returns 'busy on a contended lock (never blocks). The public704
* sqlite-step (Scheme, in sqlite.sgl) wraps this with cooperative retry. */705
REGISTER_AND_EXPORT("%sqlite-step-native", native_sqlite_step,706
SIGIL_ARITY_EXACT(1), "Execute statement step (non-blocking)");707
REGISTER_AND_EXPORT("sqlite-reset", native_sqlite_reset,708
SIGIL_ARITY_EXACT(1), "Reset statement to initial state");709
REGISTER_AND_EXPORT("sqlite-finalize", native_sqlite_finalize,710
SIGIL_ARITY_EXACT(1), "Finalize and free statement");712
/* Column access */713
REGISTER_AND_EXPORT("sqlite-column-count", native_sqlite_column_count,714
SIGIL_ARITY_EXACT(1), "Get number of result columns");715
REGISTER_AND_EXPORT("sqlite-column-name", native_sqlite_column_name,716
SIGIL_ARITY_EXACT(2), "Get column name");717
REGISTER_AND_EXPORT("sqlite-column", native_sqlite_column,718
SIGIL_ARITY_EXACT(2), "Get column value");720
/* Database info */721
REGISTER_AND_EXPORT("sqlite-last-insert-rowid", native_sqlite_last_insert_rowid,722
SIGIL_ARITY_EXACT(1), "Get last inserted rowid");723
REGISTER_AND_EXPORT("sqlite-changes", native_sqlite_changes,724
SIGIL_ARITY_EXACT(1), "Get rows changed by last statement");725
REGISTER_AND_EXPORT("sqlite-errmsg", native_sqlite_errmsg,726
SIGIL_ARITY_EXACT(1), "Get last error message");728
/* Cooperative per-connection lock (see docs/concurrency.md). */729
REGISTER_AND_EXPORT("%sqlite-try-acquire!", native_sqlite_try_acquire,730
SIGIL_ARITY_EXACT(1), "Try to acquire the connection");731
REGISTER_AND_EXPORT("%sqlite-release!", native_sqlite_release,732
SIGIL_ARITY_EXACT(1), "Release the connection");733
REGISTER_AND_EXPORT("sqlite-connection-locked?", native_sqlite_connection_locked,734
SIGIL_ARITY_EXACT(1), "Is a fiber holding this connection?");736
sigil_end_module(vm);737
}739
#undef REGISTER_AND_EXPORT