Skip to content

Raw SQL

Escape hatches for SQL the query builder doesn't cover. All three functions take a SQL string plus positional bind parameters — placeholders are ? on SQLite and $1, $2, ... on Postgres — and honor an active transaction() block. See the Raw SQL guide.

execute(sql, *args, using=None, session=None) async

Run a raw SQL statement, returning rows affected.

Honors the active transaction() block via the _CURRENT_TRANSACTION ContextVar. Outside any transaction, runs on a one-off pool connection. Two consecutive top-level execute calls outside a transaction may use different pool connections — wrap in transaction() if you need connection affinity (e.g. SET LOCAL, advisory locks, LISTEN/NOTIFY).

Source code in src/ferro/raw.py
async def execute(
    sql: str, *args: Any, using: str | None = None, session: Any | None = None
) -> int:
    """Run a raw SQL statement, returning rows affected.

    Honors the active ``transaction()`` block via the ``_CURRENT_TRANSACTION``
    ContextVar. Outside any transaction, runs on a one-off pool connection.
    Two consecutive top-level ``execute`` calls outside a transaction may use
    different pool connections — wrap in ``transaction()`` if you need
    connection affinity (e.g. ``SET LOCAL``, advisory locks, ``LISTEN/NOTIFY``).
    """
    _check_sql(sql)
    marshalled = [_marshal(a) for a in args]
    route = _transaction_or_using(using, session)
    return await _raw_execute(sql, marshalled, route)

fetch_all(sql, *args, using=None, session=None) async

Run a raw SQL query and return all rows as a list of dicts.

Values are wire-close primitives. UUID/datetime/JSON columns come back as strings. If you want typed rows, use the ORM.

Source code in src/ferro/raw.py
async def fetch_all(
    sql: str, *args: Any, using: str | None = None, session: Any | None = None
) -> list[dict[str, Any]]:
    """Run a raw SQL query and return all rows as a list of dicts.

    Values are wire-close primitives. UUID/datetime/JSON columns come back as
    strings. If you want typed rows, use the ORM.
    """
    _check_sql(sql)
    marshalled = [_marshal(a) for a in args]
    route = _transaction_or_using(using, session)
    return await _raw_fetch_all(sql, marshalled, route)

fetch_one(sql, *args, using=None, session=None) async

Run a raw SQL query and return the first row as a dict, or None.

Callers should LIMIT 1 if the query may return more than one row.

Source code in src/ferro/raw.py
async def fetch_one(
    sql: str, *args: Any, using: str | None = None, session: Any | None = None
) -> dict[str, Any] | None:
    """Run a raw SQL query and return the first row as a dict, or ``None``.

    Callers should ``LIMIT 1`` if the query may return more than one row.
    """
    _check_sql(sql)
    marshalled = [_marshal(a) for a in args]
    route = _transaction_or_using(using, session)
    return await _raw_fetch_one(sql, marshalled, route)