DATE_TRUNC: auto-cast unknown-typed time-dimension expressions#140
Conversation
A `SlayerModel` time dimension whose `sql` is a bare string literal (`Column(sql="'2025-12-01'", type=TIMESTAMP)`) — a real pattern for synthetic/reference-data time spines — generated SQL like `DATE_TRUNC('month', '2025-12-01')`, which Postgres rejects at execution time with `function date_trunc(unknown, unknown) is not unique` because the second argument carries no resolvable type and there's no single overload to pick. This PR fixes the SQL generator's single `_build_date_trunc` helper to wrap non-column, non-already-cast operands in `CAST(... AS TIMESTAMP)` before passing them to `DATE_TRUNC` — bare column references stay untouched (their live DB type is already known, and forcing a cast there could silently strip `TIMESTAMPTZ` to `TIMESTAMP`). Because all DATE_TRUNC emission sites already funnel through that one helper, the fix simultaneously covers regular SELECTs, shifted CTEs, the compare-period builder, and time-bucket window functions. Added a parametrized regression test across `postgres / mysql / bigquery / duckdb / snowflake` asserting both directions: bare-literal time dims get wrapped, bare columns don't.
📝 WalkthroughWalkthrough
ChangesDATE_TRUNC Type Casting
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/test_sql_generator.py (1)
1926-1926: ⚡ Quick winNarrow the no-CAST assertion to the DATE_TRUNC operand
assert "CAST(" not in sql.upper()is too broad and can fail on unrelated CASTs added elsewhere in query generation. Assert specifically that the bare-column operand path is uncast (e.g., noCAST(created_at AS ...)near the truncation expression).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_sql_generator.py` at line 1926, The test currently asserts globally that no "CAST(" appears in sql (assert "CAST(" not in sql.upper()), which is too broad; instead locate the DATE_TRUNC/DATETRUNC expression in the generated sql and assert that its operand is the bare column (e.g., not wrapped in a CAST). Update the assertion to parse the sql string (using a regex) to find the DATE_TRUNC/DATETRUNC call and then assert that there is no pattern like CAST\s*\(\s*created_at\s+AS\b (case-insensitive) inside that truncation expression; operate on the variable sql and keep the check case-insensitive so unrelated CASTs elsewhere are allowed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/test_sql_generator.py`:
- Line 1926: The test currently asserts globally that no "CAST(" appears in sql
(assert "CAST(" not in sql.upper()), which is too broad; instead locate the
DATE_TRUNC/DATETRUNC expression in the generated sql and assert that its operand
is the bare column (e.g., not wrapped in a CAST). Update the assertion to parse
the sql string (using a regex) to find the DATE_TRUNC/DATETRUNC call and then
assert that there is no pattern like CAST\s*\(\s*created_at\s+AS\b
(case-insensitive) inside that truncation expression; operate on the variable
sql and keep the check case-insensitive so unrelated CASTs elsewhere are
allowed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 247ee27c-e68a-4dae-9aff-57c8c083d2ad
📒 Files selected for processing (2)
slayer/sql/generator.pytests/test_sql_generator.py



A
SlayerModeltime dimension whosesqlis a bare string literal (Column(sql="'2025-12-01'", type=TIMESTAMP)) — a real pattern for synthetic/reference-data time spines — generated SQL likeDATE_TRUNC('month', '2025-12-01'), which Postgres rejects at execution time withfunction date_trunc(unknown, unknown) is not uniquebecause the second argument carries no resolvable type and there's no single overload to pick.This PR fixes the SQL generator's single
_build_date_trunchelper to wrap non-column, non-already-cast operands inCAST(... AS TIMESTAMP)before passing them toDATE_TRUNC— bare column references stay untouched (their live DB type is already known, and forcing a cast there could silently stripTIMESTAMPTZtoTIMESTAMP).Because all DATE_TRUNC emission sites already funnel through that one helper, the fix simultaneously covers regular SELECTs, shifted CTEs, the compare-period builder, and time-bucket window functions. Added a parametrized regression test across
postgres / mysql / bigquery / duckdb / snowflakeasserting both directions: bare-literal time dims get wrapped, bare columns don't.Summary by CodeRabbit
Bug Fixes
Tests