-
Notifications
You must be signed in to change notification settings - Fork 280
fix(databricks): match cascade-drop references case-insensitively #428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -587,20 +587,32 @@ def _references_dropped(expr, self_name, dropped_dims, dropped_measures): | |
| Measures are only referenceable via `measure(<name>)` (exact). Dimensions are | ||
| referenced by their bare, *unqualified* name: a name that is part of a qualified | ||
| path (`alias.name` or `name.col`) is ignored, so a join alias or joined column | ||
| that merely shares a dropped dimension's name is not over-dropped. The one | ||
| ambiguity the regex can't resolve without a SQL parser is a bare, unqualified | ||
| *source column* sharing a dropped dimension's name -- there it errs on dropping. | ||
| that merely shares a dropped dimension's name is not over-dropped. A bare token | ||
| immediately followed by `(` is a function/keyword call (e.g. `COUNT(...)`), never | ||
| a dimension reference, so it is excluded too. | ||
|
|
||
| Without a real SQL parser the bare-name match still can't tell an identifier from | ||
| a same-spelled keyword that is *not* a call (a type in `CAST(x AS DATE)`, a unit | ||
| in `EXTRACT(YEAR FROM d)` / `INTERVAL 1 DAY`, `CASE ... END`, `DISTINCT`) or from | ||
| text inside a string literal; a dropped dimension named like one of those errs on | ||
| dropping. This is the residual the paren-guard does not close. | ||
|
|
||
| Matching is case-insensitive, as Databricks SQL identifiers are case-insensitive; | ||
| the self-reference guard is case-folded to match, so a measure or dimension is | ||
| never dropped for referencing itself under a different case. | ||
| """ | ||
| for m in dropped_measures: | ||
| if re.search(r"measure\(\s*" + re.escape(m) + r"\s*\)", expr): | ||
| if m.lower() != self_name.lower() and re.search( | ||
| r"measure\(\s*" + re.escape(m) + r"\s*\)", expr, re.IGNORECASE): | ||
| return m | ||
| for d in dropped_dims: | ||
| # Match only a bare, unqualified token: the negative look-behind/ahead for a | ||
| # 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( | ||
| r"(?<![\w.])" + re.escape(d) + r"(?![\w.])", expr): | ||
| # Match only a bare, unqualified token that is not a function call: the | ||
| # negative look-behind/ahead for a word char or `.` excludes substrings of a | ||
| # larger identifier and qualified paths (`alias.name` / `name.col`), and the | ||
| # trailing `(?!\s*\()` excludes `NAME(...)` calls, so a dropped dim named e.g. | ||
| # `count` does not falsely match a surviving `COUNT(...)`. | ||
| if d.lower() != self_name.lower() and re.search( | ||
| r"(?<![\w.])" + re.escape(d) + r"(?![\w.])(?!\s*\()", expr, re.IGNORECASE): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh also, considering this: datasets:
- name: d
fields:
- name: id # DATABRICKS
- name: Region # T_SQL only -> dropped
metrics:
- name: region # DATABRICKS: SUM(Region)I believe the case-folding the self-guard suppresses a genuine cascade-drop, re-introducing the dangling reference this PR is meant to fix.
I think two things worth separating here:
|
||
| return d | ||
| return None | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this flag makes the converter silently drop valid dimensions.
Let's take this:
I think
endis dropped. TheENDofCASE ... ENDnow matches the droppedend. The new(?!\s*\()guard doesn't help: these keywords aren't calls. Same failure for a droppeddateagainstCAST(ts AS DATE)and a droppedyearagainstEXTRACT(YEAR FROM ts).I believe field names like a non-call SQL keyword (
end,date,year,interval,rows, ...) are dropped.Also, I believe the same happens for fields with name inside a string literal in another case (like
STATUS).I'm sorry I should have seen that during the first reivew.
The case insensitive matching is right here, but it only pays for itself once the matcher strips quoted literals and skips SQL reserved words first.
I suggest replacing the regex with a single tokenizing pass: strip literals, tokenize
[A-Za-z]\w*, skip tokens adjacent to.or followed by(, filter reserved word set, lowercase into a set. It would address the dropped fields plus themeasure(gaps (see my next comment on the same line 😄 ).