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..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,6 +53,9 @@ _MODEL_STASH_KEYS = ("filter", "parameters", "materialization") _JOIN_STASH_KEYS = ("rely", "cardinality") _COLUMN_STASH_KEYS = ("format", "window") +# 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",) def _warn(scope, msg): @@ -362,5 +365,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..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,6 +571,8 @@ def _convert_metric(metric, fact, seen_names): measure["format"] = stash["format"] if "window" in stash: measure["window"] = stash["window"] + 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.