47 lines
1.8 KiB
Python
47 lines
1.8 KiB
Python
"""Filter rendering — one typed `Filter` → one SQL predicate node.
|
|
|
|
Each filter shape maps to a sqlglot expression node (`exp.EQ`, `exp.In`,
|
|
`exp.Between`, or the date-range subtree). Literals go through `exp.convert`,
|
|
so quoting and escaping are sqlglot's job, not ours. The column is resolved to
|
|
its owning table and qualified via the shared `AliasMap` (allocating an alias,
|
|
and implying a join later, if the filter introduces a new table).
|
|
|
|
Adding a new filter shape is a new branch here that returns an `exp` node — no
|
|
change to the assembly in `compose.py`. The `Filter` type stays the constraint
|
|
boundary: there is no raw-SQL escape hatch.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from sqlglot import exp
|
|
|
|
from api.composer.aliases import AliasMap
|
|
from api.composer.dates import resolve_date_range
|
|
from api.composer.types import Filter, PickValidationError
|
|
from api.recon.types import Recon
|
|
|
|
|
|
def render_filter(
|
|
f: Filter,
|
|
recon: Recon,
|
|
aliases: AliasMap,
|
|
encodings: dict[str, dict[str, str]],
|
|
) -> exp.Expression:
|
|
"""Render one `Filter` to a predicate node bound to the column's table."""
|
|
table, col = recon.resolve_column(f.column)
|
|
col_expr = aliases.col(table, col.name)
|
|
|
|
kind = f.kind()
|
|
if kind == "equals":
|
|
return exp.EQ(this=col_expr, expression=exp.convert(f.equals))
|
|
if kind == "in_values":
|
|
if not f.in_values:
|
|
raise PickValidationError(f"filter on {f.column!r} has empty in_values")
|
|
return exp.In(this=col_expr, expressions=[exp.convert(v) for v in f.in_values])
|
|
if kind == "between":
|
|
lo, hi = f.between
|
|
return exp.Between(this=col_expr, low=exp.convert(lo), high=exp.convert(hi))
|
|
if kind == "date_range":
|
|
return resolve_date_range(f.date_range, col, col_expr, encodings)
|
|
raise PickValidationError(f"unknown filter kind {kind!r}")
|