Skip to content

feat Aggregations & traversed projection 2/5: traversed projection + output aliases — the tracer bullet #293

Description

@0x054

Parent

#282

What to build

select() reaches across relations, end to end. Lambda selectors traverse forward-FK paths at any depth (select(lambda t: (t.amount, t.account.name))), and the dict-returning lambda names output fields (select(lambda t: {"account_name": t.account.name}) — CONTEXT.md: Output alias, Traversed projection). Projection traversal narrows per ADR-0006 (INNER, one join per path, shared with where/order_by traversal of the same path); .left_join keeps relation-less rows and the traversed field decodes to None — even from a non-nullable source column (a projected record is not the related model). Unaliased traversed fields take the bare leaf column name; two selected fields resolving to the same output name is a build-time error naming the dict form. A selector returns a dict, a tuple, or a single field — mixed nesting is a build-time error; string selectors still never traverse. The include() × select() rejection goes final: flattened-as-final (grill decision) — error text points at traversed projection, not "until #282".

Acceptance criteria

  • Tuple and dict traversed projection at depth ≥ 2 on both backends; values decode identically to full hydration; fields land in selection/insertion order.
  • left_join interplay pinned: rows kept, None fields, NULL-tolerant decode; shared-path join identity visible in rendered SQL.
  • Build-time errors: output-name collision (naming the dict form), dict-in-tuple/tuple-in-dict, non-string dict key; static fixtures extended.
  • Golden vectors for traversed record fields; include×select final error text in place.

Blocked by

  • Slice 1 (QueryIR v5)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions