feat(dataflow): model production SQL flow patterns

This commit is contained in:
2026-07-30 18:19:18 +02:00
parent 0664570607
commit e091042948
26 changed files with 2169 additions and 36 deletions
+105 -4
View File
@@ -1128,11 +1128,37 @@ def _join_config(
) -> dict[str, Any]:
kind = str(join.args.get("kind") or "").casefold()
side = str(join.args.get("side") or "").casefold()
if kind not in {"", "inner", "outer"} or side not in {"", "left", "right", "full"}:
if kind not in {"", "inner", "outer", "semi", "anti"} or side not in {
"",
"left",
"right",
"full",
}:
raise SqlCompilationError(
[_sql_error("sql.join_type", "JOIN supports INNER, LEFT, RIGHT, or FULL joins only.")]
[
_sql_error(
"sql.join_type",
(
"JOIN supports INNER, LEFT, RIGHT, FULL, SEMI, "
"or ANTI joins only."
),
)
]
)
join_type = side or ("inner" if kind in {"", "inner"} else "")
if kind in {"semi", "anti"} and side:
raise SqlCompilationError(
[
_sql_error(
"sql.join_type",
"SEMI and ANTI joins cannot use a side qualifier.",
)
]
)
join_type = (
kind
if kind in {"semi", "anti"}
else side or ("inner" if kind in {"", "inner"} else "")
)
if not join_type:
raise SqlCompilationError(
[_sql_error("sql.join_type", "OUTER JOIN requires LEFT, RIGHT, or FULL.")]
@@ -1471,6 +1497,41 @@ def _render_expression(
state.selected = True
def _render_calculate(
node: GraphNode,
state: _SqlRenderState,
column_expression: Callable[[str], exp.Column],
) -> None:
_require_projection_slot(node, state)
calculated_targets: set[str] = set()
expressions: list[exp.Expression] = [exp.Star()]
for item in node.config["calculations"]:
parsed = parse_expression(str(item["expression"]))
dependencies = set(parsed.columns) & calculated_targets
if dependencies:
raise SqlCompilationError(
[
_node_sql_error(
node.id,
"sql.sequential_calculation",
(
"SQL view cannot render a calculated column that "
"depends on an earlier calculation in the same block."
),
)
]
)
expression = _qualified_expression(
str(item["expression"]),
column_expression=column_expression,
)
target = str(item["target_column"])
expressions.append(expression.as_(target))
calculated_targets.add(target)
state.select_expressions = expressions
state.selected = True
def _render_derive(
node: GraphNode,
state: _SqlRenderState,
@@ -1600,6 +1661,41 @@ def _render_sort(
]
def _render_rank(
node: GraphNode,
state: _SqlRenderState,
column_expression: Callable[[str], exp.Column],
) -> None:
_require_projection_slot(node, state)
function = {
"row_number": exp.RowNumber,
"rank": exp.Rank,
"dense_rank": exp.DenseRank,
}[str(node.config.get("method", "row_number"))]()
window = exp.Window(
this=function,
partition_by=[
column_expression(str(column))
for column in node.config.get("partition_by", [])
],
order=exp.Order(
expressions=[
exp.Ordered(
this=column_expression(str(item["column"])),
desc=item.get("direction", "asc") == "desc",
nulls_first=False,
)
for item in node.config["order_by"]
]
),
)
state.select_expressions = [
exp.Star(),
window.as_(str(node.config["target_column"])),
]
state.selected = True
def _render_limit(
node: GraphNode,
state: _SqlRenderState,
@@ -1642,7 +1738,10 @@ def _require_projection_slot(
_node_sql_error(
node.id,
"sql.multiple_select",
"Only one expression, conversion, derive, select, or aggregate transform is supported in SQL view.",
(
"Only one calculation, ranking, conversion, derive, "
"select, or aggregate transform is supported in SQL view."
),
)
]
)
@@ -1660,10 +1759,12 @@ def _register_sql_renderers() -> None:
"select": _render_select,
"derive": _render_derive,
"expression": _render_expression,
"calculate": _render_calculate,
"convert": _render_convert,
"replace": _render_unsupported,
"aggregate": _render_aggregate,
"sort": _render_sort,
"window.rank": _render_rank,
"limit": _render_limit,
"quality.rules": _render_unsupported,
"reconcile.compare": _render_unsupported,