diff --git a/converters/databricks/src/ossie_databricks/ossie_to_metric_view.py b/converters/databricks/src/ossie_databricks/ossie_to_metric_view.py index de828bef..9fb7482a 100644 --- a/converters/databricks/src/ossie_databricks/ossie_to_metric_view.py +++ b/converters/databricks/src/ossie_databricks/ossie_to_metric_view.py @@ -581,26 +581,43 @@ def _convert_metric(metric, fact, seen_names): return measure -def _references_dropped(expr, self_name, dropped_dims, dropped_measures): +def _references_dropped(expr, self_name, self_kind, dropped_dims, dropped_measures): """Return a dropped name referenced by `expr`, or None. Measures are only referenceable via `measure()` (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, and scoped to `self_kind`: a + field is exempt only from referencing itself within its own kind. A measure that + merely shares a dropped dimension's name (or vice versa) is a different object, so + it is still checked and cascade-dropped rather than exempted as a self-reference. """ for m in dropped_measures: - if re.search(r"measure\(\s*" + re.escape(m) + r"\s*\)", expr): + if self_kind == "measure" and m.lower() == self_name.lower(): + continue + if 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"(?