From 6a6da2b7072b3fd5e3fb4c8ee4b66e0719d27979 Mon Sep 17 00:00:00 2001 From: Chris Eubank <108756251+christianeu-db@users.noreply.github.com> Date: Thu, 3 Sep 2026 19:30:21 +0000 Subject: [PATCH 1/2] Round-trip measure display_name through the DATABRICKS stash (#326) A dimension's display_name maps to the Apache Ossie field `label`, but the Apache Ossie metric shape has no `label`, so a measure's display_name was silently dropped in the MV -> Ossie -> MV round trip. Preserve it in the DATABRICKS custom_extensions stash (the same mechanism as format/window), kept in a separate MEASURE_STASH_KEYS list so the dimension path -- which already maps display_name to `label` -- does not also stash it. The exporter restores it in _convert_metric. Fixes apache/ossie#326. Co-authored-by: Isaac --- converters/databricks/README.md | 4 ++-- .../ossie_databricks/metric_view_to_ossie.py | 9 ++++++++- .../ossie_databricks/ossie_to_metric_view.py | 5 +++++ .../databricks/tests/_roundtrip_helpers.py | 2 ++ converters/databricks/tests/test_roundtrip.py | 18 ++++++++++++++++++ 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/converters/databricks/README.md b/converters/databricks/README.md index e2fe6ccb..ed9268b1 100644 --- a/converters/databricks/README.md +++ b/converters/databricks/README.md @@ -86,10 +86,10 @@ Each row maps in both directions; the **Notes** flag where a behavior is specifi | `dataset.fields[]` | `dimensions[]` | Export: fields flatten into one list and a joined column is qualified by its full join path (`customer.c_name`; `customer.region.r_name` when nested). | | `field.expression.dialects[]` | `expr` | Export: prefer the `DATABRICKS` dialect, else `ANSI_SQL`. | | `metrics[]` | `measures[]` | Export: fact columns are referenced bare (`SUM(amount)`). | -| `field.label` | `display_name` | | +| `field.label` | dimension `display_name` | A measure's `display_name` has no `label` on the Apache Ossie metric shape, so it rides in the stash instead (see the `custom_extensions` row). | | `field` / `metric` `description` | `comment` | | | `ai_context.synonyms` | `synonyms` | | -| `custom_extensions[DATABRICKS]` | `filter`, `window`, `format`, `rely`, `materialization` | Import stashes Metric View only features here; export restores them -- keeping `MV -> Apache Ossie -> MV` lossless. | +| `custom_extensions[DATABRICKS]` | `filter`, `window`, `format`, `rely`, `materialization`, measure `display_name` | Import stashes Metric View only features here; export restores them -- keeping `MV -> Apache Ossie -> MV` lossless. | ## Requirements diff --git a/converters/databricks/src/ossie_databricks/metric_view_to_ossie.py b/converters/databricks/src/ossie_databricks/metric_view_to_ossie.py index 75cdb752..37bbe31b 100644 --- a/converters/databricks/src/ossie_databricks/metric_view_to_ossie.py +++ b/converters/databricks/src/ossie_databricks/metric_view_to_ossie.py @@ -53,6 +53,12 @@ _MODEL_STASH_KEYS = ("filter", "parameters", "materialization") _JOIN_STASH_KEYS = ("rely", "cardinality") _COLUMN_STASH_KEYS = ("format", "window") +# Measure-only fields with no native Apache Ossie Metric representation. A dimension's +# display_name maps to the Apache Ossie field `label`, but the metric shape has no `label`, +# so a measure's display_name is preserved in the DATABRICKS stash instead (the same +# mechanism as format/window). Kept separate from _COLUMN_STASH_KEYS so the dimension +# path -- which already maps display_name to `label` -- does not also stash it. +_MEASURE_STASH_KEYS = ("display_name",) def _warn(scope, msg): @@ -362,5 +368,6 @@ def _convert_measure(measure, fact_name): metric["description"] = measure["comment"] if measure.get("synonyms"): metric["ai_context"] = {"synonyms": list(measure["synonyms"])} - write_stash(metric, {k: measure[k] for k in _COLUMN_STASH_KEYS if k in measure}) + stash_keys = _COLUMN_STASH_KEYS + _MEASURE_STASH_KEYS + write_stash(metric, {k: measure[k] for k in stash_keys if k in measure}) return metric 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 3c535edc..c4d0574e 100644 --- a/converters/databricks/src/ossie_databricks/ossie_to_metric_view.py +++ b/converters/databricks/src/ossie_databricks/ossie_to_metric_view.py @@ -571,6 +571,11 @@ def _convert_metric(metric, fact, seen_names): measure["format"] = stash["format"] if "window" in stash: measure["window"] = stash["window"] + # The Apache Ossie metric shape has no `label`, so a measure's display_name round-trips + # through the DATABRICKS stash (see _MEASURE_STASH_KEYS on the import side) rather than a + # native field. + if "display_name" in stash: + measure["display_name"] = stash["display_name"] return measure diff --git a/converters/databricks/tests/_roundtrip_helpers.py b/converters/databricks/tests/_roundtrip_helpers.py index 6c4ddfb8..a74ebd71 100644 --- a/converters/databricks/tests/_roundtrip_helpers.py +++ b/converters/databricks/tests/_roundtrip_helpers.py @@ -179,6 +179,8 @@ def build_metric_view(rnd): m = {"name": names.next("c"), "expr": f"{rnd.pick(_AGGS)}({rnd.colname()})"} if rnd.chance(0.4): m["comment"] = rnd.text() + if rnd.chance(0.3): + m["display_name"] = rnd.text() if rnd.chance(0.3): m["synonyms"] = [rnd.text() for _ in range(rnd.count(1, 3))] if rnd.chance(0.3): diff --git a/converters/databricks/tests/test_roundtrip.py b/converters/databricks/tests/test_roundtrip.py index c9d5629a..32c2044d 100644 --- a/converters/databricks/tests/test_roundtrip.py +++ b/converters/databricks/tests/test_roundtrip.py @@ -79,6 +79,24 @@ def test_one_to_many_round_trips_mv_ossie_mv(): assert parse(mv_out) == parse(mv_in) +def test_measure_display_name_survives_round_trip_via_stash(): + """A measure's display_name survives MV -> Apache Ossie -> MV. A dimension's + display_name maps to the Apache Ossie field `label`, but the metric shape has no + `label`, so a measure's display_name rides in the DATABRICKS stash (like format/window) + and is restored on the way back (apache/ossie#326).""" + mv_in = ( + "version: '1.1'\nsource: c.s.fact\n" + "dimensions:\n- {name: region, expr: region}\n" + "measures:\n- {name: total_revenue, expr: SUM(amount), display_name: Total Revenue}\n" + ) + ossie = importer.convert_metric_view_to_ossie(mv_in) + # The only display_name in the input is on the measure; it must ride in the metric's + # stash, since there is no native Apache Ossie field for it. + assert "display_name" in ossie + mv_out = exporter.convert_ossie_to_metric_view(ossie) + assert parse(mv_out) == parse(mv_in) + + # Property-based round-trip coverage. The Hypothesis driver lives in # test_roundtrip_properties.py; these run the same generators/assertions under a plain # seeded RNG so the property coverage also holds where Hypothesis is not installed. From a51ed37b368119297685ba399aa092b57f32a206 Mon Sep 17 00:00:00 2001 From: Chris Eubank <108756251+christianeu-db@users.noreply.github.com> Date: Thu, 3 Sep 2026 20:30:32 +0000 Subject: [PATCH 2/2] Trim measure display_name stash comments Co-authored-by: Isaac --- .../src/ossie_databricks/metric_view_to_ossie.py | 7 ++----- .../src/ossie_databricks/ossie_to_metric_view.py | 3 --- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/converters/databricks/src/ossie_databricks/metric_view_to_ossie.py b/converters/databricks/src/ossie_databricks/metric_view_to_ossie.py index 37bbe31b..63be1bd6 100644 --- a/converters/databricks/src/ossie_databricks/metric_view_to_ossie.py +++ b/converters/databricks/src/ossie_databricks/metric_view_to_ossie.py @@ -53,11 +53,8 @@ _MODEL_STASH_KEYS = ("filter", "parameters", "materialization") _JOIN_STASH_KEYS = ("rely", "cardinality") _COLUMN_STASH_KEYS = ("format", "window") -# Measure-only fields with no native Apache Ossie Metric representation. A dimension's -# display_name maps to the Apache Ossie field `label`, but the metric shape has no `label`, -# so a measure's display_name is preserved in the DATABRICKS stash instead (the same -# mechanism as format/window). Kept separate from _COLUMN_STASH_KEYS so the dimension -# path -- which already maps display_name to `label` -- does not also stash it. +# A dimension's display_name maps to the field `label`, but a metric has no `label`, so a +# measure's display_name is stashed instead. _MEASURE_STASH_KEYS = ("display_name",) 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 c4d0574e..f96d505c 100644 --- a/converters/databricks/src/ossie_databricks/ossie_to_metric_view.py +++ b/converters/databricks/src/ossie_databricks/ossie_to_metric_view.py @@ -571,9 +571,6 @@ def _convert_metric(metric, fact, seen_names): measure["format"] = stash["format"] if "window" in stash: measure["window"] = stash["window"] - # The Apache Ossie metric shape has no `label`, so a measure's display_name round-trips - # through the DATABRICKS stash (see _MEASURE_STASH_KEYS on the import side) rather than a - # native field. if "display_name" in stash: measure["display_name"] = stash["display_name"] return measure