Skip to content

fix(databricks): match cascade-drop references case-insensitively - #428

Open
christianeu-db wants to merge 2 commits into
apache:mainfrom
christianeu-db:fix-422-cascade-drop-case-insensitive
Open

christianeu-db wants to merge 2 commits into
apache:mainfrom
christianeu-db:fix-422-cascade-drop-case-insensitive

Conversation

@christianeu-db

Copy link
Copy Markdown
Contributor

Summary

When a field is dropped (e.g. no DATABRICKS/ANSI_SQL dialect), the Databricks
converter cascade-drops any metric/dimension that references it, so the output
never carries a dangling reference. The reference match in _references_dropped
was case-sensitive, but Databricks SQL identifiers are case-insensitive. A metric
like COUNT(DISTINCT REGION_NAME) therefore failed to match a dropped
region_name and survived as a dangling reference in the emitted Metric View.

The fix adds re.IGNORECASE to both patterns in _references_dropped (the
measure(<name>) pattern and the bare-dimension token pattern). The word-boundary
guards are unchanged, so qualified alias.col references are still not
over-dropped.

Related Issues

Fixes #422

Checklist

Converters

  • Converter logic in converters/ is updated to reflect spec or ontology changes
  • New converters include tests under the converter's test directory

Tests

  • All existing tests pass (pytest / CI green)
  • New functionality is covered by tests

New regression test test_cascade_drop_matches_dropped_name_case_insensitively
reproduces the reported case (a T_SQL-only region_name dropped, plus a
DATABRICKS metric region_count = COUNT(DISTINCT REGION_NAME)). It fails without
the fix (the measure survives) and passes with it. The full
converters/databricks suite passes (96 tests).

Compliance

  • ASF license headers are present on all new source files (no new files added)
  • No third-party dependencies are added without PMC/IPMC approval

This pull request and its description were written by Isaac.

@christianeu-db

Copy link
Copy Markdown
Contributor Author

fyi @Haoranli503, this is the the python counterpart to your Java fix here: Haoranli503@a991e68

Databricks SQL identifiers are case-insensitive, but the cascade-drop
match in `_references_dropped` was case-sensitive. A metric such as
`COUNT(DISTINCT REGION_NAME)` therefore failed to match a dropped
`region_name` and survived as a dangling reference in the emitted
Metric View.

Add `re.IGNORECASE` to both patterns in `_references_dropped` (the
`measure(<name>)` pattern and the bare-dimension token pattern). The
word-boundary guards are unchanged, so qualified `alias.col` references
are still not over-dropped. Adds the reported repro as a regression
test that fails without the fix.

Fixes apache#422

Co-authored-by: Isaac <no-reply@databricks.com>
@christianeu-db
christianeu-db force-pushed the fix-422-cascade-drop-case-insensitive branch from 6491970 to 502d736 Compare September 18, 2026 21:56
@jbonofre
jbonofre self-requested a review September 19, 2026 10:10
# column sharing a dropped name is not falsely cascade-dropped.
if d != self_name and re.search(
r"(?<![\w.])" + re.escape(d) + r"(?![\w.])", expr):
r"(?<![\w.])" + re.escape(d) + r"(?![\w.])", expr, re.IGNORECASE):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Making this match case insensitive means a dropped field bare name now collides with any surviving expression that happens to contain the same token in a different case, including common SQL keywords/function names (COUNT, SUM, MIN, MAX, AVG, DATE, ...).

Since these are pretty common as both column names and SQL functions, this looks like a regression to me more than a narrow fix.
Maybe worth to tightening the match (e.g. exclude known SQL keywords, or restrict to idenfiier-shaped tokens that are not immediately followed by ().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch - you're right that matching bare tokens case-insensitively widens a collision. Digging in, it's actually a pre-existing issue that IGNORECASE broadened rather than a brand-new one: even before this PR a dropped dimension named exactly COUNT already cascade-dropped a surviving COUNT(...) measure, because the bare-token match never distinguished a column reference from a function call. Case-insensitivity just extends that to case variants.

As shown in the valid snippet of DuckDB SQL below, some vendors support mixed-case keywords:

WITH q as (select 1 as SUM)
SELECT sUM(SUM) from q

I went with the (?!\s*() exclusion rather than a SQL-keyword blocklist deliberately: a blocklist would re-open #422 for any identifier that collided with a keyword; a dimension literally named date/count would then no longer cascade-drop and would survive as a dangling reference.

One caveat a bare keyword that is not a function call (a type in CAST(x AS DATE), a unit in EXTRACT(YEAR FROM d), CASE ... END, DISTINCT), or text inside a string literal, can still be over-dropped. Fully resolving column-vs-keyword needs a full parse, which is the ambiguity the function documents; I kept this fix to the function-call case you flagged but opted not to extend the scope of the PR, especially as the python interpreter will be sunset in favor of the Java interpreter, hopefully soon.

# word char or `.` excludes both substrings of a larger identifier and
# qualified paths (`alias.name` / `name.col`), so a join alias or joined
# column sharing a dropped name is not falsely cascade-dropped.
if d != self_name and re.search(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This self reference guard is still case-sensitive here, but the regex it protects is case-insensitive (see below): so it no longer reliably excludes self-references when names differ only in case.

Should we d.lower() != self_name.lower() (or compare against a case-folded self_name) to match the new case-sensitive semantics?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed - the guard should be case-folded to match the regex. Changed to d.lower() != self_name.lower(), and mirrored the same case-folded self-guard. Added a regression test with a dropped REGION coexisting with a surviving region self-reference.

Address review feedback on apache#428:
- Exclude function-call tokens (`NAME(...)`) from the bare-dimension match,
  so a dropped dim named like a SQL function (e.g. `count`) does not falsely
  cascade-drop a surviving `COUNT(...)`. This also fixes the pre-existing
  exact-case collision.
- Case-fold the self-reference guard on both the dimension and
  `measure(<name>)` branches, so a column is never dropped for referencing
  itself under a different case.
- Add regression tests for both (each fails without its fix).

Co-authored-by: Isaac <no-reply@databricks.com>
@christianeu-db

Copy link
Copy Markdown
Contributor Author

@jbonofre thank you kindly for the review. I've addressed the comments and added a few regression tests. I've flagged some cases that likely require a fully parser but wanted to hold off for a separate issue / PR to avoid scope creep.

cc @Haoranli503 for the Java converters

@jbonofre

Copy link
Copy Markdown
Member

@christianeu-db thank you sooooo much for your updates! I'm doing a new pass 😄

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.

databricks: case-sensitive cascade-drop leaves metric views referencing dropped fields

2 participants