Skip to content

DEV-1566: pg-facade — CAST(<column> AS <type>) in projection#185

Open
ZmeiGorynych wants to merge 1 commit into
egor/dev-1562-pg-facade-live-metabase-end-to-end-integration-test-suitefrom
egor/dev-1566-pg-facade-support-castcol-as-type-in-projection
Open

DEV-1566: pg-facade — CAST(<column> AS <type>) in projection#185
ZmeiGorynych wants to merge 1 commit into
egor/dev-1562-pg-facade-live-metabase-end-to-end-integration-test-suitefrom
egor/dev-1566-pg-facade-support-castcol-as-type-in-projection

Conversation

@ZmeiGorynych

@ZmeiGorynych ZmeiGorynych commented Jun 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Pattern-match CAST(<column> AS <type>) (and col::type sugar) in the shared facade translator; the engine still executes the bare column and the wire OID is overridden via projection_types. Strict per-pair coercion allowlist gates which (source, target) combos round-trip cleanly.
  • Wire encoder fixes in slayer/pg_facade/types.py: OID_TIMESTAMP + dt.date widening (text-format DATE→TIMESTAMP was emitting bare YYYY-MM-DD); OID_TEXT + bool now emits true/false (Postgres text shape) instead of t/f.
  • ORDER BY / GROUP BY canonical-form registration so unaliased + aliased projections + qualified-ref forms all resolve via the lowercase canonical key.
  • Flips the strict-xfail tests/integration/test_metabase_e2e.py::test_timestamp_round_trip_both_formats from PR DEV-1562: live-Metabase end-to-end pg-facade integration test suite #182's suite.

