Compare commits

...

3 Commits

Author SHA1 Message Date
Evan
119afa9d68 fix(sqla): annotate main_dttm_column local in dttm_cols
Add an explicit type annotation per project type-hint conventions,
per codeant-ai review feedback on #41964.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:12:27 -07:00
Evan
1d14b5548a fix(datasets): exclude stale non-temporal main_dttm_col from dttm_cols
When a column is mistakenly marked temporal, chosen as the dataset's
main_dttm_col, saved, then later un-marked as temporal, main_dttm_col is
never cleared. Because dttm_cols unconditionally appended main_dttm_col,
the now non-temporal column kept being reported as a datetime column,
forcing an unremovable default time filter on every chart built on the
dataset.

dttm_cols now only appends main_dttm_col when the referenced column is
actually temporal, falling back to the legacy behavior only when the
column is not present on the dataset.

Closes #30510

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 17:15:12 -07:00
Claude Code
059de27ca4 test(datasets): add regression for stale temporal main_dttm_col (#30510)
Closes #30510

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:15:04 -07:00
2 changed files with 55 additions and 1 deletions

View File

@@ -1516,7 +1516,18 @@ class SqlaTable(
def dttm_cols(self) -> list[str]:
l = [c.column_name for c in self.columns if c.is_dttm] # noqa: E741
if self.main_dttm_col and self.main_dttm_col not in l:
l.append(self.main_dttm_col)
# Only treat ``main_dttm_col`` as a datetime column when the column it
# points to is actually temporal. A column whose "Is Temporal" flag was
# removed must not keep being reported as a datetime column just because
# it is still referenced by ``main_dttm_col`` (#30510). When the column
# is not present on the dataset, fall back to the legacy behavior of
# trusting ``main_dttm_col``.
main_dttm_column: TableColumn | None = next(
(c for c in self.columns if c.column_name == self.main_dttm_col),
None,
)
if main_dttm_column is None or main_dttm_column.is_dttm:
l.append(self.main_dttm_col)
return l
@property

View File

@@ -1177,3 +1177,46 @@ def test_validate_stored_expression_rejects_subquery_around_jinja(
None,
"(SELECT password FROM ab_user LIMIT 1) {# x #}",
)
def test_dttm_cols_excludes_column_after_temporal_flag_removed(
session: Session,
) -> None:
"""
Regression for #30510: when a column is mistakenly marked temporal, set as the
dataset's default datetime (``main_dttm_col``) and saved, then later has its
``is_dttm`` flag removed, the dataset must stop treating that column as temporal.
Otherwise ``dttm_cols`` (which feeds time-column selection and the default time
filter for every chart built on the dataset) keeps returning a non-temporal
column, corrupting the dataset with a time filter that cannot be removed.
"""
Database.metadata.create_all(session.bind)
database = Database(database_name="my_db", sqlalchemy_uri="sqlite://")
# A column the user mistakenly marks as temporal ("Is Temporal") and then picks
# as the dataset "Default Datetime" (``main_dttm_col``).
column = TableColumn(column_name="not_really_a_date", type="VARCHAR", is_dttm=True)
dataset = SqlaTable(
database=database,
table_name="my_table",
columns=[column],
main_dttm_col="not_really_a_date",
)
session.add(dataset)
session.commit()
# While flagged temporal, the column is (expectedly) exposed as a datetime column.
assert dataset.dttm_cols == ["not_really_a_date"]
# The user realizes the mistake and unchecks "Is Temporal", then saves. Persisting
# the update clears ``is_dttm`` on the column.
column.is_dttm = False
session.commit()
# The column is no longer temporal...
assert column.is_temporal is False
# ...so it must no longer be reported as a datetime column. On master
# ``main_dttm_col`` is never cleared, so ``dttm_cols`` still contains the stale,
# non-temporal column and this assertion fails (bug reproduced).
assert "not_really_a_date" not in dataset.dttm_cols