Skip to content
Draft
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
256c99a
DEV-1450 stage 1: typed identity primitives (dormant)
ZmeiGorynych May 21, 2026
82078d6
DEV-1450 stage 2: typed scope + bundle, with I2 anchor-optional shape
ZmeiGorynych May 21, 2026
0c59436
DEV-1450 stage 3: extract _walk_join_chain to path_resolution
ZmeiGorynych May 21, 2026
b0b29b8
DEV-1450 stage 4: extract aggregation registry helpers (dormant)
ZmeiGorynych May 21, 2026
003918f
DEV-1450 stage 5: typed errors and slack-warning carriers
ZmeiGorynych May 21, 2026
5a2c1bd
DEV-1450 stage 6: slack normalization layer (FUNC_STYLE_AGG + MISPLAC…
ZmeiGorynych May 21, 2026
332b145
DEV-1450 stages 1+6 review: bool/Decimal disambiguation + custom-agg …
ZmeiGorynych May 21, 2026
ebef83a
DEV-1450 stage 7a.1: typed plan shapes (dormant)
ZmeiGorynych May 21, 2026
0d59f58
DEV-1450 stage 6 carry-over: activate DOT_PATH_IN_SQL
ZmeiGorynych May 21, 2026
ae2b1dc
DEV-1450 stage 7a.2: cross-model planner Protocol + isolated-CTE impl…
ZmeiGorynych May 21, 2026
a8b1cc4
DEV-1450 stage 7a.3: Mode-B Python-AST parser
ZmeiGorynych May 21, 2026
baa8c8f
DEV-1450 stage 7a.4: Mode-A sqlglot wrapper
ZmeiGorynych May 21, 2026
29b274e
DEV-1450 stage 7a.5: ExpressionBinder + FilterBinder
ZmeiGorynych May 21, 2026
5ef5048
DEV-1450 stage 7a.6: ValueRegistry, TransformLowerer, ProjectionPlanner
ZmeiGorynych May 21, 2026
89d8502
DEV-1450 stage 7a.7: multi-stage DAG planner
ZmeiGorynych May 21, 2026
7b8915b
DEV-1450 stage 7b.1: variables substitution in new pipeline
ZmeiGorynych May 21, 2026
4eef4b6
DEV-1450 stage 7b.2: pre-bind ModelMeasure expansion
ZmeiGorynych May 21, 2026
d46f114
DEV-1450 stage 7b.3a: TimeTruncKey for time-dimension identity
ZmeiGorynych May 21, 2026
7654770
DEV-1450 stage 7b.3b: bind_time_dimension + planner integration
ZmeiGorynych May 21, 2026
2a5b2a9
DEV-1450 stage 7b.3c: date_range filter + main-TD disambiguation
ZmeiGorynych May 21, 2026
8fd6167
DEV-1450 stage 7b.4: planner-side transform support (11 primitives)
ZmeiGorynych May 21, 2026
6ff18a3
DEV-1450 stage 7b.5: cross-model planner wiring + HostFilterRouting
ZmeiGorynych May 21, 2026
b65fab8
DEV-1450 stage 7b.6: BoundExpr type unification
ZmeiGorynych May 21, 2026
b8816e1
DEV-1450 stage 7b.7: parity oracle test helpers (drop adapter)
ZmeiGorynych May 21, 2026
85d7a4b
DEV-1450 stage 7b.8: local-only generator slice (generate_from_planned)
ZmeiGorynych May 21, 2026
2145e55
DEV-1450 stage 7b.9: generator slice for time dimensions + model.filters
ZmeiGorynych May 22, 2026
1faa044
DEV-1450 stage 7b.10: generator slice for window transforms
ZmeiGorynych May 22, 2026
6ca3cb4
DEV-1450 stage 7b.11: generator slice for self-join CTE transforms
ZmeiGorynych May 22, 2026
c43940a
DEV-1450 stage 7b.12: generator slice for cross-model CTEs
ZmeiGorynych May 22, 2026
3ef9288
DEV-1450 stage 7b.13: generator slice for dialect parity
ZmeiGorynych May 23, 2026
d00739a
DEV-1450 stage 7b.14: production migration off MixedArithmeticField
ZmeiGorynych May 23, 2026
cff26fc
DEV-1450 stage 7b.15a: build_resolved_source_bundle
ZmeiGorynych May 23, 2026
164d975
DEV-1450 stage 7b.15b: binder/parser/core gaps for the 4 cross-model …
ZmeiGorynych May 23, 2026
1cc6508
DEV-1450 stage 7b.15c: multi-stage DAG rendering (generate_planned_st…
ZmeiGorynych May 23, 2026
cf73d3b
DEV-1450 stage 7b.15d: engine cutover (default-path flip)
ZmeiGorynych May 24, 2026
b79c4f6
DEV-1450 stage 7b.15d: integration fixes (chain re-count + time_shift…
ZmeiGorynych May 24, 2026
855dd76
DEV-1450 stage 7b.15d: integration fixes (windowed-filter ValueError …
ZmeiGorynych May 24, 2026
188ed8b
DEV-1450 stage 7b.15d: integration fixes (join cycle detection, no-jo…
ZmeiGorynych May 24, 2026
2f3a11b
DEV-1450 stage 7b.15d: integration fixes (local HAVING for aggregate …
ZmeiGorynych May 24, 2026
64a91f3
DEV-1450 stage 7b.15d: integration fix (arithmetic precedence parens …
ZmeiGorynych May 24, 2026
c92439c
DEV-1450 stage 7b.15d: integration fix (precedence parens in filter a…
ZmeiGorynych May 24, 2026
1207bc4
DEV-1450 stage 7b.15d: integration fix (first/last aggregation in pla…
ZmeiGorynych May 24, 2026
21fe98e
DEV-1450 stage 7b.15d: integration fix (composite-input window transf…
ZmeiGorynych May 24, 2026
7ffe64a
DEV-1450 stage 7b.15d: integration fix (derived columns + joined-colu…
ZmeiGorynych May 24, 2026
c00356a
DEV-1450 stage 7b.15d: integration fix (cross-model re-rooted CTE for…
ZmeiGorynych May 24, 2026
825b182
DEV-1450 stage 7b.15d: integration fix (window transform over cross-m…
ZmeiGorynych May 24, 2026
d90738d
DEV-1450 stage 7b.15e: acceptance tests via engine.execute (1445/1446…
ZmeiGorynych May 24, 2026
5601ad5
DEV-1450 stage 7b.15e: safe deletes (legacy multidot rewriter + parit…
ZmeiGorynych May 24, 2026
7a2824a
DEV-1450 follow-ups: derived-column TDs (#4a), derived-column model f…
ZmeiGorynych May 24, 2026
c1bdd75
DEV-1450 review fixes (group 3): green the Sonar reliability gate
ZmeiGorynych May 24, 2026
44047b0
DEV-1450 review fixes (group 1): cross-model derived-column completeness
ZmeiGorynych May 24, 2026
1eae2f5
DEV-1450 review fixes (group 2): correctness bugs from CodeRabbit
ZmeiGorynych May 24, 2026
2d9729e
DEV-1450 review fixes (group 4): Sonar/CodeRabbit maintainability qui…
ZmeiGorynych May 24, 2026
9e7e59e
DEV-1450 review fixes (round 2): derived agg sources, drift scope, OV…
ZmeiGorynych May 24, 2026
91d7ca2
DEV-1450: merge origin/main, reconcile tests with typed pipeline cont…
ZmeiGorynych May 26, 2026
9b36e71
DEV-1452 Stage A: AggRenderSpec — decouple dialect helpers from Enric…
ZmeiGorynych May 26, 2026
b7ba22b
DEV-1450: PR #139 review pass — Codex + CodeRabbit fixes + CI gap defer
ZmeiGorynych May 26, 2026
ee18009
Merge remote-tracking branch 'origin/egor/dev-1450-principled-redesig…
ZmeiGorynych May 26, 2026
b67eecb
DEV-1452 review fixes (group 1): ColumnSqlKey time arg + complexity
ZmeiGorynych May 26, 2026
a4f33c3
DEV-1452 review fixes (group 2): split _build_ranked_subquery_from_pl…
ZmeiGorynych May 26, 2026
9b32915
DEV-1452 review fixes (group 3): S8396 nullable idiom + DEV-1476 ref
ZmeiGorynych May 26, 2026
3c76c95
DEV-1452 review fixes (group 4): CodeRabbit outside-diff folds
ZmeiGorynych May 26, 2026
7a1110f
DEV-1450: PR #139 round 2 — complete IS/IS NOT coverage + n-ary boole…
ZmeiGorynych May 26, 2026
f1fd662
DEV-1452 review fixes (group 5): S3776 hybrid — helpers + NOSONAR
ZmeiGorynych May 27, 2026
09ab5bc
DEV-1452 Stage B: migrate query-backed expansion onto typed pipeline
ZmeiGorynych May 27, 2026
18daf82
DEV-1452 Stage B review fixes (group 1): Codex correctness — slot met…
ZmeiGorynych May 27, 2026
43fa00e
DEV-1452 Stage B review fixes (group 2): Sonar quick wins
ZmeiGorynych May 27, 2026
feb76af
DEV-1452 Stage B review fixes (group 3): S3776 NOSONAR fan-out
ZmeiGorynych May 27, 2026
eb2e6f8
DEV-1452 Stage B round 2 (group 1): Codex correctness — nested vars +…
ZmeiGorynych May 27, 2026
6ca71e9
DEV-1452 Stage B round 2 (group 2): S3776 _merge_into_existing — extr…
ZmeiGorynych May 27, 2026
351e9b2
DEV-1452 Stage B round 3: Codex — thread bundle through all 5 missing…
ZmeiGorynych May 27, 2026
cde6293
DEV-1452 Stage B round 4: Codex — sibling stage var-layer fix
ZmeiGorynych May 27, 2026
c082ae2
DEV-1452 Stage B round 5: Codex — run-by-name topo + saved ModelMeasu…
ZmeiGorynych May 27, 2026
482e243
DEV-1475: Mode-B IN / NOT IN filters with literal-tuple RHS
ZmeiGorynych May 27, 2026
e67a3de
Merge branch 'egor/dev-1450-principled-redesign-of-syntax' of https:/…
ZmeiGorynych May 27, 2026
c2b7f45
DEV-1452 Stage B round 6: Codex + CodeRabbit fixes
ZmeiGorynych May 27, 2026
5a6ac64
DEV-1452 Stage B round 7: Codex — thread filtered rank maps into cros…
ZmeiGorynych May 28, 2026
d2c32bb
DEV-1452 Stage B fix-up: binding-time errors must subclass ValueError
ZmeiGorynych May 28, 2026
37a0417
DEV-1484 Stage C (commit 1): migrate simpler test files off legacy en…
ZmeiGorynych May 28, 2026
be3aa03
DEV-1484 Stage C (commit 2): migrate reference-semantics + formula tests
ZmeiGorynych May 28, 2026
0c328bd
DEV-1484 Stage C (commit 3): drop legacy resolver unit tests + migrat…
ZmeiGorynych May 28, 2026
0a5aeb1
DEV-1484 Stage C (commit 4a): migrate cross-model derived-column test…
ZmeiGorynych May 28, 2026
1d16e7e
DEV-1484 Stage C (commit 4b): migrate test_projection_trim off legacy…
ZmeiGorynych May 28, 2026
732891b
DEV-1484 Stage C (commit 4c): scrub legacy-symbol references from tes…
ZmeiGorynych May 28, 2026
4ccb673
DEV-1452 Stage B round 8: Codex — joined-dim type + derived time-col …
ZmeiGorynych May 28, 2026
7b03363
DEV-1484 Stage C (commit 5a): migrate test_sql_generator off legacy (…
ZmeiGorynych May 28, 2026
8b2239c
Merge pull request #144 from MotleyAI/egor/dev-1452-delete-legacy-enr…
ZmeiGorynych May 28, 2026
fea8381
DEV-1484 Stage C (commit 6): complete test_sql_generator migration of…
ZmeiGorynych May 28, 2026
0065024
Merge remote-tracking branch 'origin/egor/dev-1450-principled-redesig…
ZmeiGorynych May 28, 2026
f083fbe
DEV-1494: inline cross-model derived refs in Column.filter + discover…
ZmeiGorynych May 29, 2026
db888d0
DEV-1494: address review feedback (CodeRabbit + Codex + Sonar gate)
ZmeiGorynych May 29, 2026
b343231
DEV-1494: clear remaining Sonar gate findings
ZmeiGorynych May 29, 2026
40d31b1
Merge branch 'main' of https://github.com/MotleyAI/slayer into egor/d…
ZmeiGorynych May 30, 2026
74ed95a
Merge pull request #155 from MotleyAI/egor/dev-1494-typed-pipeline-cr…
ZmeiGorynych May 30, 2026
beb6924
DEV-1500: recognize joined-model custom aggregations in FUNC_STYLE_AG…
ZmeiGorynych May 30, 2026
f7a24e3
DEV-1492: preserve transform kwargs in filter normalization
ZmeiGorynych May 31, 2026
1714df3
DEV-1501: parametric first/last in ORDER BY / HAVING — materialise + …
ZmeiGorynych May 31, 2026
8bc9504
DEV-1500: also recognize joined-model custom aggs in the quiet FUNC_S…
ZmeiGorynych May 31, 2026
dc7a775
DEV-1501 PR #159 review fixes: HAVING ranked-state threading + Sonar …
ZmeiGorynych May 31, 2026
74f2b66
Refactor: extract _chain_model + _save_4hop_chain helpers
ZmeiGorynych May 31, 2026
00a0a7a
DEV-1501 PR #159 review round 2: thread aliases through nested HAVING…
ZmeiGorynych May 31, 2026
ca84ccc
DEV-1501 PR #159 review round 3: composite first/last in projection r…
ZmeiGorynych May 31, 2026
c8fbfc5
Refactor: split normalize_model + extract save-time agg walk to a method
ZmeiGorynych May 31, 2026
93bc7cd
Merge pull request #156 from MotleyAI/egor/dev-1500-slack-func_style_…
ZmeiGorynych May 31, 2026
5073ee8
DEV-1501 PR #159 review round 4: composite-only first/last triggers r…
ZmeiGorynych May 31, 2026
248fee1
syntax: split _rewrite_comparison_equals to clear cognitive-complexit…
ZmeiGorynych May 31, 2026
a086de3
DEV-1501 PR #159 review round 5: composite first/last validation + jo…
ZmeiGorynych Jun 1, 2026
5568111
syntax: restore transform predicate-input form (bare-ref equality)
ZmeiGorynych Jun 1, 2026
426f8ad
DEV-1501 PR #159 review round 6: filtered composite + cross-model AGG…
ZmeiGorynych Jun 1, 2026
3527a09
DEV-1501 PR #159 review round 7: ScalarCallKey alias thread + dedupe …
ZmeiGorynych Jun 1, 2026
8eab620
syntax: colon-agg context disambiguates ambiguous transform/agg names
ZmeiGorynych Jun 1, 2026
a66e0e8
DEV-1501 PR #159 review round 8: derived time-col join + test dedup
ZmeiGorynych Jun 1, 2026
b1b8b09
Merge pull request #158 from MotleyAI/egor/dev-1492-parse_filter_expr…
ZmeiGorynych Jun 1, 2026
85e13c9
DEV-1501 PR #159 review round 9: cross-model HAVING args/kwargs reroot
ZmeiGorynych Jun 1, 2026
ffd85bc
Merge pull request #159 from MotleyAI/egor/dev-1501-parametric-lastfi…
ZmeiGorynych Jun 1, 2026
5aea9b0
DEV-1503: filtered-local isolation on typed pipeline + outer-WHERE wr…
ZmeiGorynych Jun 3, 2026
17d9ede
DEV-1503 review round 1: filter routing, mixed cross-model wrapper, h…
ZmeiGorynych Jun 3, 2026
1cf7ff6
DEV-1503 review round 2: composite-over-isolated + no-dim placeholder
ZmeiGorynych Jun 3, 2026
c81ab51
DEV-1503 review round 3: outer-composite routing edge cases
ZmeiGorynych Jun 3, 2026
ec90f1c
DEV-1503 review round 4: empty_base host filters + Sonar S3776
ZmeiGorynych Jun 3, 2026
3ec2fb9
DEV-1503 review round 5: skip_filter_ids in first/last base path
ZmeiGorynych Jun 3, 2026
f422473
DEV-1503 review round 5b: empty_base host filter joined-alias discovery
ZmeiGorynych Jun 3, 2026
2f22c17
DEV-1503 review round 7: dialect fallback chain on filter parse
ZmeiGorynych Jun 3, 2026
71e28f8
DEV-1503 review round 4: empty_base host filters + Sonar S3776
ZmeiGorynych Jun 3, 2026
0bd34f3
DEV-1503 review round 9: dialect fallback covers derived-ref expansion
ZmeiGorynych Jun 3, 2026
e381429
Merge pull request #163 from MotleyAI/egor/dev-1503-typed-pipeline-la…
ZmeiGorynych Jun 3, 2026
30e5e09
DEV-1502: pull joins crossed by an aggregate source's Column.sql
ZmeiGorynych Jun 3, 2026
fc0853f
DEV-1502: use pytest.approx for float SUM assertion (Sonar S1244)
ZmeiGorynych Jun 4, 2026
b8aa68d
DEV-1502: strengthen cross-model defensive test's negative assertion
ZmeiGorynych Jun 4, 2026
7862290
DEV-1502: qualify cross-model CTE join-discovery note (Codex)
ZmeiGorynych Jun 4, 2026
e04620f
DEV-1502: convert first/last derived-source test to DEV-1531 xfail
ZmeiGorynych Jun 4, 2026
1c69c78
Merge pull request #165 from MotleyAI/egor/dev-1502-typed-pipeline-__…
ZmeiGorynych Jun 5, 2026
a168d64
Merge branch 'refs/heads/egor/dev-1450-principled-redesign-of-syntax'…
ZmeiGorynych Jun 12, 2026
4278bf8
PR #153 review sweep: 5 correctness bugs + 3 keyword-only helpers + t…
ZmeiGorynych Jun 12, 2026
ed44862
PR #153 follow-up: Codex lexer fix + 3 S3776 refactors + CI for stack…
ZmeiGorynych Jun 12, 2026
2c48889
PR #153 round-3 review: 2 CI failures fixed + lexer tests + S3776 < 15
ZmeiGorynych Jun 12, 2026
e62f913
PR #153 round-3 follow-up: refactor 4 pre-existing S3776 functions in…
ZmeiGorynych Jun 12, 2026
b6edfe8
Merge pull request #153 from MotleyAI/egor/dev-1484-dev-1452-stage-c-…
ZmeiGorynych Jun 12, 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
261 changes: 261 additions & 0 deletions slayer/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,264 @@ def __init__(self, cycle: List[Tuple[str, str]]) -> None:
self.cycle: List[Tuple[str, str]] = list(cycle)
chain = " → ".join(f"{m}.{c}" for m, c in self.cycle)
super().__init__(f"Circular column reference detected: {chain}")


# ---------------------------------------------------------------------------
# DEV-1450 stage-5 errors — typed, stable str() format.
#
# All error classes below build their message via ``_format_error_message``
# so ``str(error)`` follows the documented snapshot-friendly shape::
#
# <ErrorName>: <one-line summary>
# at <location>
# scope: <short scope summary>
# suggestion: <did-you-mean>
#
# Each indented line is optional. The first line ALWAYS starts with the
# class name so log greps and snapshot tests bind to a stable prefix.
# ---------------------------------------------------------------------------


def _format_error_message(
*,
cls_name: str,
summary: str,
location: str | None = None,
scope: str | None = None,
suggestion: str | None = None,
extras: List[Tuple[str, str]] | None = None,
) -> str:
"""Build the stable error-message string used by stage-5 error classes.

``extras`` lets a class add bespoke key/value rows after the summary
while keeping the leading ``ClassName:`` token intact.
"""
lines = [f"{cls_name}: {summary}"]
if location:
lines.append(f" at {location}")
if scope:
lines.append(f" scope: {scope}")
for k, v in (extras or []):
lines.append(f" {k}: {v}")
if suggestion:
lines.append(f" suggestion: {suggestion}")
return "\n".join(lines)


class UnknownReferenceError(SlayerError):
"""A bare or dotted reference cannot be resolved in the current scope."""

def __init__(
self,
name: str,
scope_kind: str,
scope_summary: str,
suggestion: str | None = None,
) -> None:
self.name = name
self.scope_kind = scope_kind
self.scope_summary = scope_summary
self.suggestion = suggestion
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=f"Cannot resolve reference {name!r}.",
scope=f"{scope_kind}: {scope_summary}",
suggestion=suggestion,
))


class AmbiguousReferenceError(SlayerError):
"""A reference matches multiple candidates in scope and can't pick one."""

def __init__(self, name: str, candidates: List[str]) -> None:
self.name = name
self.candidates = sorted(candidates)
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=f"Reference {name!r} has multiple candidates.",
extras=[("candidates", repr(self.candidates))],
))


class IllegalScopeReferenceError(SlayerError):
"""A reference is syntactically rejected by the current scope kind.

Examples: ``__`` in a Mode-B ``ModelScope`` ref (use the dotted form);
a dotted ref against a ``StageSchema`` (downstream stages see a flat
namespace, no join syntax).
"""

def __init__(self, name: str, scope_kind: str, reason: str) -> None:
self.name = name
self.scope_kind = scope_kind
self.reason = reason
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=f"Reference {name!r} is not legal in this scope.",
scope=scope_kind,
extras=[("reason", reason)],
))


class IllegalWindowInFilterError(SlayerError):
"""A filter contains a raw ``OVER(...)`` window expression, or refers
to a ``Column.sql`` whose body contains a window function (DEV-1369 /
DEV-1336 — predicate promotion was removed). Use a rank-family
transform instead.
"""

def __init__(
self,
filter_expr: str,
source: str,
suggestion: str = "use a rank-family transform (e.g. `rank(<measure>) <= N`).",
) -> None:
self.filter_expr = filter_expr
self.source = source
self.suggestion = suggestion
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary="Window expressions are not allowed in filters.",
extras=[
("expr", repr(filter_expr)),
("source", source),
],
suggestion=suggestion,
))


class AggregationNotAllowedError(SlayerError):
"""An aggregation cannot apply to a column.

Covers type-bucket violations (``sum`` on TEXT), primary-key
restrictions (only ``count`` / ``count_distinct``), and explicit
``Column.allowed_aggregations`` whitelist violations.
"""

def __init__(self, column: str, agg: str, reason: str) -> None:
self.column = column
self.agg = agg
self.reason = reason
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=f"Aggregation {agg!r} is not allowed on column {column!r}.",
extras=[("reason", reason)],
))


class UnknownFunctionError(SlayerError):
"""A function call in Mode B is not in the ``SCALAR_FUNCTIONS`` allowlist,
the transform registry, or the model's aggregation set (C12).
"""

_DEFAULT_SUGGESTION = "move the call to a derived Column.sql (Mode A)."

def __init__(
self,
name: str,
location: str,
suggestion: str | None = None,
) -> None:
self.name = name
self.location = location
self.suggestion = suggestion or self._DEFAULT_SUGGESTION
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=f"Function {name!r} is not allowed in Mode B.",
location=location,
suggestion=self.suggestion,
))


class MeasureRecursionLimitError(SlayerError):
"""Named-measure expansion exceeded the configurable depth limit
(default 32; ``SLAYER_MEASURE_EXPANSION_DEPTH``).
"""

def __init__(self, chain: List[str], limit: int = 32) -> None:
self.chain = list(chain)
self.limit = limit
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=f"Named-measure expansion exceeded depth (limit={limit}).",
extras=[("chain", " → ".join(self.chain))],
))


class MeasureCycleError(SlayerError):
"""Named-measure expansion encountered a cycle."""

def __init__(self, chain: List[str]) -> None:
self.chain = list(chain)
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary="Cyclic reference in named-measure expansion.",
extras=[("chain", " → ".join(self.chain))],
))


class DuplicateMeasureNameError(SlayerError):
"""Two measures in the same query declare the same explicit ``name``
(DEV-1443).
"""

def __init__(self, name: str, occurrences: List[str]) -> None:
self.name = name
self.occurrences = list(occurrences)
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=f"Measure name {name!r} is declared more than once.",
extras=[("occurrences", repr(self.occurrences))],
))


class MeasureNameCollidesWithColumnError(SlayerError):
"""A declared measure ``name`` matches a source column on the model
(DEV-1443) — the alias-form filter would silently bind to the source
column instead of the aggregate.
"""

def __init__(self, name: str, model: str) -> None:
self.name = name
self.model = model
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=(
f"Declared measure name {name!r} matches a source column on "
f"model {model!r}."
),
))


class CanonicalAliasShadowsColumnError(SlayerError):
"""A formula's canonical alias (e.g., ``amount_sum`` for ``amount:sum``)
shadows a source column on the same model (DEV-1443).
"""

def __init__(self, formula: str, canonical: str, model: str) -> None:
self.formula = formula
self.canonical = canonical
self.model = model
super().__init__(_format_error_message(
cls_name=type(self).__name__,
summary=(
f"Canonical alias {canonical!r} for formula {formula!r} "
f"shadows a source column on model {model!r}."
),
))


class UnreachableFilterDroppedWarning(UserWarning):
"""A host filter referenced slots that aren't reachable from a
cross-model CTE's root, so the filter was dropped from the CTE.
The host query still applies the filter to its own rows; this is a
visibility/debug warning, not an error.
"""

def __init__(self, filter_text: str, reason: str) -> None:
self.filter_text = filter_text
self.reason = reason
super().__init__(
f"Filter {filter_text!r} dropped from cross-model CTE "
f"(unreachable from CTE root): {reason}"
)
Loading