SQLiteLib — SQLite Database Library
Comprehensive SQLite database support for Plan9Basic. Create, query, and manage databases with JSON integration, transaction support, and table introspection. Depends on JsonLib. 55+ functions.
| Category | Count | Description |
|---|---|---|
| Error Handling | 4 | sql_error, sql_errormsg$, sql_strerror$, sql_clearerror |
| Connection | 6 | sql_open# (2 overloads), sql_close, sql_isopen, sql_path$, sql_version$ |
| SQL Execution | 4 | sql_exec (2 overloads), sql_query# (2 overloads) |
| Statement Mgmt | 5 | sql_prepare#, sql_step, sql_reset#, sql_finalize, sql_eof |
| Parameter Binding | 5 | sql_bindstr#, sql_bindnum#, sql_bindnull#, sql_bindjson#, sql_clearbind# |
| Column Info | 5 | sql_colcount, sql_colname$, sql_coltype, sql_coltypename$, sql_colindex |
| Results (by Index) | 4 | sql_getstr$, sql_getnum, sql_isnull, sql_isblob |
| Results (by Name) | 3 | sql_gets$, sql_getn, sql_isn |
| JSON Integration | 5 | sql_row#, sql_fetchall#, sql_fetchone#, sql_insertjson, sql_updatejson |
| Transactions | 4 | sql_begin, sql_commit, sql_rollback, sql_intrans |
| Utility | 8 | sql_lastid, sql_changes, sql_totalchanges, sql_tableexists, sql_tables#, sql_columns#, sql_backup, sql_vacuum |
| String Helpers | 2 | sql_escape$, sql_quote$ |
Memory Management
| Object | Managed By | Notes |
|---|---|---|
| Database connections | Garbage collector | Automatically freed on scope exit |
| Statements / cursors | Parent connection | Not in GC — freed when connection closes |
| JSON results | Garbage collector | Safe to use after cursor is finalized |
ⓘ Note: When a connection is freed, all its statements are automatically freed. This parent-child model prevents double-free issues.
Error Handling
| Code | Constant | Description |
|---|---|---|
| 0 | SQL_ERR_NONE | No error |
| 1 | SQL_ERR_NOT_OPEN | Database not open |
| 2 | SQL_ERR_INVALID_CONN | Invalid connection |
| 3 | SQL_ERR_INVALID_STMT | Invalid statement |
| 4 | SQL_ERR_EXEC_FAILED | SQL execution failed |
| 5 | SQL_ERR_QUERY_FAILED | Query execution failed |
| 6 | SQL_ERR_PREPARE_FAIL | Statement preparation failed |
| 7 | SQL_ERR_BIND_FAILED | Parameter binding failed |
| 8 | SQL_ERR_STEP_FAILED | Step execution failed |
| 9 | SQL_ERR_COLUMN_INDEX | Column index out of bounds |
| 10 | SQL_ERR_COLUMN_NAME | Column not found |
| 11 | SQL_ERR_TRANSACTION | Transaction error |
| 12 | SQL_ERR_FILE_ERROR | File operation error |
| 13 | SQL_ERR_JSON_INVALID | Invalid JSON data |
| 14 | SQL_ERR_TABLE_ERROR | Table operation error |
| 15 | SQL_ERR_BACKUP_FAILED | Database backup failed |
| Function | Signature | Description |
|---|---|---|
sql_error() | sql_error@ | Last error code (0–15) |
sql_errormsg$() | sql_errormsg$@ | Last error message |
sql_strerror$(code) | sql_strerror$@n | Description for error code |
sql_clearerror() | sql_clearerror@ | Clear error state |
| Code | Column Type |
|---|---|
| 0 | NULL |
| 1 | INTEGER |
| 2 | FLOAT |
| 3 | TEXT |
| 4 | BLOB |
| Code | Step Result |
|---|---|
| 1 | SQL_STEP_ROW — row available |
| 0 | SQL_STEP_DONE — no more rows |
| -1 | SQL_STEP_ERROR — error |
Connection Management
| Function | Signature | Description |
|---|---|---|
sql_open#(path$) | sql_open#@$ | Open/create database file |
sql_open#() | sql_open#@ | Open in-memory database |
sql_close(db#) | sql_close@# | Close connection |
sql_isopen(db#) | sql_isopen@# | Database open? (1/0) |
sql_path$(db#) | sql_path$@# | Database file path |
sql_version$() | sql_version$@ | SQLite version string |
let db# = sql_open#("mydata.db") if PntToNum(db#) = 0 then println "Failed: " + sql_errormsg$() endif println "SQLite: " + sql_version$() ' In-memory database let memdb# = sql_open#() sql_close(db#)
SQL Execution
| Function | Signature | Description |
|---|---|---|
sql_exec(db#, sql$) | sql_exec@#$ | Execute statement (INSERT, UPDATE, DELETE, CREATE, etc.) |
sql_exec(db#, sql$, params#) | sql_exec@#$# | Execute with JSON parameter binding |
sql_query#(db#, sql$) | sql_query#@#$ | Execute SELECT, returns cursor |
sql_query#(db#, sql$, params#) | sql_query#@#$# | SELECT with JSON parameters |
' Create table sql_exec(db#, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)") ' Insert with parameters let params# = json_object#() json_sets#(params#, "name", "John") json_setn#(params#, "age", 30) sql_exec(db#, "INSERT INTO users (name, age) VALUES (:name, :age)", params#) ' Query let cursor# = sql_query#(db#, "SELECT * FROM users") while sql_step(cursor#) = 1 println sql_gets$(cursor#, "name") end while sql_finalize(cursor#)
⚠ Warning: Always use parameter binding (
:name or ?) instead of string concatenation to prevent SQL injection.Statement Management
| Function | Signature | Description |
|---|---|---|
sql_prepare#(db#, sql$) | sql_prepare#@#$ | Prepare statement for repeated execution |
sql_step(cursor#) | sql_step@# | Execute one step: 1=row, 0=done, -1=error |
sql_reset#(stmt#) | sql_reset#@# | Reset for re-execution with new parameters |
sql_finalize(cursor#) | sql_finalize@# | Finalize (free) statement |
sql_eof(cursor#) | sql_eof@# | End of results? (1/0) |
Parameter Binding
| Function | Signature | Description |
|---|---|---|
sql_bindstr#(stmt#, idx, val$) | sql_bindstr#@#n$ | Bind string by index (0-based) |
sql_bindnum#(stmt#, idx, val) | sql_bindnum#@#nn | Bind number by index |
sql_bindnull#(stmt#, idx) | sql_bindnull#@#n | Bind NULL by index |
sql_bindjson#(stmt#, params#) | sql_bindjson#@## | Bind all from JSON object (by :name) |
sql_clearbind#(stmt#) | sql_clearbind#@# | Clear all bindings |
let stmt# = sql_prepare#(db#, "INSERT INTO users (name, age) VALUES (:name, :age)") let params# = json_object#() json_sets#(params#, "name", "Alice") json_setn#(params#, "age", 25) sql_bindjson#(stmt#, params#) sql_step(stmt#) ' Reuse with new data sql_reset#(stmt#) sql_clearbind#(stmt#) json_sets#(params#, "name", "Bob") json_setn#(params#, "age", 35) sql_bindjson#(stmt#, params#) sql_step(stmt#) sql_finalize(stmt#)
Column Information
| Function | Signature | Description |
|---|---|---|
sql_colcount(cursor#) | sql_colcount@# | Number of columns in result set |
sql_colname$(cursor#, idx) | sql_colname$@#n | Column name at index (0-based) |
sql_coltype(cursor#, idx) | sql_coltype@#n | Column type code at index |
sql_coltypename$(code) | sql_coltypename$@n | Type name for code (NULL/INTEGER/FLOAT/TEXT/BLOB) |
sql_colindex(cursor#, name$) | sql_colindex@#$ | Column index by name (-1 if not found) |
Result Access
By Index (0-based)
| Function | Signature | Description |
|---|---|---|
sql_getstr$(cursor#, idx) | sql_getstr$@#n | String value at column index |
sql_getnum(cursor#, idx) | sql_getnum@#n | Numeric value at column index |
sql_isnull(cursor#, idx) | sql_isnull@#n | Column is NULL? (1/0) |
sql_isblob(cursor#, idx) | sql_isblob@#n | Column is BLOB? (1/0) |
By Name
| Function | Signature | Description |
|---|---|---|
sql_gets$(cursor#, name$) | sql_gets$@#$ | String value by column name |
sql_getn(cursor#, name$) | sql_getn@#$ | Numeric value by column name |
sql_isn(cursor#, name$) | sql_isn@#$ | Column is NULL? (1/0) |
let cursor# = sql_query#(db#, "SELECT * FROM users") while sql_step(cursor#) = 1 println sql_gets$(cursor#, "name") + " - age " + str$(sql_getn(cursor#, "age")) if sql_isn(cursor#, "email") = 1 then println " (no email)" endif end while sql_finalize(cursor#)
JSON Integration
| Function | Signature | Description |
|---|---|---|
sql_row#(cursor#) | sql_row#@# | Current row as JSON object |
sql_fetchall#(cursor#) | sql_fetchall#@# | All remaining rows as JSON array |
sql_fetchone#(cursor#) | sql_fetchone#@# | Current row as JSON, advance to next (nil when done) |
sql_insertjson(db#, table$, json#) | sql_insertjson@#$# | Insert JSON object as new row (1/0) |
sql_updatejson(db#, table$, json#, where$) | sql_updatejson@#$#$ | Update rows with JSON SET values (1/0) |
' Insert with JSON let user# = json_object#() json_sets#(user#, "name", "Charlie") json_setn#(user#, "age", 28) sql_insertjson(db#, "users", user#) println "New ID: " + str$(sql_lastid(db#)) ' Update with JSON let upd# = json_object#() json_setn#(upd#, "age", 29) sql_updatejson(db#, "users", upd#, "name = 'Charlie'") ' Fetch all as JSON array let cursor# = sql_query#(db#, "SELECT * FROM users") sql_step(cursor#) let all# = sql_fetchall#(cursor#) println json_pretty$(all#) sql_finalize(cursor#)
Transactions
| Function | Signature | Description |
|---|---|---|
sql_begin(db#) | sql_begin@# | Begin transaction (1/0) |
sql_commit(db#) | sql_commit@# | Commit transaction (1/0) |
sql_rollback(db#) | sql_rollback@# | Rollback transaction (1/0) |
sql_intrans(db#) | sql_intrans@# | In transaction? (1/0) |
sql_begin(db#) let ok = sql_exec(db#, "UPDATE accounts SET balance = balance - 100 WHERE id = 1") if ok = 1 then ok = sql_exec(db#, "UPDATE accounts SET balance = balance + 100 WHERE id = 2") endif if ok = 1 then sql_commit(db#) println "Transfer complete" else sql_rollback(db#) println "Transfer failed" endif
Utility Functions
| Function | Signature | Description |
|---|---|---|
sql_lastid(db#) | sql_lastid@# | Last inserted rowid |
sql_changes(db#) | sql_changes@# | Rows affected by last statement |
sql_totalchanges(db#) | sql_totalchanges@# | Total changes since connection opened |
sql_tableexists(db#, table$) | sql_tableexists@#$ | Table exists? (1/0) |
sql_tables#(db#) | sql_tables#@# | JSON array of all table names |
sql_columns#(db#, table$) | sql_columns#@#$ | JSON array of column info for table |
sql_backup(db#, path$) | sql_backup@#$ | Backup database to file (1/0) |
sql_vacuum(db#) | sql_vacuum@# | Optimize/rebuild database |
' Table introspection let tables# = sql_tables#(db#) for i = 0 to json_len(tables#) - 1 let tbl$ = json_items$(tables#, i) println "Table: " + tbl$ let cols# = sql_columns#(db#, tbl$) for j = 0 to json_len(cols#) - 1 let col# = json_item#(cols#, j) println " " + json_gets$(col#, "name") + " (" + json_gets$(col#, "type") + ")" next next
ⓘ Note:
sql_columns#() returns objects with fields: cid, name, type, notnull, pk, default.String Helpers
| Function | Signature | Description |
|---|---|---|
sql_escape$(text$) | sql_escape$@$ | Escape string for SQL (doubles single quotes) |
sql_quote$(text$) | sql_quote$@$ | Escape and wrap in single quotes |
let safe$ = sql_escape$("O'Brien") ' "O''Brien" let quoted$ = sql_quote$("O'Brien") ' "'O''Brien'"
Complete Examples
Basic CRUD
let db# = sql_open#("contacts.db") sql_exec(db#, "CREATE TABLE IF NOT EXISTS contacts (id INTEGER PRIMARY KEY, name TEXT, phone TEXT, email TEXT)") ' Insert via JSON let c1# = json_object#() json_sets#(c1#, "name", "Alice Smith") json_sets#(c1#, "phone", "555-0100") json_sets#(c1#, "email", "alice@example.com") sql_insertjson(db#, "contacts", c1#) ' Query let cursor# = sql_query#(db#, "SELECT * FROM contacts ORDER BY name") while sql_step(cursor#) = 1 println sql_gets$(cursor#, "name") + " - " + sql_gets$(cursor#, "phone") end while sql_finalize(cursor#) ' Update let upd# = json_object#() json_sets#(upd#, "email", "alice.smith@example.com") sql_updatejson(db#, "contacts", upd#, "name = 'Alice Smith'") sql_close(db#)
Batch Insert with JSON Array
let db# = sql_open#("products.db") sql_exec(db#, "CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT, price REAL, stock INTEGER)") let products# = json_array#() let p1# = json_object#() json_sets#(p1#, "name", "Widget") json_setn#(p1#, "price", 9.99) json_setn#(p1#, "stock", 100) json_push#(products#, p1#) let p2# = json_object#() json_sets#(p2#, "name", "Gadget") json_setn#(p2#, "price", 19.99) json_setn#(p2#, "stock", 50) json_push#(products#, p2#) sql_begin(db#) for i = 0 to json_len(products#) - 1 sql_insertjson(db#, "products", json_item#(products#, i)) next sql_commit(db#) println "Inserted " + str$(json_len(products#)) + " products" sql_close(db#)
Best Practices
| Practice | Why |
|---|---|
| Check errors after open/exec/query | Operations can fail silently |
| Use transactions for batch operations | Atomicity + performance (much faster) |
| Use parameter binding, not string concat | Prevents SQL injection |
| Finalize cursors when done | Frees resources held by the statement |
| Use JSON functions for multi-field ops | Cleaner code, automatic type mapping |
Backup regularly with sql_backup | Protect against data loss |
Quick Reference
| Function | Signature | Description |
|---|---|---|
sql_error/errormsg$/strerror$/clearerror | various | Error handling (4) |
sql_open#(path$) / sql_open#() | sql_open#@$ / @ | Open file / in-memory DB |
sql_close/isopen/path$/version$ | various | Connection info (4) |
sql_exec(db#, sql$[, params#]) | sql_exec@#$[#] | Execute non-query (1/0) |
sql_query#(db#, sql$[, params#]) | sql_query#@#$[#] | Execute SELECT (cursor) |
sql_prepare#/step/reset#/finalize/eof | various | Statement lifecycle (5) |
sql_bindstr#/bindnum#/bindnull#/bindjson#/clearbind# | various | Binding (5) |
sql_colcount/colname$/coltype/coltypename$/colindex | various | Column info (5) |
sql_getstr$/getnum/isnull/isblob | various | Results by index (4) |
sql_gets$/getn/isn | various | Results by name (3) |
sql_row#/fetchall#/fetchone#/insertjson/updatejson | various | JSON integration (5) |
sql_begin/commit/rollback/intrans | various | Transactions (4) |
sql_lastid/changes/totalchanges | various | Row info (3) |
sql_tableexists/tables#/columns#/backup/vacuum | various | Utility (5) |
sql_escape$/sql_quote$ | various | String helpers (2) |
55+ functions across 12 categories. Depends on JsonLib.