Skip to content
Merged
Show file tree
Hide file tree
Changes from 116 commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
4ec8d0d
DEV-1744: one naming authority + one ValueKey renderer (B4, B5, B10)
ZmeiGorynych Aug 5, 2026
e2e4d88
DEV-1744: address review — renderer correctness + drift removal
ZmeiGorynych Aug 5, 2026
d6bd014
DEV-1744: document the scalar dialect semantics; drop the like duplic…
ZmeiGorynych Aug 5, 2026
69c4103
DEV-1744: drop unreachable None guards in the registry tests
ZmeiGorynych Aug 5, 2026
01717e7
DEV-1744: add the missing tests — and one proves a wrong-answer bug
ZmeiGorynych Aug 5, 2026
a6b5524
DEV-1744: review round 2 — cross-model star fails closed
ZmeiGorynych Aug 5, 2026
26eb329
DEV-1744: sweep for silently-dropped key fields — finds a 7th instance
ZmeiGorynych Aug 5, 2026
760c001
DEV-1744: narrow the last pytest.raises to one throwing call
ZmeiGorynych Aug 5, 2026
c8ba0a8
DEV-1744: Codex round — three more silently-wrong composition edges
ZmeiGorynych Aug 5, 2026
a78106a
DEV-1744: second Codex round — associativity, arity, transform deps
ZmeiGorynych Aug 5, 2026
09106e6
DEV-1744: third Codex round — scalar arity, and no associativity exce…
ZmeiGorynych Aug 5, 2026
1bbf618
DEV-1744: reject NULL inside an IN list — it silently returned zero rows
ZmeiGorynych Aug 5, 2026
2adb368
DEV-1744: put the render shape in the cross-model dedup identity; fix…
ZmeiGorynych Aug 5, 2026
ee2adca
DEV-1744: group unary operands too — -(a+b) was rendering as -a + b
ZmeiGorynych Aug 5, 2026
14baa64
DEV-1745: test pack, the one Mode-A door (W1), W8 and the _cm_ fragme…
ZmeiGorynych Aug 5, 2026
ef58a0d
DEV-1744: comparisons are non-associative — (a = 5) IS NULL lost its …
ZmeiGorynych Aug 5, 2026
55cea93
DEV-1745: approved SQL divergences from the door migration
ZmeiGorynych Aug 5, 2026
97b0494
DEV-1744: fix four live grouping bugs in the generator's three arithm…
ZmeiGorynych Aug 5, 2026
a305e6b
DEV-1745: W6 date_range warning, W3 plan-time outer-WHERE routing, D6…
ZmeiGorynych Aug 5, 2026
8c2b18e
DEV-1744: one arithmetic composer — the generator's three each mis-gr…
ZmeiGorynych Aug 5, 2026
6b27565
DEV-1744: delete the generator's now-unreferenced weaker precedence h…
ZmeiGorynych Aug 5, 2026
2f6ee3c
DEV-1745: W4 — structural reachability replaces the model-name heuristic
ZmeiGorynych Aug 5, 2026
11e76d7
DEV-1744: parenthesise % unconditionally — sqlglot re-parses a + b % …
ZmeiGorynych Aug 5, 2026
cd8636b
DEV-1745: W5 — the dropped-filter warning contract, end to end
ZmeiGorynych Aug 5, 2026
ae2c720
DEV-1744: cover boolean-valued comparison operands in the round-trip …
ZmeiGorynych Aug 5, 2026
501cbe7
DEV-1745: W7 — docs, skills, and the DECISIONS entry
ZmeiGorynych Aug 5, 2026
af4af8b
DEV-1744: fix the ClickHouse integration assertion missed by the SUBS…
ZmeiGorynych Aug 5, 2026
891dca7
DEV-1745: fix host-locality for a derived column that is BOTH local a…
ZmeiGorynych Aug 5, 2026
a912412
DEV-1744: clear the open Sonar issues and the CodeRabbit allowlist-dr…
ZmeiGorynych Aug 5, 2026
e7c7b73
DEV-1744: hoist test imports to the top and correct the _wm_ docstrin…
ZmeiGorynych Aug 5, 2026
990f0eb
DEV-1745: drop the redundant target params from _register_agg_key_joins
ZmeiGorynych Aug 6, 2026
bf07d0c
Merge remote-tracking branch 'origin/egor/dev-1744-pr-1-of-6-foundati…
ZmeiGorynych Aug 6, 2026
3a1ed93
DEV-1744: admit ceiling/sign/ltrim/rtrim/substring — the parser alrea…
ZmeiGorynych Aug 6, 2026
c5b3f36
DEV-1744: name the follow-up issue for the four deferred scalars
ZmeiGorynych Aug 6, 2026
8fda1c7
Merge pull request #282 from MotleyAI/egor/dev-1744-pr-1-of-6-foundat…
ZmeiGorynych Aug 6, 2026
be740a8
DEV-1745: address CodeRabbit + Sonar review — two production bugs
ZmeiGorynych Aug 6, 2026
997bfef
Merge remote-tracking branch 'origin/egor/dev-1742-one-doctrine-conso…
ZmeiGorynych Aug 6, 2026
d15372d
DEV-1745: clear the remaining Sonar issues
ZmeiGorynych Aug 6, 2026
ee8dca7
DEV-1745: build the leaf-path list fresh instead of appending to _pre…
ZmeiGorynych Aug 6, 2026
e03cb1f
DEV-1745: CSV output regression, one reachability rule, and review fo…
ZmeiGorynych Aug 6, 2026
6593955
DEV-1746 stages 1-2: null-safe grain doctrine (B1/B2) + pagination ho…
ZmeiGorynych Aug 6, 2026
142133d
DEV-1745: review nitpicks — imports, async-without-await, typing, docs
ZmeiGorynych Aug 6, 2026
9207d7a
DEV-1746 stage 3: the combined layer is one sqlglot AST (B3, B7, §5.6)
ZmeiGorynych Aug 6, 2026
901eecf
DEV-1745: hoist the remaining test-local imports to module scope
ZmeiGorynych Aug 6, 2026
6f2ae85
DEV-1745: hoist PlannedQuery import and name the expected frame-bound…
ZmeiGorynych Aug 6, 2026
8de2522
DEV-1746 stages 4-5 + §5.1: plan-order carry lists, the projection in…
ZmeiGorynych Aug 6, 2026
4a9dd91
DEV-1746 stage 7: docs + design-decision record
ZmeiGorynych Aug 6, 2026
b090d99
Merge pull request #285 from MotleyAI/egor/dev-1745-pr-2-of-6-one-doo…
ZmeiGorynych Aug 6, 2026
1e4470e
Merge remote-tracking branch 'origin/egor/dev-1742-one-doctrine-conso…
ZmeiGorynych Aug 6, 2026
1a908df
DEV-1746 stage 6: one isolation decision, and the DEV-1688 seam
ZmeiGorynych Aug 6, 2026
222f412
DEV-1746: fix SonarCloud S5863 — assert the assembly order, not run-v…
ZmeiGorynych Aug 6, 2026
fc9731d
DEV-1746: two fixes from the Codex implementation review
ZmeiGorynych Aug 6, 2026
c3b5d92
DEV-1746: fail loudly on a duplicate carried alias, and pin the proje…
ZmeiGorynych Aug 6, 2026
b3ea0eb
DEV-1746: make the shared-slot regression test actually test the guard
ZmeiGorynych Aug 6, 2026
6f2cfd9
DEV-1746: parse the combined CTE's SELECT list instead of regexing it
ZmeiGorynych Aug 6, 2026
a698665
DEV-1746: drop the assertion the exact-list check subsumes
ZmeiGorynych Aug 6, 2026
ad94281
DEV-1746: fail on an under-rendered projection occurrence too (CodeRa…
ZmeiGorynych Aug 6, 2026
69a1de2
DEV-1746: address the CodeRabbit review, including a real T-SQL ORDER…
ZmeiGorynych Aug 6, 2026
79b8a0c
DEV-1746: clear the 10 SonarCloud issues and route the _wm_ inner gra…
ZmeiGorynych Aug 6, 2026
c5b3f80
Merge pull request #288 from MotleyAI/egor/dev-1746-pr-3-of-6-scope-a…
ZmeiGorynych Aug 6, 2026
a224cb2
DEV-1747: the test suite, written first
ZmeiGorynych Aug 6, 2026
e5eaa63
DEV-1747: the pre-bound seam, host-grain ordering, and B6
ZmeiGorynych Aug 6, 2026
02f757f
Merge remote-tracking branch 'origin/egor/dev-1742-one-doctrine-conso…
ZmeiGorynych Aug 6, 2026
c3822dd
DEV-1747 §5.10: one ORDER BY resolver, and D4 stops the silent drops
ZmeiGorynych Aug 6, 2026
fc29af2
DEV-1747 D9: an ungrouped derived sort key crosses like any other ref
ZmeiGorynych Aug 6, 2026
4cf22c1
DEV-1747 D8: the transform chains hand the assembler AST
ZmeiGorynych Aug 6, 2026
05aa0bd
DEV-1747 B6: the reroot keeps the filter AUDIT, not the filter ROUTING
ZmeiGorynych Aug 6, 2026
162e176
DEV-1747: docs, the design log, and a golden baseline for PRs 5-6
ZmeiGorynych Aug 6, 2026
741dbb7
DEV-1747: mark the four superseded ORDER BY resolvers (P-J state 1)
ZmeiGorynych Aug 6, 2026
373ead9
DEV-1747 B6: cover the bug at the layer it actually broke — the rows
ZmeiGorynych Aug 6, 2026
09378a6
DEV-1747 B6 (2nd instance): a ROW-phase filter applies at the host too
ZmeiGorynych Aug 6, 2026
9db990f
DEV-1747: review round 1 — two wrong-SQL bugs, one identity bug
ZmeiGorynych Aug 7, 2026
256ea9f
DEV-1747: constrain the pre-bound seam's slice bounds (Codex round 2)
ZmeiGorynych Aug 7, 2026
9d52792
DEV-1747: review round 2 — the grain prefix, and imports at the top
ZmeiGorynych Aug 7, 2026
327b584
DEV-1747: review round 3 — the last six Sonar test-quality issues
ZmeiGorynych Aug 7, 2026
6b53d6a
DEV-1747: imports at the top, across the whole DEV-1747 test pack
ZmeiGorynych Aug 7, 2026
4a27168
Merge pull request #290 from MotleyAI/egor/dev-1747-pr-4-of-6-typed-r…
ZmeiGorynych Aug 7, 2026
640dc8d
DEV-1748: first/last as a plan-shaped isolated CTE (B9)
ZmeiGorynych Aug 7, 2026
e2a8715
DEV-1748: review round 1 — a duplicate-_base regression, and the rest
ZmeiGorynych Aug 7, 2026
e747123
DEV-1748: keep the two allowed-delta tests separate
ZmeiGorynych Aug 7, 2026
02817c3
DEV-1748: review round 2 — hoist the bundle out of pytest.raises
ZmeiGorynych Aug 7, 2026
6ac6991
Merge pull request #295 from MotleyAI/egor/dev-1748-pr-5-of-6-firstla…
ZmeiGorynych Aug 7, 2026
13d8b96
DEV-1763: route the five live render families through render_value_ke…
ZmeiGorynych Aug 10, 2026
3797748
DEV-1763: address Sonar/CodeRabbit review on PR 296
ZmeiGorynych Aug 10, 2026
144529e
DEV-1763: update sentinel/inventory docstrings to the post-migration …
ZmeiGorynych Aug 11, 2026
12d23f8
Merge pull request #296 from MotleyAI/egor/dev-1763-pr-55-of-6-p-g-ca…
ZmeiGorynych Aug 11, 2026
b1949e1
DEV-1769: validate routed ColumnSqlKey paths symmetrically with Colum…
ZmeiGorynych Aug 11, 2026
4d748c2
DEV-1769: hoist setup out of pytest.raises (Sonar S5778)
ZmeiGorynych Aug 11, 2026
fdbc8fe
Merge pull request #297 from MotleyAI/egor/dev-1769-validate-multi-ho…
ZmeiGorynych Aug 11, 2026
9aba6ef
DEV-1749: PR 6 of 6 — deletion, sweep, docs (B12; P-J states 2+3)
ZmeiGorynych Aug 11, 2026
f15f0a3
Merge remote-tracking branch 'origin/egor/dev-1742-one-doctrine-conso…
ZmeiGorynych Aug 11, 2026
470d565
DEV-1749: address CodeRabbit review on PR 298
ZmeiGorynych Aug 11, 2026
02ce207
DEV-1749: strengthen migrated tests per Codex review
ZmeiGorynych Aug 11, 2026
e019b4d
Merge pull request #298 from MotleyAI/egor/dev-1749-pr-6-of-6-deletio…
ZmeiGorynych Aug 11, 2026
04fb33b
DEV-1771: single-source _is_bare_identifier on canonical IDENTIFIER_RE
ZmeiGorynych Aug 12, 2026
622ce88
DEV-1783: PR #286 review G1 production correctness fixes
ZmeiGorynych Aug 12, 2026
2986d08
DEV-1785: PR #286 review G3 — docs & docstring corrections
ZmeiGorynych Aug 12, 2026
0bafa61
DEV-1783: address PR #300 review (CodeRabbit + Sonar + Codex)
ZmeiGorynych Aug 12, 2026
a4ff970
DEV-1784: prod-code polish — typing, Literals, docstrings, policy sha…
ZmeiGorynych Aug 12, 2026
64ef7b3
DEV-1784: prod-code polish — log-alias single source, keyword-only AP…
ZmeiGorynych Aug 12, 2026
1f0e027
Merge pull request #300 from MotleyAI/egor/dev-1783-pr-286-review-g1-…
ZmeiGorynych Aug 12, 2026
b1a3417
DEV-1786: PR #286 review G4 — test hardening (29 items)
ZmeiGorynych Aug 12, 2026
2b69329
DEV-1785: address PR #301 review (Codex + CodeRabbit)
ZmeiGorynych Aug 12, 2026
1712285
DEV-1784: address SonarCloud PR review
ZmeiGorynych Aug 12, 2026
1a05b5b
DEV-1786: address Sonar + Codex review on PR 303
ZmeiGorynych Aug 12, 2026
e71dbba
DEV-1785: address PR #301 review round 2 (Codex)
ZmeiGorynych Aug 12, 2026
9022aa7
Merge pull request #301 from MotleyAI/egor/dev-1785-pr-286-review-g3-…
ZmeiGorynych Aug 12, 2026
186fa70
DEV-1784: move S107 suppression onto the parameter-list line Sonar cites
ZmeiGorynych Aug 12, 2026
4b09694
Merge branch 'egor/dev-1742-one-doctrine-consolidation-of-sql-generat…
ZmeiGorynych Aug 12, 2026
ff2654c
Merge pull request #303 from MotleyAI/egor/dev-1786-pr-286-review-g4-…
ZmeiGorynych Aug 12, 2026
5947ef4
Merge umbrella (DEV-1742) forward into DEV-1784
ZmeiGorynych Aug 12, 2026
7b0db4f
DEV-1784: fold sql_expr's log-alias copy into the single-source policy
ZmeiGorynych Aug 16, 2026
3e5a0b5
DEV-1784: keep parse_sql_expr total over non-registry sqlglot dialects
ZmeiGorynych Aug 16, 2026
fbca514
Merge pull request #302 from MotleyAI/egor/dev-1784-pr-286-review-g2-…
ZmeiGorynych Aug 16, 2026
db3428e
Merge pull request #306 from MotleyAI/egor/dev-1771-single-source-bar…
ZmeiGorynych Aug 16, 2026
03d894d
DEV-1788: unify aggregated slot-type and display-format inference
ZmeiGorynych Aug 16, 2026
8ead7ce
Merge remote-tracking branch 'origin/egor/dev-1742-one-doctrine-conso…
ZmeiGorynych Aug 16, 2026
7d7d0bb
DEV-1742: fix Sonar S9002/S9100 in DEV-1744 tests (tmp_path over tmp_…
ZmeiGorynych Aug 16, 2026
7f6e90d
Merge pull request #307 from MotleyAI/egor/dev-1788-aggregated-slot-f…
ZmeiGorynych Aug 16, 2026
48c9b13
DEV-1742: scope order[].column short-alias rule to declared measures …
ZmeiGorynych Aug 16, 2026
a150fce
Merge branch 'egor/dev-1742-one-doctrine-consolidation-of-sql-generat…
ZmeiGorynych Aug 16, 2026
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
11 changes: 7 additions & 4 deletions .claude/skills/slayer-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ A `SlayerQuery` is a JSON/dict object. The same shape works across the REST API,

`order[].column` is the short alias (`count`, `revenue_sum`) — not the colon form.

**Ordering by something you don't project.** `order` may name an undeclared column/aggregate/expression ("top-N by X, show only Y, Z"). Computed hidden, sorted on, and stripped from the result: an **aggregate** (`amount:sum`, `customers.revenue:sum`), an inline **transform** (`rank(amount:sum)`, `change(...)`, `cumsum`/`lag`/`lead`/`ntile`), an inline **composite** (`revenue:sum / cnt:sum`, `abs(amount:sum)`), and a **windowed** aggregate (`amount:sum(window='90d')`, alone or inside a composite). A **raw row column** is orderable only in a raw-rows query (`distinct_dimension_values: false`); in a grouped/dedup query it's rejected (HTTP 400 — add it to `dimensions` or order by an aggregate of it). A **joined** row column is rejected — project it. Order expressions must use formula syntax for their operands, not the `name`s of measures declared in the same query: `{"column": "revenue:sum / cnt:sum"}` works, `{"column": "rev / cnt"}` is rejected.
**Ordering by something you don't project.** `order` may name an undeclared column/aggregate/expression ("top-N by X, show only Y, Z"). Computed hidden, sorted on, and stripped from the result: an **aggregate** (`amount:sum`, `customers.revenue:sum`), an inline **transform** (`rank(amount:sum)`, `change(...)`, `cumsum`/`lag`/`lead`/`ntile`), an inline **composite** (`revenue:sum / cnt:sum`, `abs(amount:sum)`), and a **windowed** aggregate (`amount:sum(window='90d')`, alone or inside a composite). A **raw row column** sorts directly in a raw-rows query (`distinct_dimension_values: false`); in a grouped/dedup query there is no single value per group, so it sorts **per group** by the extreme the direction puts first — `asc` by each group's `min`, `desc` by each group's `max`. Write `{"column": "created_at:max", "direction": "asc"}` explicitly for the other one. A **joined** row column (`customers.regions.name`), and a derived column whose `sql` reaches through a join, behave the same way — the join is pulled in for the sort, and in a grouped query the wrap is computed per host row-group rather than globally. NULLs sort **last** in both directions on every database (SQL Server excepted: its native ordering is used, because the portable emulation makes the statement fail there). An order target SLayer cannot resolve is an error, never a silently unsorted result. Order expressions must use formula syntax for their operands, not the `name`s of measures declared in the same query: `{"column": "revenue:sum / cnt:sum"}` works, `{"column": "rev / cnt"}` is rejected.

**Dim-only queries deduplicate.** A query with no measures and at least one dimension or time-dimension auto-emits `GROUP BY <dim/td aliases>` and returns the distinct combinations. The `GROUP BY` is applied before `LIMIT`, so a row cap can't silently drop unique tuples. To opt out, set `"distinct_dimension_values": false` on the query — emits raw rows (no top-level `GROUP BY`), with WHERE / ORDER BY / LIMIT applied as usual. Any measure reference in `measures` / `filters` / `order` raises `DistinctDimensionValuesError` in this mode.

Expand Down Expand Up @@ -72,7 +72,7 @@ Result column naming: `revenue:sum` → `orders.revenue_sum` (colon becomes unde

**Boolean logic**: `AND`, `OR`, `NOT`

**String-hygiene scalars** (DEV-1378, lowercase only): `lower`, `upper`, `trim`, `replace`, `substr`, `instr`, `length`, `concat`. Plus the SQL `||` operator (folded into `concat(...)`). Examples: `"lower(status) = 'active'"`, `"length(replace(x, ',', '')) > 0"`, `"substr(s, 1, instr(s, ',') - 1) = 'first'"`, `"first || ' ' || last = 'jane doe'"`. Calls outside this allowlist (`json_extract`, `coalesce`, …) belong in `Column.sql` / `Column.filter` / `SlayerModel.filters` (Mode A SQL), not query filters.
**Mode-B scalars** (matched case-insensitively): string hygiene (`lower`, `upper`, `trim`, `ltrim`, `rtrim`, `replace`, `substr`, `substring`, `instr`, `length`, `concat`), null handling (`coalesce`, `nullif`, `ifnull`), and math (`round`, `abs`, `ceil`, `floor`, `sign`, `log10`, …). Plus the SQL `||` operator (folded into `concat(...)`). Examples: `"lower(status) = 'active'"`, `"coalesce(nickname, name) = 'Ada'"`, `"length(replace(x, ',', '')) > 0"`, `"first || ' ' || last = 'jane doe'"`. Raw SQL functions outside the allowlist (`json_extract`, `date_trunc`, …) belong in `Column.sql` / `Column.filter` / `SlayerModel.filters` (Mode A SQL), not query filters.

**Filtering on computed measures**: `"change(revenue:sum) > 0"`, `"last(change(revenue:sum)) < 0"`. Applied as post-filters on the outer query.

Expand All @@ -88,7 +88,10 @@ Result column naming: `revenue:sum` → `orders.revenue_sum` (colon becomes unde
engine = SlayerQueryEngine(storage=storage)

# Async (most callers — REST/MCP):
result = await engine.execute(query=query) # SlayerResponse with .data, .columns, .row_count, .sql, .attributes
result = await engine.execute(query=query) # SlayerResponse with .data, .columns, .row_count, .sql, .attributes, .warnings
# .warnings holds advisories, each tagged with .kind — "normalization" for an input
# rewrite, "unreachable_filter_dropped" for a filter dropped from a cross-model CTE
# (it still applies at the host). Empty for a clean query.

# With runtime variables (highest precedence — wins over query.variables / model defaults):
result = await engine.execute(query=query, variables={"region": "US"})
Expand Down Expand Up @@ -180,7 +183,7 @@ Surfaces: Python SDK `engine.execute(query=[...])`; CLI `slayer query @file.json

## Result format

Column keys use `model_name.column_name` format: `"orders._count"`, `"orders.revenue_sum"`. For multi-hop joined dimensions, the full path is included: `"orders.customers.regions.name"`. An explicit `name` on a measure spec swaps the canonical leaf — local (`{"formula": "amount:sum", "name": "rev"}` → `"orders.rev"`) or cross-model (`{"formula": "customers.revenue:sum", "name": "cust_rev"}` → `"orders.customers.cust_rev"`, hop path preserved). In any downstream stage of a `query_nested` DAG the column is exposed under the bare `name` (e.g. `cust_rev`) — that's what you type in stage 2's `formula` to reference the value. The response also includes `attributes` — a `ResponseAttributes` object with `.dimensions` and `.measures` dicts, each mapping column alias → `FieldMetadata` (label, format).
Column keys use `model_name.column_name` format: `"orders._count"`, `"orders.revenue_sum"`. For multi-hop joined dimensions, the full path is included: `"orders.customers.regions.name"`. Columns come back in the order you declare them in the query — dimensions, then time dimensions, then measures — regardless of measure kind (local, cross-model, or windowed); hidden order-only / filter-only targets never appear. An explicit `name` on a measure spec swaps the canonical leaf — local (`{"formula": "amount:sum", "name": "rev"}` → `"orders.rev"`) or cross-model (`{"formula": "customers.revenue:sum", "name": "cust_rev"}` → `"orders.customers.cust_rev"`, hop path preserved). In any downstream stage of a `query_nested` DAG the column is exposed under the bare `name` (e.g. `cust_rev`) — that's what you type in stage 2's `formula` to reference the value. The response also includes `attributes` — a `ResponseAttributes` object with `.dimensions` and `.measures` dicts, each mapping column alias → `FieldMetadata` (label, format).

## Strict validation (v3)

Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ slayer/
core/ # Domain models, enums, query/formula parsers
engine/ # Query orchestration
query_engine.py # Central orchestrator (execute, model resolution)
enrichment.py # SlayerQuery → EnrichedQuery transformation
enriched.py # EnrichedQuery dataclasses
binding.py # SlayerQuery → typed bound keys
stage_planner.py # Bound query → typed PlannedQuery (rendered by sql/generator.py)
ingestion.py # Auto-ingestion from database schemas
sql/ # SQL generation (sqlglot) and execution (SQLAlchemy)
storage/ # Storage backends (YAML, SQLite, pluggable registry)
Expand Down Expand Up @@ -116,7 +116,7 @@ docs/
1. Add the function name to `ALL_TRANSFORMS` and/or `TIME_TRANSFORMS` in `core/formula.py`
2. Handle it in the formula parser (`parse_formula`)
3. Add the SQL generation in `generator.py`
4. Add enrichment support in `enrichment.py` if it needs special handling
4. Add binder/planner support in `binding.py` / `stage_planner.py` if it needs special handling
5. Add unit tests in `test_sql_generator.py` and integration tests
6. Document in `docs/concepts/formulas.md`

Expand Down
Loading
Loading