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.
263 lines
8.1 KiB
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()
|
|
```
|
|
|