# "SQL Database"
_Path: en/lua/storage/sql_
> "Execute SQL queries against PostgreSQL, MySQL, and SQLite databases. Features include parameterized queries, transactions, prepared statements, and a…"
## Table of Contents
- SQL Database
## Content
# SQL Database
The `sql` module runs queries against configured PostgreSQL, MySQL, and SQLite databases. It supports parameterized queries, transactions, prepared statements, and query builders.
This page is an API reference. Its snippets assume a configured database, permission to acquire it, and any tables named by the query. They illustrate individual calls rather than a standalone application. The combined recipe at the end states its additional schema and driver assumptions.
For database configuration, see [Database](system/database.md).
## Loading
```lua
local sql = require("sql")
```
## `sql.get`
Acquire a database connection from the resource registry:
```lua
local db, err = sql.get("app.db:main")
if err then
return nil, err
end
local function finish(value, primary_err)
local _, release_err = db:release()
if primary_err then return nil, primary_err end
if release_err then return nil, release_err end
return value
end
local rows, err = db:query("SELECT * FROM users WHERE active = ?", {1})
if err then
return finish(nil, err)
end
return finish(rows)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Resource ID (e.g., "app.db:main") |
**Returns:** `DB, error`
Database leases are released during execution-frame cleanup. Call `db:release()` explicitly when database work finishes, especially in long-running operations.
Placeholders are passed to the database driver unchanged; the runtime does not rewrite them. SQLite and MySQL use `?`, PostgreSQL uses `$1, $2` — write them in the form your driver expects. The examples below use `?` (SQLite/MySQL). For queries that target more than one engine, build them with the [query builder](#query-builder): `run_with` rewrites placeholders to `$1, $2` when the handle is PostgreSQL, and `to_sql` uses the builder's `placeholder_format`.
### Database Types
```lua
sql.type.POSTGRES -- "postgres"
sql.type.MYSQL -- "mysql"
sql.type.SQLITE -- "sqlite"
sql.type.UNKNOWN -- "unknown"
```
### Isolation Levels
```lua
sql.isolation.DEFAULT -- "default"
sql.isolation.READ_UNCOMMITTED -- "read_uncommitted"
sql.isolation.READ_COMMITTED -- "read_committed"
sql.isolation.WRITE_COMMITTED -- "write_committed"
sql.isolation.REPEATABLE_READ -- "repeatable_read"
sql.isolation.SERIALIZABLE -- "serializable"
```
### NULL Value
```lua
local insert = sql.builder.insert("users")
:columns("name", "email")
:values("alice", sql.NULL)
```
### `sql.as.int`
Coerce a value to the SQL integer type.
```lua
local value = sql.as.int(42)
```
**Returns:** `userdata`
### `sql.as.float`
Coerce a value to the SQL float type.
```lua
local value = sql.as.float(19.99)
```
**Returns:** `userdata`
### `sql.as.text`
Coerce a value to the SQL text type.
```lua
local value = sql.as.text("hello")
```
**Returns:** `userdata`
### `sql.as.binary`
Coerce a value to the SQL binary type.
```lua
local value = sql.as.binary("binary data")
```
**Returns:** `userdata`
### `sql.as.null`
Return the SQL `NULL` marker.
```lua
local value = sql.as.null()
```
**Returns:** `userdata`
### `sql.builder.select`
Create a `SELECT` query builder.
```lua
local query = sql.builder.select("id", "name")
:from("users")
:where({active = 1})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `columns` | ...string | Column names (optional) |
**Returns:** `SelectBuilder`
### `sql.builder.insert`
Create an `INSERT` query builder.
```lua
local query = sql.builder.insert("users")
:columns("name", "email")
:values("alice", "alice@example.com")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `table` | string | Table name (optional) |
**Returns:** `InsertBuilder`
### `sql.builder.update`
Create an `UPDATE` query builder.
```lua
local query = sql.builder.update("users")
:set("status", "active")
:where({id = 123})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `table` | string | Table name (optional) |
**Returns:** `UpdateBuilder`
### `sql.builder.delete`
Create a `DELETE` query builder.
```lua
local query = sql.builder.delete("users")
:where({active = 0})
:limit(100)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `table` | string | Table name (optional) |
**Returns:** `DeleteBuilder`
### `sql.builder.expr`
Create a raw SQL expression for use in `WHERE` or `HAVING` clauses.
```lua
local expr = sql.builder.expr("score BETWEEN ? AND ?", 80, 90)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL expression with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `Sqlizer`
### `sql.builder.eq`
Create equality conditions from a table.
```lua
local cond = sql.builder.eq({active = 1, status = "open"})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `Sqlizer`
### `sql.builder.not_eq`
Create inequality conditions from a table.
```lua
local cond = sql.builder.not_eq({status = "closed"})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `Sqlizer`
### `sql.builder.lt`
Create less-than conditions from a table.
```lua
local cond = sql.builder.lt({age = 18})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `Sqlizer`
### `sql.builder.lte`
Create less-than-or-equal conditions from a table.
```lua
local cond = sql.builder.lte({price = 100})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `Sqlizer`
### `sql.builder.gt`
Create greater-than conditions from a table.
```lua
local cond = sql.builder.gt({score = 80})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `Sqlizer`
### `sql.builder.gte`
Create greater-than-or-equal conditions from a table.
```lua
local cond = sql.builder.gte({age = 21})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `Sqlizer`
### `sql.builder.like`
Create `LIKE` conditions from a table.
```lua
local cond = sql.builder.like({name = "john%"})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `Sqlizer`
### `sql.builder.not_like`
Create `NOT LIKE` conditions from a table.
```lua
local cond = sql.builder.not_like({email = "%@spam.com"})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `Sqlizer`
### `sql.builder.and_`
Combine multiple conditions with `AND`.
```lua
local cond = sql.builder.and_({
sql.builder.eq({active = 1}),
sql.builder.gt({score = 80})
})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `conditions` | table | Array of Sqlizer or table conditions |
**Returns:** `Sqlizer`
### `sql.builder.or_`
Combine multiple conditions with `OR`.
```lua
local cond = sql.builder.or_({
sql.builder.eq({status = "pending"}),
sql.builder.eq({status = "active"})
})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `conditions` | table | Array of Sqlizer or table conditions |
**Returns:** `Sqlizer`
## sqlizer:to_sql
Generates the SQL fragment and bind arguments of a condition.
```lua
local frag, args = sql.builder.eq({active = 1}):to_sql()
```
**Returns:** `string, table`
## builder.question
Use `?` placeholders (default). This format is also available as `sql.builder.default_placeholder`.
```lua
local query = sql.builder.select("*")
:from("users")
:placeholder_format(sql.builder.question)
```
### `sql.builder.dollar`
Use `$1, $2, ...` placeholders.
```lua
local query = sql.builder.select("*")
:from("users")
:placeholder_format(sql.builder.dollar)
```
### `sql.builder.at`
Use `@p1, @p2, ...` placeholders (SQL Server style). Pass this format to `placeholder_format` like the formats above.
### `sql.builder.colon`
Use `:1, :2, ...` placeholders. Pass this format to `placeholder_format` like the formats above.
## Connection Methods
A connection handle returned by `sql.get()` provides query, transaction, statement, and pool operations.
### `db:type`
Return the database type constant.
```lua
local dbtype, err = db:type()
```
**Returns:** `string, error`
### `db:query`
Run a `SELECT` query and return its rows.
```lua
local rows, err = db:query("SELECT id, name FROM users WHERE active = ?", {1})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL query with ? placeholders |
| `params` | table | Array of bind parameters (optional) |
**Returns:** `table[], error`
### `db:execute`
Run an `INSERT`, `UPDATE`, or `DELETE` statement.
```lua
local result, err = db:execute("INSERT INTO users (name) VALUES (?)", {"alice"})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL statement with ? placeholders |
| `params` | table | Array of bind parameters (optional) |
**Returns:** `table, error`
Returns table with fields:
- `last_insert_id` - Last inserted ID
- `rows_affected` - Number of rows affected
### `db:prepare`
Create a prepared statement for repeated execution.
```lua
local stmt, err = db:prepare("SELECT * FROM users WHERE id = ?")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL with ? placeholders |
**Returns:** `Statement, error`
### `db:begin`
Begin a database transaction.
```lua
local tx, err = db:begin({
isolation = sql.isolation.SERIALIZABLE,
read_only = false
})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `options` | table | Transaction options (optional) |
Options table fields:
- `isolation` - Isolation level from sql.isolation.* (default: DEFAULT)
- `read_only` - Read-only transaction flag (default: false)
**Returns:** `Transaction, error`
### `db:release`
Release the database resource back to the pool.
```lua
local ok, err = db:release()
```
**Returns:** `boolean, error`
The operation is idempotent.
### `db:stats`
Return connection-pool statistics.
```lua
local stats, err = db:stats()
```
**Returns:** `table, error`
Returns table with fields:
- `max_open_connections` - Max allowed open connections
- `open_connections` - Current open connections
- `in_use` - Connections currently in use
- `idle` - Idle connections in pool
- `wait_count` - Total connection wait count
- `wait_duration` - Total wait duration
- `max_idle_closed` - Connections closed due to max idle
- `max_idle_time_closed` - Connections closed due to idle timeout
- `max_lifetime_closed` - Connections closed due to max lifetime
## Prepared Statements
A prepared statement returned by `db:prepare()` can be queried or executed repeatedly.
### `stmt:query`
Run the prepared statement as a `SELECT` query.
```lua
local rows, err = stmt:query({123})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `params` | table | Array of bind parameters (optional) |
**Returns:** `table[], error`
### `stmt:execute`
Run the prepared statement as an `INSERT`, `UPDATE`, or `DELETE` statement.
```lua
local result, err = stmt:execute({"alice"})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `params` | table | Array of bind parameters (optional) |
**Returns:** `table, error`
Returns table with fields:
- `last_insert_id` - Last inserted ID
- `rows_affected` - Number of rows affected
### `stmt:close`
Close the prepared statement.
```lua
local ok, err = stmt:close()
```
**Returns:** `boolean, error`
## Transactions
A transaction returned by `db:begin()` provides query, statement, savepoint, commit, and rollback operations.
An active transaction is rolled back automatically during execution-frame cleanup. Commit or roll it back explicitly as soon as its work is complete.
### `tx:db_type`
Return the database type constant.
```lua
local dbtype, err = tx:db_type()
```
**Returns:** `string, error`
### `tx:query`
Run a `SELECT` query within the transaction.
```lua
local rows, err = tx:query("SELECT id, name FROM users WHERE active = ?", {1})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL query with ? placeholders |
| `params` | table | Array of bind parameters (optional) |
**Returns:** `table[], error`
### `tx:execute`
Run an `INSERT`, `UPDATE`, or `DELETE` statement within the transaction.
```lua
local result, err = tx:execute("INSERT INTO users (name) VALUES (?)", {"alice"})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL statement with ? placeholders |
| `params` | table | Array of bind parameters (optional) |
**Returns:** `table, error`
Returns table with fields:
- `last_insert_id` - Last inserted ID
- `rows_affected` - Number of rows affected
### `tx:prepare`
Create a prepared statement within the transaction.
```lua
local stmt, err = tx:prepare("SELECT * FROM users WHERE id = ?")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL with ? placeholders |
**Returns:** `Statement, error`
### `tx:commit`
Commit the transaction.
```lua
local ok, err = tx:commit()
```
**Returns:** `boolean, error`
### `tx:rollback`
Roll back the transaction.
```lua
local ok, err = tx:rollback()
```
**Returns:** `boolean, error`
### `tx:savepoint`
Create a named savepoint within the transaction.
```lua
local ok, err = tx:savepoint("sp1")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | string | Savepoint name (alphanumeric and underscore only) |
**Returns:** `boolean, error`
### `tx:rollback_to`
Roll back to a named savepoint.
```lua
local ok, err = tx:rollback_to("sp1")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | string | Savepoint name |
**Returns:** `boolean, error`
### `tx:release`
Release a savepoint.
```lua
local ok, err = tx:release("sp1")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `name` | string | Savepoint name |
**Returns:** `boolean, error`
## SELECT Builder
Build a `SELECT` query one clause at a time.
### `select:from`
Set the `FROM` clause.
```lua
local query = sql.builder.select("id", "name"):from("users")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `table` | string | Table name |
**Returns:** `SelectBuilder`
### `select:join`
Add a `JOIN` clause.
```lua
local query = sql.builder.select("*")
:from("users")
:join("orders ON orders.user_id = users.id")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `join` | string | JOIN clause with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `SelectBuilder`
### `select:left_join`
Add a `LEFT JOIN` clause.
```lua
local query = sql.builder.select("*")
:from("users")
:left_join("orders ON orders.user_id = users.id")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `join` | string | JOIN clause with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `SelectBuilder`
### `select:right_join`
Add a `RIGHT JOIN` clause.
```lua
local query = sql.builder.select("*")
:from("users")
:right_join("orders ON orders.user_id = users.id")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `join` | string | JOIN clause with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `SelectBuilder`
### `select:inner_join`
Add an `INNER JOIN` clause.
```lua
local query = sql.builder.select("*")
:from("users")
:inner_join("orders ON orders.user_id = users.id")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `join` | string | JOIN clause with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `SelectBuilder`
### `select:where`
Add a `WHERE` condition.
```lua
local query = sql.builder.select("*")
:from("users")
:where({active = 1})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `condition` | string\|table\|Sqlizer | WHERE condition |
| `args` | ...any | Bind arguments (optional, when using string) |
The method accepts three formats:
- String: `where("status = ?", "active")`
- Table: `where({status = "active"})`
- Sqlizer: `where(sql.builder.gt({score = 80}))`
**Returns:** `SelectBuilder`
### `select:order_by`
Add an `ORDER BY` clause.
```lua
local query = sql.builder.select("*")
:from("users")
:order_by("name ASC", "created_at DESC")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `columns` | ...string | Column names with optional ASC/DESC |
**Returns:** `SelectBuilder`
### `select:group_by`
Add a `GROUP BY` clause.
```lua
local query = sql.builder.select("status", "COUNT(*)")
:from("users")
:group_by("status")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `columns` | ...string | Column names |
**Returns:** `SelectBuilder`
### `select:having`
Add a `HAVING` condition.
```lua
local query = sql.builder.select("status", "COUNT(*) as cnt")
:from("users")
:group_by("status")
:having(sql.builder.gt({cnt = 10}))
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `condition` | string\|table\|Sqlizer | HAVING condition |
| `args` | ...any | Bind arguments (optional, when using string) |
**Returns:** `SelectBuilder`
### `select:limit`
Set the `LIMIT` value.
```lua
local query = sql.builder.select("*")
:from("users")
:limit(10)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `n` | integer | Limit value |
**Returns:** `SelectBuilder`
### `select:offset`
Set the `OFFSET` value.
```lua
local query = sql.builder.select("*")
:from("users")
:offset(20)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `n` | integer | Offset value |
**Returns:** `SelectBuilder`
### `select:columns`
Add columns to the `SELECT` list.
```lua
local query = sql.builder.select():columns("id", "name", "email")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `columns` | ...string | Column names |
**Returns:** `SelectBuilder`
### `select:distinct`
Add the `DISTINCT` modifier.
```lua
local query = sql.builder.select("status")
:from("users")
:distinct()
```
**Returns:** `SelectBuilder`
### `select:suffix`
Add an SQL suffix.
```lua
local query = sql.builder.select("*")
:from("users")
:suffix("FOR UPDATE")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL suffix with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `SelectBuilder`
### `select:placeholder_format`
Set the placeholder format.
```lua
local query = sql.builder.select("*")
:from("users")
:placeholder_format(sql.builder.dollar)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `format` | userdata | Placeholder format (sql.builder.*) |
**Returns:** `SelectBuilder`
### `select:to_sql`
Generate the SQL string and bind arguments.
```lua
local sql_str, args = query:to_sql()
```
**Returns:** `string, table` on success; `nil, error` for an invalid builder state
### `select:run_with`
Create an executor for the query.
```lua
local executor, err = query:run_with(db)
if err then
return nil, err
end
local rows, err = executor:query()
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `db` | DB\|Transaction | Database or transaction handle |
**Returns:** `QueryExecutor, error`
## INSERT Builder
Build an `INSERT` query one clause at a time.
### `insert:into`
Set the table name.
```lua
local query = sql.builder.insert():into("users")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `table` | string | Table name |
**Returns:** `InsertBuilder`
### `insert:columns`
Set the column names.
```lua
local query = sql.builder.insert("users"):columns("name", "email")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `columns` | ...string | Column names |
**Returns:** `InsertBuilder`
### `insert:values`
Add row values.
```lua
local query = sql.builder.insert("users")
:columns("name", "email")
:values("alice", "alice@example.com")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `values` | ...any | Row values |
**Returns:** `InsertBuilder`
### `insert:set_map`
Set columns and values from a table.
```lua
local query = sql.builder.insert("users")
:set_map({name = "alice", email = "alice@example.com"})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs |
**Returns:** `InsertBuilder`
### `insert:select`
Insert rows from a `SELECT` query.
```lua
local select_query = sql.builder.select("name", "email"):from("temp_users")
local query = sql.builder.insert("users")
:columns("name", "email")
:select(select_query)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `query` | SelectBuilder | SELECT query |
**Returns:** `InsertBuilder`
### `insert:prefix`
Add an SQL prefix.
```lua
local query = sql.builder.insert("users")
:prefix("/* audit import */")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL prefix with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `InsertBuilder`
### `insert:suffix`
Add an SQL suffix.
```lua
local query = sql.builder.insert("users")
:columns("name")
:values("alice")
:suffix("RETURNING id")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL suffix with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `InsertBuilder`
### `insert:options`
Add `INSERT` options.
```lua
local query = sql.builder.insert("users")
:options("DELAYED", "IGNORE")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `options` | ...string | INSERT options |
**Returns:** `InsertBuilder`
### `insert:placeholder_format`
Set the placeholder format.
```lua
local query = sql.builder.insert("users")
:placeholder_format(sql.builder.dollar)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `format` | userdata | Placeholder format (sql.builder.*) |
**Returns:** `InsertBuilder`
### `insert:to_sql`
Generate the SQL string and bind arguments.
```lua
local sql_str, args = query:to_sql()
```
**Returns:** `string, table` on success; `nil, error` for an invalid builder state
### `insert:run_with`
Create an executor for the query.
```lua
local executor, err = query:run_with(db)
if err then
return nil, err
end
local result, err = executor:exec()
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `db` | DB\|Transaction | Database or transaction handle |
**Returns:** `QueryExecutor, error`
## UPDATE Builder
Build an `UPDATE` query one clause at a time.
### `update:table`
Set the table name.
```lua
local query = sql.builder.update():table("users")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `table` | string | Table name |
**Returns:** `UpdateBuilder`
### `update:set`
Set a column value.
```lua
local query = sql.builder.update("users")
:set("status", "active")
:set("updated_at", sql.builder.expr("NOW()"))
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `column` | string | Column name |
| `value` | any | Column value |
**Returns:** `UpdateBuilder`
### `update:set_map`
Set multiple columns from a table.
```lua
local query = sql.builder.update("users")
:set_map({status = "active", login_count = 0})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `map` | table | {column = value} pairs; values are plain values, `sql.NULL`, or `sql.as.*` (use `set` for expressions) |
**Returns:** `UpdateBuilder`
### `update:where`
Add a `WHERE` condition.
```lua
local query = sql.builder.update("users")
:set("status", "active")
:where({id = 123})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `condition` | string\|table\|Sqlizer | WHERE condition |
| `args` | ...any | Bind arguments (optional, when using string) |
**Returns:** `UpdateBuilder`
### `update:order_by`
Add an `ORDER BY` clause.
```lua
local query = sql.builder.update("users")
:set("rank", 1)
:order_by("score DESC")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `columns` | ...string | Column names with optional ASC/DESC |
**Returns:** `UpdateBuilder`
### `update:limit`
Set the `LIMIT` value.
```lua
local query = sql.builder.update("users")
:set("status", "active")
:limit(10)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `n` | integer | Limit value |
**Returns:** `UpdateBuilder`
### `update:offset`
Set the `OFFSET` value.
```lua
local query = sql.builder.update("users")
:set("status", "active")
:offset(5)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `n` | integer | Offset value |
**Returns:** `UpdateBuilder`
### `update:suffix`
Add an SQL suffix.
```lua
local query = sql.builder.update("users")
:set("status", "active")
:suffix("RETURNING id")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL suffix with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `UpdateBuilder`
### `update:from`
Add a `FROM` clause.
```lua
local query = sql.builder.update("users")
:set("status", "active")
:from("other_table")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `table` | string | Table name |
**Returns:** `UpdateBuilder`
### `update:from_select`
Update rows from a `SELECT` query.
```lua
local select_query = sql.builder.select("*"):from("temp_users")
local query = sql.builder.update("users")
:set("status", "active")
:from_select(select_query, "t")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `query` | SelectBuilder | SELECT query |
| `alias` | string | Table alias |
**Returns:** `UpdateBuilder`
### `update:placeholder_format`
Set the placeholder format.
```lua
local query = sql.builder.update("users")
:placeholder_format(sql.builder.dollar)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `format` | userdata | Placeholder format (sql.builder.*) |
**Returns:** `UpdateBuilder`
### `update:to_sql`
Generate the SQL string and bind arguments.
```lua
local sql_str, args = query:to_sql()
```
**Returns:** `string, table` on success; `nil, error` for an invalid builder state
### `update:run_with`
Create an executor for the query.
```lua
local executor, err = query:run_with(db)
if err then
return nil, err
end
local result, err = executor:exec()
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `db` | DB\|Transaction | Database or transaction handle |
**Returns:** `QueryExecutor, error`
## DELETE Builder
Build a `DELETE` query one clause at a time.
### `delete:from`
Set the table name.
```lua
local query = sql.builder.delete():from("users")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `table` | string | Table name |
**Returns:** `DeleteBuilder`
### `delete:where`
Add a `WHERE` condition.
```lua
local query = sql.builder.delete("users")
:where({active = 0})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `condition` | string\|table\|Sqlizer | WHERE condition |
| `args` | ...any | Bind arguments (optional, when using string) |
**Returns:** `DeleteBuilder`
### `delete:order_by`
Add an `ORDER BY` clause.
```lua
local query = sql.builder.delete("users")
:where({active = 0})
:order_by("created_at ASC")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `columns` | ...string | Column names with optional ASC/DESC |
**Returns:** `DeleteBuilder`
### `delete:limit`
Set the `LIMIT` value.
```lua
local query = sql.builder.delete("users")
:where({active = 0})
:limit(100)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `n` | integer | Limit value |
**Returns:** `DeleteBuilder`
### `delete:offset`
Set the `OFFSET` value.
```lua
local query = sql.builder.delete("users")
:where({active = 0})
:offset(10)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `n` | integer | Offset value |
**Returns:** `DeleteBuilder`
### `delete:suffix`
Add an SQL suffix.
```lua
local query = sql.builder.delete("users")
:where({active = 0})
:suffix("RETURNING id")
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `sql` | string | SQL suffix with ? placeholders |
| `args` | ...any | Bind arguments (optional) |
**Returns:** `DeleteBuilder`
### `delete:placeholder_format`
Set the placeholder format.
```lua
local query = sql.builder.delete("users")
:placeholder_format(sql.builder.dollar)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `format` | userdata | Placeholder format (sql.builder.*) |
**Returns:** `DeleteBuilder`
### `delete:to_sql`
Generate the SQL string and bind arguments.
```lua
local sql_str, args = query:to_sql()
```
**Returns:** `string, table` on success; `nil, error` for an invalid builder state
### `delete:run_with`
Create an executor for the query.
```lua
local executor, err = query:run_with(db)
if err then
return nil, err
end
local result, err = executor:exec()
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `db` | DB\|Transaction | Database or transaction handle |
**Returns:** `QueryExecutor, error`
## Executing Queries
The query executor runs builder-generated queries.
### `executor:query`
Run the query and return rows for a `SELECT` statement.
```lua
local rows, err = executor:query()
```
**Returns:** `table[], error`
### `executor:exec`
Run the query and return the result of an `INSERT`, `UPDATE`, or `DELETE` statement.
```lua
local result, err = executor:exec()
```
**Returns:** `table, error`
Returns table with fields:
- `last_insert_id` - Last inserted ID
- `rows_affected` - Number of rows affected
### `executor:to_sql`
Return the generated SQL and arguments without executing the query.
```lua
local sql_str, args = executor:to_sql()
```
**Returns:** `string, table`
## Permissions
Database access is subject to security policy evaluation.
| Action | Resource | Description |
|--------|----------|-------------|
| `db.get` | Database ID | Acquire database connection |
## Errors
| Condition | Kind | Retryable |
|-----------|------|-----------|
| Empty resource ID | `errors.INVALID` | no |
| Permission denied | `errors.PERMISSION_DENIED` | no |
| Resource not found | `errors.NOT_FOUND` | no |
| Resource not database | `errors.INVALID` | no |
| Invalid parameters | `errors.INVALID` | no |
| SQL syntax error | `errors.UNKNOWN` | nil |
| Statement closed | `errors.INVALID` | no |
| Transaction not active | `errors.INVALID` | no |
| Invalid savepoint name | `errors.INVALID` | no |
| Query execution error | `errors.UNKNOWN` | nil |
See [Error Handling](lua/core/errors.md) for working with errors.
## Combined Partial Recipe
This recipe assumes `app.db:main` is a configured SQLite or MySQL database and already contains `users`, `orders`, and `logs` tables with the referenced columns. It uses `?` placeholders; use `$1`, `$2`, and so on for a PostgreSQL resource. Returned rows depend on the application's data. The surrounding application supplies `report_cleanup_error(err)` so rollback or close failures are observable without replacing the initiating operation error.
```lua
local sql = require("sql")
local db, err = sql.get("app.db:main")
if err then return nil, err end
local function finish(value, primary_err)
local _, release_err = db:release()
if primary_err then return nil, primary_err end
if release_err then return nil, release_err end
return value
end
-- Direct query
local users, err = db:query("SELECT id, name FROM users WHERE active = ?", {1})
if err then
return finish(nil, err)
end
for _, user in ipairs(users) do
print(user.id, user.name)
end
-- Builder pattern
local query = sql.builder.select("u.id", "u.name", "COUNT(o.id) as order_count")
:from("users u")
:left_join("orders o ON o.user_id = u.id")
:where(sql.builder.and_({
sql.builder.eq({["u.active"] = 1}),
sql.builder.gte({["u.score"] = 80})
}))
:group_by("u.id", "u.name")
:having(sql.builder.gt({["COUNT(o.id)"] = 0}))
:order_by("order_count DESC")
:limit(10)
local executor, build_err = query:run_with(db)
if build_err then
return finish(nil, build_err)
end
local results, err = executor:query()
if err then
return finish(nil, err)
end
-- Transaction
local tx, err = db:begin({isolation = sql.isolation.SERIALIZABLE})
if err then
return finish(nil, err)
end
local _, err = tx:execute("INSERT INTO users (name) VALUES (?)", {"alice"})
if err then
local _, rollback_err = tx:rollback()
if rollback_err then report_cleanup_error(rollback_err) end
return finish(nil, err)
end
local _, commit_err = tx:commit()
if commit_err then
return finish(nil, commit_err)
end
-- Prepared statements
local stmt, err = db:prepare("INSERT INTO logs (message, level) VALUES (?, ?)")
if err then
return finish(nil, err)
end
for i = 1, 3 do
local _, err = stmt:execute({"log message " .. i, "info"})
if err then
local _, close_err = stmt:close()
if close_err then report_cleanup_error(close_err) end
return finish(nil, err)
end
end
local _, close_err = stmt:close()
if close_err then
return finish(nil, close_err)
end
return finish({users = users, ranked_users = results})
```
## Navigation
Previous: "HTML Sanitization" (lua/http/html)
Next: "Key-Value Store" (lua/storage/store)