Manual SQL¶
When the query builder is not enough, yara_orm lets you drop down to raw SQL while
staying fully async. You can hydrate hand-written SELECTs straight into model
instances with Model.raw, or reach the active executor through connections.get
for low-level execute and fetch_* calls. Every entry point takes parameterized
queries, so values are bound by the driver rather than spliced into the SQL string.
Model.raw — rows as model instances¶
await Model.raw(sql, params=None) runs a query and returns a list[Model]
instances built from each row.
Important
The SELECT must return columns in the model's field-list order. Rows are
consumed positionally, so prefer listing columns explicitly (or SELECT * only
when the table column order matches the model's fields).
class Thing(Model):
name = fields.CharField(max_length=50)
class Meta:
table = "m_thing"
# Returns [Thing(...), ...] — real model instances, not raw rows.
things = await Thing.raw(
"SELECT * FROM m_thing WHERE name = $1",
["alpha"],
)
assert things[0].name == "alpha"
params defaults to None (treated as no parameters). Pass a list to bind values.
Low-level access with connections.get¶
For SQL that does not map to a model — INSERT, UPDATE, DELETE, aggregates,
DDL — use the executor returned by connections.get(name="default"). It is the
active executor: an open transaction when one is in scope, otherwise the named
connection's pool.
The executor exposes four async methods:
| Method | Returns |
|---|---|
await conn.execute(sql, params) |
The driver's execute result — for a single statement, the number of affected rows. |
await conn.fetch_all(sql, params) |
All result rows as dict-like rows keyed by column name. |
await conn.fetch_rows(sql, params) |
All result rows as positional rows (what Model.raw consumes internally). |
await conn.fetch_row(sql, params) |
A single row, or None when the query matches nothing. |
conn = connections.get("default")
# execute() reports affected rows.
affected = await conn.execute(
"INSERT INTO m_thing (name) VALUES ($1)",
["x"],
)
assert affected == 1
# fetch_all() returns dict rows you can index by column name.
rows = await conn.fetch_all("SELECT name FROM m_thing ORDER BY name")
names = [r["name"] for r in rows]
Parameter placeholders per backend¶
Placeholder syntax is dialect-specific. PostgreSQL uses $1, $2, ...; SQLite
uses ?1, ?2, .... Use the form that matches the backend you connected to.
Always bind values via params
Never build SQL by interpolating values into the string (f-strings, +,
.format()). Pass every value through the params list and reference it with a
placeholder so the driver binds it. String interpolation opens you to SQL
injection and breaks on quoting, NULL, and type coercion.
Manual SQL inside a transaction¶
When you are inside an in_transaction() block, connections.get and Model.raw
both route through the active transaction automatically — there is nothing extra
to wire up. Your raw statements share the same transaction as the surrounding
ORM calls and are committed or rolled back together.
from yara_orm import in_transaction
async with in_transaction():
conn = connections.get("default")
await conn.execute("INSERT INTO m_thing (name) VALUES ($1)", ["x"])
await Thing.create(name="y") # same transaction
See Transactions for the full lifecycle and rollback semantics.