AtlatestRepositorysigil-sqlite
1/*
2 * Sigil SQLite Bindings
3 *
4 * Provides SQLite database access for Sigil applications.
5 * Uses SQLite amalgamation for easy embedding.
6 */
7
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 structure
16 */
17typedef struct {
18 sqlite3 *db;
19 int closed;
20} SqliteDB;
22/*
23 * Statement handle structure
24 */
25typedef struct {
26 sqlite3_stmt *stmt;
27 sqlite3 *db; /* Reference to parent database */
28 int finalized;
29} SqliteStmt;
31/* Type tags for foreign objects */
32static Value db_type_tag = SIGIL_UNDEFINED;
33static Value stmt_type_tag = SIGIL_UNDEFINED;
35/*
36 * Finalizer for database handles
37 */
38static void db_finalizer(void *data)
40 SqliteDB *handle = (SqliteDB *)data;
41 if (handle && !handle->closed && handle->db) {
42 sqlite3_close(handle->db);
43 }
44 free(handle);
47/*
48 * Finalizer for statement handles
49 */
50static void stmt_finalizer(void *data)
52 SqliteStmt *handle = (SqliteStmt *)data;
53 if (handle && !handle->finalized && handle->stmt) {
54 sqlite3_finalize(handle->stmt);
55 }
56 free(handle);
59/*
60 * Helper: Check if value is a database handle
61 */
62static int is_db(Value v)
64 if (!sigil_is_foreign(v)) return 0;
65 return sigil_foreign_type(v) == db_type_tag;
68/*
69 * Helper: Check if value is a statement handle
70 */
71static int is_stmt(Value v)
73 if (!sigil_is_foreign(v)) return 0;
74 return sigil_foreign_type(v) == stmt_type_tag;
77/*
78 * Helper: Get database data from value
79 */
80static SqliteDB *as_db(Value v)
82 return (SqliteDB *)sigil_foreign_data(v);
85/*
86 * Helper: Get statement data from value
87 */
88static SqliteStmt *as_stmt(Value v)
90 return (SqliteStmt *)sigil_foreign_data(v);
93/*
94 * Helper: Extract null-terminated string from Sigil string
95 * Caller must free the returned string.
96 */
97static char *extract_string(Value v)
99 SigilString *s = (SigilString *)sigil_as_ptr(v);
100 char *result = malloc(s->byte_length + 1);
101 if (!result) return NULL;
102 memcpy(result, s->data, s->byte_length);
103 result[s->byte_length] = '\0';
104 return result;
107/*
108 * sqlite-open path -> db | #f
109 * Open a database file. Creates if it doesn't exist.
110 */
111static Value native_sqlite_open(SigilVM *vm, int argc, Value *args)
113 (void)argc;
115 if (!sigil_is_string(args[0])) {
116 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-open: expected string for path");
117 return SIGIL_UNDEFINED;
118 }
120 char *path = extract_string(args[0]);
121 if (!path) return SIGIL_FALSE;
123 sqlite3 *db;
124 int rc = sqlite3_open(path, &db);
125 free(path);
127 if (rc != SQLITE_OK) {
128 if (db) sqlite3_close(db);
129 return SIGIL_FALSE;
130 }
132 SqliteDB *handle = malloc(sizeof(SqliteDB));
133 if (!handle) {
134 sqlite3_close(db);
135 return SIGIL_FALSE;
136 }
138 handle->db = db;
139 handle->closed = 0;
141 return sigil_make_foreign(vm, db_type_tag, handle, db_finalizer, sizeof(SqliteDB));
144/*
145 * sqlite-close db -> boolean
146 * Close a database connection.
147 */
148static Value native_sqlite_close(SigilVM *vm, int argc, Value *args)
150 (void)argc;
152 if (!is_db(args[0])) {
153 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-close: expected database handle");
154 return SIGIL_UNDEFINED;
155 }
157 SqliteDB *handle = as_db(args[0]);
158 if (handle->closed) {
159 return SIGIL_TRUE; /* Already closed */
160 }
162 int rc = sqlite3_close(handle->db);
163 if (rc != SQLITE_OK) {
164 return SIGIL_FALSE;
165 }
167 handle->db = NULL;
168 handle->closed = 1;
169 return SIGIL_TRUE;
172/*
173 * sqlite-db? value -> boolean
174 * Check if value is a database handle.
175 */
176static Value native_sqlite_dbp(SigilVM *vm, int argc, Value *args)
178 (void)vm;
179 (void)argc;
180 return sigil_bool(is_db(args[0]));
183/*
184 * sqlite-stmt? value -> boolean
185 * Check if value is a statement handle.
186 */
187static Value native_sqlite_stmtp(SigilVM *vm, int argc, Value *args)
189 (void)vm;
190 (void)argc;
191 return sigil_bool(is_stmt(args[0]));
194/*
195 * sqlite-exec db sql -> boolean
196 * Execute SQL that doesn't return rows (CREATE, INSERT, UPDATE, DELETE).
197 */
198static Value native_sqlite_exec(SigilVM *vm, int argc, Value *args)
200 (void)argc;
202 if (!is_db(args[0])) {
203 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-exec: expected database handle");
204 return SIGIL_UNDEFINED;
205 }
206 if (!sigil_is_string(args[1])) {
207 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-exec: expected string for SQL");
208 return SIGIL_UNDEFINED;
209 }
211 SqliteDB *handle = as_db(args[0]);
212 if (handle->closed) {
213 sigil__vm_error(vm, SIGIL_ERR_IO, "sqlite-exec: database is closed");
214 return SIGIL_UNDEFINED;
215 }
217 char *sql = extract_string(args[1]);
218 if (!sql) return SIGIL_FALSE;
220 /* Force non-blocking: never let sqlite3_exec block the single cooperative
221 * scheduler thread waiting on a lock. A contended lock returns SQLITE_BUSY
222 * immediately, which we surface as the symbol 'busy so the Scheme wrapper
223 * can yield to the scheduler + retry cooperatively (letting the lock holder
224 * run and commit). This overrides any PRAGMA busy_timeout the caller set —
225 * that intent is honored by the wrapper's retry budget instead. */
226 sqlite3_busy_timeout(handle->db, 0);
228 char *errmsg = NULL;
229 int rc = sqlite3_exec(handle->db, sql, NULL, NULL, &errmsg);
230 free(sql);
231 if (errmsg) sqlite3_free(errmsg);
233 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
234 return sigil_intern_symbol(vm, "busy", 4);
235 }
236 if (rc != SQLITE_OK) {
237 return SIGIL_FALSE;
238 }
239 return SIGIL_TRUE;
242/*
243 * sqlite-prepare db sql -> stmt | #f
244 * Prepare a SQL statement for execution.
245 */
246static Value native_sqlite_prepare(SigilVM *vm, int argc, Value *args)
248 (void)argc;
250 if (!is_db(args[0])) {
251 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-prepare: expected database handle");
252 return SIGIL_UNDEFINED;
253 }
254 if (!sigil_is_string(args[1])) {
255 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-prepare: expected string for SQL");
256 return SIGIL_UNDEFINED;
257 }
259 SqliteDB *db_handle = as_db(args[0]);
260 if (db_handle->closed) {
261 sigil__vm_error(vm, SIGIL_ERR_IO, "sqlite-prepare: database is closed");
262 return SIGIL_UNDEFINED;
263 }
265 SigilString *sql_str = (SigilString *)sigil_as_ptr(args[1]);
267 sqlite3_stmt *stmt;
268 int rc = sqlite3_prepare_v2(db_handle->db, sql_str->data, sql_str->byte_length,
269 &stmt, NULL);
271 if (rc != SQLITE_OK || !stmt) {
272 return SIGIL_FALSE;
273 }
275 SqliteStmt *handle = malloc(sizeof(SqliteStmt));
276 if (!handle) {
277 sqlite3_finalize(stmt);
278 return SIGIL_FALSE;
279 }
281 handle->stmt = stmt;
282 handle->db = db_handle->db;
283 handle->finalized = 0;
285 return sigil_make_foreign(vm, stmt_type_tag, handle, stmt_finalizer, sizeof(SqliteStmt));
288/*
289 * sqlite-bind stmt index value -> boolean
290 * Bind a value to a parameter in a prepared statement.
291 * Index is 1-based.
292 */
293static Value native_sqlite_bind(SigilVM *vm, int argc, Value *args)
295 (void)argc;
297 if (!is_stmt(args[0])) {
298 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-bind: expected statement handle");
299 return SIGIL_UNDEFINED;
300 }
301 if (!sigil_is_fixnum(args[1])) {
302 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-bind: expected integer for index");
303 return SIGIL_UNDEFINED;
304 }
306 SqliteStmt *handle = as_stmt(args[0]);
307 if (handle->finalized) {
308 sigil__vm_error(vm, SIGIL_ERR_IO, "sqlite-bind: statement is finalized");
309 return SIGIL_UNDEFINED;
310 }
312 int index = (int)sigil_as_fixnum(args[1]);
313 Value value = args[2];
314 int rc;
316 if (sigil_is_null(value) || sigil_is_false(value)) {
317 /* Bind NULL */
318 rc = sqlite3_bind_null(handle->stmt, index);
319 } else if (sigil_is_fixnum(value)) {
320 /* Bind integer */
321 rc = sqlite3_bind_int64(handle->stmt, index, sigil_as_fixnum(value));
322 } else if (sigil_is_flonum(value)) {
323 /* Bind double */
324 rc = sqlite3_bind_double(handle->stmt, index, sigil_as_flonum(value));
325 } else if (sigil_is_string(value)) {
326 /* Bind text */
327 SigilString *s = (SigilString *)sigil_as_ptr(value);
328 rc = sqlite3_bind_text(handle->stmt, index, s->data, s->byte_length,
329 SQLITE_TRANSIENT);
330 } else if (sigil_is_bytevector(value)) {
331 /* Bind blob */
332 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(value);
333 rc = sqlite3_bind_blob(handle->stmt, index, bv->data, bv->length,
334 SQLITE_TRANSIENT);
335 } else {
336 sigil__vm_error(vm, SIGIL_ERR_TYPE,
337 "sqlite-bind: value must be null, boolean, integer, real, string, or bytevector");
338 return SIGIL_UNDEFINED;
339 }
341 return sigil_bool(rc == SQLITE_OK);
344/*
345 * sqlite-step stmt -> 'row | 'done | #f
346 * Execute one step of a prepared statement.
347 * Returns 'row if a row is available, 'done if finished, #f on error.
348 */
349static Value native_sqlite_step(SigilVM *vm, int argc, Value *args)
351 (void)argc;
353 if (!is_stmt(args[0])) {
354 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-step: expected statement handle");
355 return SIGIL_UNDEFINED;
356 }
358 SqliteStmt *handle = as_stmt(args[0]);
359 if (handle->finalized) {
360 sigil__vm_error(vm, SIGIL_ERR_IO, "sqlite-step: statement is finalized");
361 return SIGIL_UNDEFINED;
362 }
364 /* Force non-blocking (see native_sqlite_exec): a contended lock surfaces as
365 * 'busy for the Scheme wrapper to yield + retry, rather than blocking the
366 * scheduler thread inside sqlite3_step. */
367 sqlite3_busy_timeout(sqlite3_db_handle(handle->stmt), 0);
369 int rc = sqlite3_step(handle->stmt);
371 if (rc == SQLITE_ROW) {
372 return sigil_intern_symbol(vm, "row", 3);
373 } else if (rc == SQLITE_DONE) {
374 return sigil_intern_symbol(vm, "done", 4);
375 } else if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
376 return sigil_intern_symbol(vm, "busy", 4);
377 } else {
378 return SIGIL_FALSE;
379 }
382/*
383 * sqlite-column-count stmt -> integer
384 * Get the number of columns in the result set.
385 */
386static Value native_sqlite_column_count(SigilVM *vm, int argc, Value *args)
388 (void)argc;
390 if (!is_stmt(args[0])) {
391 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column-count: expected statement handle");
392 return SIGIL_UNDEFINED;
393 }
395 SqliteStmt *handle = as_stmt(args[0]);
396 if (handle->finalized) {
397 return sigil_fixnum(0);
398 }
400 return sigil_fixnum(sqlite3_column_count(handle->stmt));
403/*
404 * sqlite-column-name stmt index -> string
405 * Get the name of a column (0-based index).
406 */
407static Value native_sqlite_column_name(SigilVM *vm, int argc, Value *args)
409 (void)argc;
411 if (!is_stmt(args[0])) {
412 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column-name: expected statement handle");
413 return SIGIL_UNDEFINED;
414 }
415 if (!sigil_is_fixnum(args[1])) {
416 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column-name: expected integer for index");
417 return SIGIL_UNDEFINED;
418 }
420 SqliteStmt *handle = as_stmt(args[0]);
421 if (handle->finalized) {
422 return SIGIL_FALSE;
423 }
425 int index = (int)sigil_as_fixnum(args[1]);
426 const char *name = sqlite3_column_name(handle->stmt, index);
428 if (!name) return SIGIL_FALSE;
429 return sigil_make_string(vm, name, strlen(name));
432/*
433 * sqlite-column stmt index -> value
434 * Get the value of a column in the current row (0-based index).
435 * Returns appropriate Sigil type based on SQLite column type.
436 */
437static Value native_sqlite_column(SigilVM *vm, int argc, Value *args)
439 (void)argc;
441 if (!is_stmt(args[0])) {
442 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column: expected statement handle");
443 return SIGIL_UNDEFINED;
444 }
445 if (!sigil_is_fixnum(args[1])) {
446 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-column: expected integer for index");
447 return SIGIL_UNDEFINED;
448 }
450 SqliteStmt *handle = as_stmt(args[0]);
451 if (handle->finalized) {
452 return SIGIL_FALSE;
453 }
455 int index = (int)sigil_as_fixnum(args[1]);
456 int type = sqlite3_column_type(handle->stmt, index);
458 switch (type) {
459 case SQLITE_NULL:
460 return SIGIL_FALSE; /* Use #f for NULL */
462 case SQLITE_INTEGER:
463 return sigil_fixnum(sqlite3_column_int64(handle->stmt, index));
465 case SQLITE_FLOAT:
466 return sigil_flonum(sqlite3_column_double(handle->stmt, index));
468 case SQLITE_TEXT: {
469 const char *text = (const char *)sqlite3_column_text(handle->stmt, index);
470 int len = sqlite3_column_bytes(handle->stmt, index);
471 return sigil_make_string(vm, text, len);
472 }
474 case SQLITE_BLOB: {
475 const void *data = sqlite3_column_blob(handle->stmt, index);
476 int len = sqlite3_column_bytes(handle->stmt, index);
477 Value bv = sigil_make_bytevector(vm, (size_t)len);
478 if (sigil_is_bytevector(bv)) {
479 memcpy(sigil_bytevector_data(bv), data, len);
480 }
481 return bv;
482 }
484 default:
485 return SIGIL_FALSE;
486 }
489/*
490 * sqlite-reset stmt -> boolean
491 * Reset a prepared statement to its initial state.
492 */
493static Value native_sqlite_reset(SigilVM *vm, int argc, Value *args)
495 (void)argc;
497 if (!is_stmt(args[0])) {
498 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-reset: expected statement handle");
499 return SIGIL_UNDEFINED;
500 }
502 SqliteStmt *handle = as_stmt(args[0]);
503 if (handle->finalized) {
504 return SIGIL_FALSE;
505 }
507 int rc = sqlite3_reset(handle->stmt);
508 return sigil_bool(rc == SQLITE_OK);
511/*
512 * sqlite-finalize stmt -> boolean
513 * Finalize a prepared statement, freeing resources.
514 */
515static Value native_sqlite_finalize(SigilVM *vm, int argc, Value *args)
517 (void)argc;
519 if (!is_stmt(args[0])) {
520 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-finalize: expected statement handle");
521 return SIGIL_UNDEFINED;
522 }
524 SqliteStmt *handle = as_stmt(args[0]);
525 if (handle->finalized) {
526 return SIGIL_TRUE; /* Already finalized */
527 }
529 int rc = sqlite3_finalize(handle->stmt);
530 handle->stmt = NULL;
531 handle->finalized = 1;
533 return sigil_bool(rc == SQLITE_OK);
536/*
537 * sqlite-last-insert-rowid db -> integer
538 * Get the rowid of the last inserted row.
539 */
540static Value native_sqlite_last_insert_rowid(SigilVM *vm, int argc, Value *args)
542 (void)argc;
544 if (!is_db(args[0])) {
545 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-last-insert-rowid: expected database handle");
546 return SIGIL_UNDEFINED;
547 }
549 SqliteDB *handle = as_db(args[0]);
550 if (handle->closed) {
551 return sigil_fixnum(0);
552 }
554 return sigil_fixnum(sqlite3_last_insert_rowid(handle->db));
557/*
558 * sqlite-changes db -> integer
559 * Get the number of rows changed by the last statement.
560 */
561static Value native_sqlite_changes(SigilVM *vm, int argc, Value *args)
563 (void)argc;
565 if (!is_db(args[0])) {
566 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-changes: expected database handle");
567 return SIGIL_UNDEFINED;
568 }
570 SqliteDB *handle = as_db(args[0]);
571 if (handle->closed) {
572 return sigil_fixnum(0);
573 }
575 return sigil_fixnum(sqlite3_changes(handle->db));
578/*
579 * sqlite-errmsg db -> string
580 * Get the error message for the most recent error.
581 */
582static Value native_sqlite_errmsg(SigilVM *vm, int argc, Value *args)
584 (void)argc;
586 if (!is_db(args[0])) {
587 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sqlite-errmsg: expected database handle");
588 return SIGIL_UNDEFINED;
589 }
591 SqliteDB *handle = as_db(args[0]);
592 if (handle->closed) {
593 return sigil_make_string(vm, "database is closed", 18);
594 }
596 const char *msg = sqlite3_errmsg(handle->db);
597 return sigil_make_string(vm, msg, strlen(msg));
600/*
601 * Helper macro for module-scoped registration with export
602 */
603#define REGISTER_AND_EXPORT(name, func, arity, doc) \
604 sigil_module_register_native(vm, name, func, arity, doc); \
605 sigil_module_export(vm, name)
607/*
608 * Initialize the (sigil sqlite) module.
609 */
610void sigil__init_sigil_sqlite_module(SigilVM *vm)
612 /* Initialize type tags */
613 db_type_tag = sigil_intern_symbol(vm, "sqlite-db", 9);
614 stmt_type_tag = sigil_intern_symbol(vm, "sqlite-stmt", 11);
616 SigilModule *module = sigil_begin_module(vm, "(sigil sqlite)");
617 if (!module) return;
619 /* Type predicates */
620 REGISTER_AND_EXPORT("sqlite-db?", native_sqlite_dbp,
621 SIGIL_ARITY_EXACT(1), "Check if value is a database handle");
622 REGISTER_AND_EXPORT("sqlite-stmt?", native_sqlite_stmtp,
623 SIGIL_ARITY_EXACT(1), "Check if value is a statement handle");
625 /* Database operations */
626 REGISTER_AND_EXPORT("sqlite-open", native_sqlite_open,
627 SIGIL_ARITY_EXACT(1), "Open database file");
628 REGISTER_AND_EXPORT("sqlite-close", native_sqlite_close,
629 SIGIL_ARITY_EXACT(1), "Close database connection");
630 /* Internal: returns 'busy on a contended lock (never blocks). The public
631 * sqlite-exec (Scheme, in sqlite.sgl) wraps this with cooperative retry. */
632 REGISTER_AND_EXPORT("%sqlite-exec-native", native_sqlite_exec,
633 SIGIL_ARITY_EXACT(2), "Execute SQL without results (non-blocking)");
635 /* Prepared statements */
636 REGISTER_AND_EXPORT("sqlite-prepare", native_sqlite_prepare,
637 SIGIL_ARITY_EXACT(2), "Prepare SQL statement");
638 REGISTER_AND_EXPORT("sqlite-bind", native_sqlite_bind,
639 SIGIL_ARITY_EXACT(3), "Bind parameter value");
640 /* Internal: returns 'busy on a contended lock (never blocks). The public
641 * sqlite-step (Scheme, in sqlite.sgl) wraps this with cooperative retry. */
642 REGISTER_AND_EXPORT("%sqlite-step-native", native_sqlite_step,
643 SIGIL_ARITY_EXACT(1), "Execute statement step (non-blocking)");
644 REGISTER_AND_EXPORT("sqlite-reset", native_sqlite_reset,
645 SIGIL_ARITY_EXACT(1), "Reset statement to initial state");
646 REGISTER_AND_EXPORT("sqlite-finalize", native_sqlite_finalize,
647 SIGIL_ARITY_EXACT(1), "Finalize and free statement");
649 /* Column access */
650 REGISTER_AND_EXPORT("sqlite-column-count", native_sqlite_column_count,
651 SIGIL_ARITY_EXACT(1), "Get number of result columns");
652 REGISTER_AND_EXPORT("sqlite-column-name", native_sqlite_column_name,
653 SIGIL_ARITY_EXACT(2), "Get column name");
654 REGISTER_AND_EXPORT("sqlite-column", native_sqlite_column,
655 SIGIL_ARITY_EXACT(2), "Get column value");
657 /* Database info */
658 REGISTER_AND_EXPORT("sqlite-last-insert-rowid", native_sqlite_last_insert_rowid,
659 SIGIL_ARITY_EXACT(1), "Get last inserted rowid");
660 REGISTER_AND_EXPORT("sqlite-changes", native_sqlite_changes,
661 SIGIL_ARITY_EXACT(1), "Get rows changed by last statement");
662 REGISTER_AND_EXPORT("sqlite-errmsg", native_sqlite_errmsg,
663 SIGIL_ARITY_EXACT(1), "Get last error message");
665 sigil_end_module(vm);
668#undef REGISTER_AND_EXPORT