This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 50c1504cac4 fix(db_engine_specs): cast VARCHAR before Postgres 
DATE_TRUNC (#43167)
50c1504cac4 is described below

commit 50c1504cac4184142848e966f733bc45907d1bd4
Author: nacretion <[email protected]>
AuthorDate: Wed Sep 9 07:38:37 2026 +0300

    fix(db_engine_specs): cast VARCHAR before Postgres DATE_TRUNC (#43167)
---
 superset/db_engine_specs/postgres.py              | 17 +++++++++++-
 tests/unit_tests/db_engine_specs/test_postgres.py | 34 +++++++++++++++++++++++
 2 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/superset/db_engine_specs/postgres.py 
b/superset/db_engine_specs/postgres.py
index f7c44aa5262..cfcdbae60fa 100644
--- a/superset/db_engine_specs/postgres.py
+++ b/superset/db_engine_specs/postgres.py
@@ -306,17 +306,32 @@ class PostgresBaseEngineSpec(BaseEngineSpec):
         time_grain: str | None,
     ) -> TimestampExpression:
         """
-        Construct a timestamp expression while preserving pure ``DATE`` 
semantics.
+        Construct a timestamp expression for Postgres temporal columns.
 
         Applying ``DATE_TRUNC`` to a ``DATE`` column implicitly casts the 
value to
         ``TIMESTAMP``, which can trigger unwanted timezone conversion on the 
client
         and shift the displayed date by a day. To avoid this, the truncated 
value is
         cast back to ``DATE`` when the source column is a pure ``DATE`` type.
 
+        String columns explicitly marked as temporal are cast to ``TIMESTAMP`` 
before
+        applying a time grain because Postgres does not implicitly cast 
strings for
+        ``DATE_TRUNC`` or ``EXTRACT``.
+
         See https://github.com/apache/superset/issues/42254.
+        See https://github.com/apache/superset/issues/42386.
         """
         expr = super().get_timestamp_expr(col, pdf, time_grain)
         col_type = getattr(col, "type", None)
+        if (
+            time_grain
+            and isinstance(col_type, String)
+            and pdf not in ("epoch_s", "epoch_ms")
+        ):
+            expr = TimestampExpression(
+                expr.name.replace("{col}", "CAST({col} AS TIMESTAMP)"),
+                col,
+                type_=DateTime(),
+            )
         # ``DateTime``/``TIMESTAMP`` are distinct SQLAlchemy types (not 
subclasses
         # of ``Date``), so this only matches pure ``DATE`` columns.
         if time_grain and isinstance(col_type, Date):
diff --git a/tests/unit_tests/db_engine_specs/test_postgres.py 
b/tests/unit_tests/db_engine_specs/test_postgres.py
index e70ee41798d..2903ef8761c 100644
--- a/tests/unit_tests/db_engine_specs/test_postgres.py
+++ b/tests/unit_tests/db_engine_specs/test_postgres.py
@@ -485,6 +485,40 @@ def test_get_timestamp_expr_datetime_column_not_cast() -> 
None:
     assert _compile(expr) == "DATE_TRUNC('day', event_ts)"
 
 
+def test_get_timestamp_expr_string_column_casts_to_timestamp() -> None:
+    """DB Eng Specs (postgres): temporal string columns are cast before 
truncation."""
+    col = column("event_timestamp", type_=types.String())
+    expr = spec.get_timestamp_expr(col, None, "P1D")
+    assert _compile(expr) == "DATE_TRUNC('day', CAST(event_timestamp AS 
TIMESTAMP))"
+
+
+def test_get_timestamp_expr_string_column_without_grain_not_cast() -> None:
+    """DB Eng Specs (postgres): strings without a time grain remain 
unchanged."""
+    col = column("event_timestamp", type_=types.String())
+    expr = spec.get_timestamp_expr(col, None, None)
+    assert _compile(expr) == "event_timestamp"
+
+
+def test_get_timestamp_expr_epoch_string_column_not_cast() -> None:
+    """DB Eng Specs (postgres): timestamp casts are not added to epoch 
expressions."""
+    col = column("event_timestamp", type_=types.String())
+    expr = spec.get_timestamp_expr(col, "epoch_s", "P1D")
+    assert _compile(expr) == (
+        "DATE_TRUNC('day', (timestamp 'epoch' + event_timestamp * interval '1 
second'))"
+    )
+
+
+def test_get_timestamp_expr_string_column_casts_every_grain_reference() -> 
None:
+    """DB Eng Specs (postgres): compound grains cast every string reference."""
+    col = column("event_timestamp", type_=types.String())
+    expr = spec.get_timestamp_expr(col, None, "PT5S")
+    assert _compile(expr) == (
+        "DATE_TRUNC('minute', CAST(event_timestamp AS TIMESTAMP)) "
+        "+ INTERVAL '5 seconds' * "
+        "FLOOR(EXTRACT(SECOND FROM CAST(event_timestamp AS TIMESTAMP)) / 5)"
+    )
+
+
 def test_get_timestamp_expr_date_column_without_grain_not_cast() -> None:
     """
     DB Eng Specs (postgres): without a time grain there is no DATE_TRUNC, so 
the

Reply via email to