Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,15 @@ Phantom diffs are the canonical symptom that this invariant has been broken.
pin the names (`test_index_name_matches_rust_runtime_*`).
- `docs/solutions/patterns/cross-emitter-ddl-parity.md` documents the rule and
the recipes below.
- On the query side, the rendered-statement parity pin in
`tests/test_rendered_statement.py` is the counterpart of the cross-emitter
pins: for every `Query` terminal (`all`, `first`, `count`, `exists`,
`update` in both forms, `delete`, through `Relation` and `ProjectedQuery`
too) on both dialects, with a secret column, `await query.<terminal>.sql()`
equals the `ferro.sql` record the executed call writes in `sql`, `params`
(the `REDACTED` sentinel included) and `dialect`. Both doors build the
statement through one `build_<terminal>` function in `src/operations.rs`,
which the executing entry sends and its `render_<terminal>` twin returns.

### Adding a new emitter

Expand Down
6 changes: 6 additions & 0 deletions docs/pages/api/queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ An aggregate-only projection collapses to one record (read with `first()`). Mixi

::: ferro.query.builder.ProjectedQuery

::: ferro.RenderedSql

::: ferro.REDACTED

::: ferro.query.rendered.Terminal

::: ferro.query.Row

::: ferro.query.Rows
Expand Down
4 changes: 3 additions & 1 deletion src/ferro/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
_carrying_report,
_run_pass,
)
from .query import Relation, Row, Rows, now
from .query import REDACTED, Relation, RenderedSql, Row, Rows, now
from .raw import Transaction, execute, fetch_all, fetch_one
from .rowsecurity import RowPolicy, RowSecurity
from .session import Session, current_session, engines
Expand Down Expand Up @@ -587,6 +587,8 @@ async def migrate(
"Relation",
"Row",
"Rows",
"RenderedSql",
"REDACTED",
"version",
"create_tables",
"migrate",
Expand Down
36 changes: 36 additions & 0 deletions src/ferro/_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,42 @@ async def update_filtered(
*,
secret_values: list[object] = ...,
) -> int: ...

RenderedParts = tuple[str, tuple[object, ...], tuple[bool, ...], str]
"""A rendering entry's answer: the SQL, the parameters (`None` where
masked), the mask (`True` where the parameter is a secret) and the dialect."""

async def render_fetch(
cls: object,
query_ir_json: str,
route: RouteHandle,
record_cls: type | None = None,
hop_classes: dict[str, type] | None = None,
*,
secret_values: list[object] = ...,
) -> RenderedParts: ...
async def render_count(
name: str,
query_ir_json: str,
route: RouteHandle,
*,
secret_values: list[object] = ...,
operation: Literal["count", "exists"] = "count",
) -> RenderedParts: ...
async def render_update(
name: str,
query_ir_json: str,
route: RouteHandle,
*,
secret_values: list[object] = ...,
) -> RenderedParts: ...
async def render_delete(
name: str,
query_ir_json: str,
route: RouteHandle,
*,
secret_values: list[object] = ...,
) -> RenderedParts: ...
async def add_m2m_links(
join_table: str,
source_col: str,
Expand Down
3 changes: 3 additions & 0 deletions src/ferro/query/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
ValueExpr,
now,
)
from .rendered import REDACTED, RenderedSql
from .rows import Row, Rows

__all__ = [
"REDACTED",
"AggregateExpr",
"FieldProxy",
"Predicate",
Expand All @@ -24,6 +26,7 @@
"QueryProxy",
"Relation",
"RelationProxy",
"RenderedSql",
"Row",
"RowSelector",
"Rows",
Expand Down
Loading
Loading