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 775f7c5a1fd fix(db2): Use DATE_TRUNC to improve timegrain expressions
(#43236)
775f7c5a1fd is described below
commit 775f7c5a1fd976e10e6f12f8be44f8a5350e3138
Author: Hong Ng <[email protected]>
AuthorDate: Thu Sep 24 06:21:47 2026 +1000
fix(db2): Use DATE_TRUNC to improve timegrain expressions (#43236)
Co-authored-by: Hong Yang Ng <[email protected]>
Co-authored-by: Evan Rusackas <[email protected]>
---
UPDATING.md | 10 ++++
superset/db_engine_specs/db2.py | 27 ++++------
superset/db_engine_specs/ibmi.py | 21 ++++++++
tests/unit_tests/db_engine_specs/test_db2.py | 35 +++---------
tests/unit_tests/db_engine_specs/test_ibmi.py | 76 +++++++++++++++++++++++++++
5 files changed, 126 insertions(+), 43 deletions(-)
diff --git a/UPDATING.md b/UPDATING.md
index aa6a0b7dcbf..ba734724776 100644
--- a/UPDATING.md
+++ b/UPDATING.md
@@ -199,6 +199,16 @@ Scheduled report and alert captures require chart
readiness to remain stable
immediately before Chromium captures the image. A capture that re-enters a
loading
state during that window fails instead of delivering a screenshot with
spinners.
+### Improve Db2 Time Grain Expressions
+The Db2 engine spec has been streamlined by using the DATE_TRUNC scalar
function,
+which requires Db2 11.1.0 or higher. Per the ISO 8601 standards, the `WEEK`
time
+grain now shifts the first day of the week to Monday as part of this change.
+
+### Update IBM Db2 for i Time Grain Expressions
+IBM Db2 for i inherits its engine spec from Db2 but does not support the
DATE_TRUNC
+scalar function, so it will use the previous arithmetic expressions defined
for Db2.
+Its `WEEK` time grain now uses `DAYOFWEEK_ISO` to align with the Db2 change.
+
### Scheduled rendered reports fail closed after capture rejection
Scheduled PDF and PNG delivery requires an accepted report capture context. A
diff --git a/superset/db_engine_specs/db2.py b/superset/db_engine_specs/db2.py
index 33b02130ae7..dc66dc31923 100644
--- a/superset/db_engine_specs/db2.py
+++ b/superset/db_engine_specs/db2.py
@@ -57,7 +57,9 @@ class Db2EngineSpec(BaseEngineSpec):
"connection_string":
"ibm_db_sa://{username}:{password}@{hostname}:{port}/{database}",
"is_recommended": False,
"notes": (
- "Use for older DB2 versions without LIMIT [n] syntax. "
+ "Db2 11.1.0 or higher is required to support SQL
compatibility "
+ "enhancements. "
+ "Use for older Db2 versions without LIMIT [n] syntax. "
"Recommended for SQL Lab."
),
},
@@ -95,21 +97,14 @@ class Db2EngineSpec(BaseEngineSpec):
_time_grain_expressions = {
None: "{col}",
- TimeGrain.SECOND: "CAST({col} as TIMESTAMP) - MICROSECOND({col})
MICROSECONDS",
- TimeGrain.MINUTE: "CAST({col} as TIMESTAMP)"
- " - SECOND({col}) SECONDS"
- " - MICROSECOND({col}) MICROSECONDS",
- TimeGrain.HOUR: "CAST({col} as TIMESTAMP)"
- " - MINUTE({col}) MINUTES"
- " - SECOND({col}) SECONDS"
- " - MICROSECOND({col}) MICROSECONDS ",
- TimeGrain.DAY: "DATE({col})",
- TimeGrain.WEEK: "{col} - (DAYOFWEEK({col})) DAYS",
- TimeGrain.MONTH: "{col} - (DAY({col})-1) DAYS",
- TimeGrain.QUARTER: "{col} - (DAY({col})-1) DAYS"
- " - (MONTH({col})-1) MONTHS"
- " + ((QUARTER({col})-1) * 3) MONTHS",
- TimeGrain.YEAR: "{col} - (DAY({col})-1) DAYS - (MONTH({col})-1)
MONTHS",
+ TimeGrain.SECOND: "DATE_TRUNC('SECOND', {col})",
+ TimeGrain.MINUTE: "DATE_TRUNC('MINUTE', {col})",
+ TimeGrain.HOUR: "DATE_TRUNC('HOUR', {col})",
+ TimeGrain.DAY: "DATE_TRUNC('DAY', {col})",
+ TimeGrain.WEEK: "DATE_TRUNC('WEEK', {col})",
+ TimeGrain.MONTH: "DATE_TRUNC('MONTH', {col})",
+ TimeGrain.QUARTER: "DATE_TRUNC('QUARTER', {col})",
+ TimeGrain.YEAR: "DATE_TRUNC('YEAR', {col})",
}
@classmethod
diff --git a/superset/db_engine_specs/ibmi.py b/superset/db_engine_specs/ibmi.py
index 481c714d793..35756709cc9 100644
--- a/superset/db_engine_specs/ibmi.py
+++ b/superset/db_engine_specs/ibmi.py
@@ -14,6 +14,8 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+from superset.constants import TimeGrain
+
from .db2 import Db2EngineSpec
@@ -28,6 +30,25 @@ class IBMiEngineSpec(Db2EngineSpec):
engine_name = "IBM Db2 for i"
max_column_name_length = 128
+ _time_grain_expressions = {
+ None: "{col}",
+ TimeGrain.SECOND: "CAST({col} as TIMESTAMP) - MICROSECOND({col})
MICROSECONDS",
+ TimeGrain.MINUTE: "CAST({col} as TIMESTAMP)"
+ " - SECOND({col}) SECONDS"
+ " - MICROSECOND({col}) MICROSECONDS",
+ TimeGrain.HOUR: "CAST({col} as TIMESTAMP)"
+ " - MINUTE({col}) MINUTES"
+ " - SECOND({col}) SECONDS"
+ " - MICROSECOND({col}) MICROSECONDS ",
+ TimeGrain.DAY: "DATE({col})",
+ TimeGrain.WEEK: "{col} - (DAYOFWEEK_ISO({col})-1) DAYS",
+ TimeGrain.MONTH: "{col} - (DAY({col})-1) DAYS",
+ TimeGrain.QUARTER: "{col} - (DAY({col})-1) DAYS"
+ " - (MONTH({col})-1) MONTHS"
+ " + ((QUARTER({col})-1) * 3) MONTHS",
+ TimeGrain.YEAR: "{col} - (DAY({col})-1) DAYS - (MONTH({col})-1)
MONTHS",
+ }
+
@classmethod
def epoch_to_dttm(cls) -> str:
return "(DAYS({col}) - DAYS('1970-01-01')) * 86400 +
MIDNIGHT_SECONDS({col})"
diff --git a/tests/unit_tests/db_engine_specs/test_db2.py
b/tests/unit_tests/db_engine_specs/test_db2.py
index 2d2421449b1..ad87c4fc737 100644
--- a/tests/unit_tests/db_engine_specs/test_db2.py
+++ b/tests/unit_tests/db_engine_specs/test_db2.py
@@ -109,33 +109,14 @@ def test_get_prequeries(mocker: MockerFixture) -> None:
("grain", "expected_expression"),
[
(None, "my_col"),
- (
- TimeGrain.SECOND,
- "CAST(my_col as TIMESTAMP) - MICROSECOND(my_col) MICROSECONDS",
- ),
- (
- TimeGrain.MINUTE,
- "CAST(my_col as TIMESTAMP)"
- " - SECOND(my_col) SECONDS - MICROSECOND(my_col) MICROSECONDS",
- ),
- (
- TimeGrain.HOUR,
- "CAST(my_col as TIMESTAMP)"
- " - MINUTE(my_col) MINUTES"
- " - SECOND(my_col) SECONDS - MICROSECOND(my_col) MICROSECONDS ",
- ),
- (TimeGrain.DAY, "DATE(my_col)"),
- (TimeGrain.WEEK, "my_col - (DAYOFWEEK(my_col)) DAYS"),
- (TimeGrain.MONTH, "my_col - (DAY(my_col)-1) DAYS"),
- (
- TimeGrain.QUARTER,
- "my_col - (DAY(my_col)-1) DAYS"
- " - (MONTH(my_col)-1) MONTHS + ((QUARTER(my_col)-1) * 3) MONTHS",
- ),
- (
- TimeGrain.YEAR,
- "my_col - (DAY(my_col)-1) DAYS - (MONTH(my_col)-1) MONTHS",
- ),
+ (TimeGrain.SECOND, "DATE_TRUNC('SECOND', my_col)"),
+ (TimeGrain.MINUTE, "DATE_TRUNC('MINUTE', my_col)"),
+ (TimeGrain.HOUR, "DATE_TRUNC('HOUR', my_col)"),
+ (TimeGrain.DAY, "DATE_TRUNC('DAY', my_col)"),
+ (TimeGrain.WEEK, "DATE_TRUNC('WEEK', my_col)"),
+ (TimeGrain.MONTH, "DATE_TRUNC('MONTH', my_col)"),
+ (TimeGrain.QUARTER, "DATE_TRUNC('QUARTER', my_col)"),
+ (TimeGrain.YEAR, "DATE_TRUNC('YEAR', my_col)"),
],
)
def test_time_grain_expressions(grain: TimeGrain, expected_expression: str) ->
None:
diff --git a/tests/unit_tests/db_engine_specs/test_ibmi.py
b/tests/unit_tests/db_engine_specs/test_ibmi.py
new file mode 100644
index 00000000000..e71f96ddf1c
--- /dev/null
+++ b/tests/unit_tests/db_engine_specs/test_ibmi.py
@@ -0,0 +1,76 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+
+from superset.constants import TimeGrain
+
+
+def test_epoch_to_dttm() -> None:
+ """
+ Test the `epoch_to_dttm` method.
+ """
+ from superset.db_engine_specs.ibmi import IBMiEngineSpec
+
+ assert (
+ IBMiEngineSpec.epoch_to_dttm().format(col="epoch_dttm")
+ == "(DAYS(epoch_dttm) - DAYS('1970-01-01')) * 86400"
+ " + MIDNIGHT_SECONDS(epoch_dttm)"
+ )
+
+
[email protected](
+ ("grain", "expected_expression"),
+ [
+ (None, "my_col"),
+ (
+ TimeGrain.SECOND,
+ "CAST(my_col as TIMESTAMP) - MICROSECOND(my_col) MICROSECONDS",
+ ),
+ (
+ TimeGrain.MINUTE,
+ "CAST(my_col as TIMESTAMP)"
+ " - SECOND(my_col) SECONDS - MICROSECOND(my_col) MICROSECONDS",
+ ),
+ (
+ TimeGrain.HOUR,
+ "CAST(my_col as TIMESTAMP)"
+ " - MINUTE(my_col) MINUTES"
+ " - SECOND(my_col) SECONDS - MICROSECOND(my_col) MICROSECONDS ",
+ ),
+ (TimeGrain.DAY, "DATE(my_col)"),
+ (TimeGrain.WEEK, "my_col - (DAYOFWEEK_ISO(my_col)-1) DAYS"),
+ (TimeGrain.MONTH, "my_col - (DAY(my_col)-1) DAYS"),
+ (
+ TimeGrain.QUARTER,
+ "my_col - (DAY(my_col)-1) DAYS"
+ " - (MONTH(my_col)-1) MONTHS + ((QUARTER(my_col)-1) * 3) MONTHS",
+ ),
+ (
+ TimeGrain.YEAR,
+ "my_col - (DAY(my_col)-1) DAYS - (MONTH(my_col)-1) MONTHS",
+ ),
+ ],
+)
+def test_time_grain_expressions(grain: TimeGrain, expected_expression: str) ->
None:
+ """
+ Test that time grain expressions generate the expected SQL.
+ """
+ from superset.db_engine_specs.ibmi import IBMiEngineSpec
+
+ actual = IBMiEngineSpec._time_grain_expressions[grain].format(col="my_col")
+ assert actual == expected_expression