Base is egor/dev-1562-... (PR #182) because that branch carries test_metabase_e2e.py; will re-target main once #182 lands.

Two rounds of Codex review folded in pre-PR. Decisions documented inline in the commit message.

Test plan

  • poetry run pytest -m \"not integration\" clean — verified locally: 4630 pass, 1 unrelated flake (test_save_memory_description_plumbing.py::test_cli_argparse_subprocess_accepts_description_flag, passes in isolation)
  • poetry run ruff check slayer/ tests/ clean — verified
  • poetry run pytest tests/integration/test_integration_pg_facade.py -m integration (new rejected-coercion case)
  • Live-Metabase test_timestamp_round_trip_both_formats passes once DEV-1562: live-Metabase end-to-end pg-facade integration test suite #182 merges and this rebases onto main
  • asyncpg repro from the Linear issue returns a datetime.datetime for SELECT CAST(ordered_at AS TIMESTAMP) FROM orders LIMIT 1

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

New Features

  • Added CAST expression support in SQL projections enabling type coercions on column references
  • Supports both CAST( AS type) and PostgreSQL column::type syntax
  • Includes common type conversions such as date-to-timestamp and text coercions
  • Provides clear error messages when cast operations are not supported

Pattern-match CAST around a bare or qualified Column in the shared facade
translator. The engine still executes the bare column; the wire OID is
overridden via projection_types, made safe by a strict per-pair coercion
allowlist. Flips the DEV-1562 strict-xfail test_timestamp_round_trip_both_formats.

Codex round-1 follow-ups folded in: OID_TIMESTAMP + dt.date widening in
value_to_text (text-format DATE→TIMESTAMP was emitting bare YYYY-MM-DD);
OID_TEXT + bool now emits true/false (Postgres text shape) rather than t/f;
_order_by_name and _validate_group_by gain CAST detection branches so the
canonical lowercase form resolves; DATE / BOOLEAN columns added to the
translator test fixture; DOUBLE→INT dropped from the allowlist (Python
int(x) truncates while Postgres rounds half-to-even); unknown source data
type admits TEXT only.

Codex round-2 follow-ups: column_name_mapping for aliased CAST now points
at the bare engine column (engine still executes it); type-alias coverage
for VARCHAR/INTEGER/BIGINT/SMALLINT/FLOAT/REAL/DECIMAL/DATETIME/TIMESTAMPTZ;
aliased + qualified-ref ORDER BY / GROUP BY canonical resolution; full
error-contract pinning; non-aggregate non-column inner rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear

linear Bot commented Jun 16, 2026

Copy link
Copy Markdown

DEV-1566

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Implements CAST(<column> AS <type>) (and col::type) support in the Postgres facade translator. A strict coercion allowlist maps source/target type pairs; valid casts override the projected column's Postgres OID at the wire layer without changing engine execution. ORDER BY and GROUP BY validation is extended to canonicalize CAST expressions. Two value_to_text coercions handle DATE-to-TIMESTAMP widening and BOOL-to-TEXT encoding.

Changes

CAST Projection Support (DEV-1566)

Layer / File(s) Summary
CAST type map, allowlist, and _ProjectionItem model
slayer/facade/translator.py
Adds the sqlglot→SLayer DataType map, the per-source/target coercion allowlist (unknown source admits only TEXT), the canonical cast(<dotted> as <type>) key generator, and cast_target: Optional[DataType] on _ProjectionItem.
CAST detection, resolution, and projection wiring
slayer/facade/translator.py
Adds _detect_column_cast and _resolve_column_cast_projection; wires them into _resolve_projection; overrides projection_types with cast_target for metric and dimension plans; registers canonical cast keys in item_by_projected_name for ORDER BY lookup.
ORDER BY and GROUP BY CAST canonicalization
slayer/facade/translator.py
Extends _order_by_name to resolve CAST expressions by canonical key, and _validate_group_by to map CAST(col AS T) into the derived-dimension set.
Wire-layer OID value coercions
slayer/pg_facade/types.py
Adds two OID-aware branches in value_to_text: widens date to datetime for OID_TIMESTAMP, and encodes bool as "true"/"false" for OID_TEXT.
Unit, wire-type, and integration tests
tests/facade/test_translator.py, tests/pg_facade/test_types.py, tests/integration/test_integration_pg_facade.py, tests/integration/test_metabase_e2e.py
Adds DATE/BOOLEAN columns to the shared test catalog; adds a comprehensive CAST-projection test block covering allowlist, aliases, error messages, ORDER BY/GROUP BY, and metrics; adds OID override and value_to_text widening tests; adds an integration test for unsupported CAST error; removes xfail from the timestamp round-trip e2e test.
CAST projection documentation
docs/interfaces/pg-facade.md
Documents CAST acceptance criteria, wire-layer OID-override mechanism, supported BI patterns, admitted coercions, unsupported forms, and error behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 A CAST is requested, the bunny checks twice,
The allowlist consulted — is this pair nice?
The OID is swapped at the wire's thin seam,
DATE widens to TIMESTAMP, a neat little dream.
TRY_CAST? Rejected! Expressions? No way!
The facade hops forward — DEV-1566 hooray! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and accurately summarizes the main change: implementing CAST( AS ) support in the pg-facade projection system, which aligns with the primary objective and the DEV-1566 feature across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch egor/dev-1566-pg-facade-support-castcol-as-type-in-projection

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
slayer/facade/translator.py (1)

1421-1423: ⚡ Quick win

Use keyword arguments for _alias_for_column_cast calls.

These new call sites use positional arguments on a 2-parameter function; switch to keyword arguments to match repo rules.

Proposed diff
-        return _alias_for_column_cast(
-            _column_to_dotted(inner_col, strip_prefix=strip_prefix), target,
-        )
+        return _alias_for_column_cast(
+            dotted=_column_to_dotted(inner_col, strip_prefix=strip_prefix),
+            target=target,
+        )
...
-                user_items.append(_alias_for_column_cast(
-                    _column_to_dotted(inner_col, strip_prefix=strip_prefix),
-                    target,
-                ))
+                user_items.append(_alias_for_column_cast(
+                    dotted=_column_to_dotted(inner_col, strip_prefix=strip_prefix),
+                    target=target,
+                ))
...
-        item_by_projected_name.setdefault(
-            _alias_for_column_cast(dotted, item.cast_target), item,
-        )
+        item_by_projected_name.setdefault(
+            _alias_for_column_cast(dotted=dotted, target=item.cast_target), item,
+        )

As per coding guidelines, **/*.py: “Use keyword arguments for functions with more than 1 parameter.”

Also applies to: 1454-1457, 1781-1783

🤖 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 `@slayer/facade/translator.py` around lines 1421 - 1423, The
_alias_for_column_cast function calls use positional arguments instead of
keyword arguments, which violates the repository's coding guidelines requiring
keyword arguments for functions with more than one parameter. In
slayer/facade/translator.py at lines 1421-1423 (the anchor location), convert
the positional arguments in the _alias_for_column_cast call to use explicit
keyword argument names. Apply the same conversion at the two sibling locations:
lines 1454-1457 and lines 1781-1783, ensuring all three call sites use keyword
arguments to match repository conventions.

Source: Coding guidelines

🤖 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 `@slayer/facade/translator.py`:
- Around line 1421-1423: The _alias_for_column_cast function calls use
positional arguments instead of keyword arguments, which violates the
repository's coding guidelines requiring keyword arguments for functions with
more than one parameter. In slayer/facade/translator.py at lines 1421-1423 (the
anchor location), convert the positional arguments in the _alias_for_column_cast
call to use explicit keyword argument names. Apply the same conversion at the
two sibling locations: lines 1454-1457 and lines 1781-1783, ensuring all three
call sites use keyword arguments to match repository conventions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4dac113f-597d-4190-949f-68bf290a1e5d

📥 Commits

Reviewing files that changed from the base of the PR and between 3543b04 and 71bffe6.

📒 Files selected for processing (7)
  • docs/interfaces/pg-facade.md
  • slayer/facade/translator.py
  • slayer/pg_facade/types.py
  • tests/facade/test_translator.py
  • tests/integration/test_integration_pg_facade.py
  • tests/integration/test_metabase_e2e.py
  • tests/pg_facade/test_types.py
💤 Files with no reviewable changes (1)
  • tests/integration/test_metabase_e2e.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant