SQL Database

Execute SQL queries against PostgreSQL, MySQL, SQLite, MSSQL, and Oracle databases. Features include parameterized queries, transactions, prepared statements, and a fluent query builder.

For database configuration, see Database.

Loading

local sql = require("sql")

Acquiring a Connection

Get a database connection from the resource registry:

local db, err = sql.get("app.db:main")
if err then
    return nil, err
end

local rows = db:query("SELECT * FROM users WHERE active = ?", {1})

db:release()
Parameter Type Description
id string Resource ID (e.g., "app.db:main")

Returns: DB, error

Connections are automatically returned to the pool when the function exits, but calling `db:release()` explicitly is recommended for long-running operations.

Constants

Database Types

sql.type.POSTGRES    -- "postgres"
sql.type.MYSQL       -- "mysql"
sql.type.SQLITE      -- "sqlite"
sql.type.MSSQL       -- "mssql"
sql.type.ORACLE      -- "oracle"
sql.type.UNKNOWN     -- "unknown"

Isolation Levels

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

local insert = sql.builder.insert("users")
    :columns("name", "email")
    :values("alice", sql.NULL)

Type Coercion

as.int

local value = sql.as.int(42)

Returns: userdata

as.float

Coerces value to SQL float type.

local value = sql.as.float(19.99)

Returns: userdata

as.text

Coerces value to SQL text type.

local value = sql.as.text("hello")

Returns: userdata

as.binary

Coerces value to SQL binary type.

local value = sql.as.binary("binary data")

Returns: userdata

as.null

Returns SQL NULL marker.

local value = sql.as.null()

Returns: userdata

Query Builder

Creating Queries

local query = sql.builder.select("id", "name")
    :from("users")
    :where({active = 1})
Parameter Type Description
columns ...string Column names (optional)

Returns: SelectBuilder

builder.insert

Creates INSERT query builder.

local query = sql.builder.insert("users")
    :columns("name", "email")
    :values("alice", "alice@example.com")
Parameter Type Description
table string Table name (optional)

Returns: InsertBuilder

builder.update

Creates UPDATE query builder.

local query = sql.builder.update("users")
    :set("status", "active")
    :where({id = 123})
Parameter Type Description
table string Table name (optional)

Returns: UpdateBuilder

builder.delete

Creates DELETE query builder.

local query = sql.builder.delete("users")
    :where({active = 0})
    :limit(100)
Parameter Type Description
table string Table name (optional)

Returns: DeleteBuilder

builder.expr

Creates raw SQL expression for use in where/having clauses.

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

builder.eq

Creates equality condition from table.

local cond = sql.builder.eq({active = 1, status = "open"})
Parameter Type Description
map table {column = value} pairs

Returns: Sqlizer

builder.not_eq

Creates inequality condition from table.

local cond = sql.builder.not_eq({status = "closed"})
Parameter Type Description
map table {column = value} pairs

Returns: Sqlizer

builder.lt

Creates less-than condition from table.

local cond = sql.builder.lt({age = 18})
Parameter Type Description
map table {column = value} pairs

Returns: Sqlizer

builder.lte

Creates less-than-or-equal condition from table.

local cond = sql.builder.lte({price = 100})
Parameter Type Description
map table {column = value} pairs

Returns: Sqlizer

builder.gt

Creates greater-than condition from table.

local cond = sql.builder.gt({score = 80})
Parameter Type Description
map table {column = value} pairs

Returns: Sqlizer

builder.gte

Creates greater-than-or-equal condition from table.

local cond = sql.builder.gte({age = 21})
Parameter Type Description
map table {column = value} pairs

Returns: Sqlizer

builder.like

Creates LIKE condition from table.

local cond = sql.builder.like({name = "john%"})
Parameter Type Description
map table {column = value} pairs

Returns: Sqlizer

builder.not_like

Creates NOT LIKE condition from table.

local cond = sql.builder.not_like({email = "%@spam.com"})
Parameter Type Description
map table {column = value} pairs

Returns: Sqlizer

builder.and_

Combines multiple conditions with AND.

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

builder.or_

Combines multiple conditions with OR.

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

builder.question

Placeholder format for ? placeholders (default).

local query = sql.builder.select("*")
    :from("users")
    :placeholder_format(sql.builder.question)

builder.dollar

Placeholder format for $1, $2, ... placeholders.

local query = sql.builder.select("*")
    :from("users")
    :placeholder_format(sql.builder.dollar)

builder.at

Placeholder format for @p1, @p2, ... placeholders.

local query = sql.builder.select("*")
    :from("users")
    :placeholder_format(sql.builder.at)

builder.colon

Placeholder format for :1, :2, ... placeholders.

local query = sql.builder.select("*")
    :from("users")
    :placeholder_format(sql.builder.colon)

Connection Methods

Database connection handle returned by sql.get().

db:type

Returns database type constant.

local dbtype, err = db:type()

Returns: string, error

db:query

Executes SELECT query and returns rows.

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

Executes INSERT/UPDATE/DELETE query.

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

Creates prepared statement for repeated execution.

local stmt, err = db:prepare("SELECT * FROM users WHERE id = ?")
Parameter Type Description
sql string SQL with ? placeholders

Returns: Statement, error

db:begin

Begins database transaction.

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

Releases database resource back to pool.

local ok, err = db:release()

Returns: boolean, error

db:stats

Returns connection pool statistics.

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

Prepared statement returned by db:prepare().

stmt:query

Executes prepared statement as SELECT.

local rows, err = stmt:query({123})
Parameter Type Description
params table Array of bind parameters (optional)

Returns: table[], error

stmt:execute

Executes prepared statement as INSERT/UPDATE/DELETE.

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

Closes prepared statement.

local ok, err = stmt:close()

Returns: boolean, error

Transactions

Database transaction returned by db:begin().

tx:db_type

Returns database type constant.

local dbtype, err = tx:db_type()

Returns: string, error

tx:query

Executes SELECT query within transaction.

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

Executes INSERT/UPDATE/DELETE within transaction.

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

Creates prepared statement within transaction.

local stmt, err = tx:prepare("SELECT * FROM users WHERE id = ?")
Parameter Type Description
sql string SQL with ? placeholders

Returns: Statement, error

tx:commit

Commits transaction.

local ok, err = tx:commit()

Returns: boolean, error

tx:rollback

Rolls back transaction.

local ok, err = tx:rollback()

Returns: boolean, error

tx:savepoint

Creates named savepoint within transaction.

local ok, err = tx:savepoint("sp1")
Parameter Type Description
name string Savepoint name (alphanumeric and underscore only)

Returns: boolean, error

tx:rollback_to

Rolls back to named savepoint.

local ok, err = tx:rollback_to("sp1")
Parameter Type Description
name string Savepoint name

Returns: boolean, error

tx:release

Releases savepoint.

local ok, err = tx:release("sp1")
Parameter Type Description
name string Savepoint name

Returns: boolean, error

SELECT Builder

Fluent interface for building SELECT queries.

select:from

Sets FROM clause.

local query = sql.builder.select("id", "name"):from("users")
Parameter Type Description
table string Table name

Returns: SelectBuilder

select:join

Adds JOIN clause.

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

Adds LEFT JOIN clause.

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

Adds RIGHT JOIN clause.

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

Adds INNER JOIN clause.

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

Adds WHERE condition.

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)

Supports three formats:

  • String: where("status = ?", "active")
  • Table: where({status = "active"})
  • Sqlizer: where(sql.builder.gt({score = 80}))

Returns: SelectBuilder

select:order_by

Adds ORDER BY clause.

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

Adds GROUP BY clause.

local query = sql.builder.select("status", "COUNT(*)")
    :from("users")
    :group_by("status")
Parameter Type Description
columns ...string Column names

Returns: SelectBuilder

select:having

Adds HAVING condition.

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

Sets LIMIT.

local query = sql.builder.select("*")
    :from("users")
    :limit(10)
Parameter Type Description
n integer Limit value

Returns: SelectBuilder

select:offset

Sets OFFSET.

local query = sql.builder.select("*")
    :from("users")
    :offset(20)
Parameter Type Description
n integer Offset value

Returns: SelectBuilder

select:columns

Adds columns to SELECT.

local query = sql.builder.select():columns("id", "name", "email")
Parameter Type Description
columns ...string Column names

Returns: SelectBuilder

select:distinct

Adds DISTINCT modifier.

local query = sql.builder.select("status")
    :from("users")
    :distinct()

Returns: SelectBuilder

select:suffix

Adds SQL suffix.

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

Sets placeholder format.

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

Generates SQL string and bind arguments.

local sql_str, args = query:to_sql()

Returns: string, table

select:run_with

Creates executor for query.

local executor = query:run_with(db)
local rows, err = executor:query()
Parameter Type Description
db DB|Transaction Database or transaction handle

Returns: QueryExecutor

INSERT Builder

Fluent interface for building INSERT queries.

insert:into

Sets table name.

local query = sql.builder.insert():into("users")
Parameter Type Description
table string Table name

Returns: InsertBuilder

insert:columns

Sets column names.

local query = sql.builder.insert("users"):columns("name", "email")
Parameter Type Description
columns ...string Column names

Returns: InsertBuilder

insert:values

Adds row values.

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

Sets columns and values from table.

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

Inserts from SELECT query.

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

Adds SQL prefix.

local query = sql.builder.insert("users")
    :prefix("INSERT IGNORE INTO")
Parameter Type Description
sql string SQL prefix with ? placeholders
args ...any Bind arguments (optional)

Returns: InsertBuilder

insert:suffix

Adds SQL suffix.

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

Adds INSERT options.

local query = sql.builder.insert("users")
    :options("DELAYED", "IGNORE")
Parameter Type Description
options ...string INSERT options

Returns: InsertBuilder

insert:placeholder_format

Sets placeholder format.

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

Generates SQL string and bind arguments.

local sql_str, args = query:to_sql()

Returns: string, table

insert:run_with

Creates executor for query.

local executor = query:run_with(db)
local result, err = executor:exec()
Parameter Type Description
db DB|Transaction Database or transaction handle

Returns: QueryExecutor

UPDATE Builder

Fluent interface for building UPDATE queries.

update:table

Sets table name.

local query = sql.builder.update():table("users")
Parameter Type Description
table string Table name

Returns: UpdateBuilder

update:set

Sets column value.

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

Sets multiple columns from table.

local query = sql.builder.update("users")
    :set_map({status = "active", updated_at = sql.builder.expr("NOW()")})
Parameter Type Description
map table {column = value} pairs

Returns: UpdateBuilder

update:where

Adds WHERE condition.

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

Adds ORDER BY clause.

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

Sets LIMIT.

local query = sql.builder.update("users")
    :set("status", "active")
    :limit(10)
Parameter Type Description
n integer Limit value

Returns: UpdateBuilder

update:offset

Sets OFFSET.

local query = sql.builder.update("users")
    :set("status", "active")
    :offset(5)
Parameter Type Description
n integer Offset value

Returns: UpdateBuilder

update:suffix

Adds SQL suffix.

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

Adds FROM clause.

local query = sql.builder.update("users")
    :set("status", "active")
    :from("other_table")
Parameter Type Description
table string Table name

Returns: UpdateBuilder

update:from_select

Updates from SELECT query.

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

Sets placeholder format.

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

Generates SQL string and bind arguments.

local sql_str, args = query:to_sql()

Returns: string, table

update:run_with

Creates executor for query.

local executor = query:run_with(db)
local result, err = executor:exec()
Parameter Type Description
db DB|Transaction Database or transaction handle

Returns: QueryExecutor

DELETE Builder

Fluent interface for building DELETE queries.

delete:from

Sets table name.

local query = sql.builder.delete():from("users")
Parameter Type Description
table string Table name

Returns: DeleteBuilder

delete:where

Adds WHERE condition.

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

Adds ORDER BY clause.

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

Sets LIMIT.

local query = sql.builder.delete("users")
    :where({active = 0})
    :limit(100)
Parameter Type Description
n integer Limit value

Returns: DeleteBuilder

delete:offset

Sets OFFSET.

local query = sql.builder.delete("users")
    :where({active = 0})
    :offset(10)
Parameter Type Description
n integer Offset value

Returns: DeleteBuilder

delete:suffix

Adds SQL suffix.

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

Sets placeholder format.

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

Generates SQL string and bind arguments.

local sql_str, args = query:to_sql()

Returns: string, table

delete:run_with

Creates executor for query.

local executor = query:run_with(db)
local result, err = executor:exec()
Parameter Type Description
db DB|Transaction Database or transaction handle

Returns: QueryExecutor

Executing Queries

The query executor runs builder-generated queries.

executor:query

Executes query and returns rows (for SELECT).

local rows, err = executor:query()

Returns: table[], error

executor:exec

Executes query and returns result (for INSERT/UPDATE/DELETE).

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

Returns generated SQL and arguments without executing.

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.INVALID no
Statement closed errors.INVALID no
Transaction not active errors.INVALID no
Invalid savepoint name errors.INVALID no
Query execution error varies varies

See Error Handling for working with errors.

Example

local sql = require("sql")

-- Get database connection
local db, err = sql.get("app.db:main")
if err then error(err) end

-- Check database type
local dbtype, _ = db:type()
print("Database type:", dbtype)

-- Direct query
local users, err = db:query("SELECT id, name FROM users WHERE active = ?", {1})
if err then error(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 = query:run_with(db)
local results, err = executor:query()
if err then error(err) end

-- Transaction with savepoints
local tx, err = db:begin({isolation = sql.isolation.SERIALIZABLE})
if err then error(err) end

local _, err = tx:execute("INSERT INTO users (name) VALUES (?)", {"alice"})
if err then
    tx:rollback()
    error(err)
end

tx:savepoint("sp1")

local _, err = tx:execute("UPDATE users SET status = ? WHERE id = ?", {"active", 1})
if err then
    tx:rollback_to("sp1")
else
    tx:release("sp1")
end

local ok, err = tx:commit()
if err then error(err) end

-- Prepared statements
local stmt, err = db:prepare("INSERT INTO logs (message, level) VALUES (?, ?)")
if err then error(err) end

for i = 1, 100 do
    local _, err = stmt:execute({"log message " .. i, "info"})
    if err then
        stmt:close()
        error(err)
    end
end

stmt:close()

-- NULL and typed values
local insert = sql.builder.insert("products")
    :columns("name", "price", "description")
    :values("Widget", sql.as.float(19.99), sql.NULL)

local executor = insert:run_with(db)
local result, err = executor:exec()
if err then error(err) end

print("Inserted ID:", result.last_insert_id)

db:release()