Lua runner with rich builtin stdlib
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
a/docs/sqlite.md

263 lines
8.1 KiB

# `sqlite`
The `sqlite` module provides access to [SQLite](https://sqlite.org/) databases.
SQLite is compiled into the binary, so no system library is required. The module
handles type conversion and parameter binding; it is not an ORM. You open a
connection and call methods on it.
```lua
local con = sqlite.connect(":memory:")
con:execute("CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
con:execute("INSERT INTO people (name, age) VALUES (?, ?)", {"Alice", 30})
for _, row in ipairs(con:query("SELECT * FROM people")) do
print(row.id, row.name, row.age)
end
```
## Connecting
```lua
local con = sqlite.connect(path)
```
`path` is a filename (`"data.sqlite"`), created if it does not exist, or the special
string `":memory:"` for a private in-memory database that vanishes when closed. The
returned connection is an object you call methods on with `:` syntax.
Opening a file-backed database runs the [fs permission system](fs.md#permissions):
one combined read/write question for the database's directory (SQLite keeps its
WAL/journal files next to the file). In-memory databases touch no file and are
always allowed, even under `--no-fs` / `--sandbox`.
The connection closes automatically when it is garbage-collected. Call
[`con:close()`](#conclose) to release it eagerly.
## Queries
### `con:execute(sql [, params])`
Run a statement that changes data (`INSERT`, `UPDATE`, `DELETE`, `CREATE`, …).
Returns the number of rows changed as an integer.
```lua
local changed = con:execute("UPDATE people SET age = age + 1 WHERE name = ?", {"Alice"})
-- changed == 1
```
### `con:query(sql [, params])`
Run a `SELECT` and return **all** matching rows as an array of tables. Each row is a
table keyed by column name. An empty result is an empty array (`#rows == 0`).
```lua
local rows = con:query("SELECT id, name FROM people WHERE age > ?", {18})
for _, row in ipairs(rows) do
print(row.id, row.name)
end
```
### `con:queryOne(sql [, params])`
Like `query`, but returns the **first** row as a table, or `nil` if nothing matched.
Use it for lookups that return at most one row, such as a fetch by primary key.
```lua
local row = con:queryOne("SELECT * FROM people WHERE id = ?", {1})
if row then
print(row.name)
end
```
## Parameters
Pass values as a `params` table rather than building SQL by string concatenation;
the values are then bound and escaped by SQLite. The table is bound either
**positionally** or **by name**, decided by its keys.
### Positional
A plain array binds to `?` placeholders in order. It must be a gapless array starting
at index 1.
```lua
con:query("SELECT * FROM people WHERE age > ? AND name <> ?", {18, "Bob"})
```
### Named
A table with string keys binds to named placeholders. In the SQL the placeholder
carries a sigil (`:name`, `@name`, or `$name`); in the table the key is the bare name
without it.
```lua
con:query("SELECT * FROM people WHERE age > :min", {min = 18})
con:execute("INSERT INTO people (name, age) VALUES (:name, :age)", {name = "Carol", age = 25})
```
### Rules
- Omitting `params` (or passing `nil`) means the statement takes no parameters.
- A table may be **all positional** or **all named** — mixing the two is an error.
- Every named placeholder in the SQL must have a matching key, or the call errors.
## Type mapping
Values convert automatically in both directions.
**Lua → SQLite** (binding parameters):
| Lua | SQLite |
|--------------|-----------------|
| `nil` | `NULL` |
| `utils.NULL` | `NULL` |
| `boolean` | `INTEGER` (0/1) |
| integer | `INTEGER` |
| number | `REAL` |
| `string` | `TEXT` |
**SQLite → Lua** (reading rows):
| SQLite | Lua |
|-----------|----------------------|
| `NULL` | `nil` |
| `INTEGER` | integer |
| `REAL` | number |
| `TEXT` | `string` |
| `BLOB` | `string` (raw bytes) |
Two consequences worth knowing:
- **Booleans don't round-trip as booleans.** SQLite has no boolean type, so `true`
is stored as the integer `1` and reads back as `1`, not `true`. Compare against
`1`/`0`, or store a real `INTEGER` column and interpret it yourself.
- **`NULL` reads back as an absent key.** A column holding `NULL` simply isn't set on
the row table, so `row.col` is `nil` — exactly as if the key were present and `nil`.
To *bind* a SQL `NULL`, pass `utils.NULL`: a literal Lua `nil` cannot
live inside a table, so it can never reach a parameter slot.
```lua
con:execute("INSERT INTO people (name, age) VALUES (?, ?)", {"Dave", utils.NULL})
local row = con:queryOne("SELECT age FROM people WHERE name = ?", {"Dave"})
print(row.age) --> nil
```
## Metadata
### `con:lastInsertRowid()`
The rowid of the most recent successful `INSERT` on this connection, as an integer.
For a table with an `INTEGER PRIMARY KEY`, this is that key.
```lua
con:execute("INSERT INTO people (name, age) VALUES (?, ?)", {"Eve", 40})
local id = con:lastInsertRowid()
```
### `con:changes()`
The number of rows changed by the most recent `INSERT`/`UPDATE`/`DELETE`. This is the
same number `execute` returns, but you can query it separately at any time.
## Transactions
### `con:transaction(fn)`
Run `fn` inside a transaction. If it returns normally the transaction is **committed**;
if it raises an error the transaction is **rolled back** and the original error is
re-raised unchanged.
```lua
con:transaction(function()
con:execute("INSERT INTO people (name, age) VALUES (?, ?)", {"Frank", 50})
con:execute("INSERT INTO people (name, age) VALUES (?, ?)", {"Grace", 60})
end)
-- Both inserts committed together; if either had thrown, neither would persist.
```
### Manual control
For flows that don't fit a single callback, drive the transaction yourself:
```lua
con:begin()
local ok, err = pcall(function()
con:execute("DELETE FROM people WHERE age > ?", {100})
end)
if ok then con:commit() else con:rollback() end
```
| Method | Effect |
|-------------------|---------------------------------|
| `con:begin()` | Start a transaction (`BEGIN`). |
| `con:commit()` | Commit it (`COMMIT`). |
| `con:rollback()` | Discard it (`ROLLBACK`). |
You can mix both styles in the same program.
## Closing
### `con:close()`
Release the connection and its underlying database handle. Any further call on the
connection raises an error. Closing is optional — a connection also closes when
garbage-collected — but it is the clean way to release a file lock promptly.
```lua
con:close()
```
## Errors
Failures raise Lua errors rather than returning error codes: a bad SQL statement, a
type that can't be bound, a parameter mismatch, or use of a closed connection all
`error()` out. Wrap calls in `pcall` or `utils.try` where you want to
handle failure rather than abort:
```lua
local rows, err = utils.try(function()
return con:query("SELECT * FROM nonexistent")
end)
if not rows then
log.error("query failed: " .. tostring(err))
end
```
## Notes
- **Statements are cached.** Each query is prepared through SQLite's statement cache,
so repeating the same SQL reuses the compiled statement. There is no separate
prepared-statement API to manage.
- **I/O runs off the event loop.** `execute`, `query`, and `queryOne` run SQLite's
blocking work on a background thread, so a slow query does not stall other async
work (timers, `os.sleep`, networking) in the same script.
## Full example
```lua
local con = sqlite.connect(":memory:")
con:execute([[
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0
)
]])
con:transaction(function()
for _, title in ipairs({"write docs", "review code", "update changelog"}) do
con:execute("INSERT INTO tasks (title) VALUES (:title)", {title = title})
end
end)
con:execute("UPDATE tasks SET done = 1 WHERE title = ?", {"write docs"})
local pending = con:query("SELECT id, title FROM tasks WHERE done = 0 ORDER BY id")
for _, task in ipairs(pending) do
print(task.id, task.title)
end
con:close()
```