From 93e2f797f63b106ab1acccfd58286063c2c9b151 Mon Sep 17 00:00:00 2001 From: Matt Faltyn Date: Sat, 29 Aug 2026 10:20:13 +0200 Subject: [PATCH 1/2] fix(gooddata): preserve grain for aliased key fields Signed-off-by: Matt Faltyn --- .../src/ossie_gooddata/ossie_to_gooddata.py | 5 +-- .../gooddata/tests/test_ossie_to_gooddata.py | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/converters/gooddata/src/ossie_gooddata/ossie_to_gooddata.py b/converters/gooddata/src/ossie_gooddata/ossie_to_gooddata.py index 21a4a910..3d5b29c7 100644 --- a/converters/gooddata/src/ossie_gooddata/ossie_to_gooddata.py +++ b/converters/gooddata/src/ossie_gooddata/ossie_to_gooddata.py @@ -153,13 +153,12 @@ def _convert_ossie_dataset( pk_columns = set(ds.get("primary_key", [])) for field_def in fields: - field_name = field_def["name"] is_dimension = field_def.get("dimension") is not None if is_dimension: attr = _convert_to_attribute(field_def, ds_name) attributes.append(attr) - if field_name in pk_columns: + if attr.source_column in pk_columns: grain_ids.append(attr.id) else: # Check MAQL expression to determine if fact or attribute @@ -167,7 +166,7 @@ def _convert_ossie_dataset( if maql_type == "attribute": attr = _convert_to_attribute(field_def, ds_name) attributes.append(attr) - if field_name in pk_columns: + if attr.source_column in pk_columns: grain_ids.append(attr.id) else: facts.append(_convert_to_fact(field_def, ds_name)) diff --git a/converters/gooddata/tests/test_ossie_to_gooddata.py b/converters/gooddata/tests/test_ossie_to_gooddata.py index 5f44be70..73c2f831 100644 --- a/converters/gooddata/tests/test_ossie_to_gooddata.py +++ b/converters/gooddata/tests/test_ossie_to_gooddata.py @@ -338,6 +338,50 @@ def test_grain_from_primary_key(ossie_tpcds_dict: dict): assert len(grain_ids) == 2 +@pytest.mark.parametrize( + "field", + [ + { + "name": "customer_key", + "expression": {"dialects": [{"dialect": "ANSI_SQL", "expression": "customer_id"}]}, + "dimension": {}, + }, + { + "name": "customer_key", + "expression": { + "dialects": [ + {"dialect": "ANSI_SQL", "expression": "customer_id"}, + {"dialect": "MAQL", "expression": "{label/customers.customer_key}"}, + ] + }, + }, + ], + ids=["dimension", "maql-attribute"], +) +def test_grain_uses_source_column_for_aliased_attribute(field: dict): + """Verify physical primary keys select aliased GoodData grain attributes.""" + model = { + "semantic_model": [ + { + "name": "m", + "datasets": [ + { + "name": "customers", + "source": "db.s.customers", + "primary_key": ["customer_id"], + "fields": [field], + } + ], + } + ] + } + + customer = ossie_to_gooddata(model).ldm.datasets[0] + + assert customer.attributes[0].source_column == "customer_id" + assert [grain.id for grain in customer.grain] == ["attr.customers.customer_key"] + + def test_relationships_become_references(ossie_tpcds_dict: dict): """Verify Ossie relationships become GoodData references.""" result = ossie_to_gooddata(ossie_tpcds_dict) From 68f7dd9108a2152bee56921d22b8986387e1af76 Mon Sep 17 00:00:00 2001 From: Matt Faltyn Date: Sat, 12 Sep 2026 18:02:21 +0200 Subject: [PATCH 2/2] fix(gooddata): reject ambiguous source mappings Signed-off-by: Matt Faltyn --- .../src/ossie_gooddata/ossie_to_gooddata.py | 18 +++---- .../gooddata/tests/test_ossie_to_gooddata.py | 48 +++++++++++++++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/converters/gooddata/src/ossie_gooddata/ossie_to_gooddata.py b/converters/gooddata/src/ossie_gooddata/ossie_to_gooddata.py index 3d5b29c7..bb5b25fe 100644 --- a/converters/gooddata/src/ossie_gooddata/ossie_to_gooddata.py +++ b/converters/gooddata/src/ossie_gooddata/ossie_to_gooddata.py @@ -91,6 +91,10 @@ def _build_target_info(sm: dict[str, Any]) -> dict[str, dict[str, Any]]: if not is_date: for f in ds.get("fields", []): src = _get_source_column(f) + if src in col_to_attr: + raise ValueError( + f"Dataset '{ds_name}': source column '{src}' maps to multiple fields." + ) col_to_attr[src] = f"attr.{ds_name}.{f['name']}" info[ds_name] = {"is_date": is_date, "col_to_attr": col_to_attr} return info @@ -148,9 +152,6 @@ def _convert_ossie_dataset( # Regular dataset attributes: list[GdAttribute] = [] facts: list[GdFact] = [] - grain_ids: list[str] = [] - - pk_columns = set(ds.get("primary_key", [])) for field_def in fields: is_dimension = field_def.get("dimension") is not None @@ -158,20 +159,21 @@ def _convert_ossie_dataset( if is_dimension: attr = _convert_to_attribute(field_def, ds_name) attributes.append(attr) - if attr.source_column in pk_columns: - grain_ids.append(attr.id) else: # Check MAQL expression to determine if fact or attribute maql_type = _detect_type_from_maql(field_def) if maql_type == "attribute": attr = _convert_to_attribute(field_def, ds_name) attributes.append(attr) - if attr.source_column in pk_columns: - grain_ids.append(attr.id) else: facts.append(_convert_to_fact(field_def, ds_name)) - grain = [GdGrain(id=gid, type="attribute") for gid in grain_ids] + attribute_ids_by_column = {attr.source_column: attr.id for attr in attributes} + grain = [ + GdGrain(id=attribute_ids_by_column[column], type="attribute") + for column in ds.get("primary_key", []) + if column in attribute_ids_by_column + ] # Convert relationships from this dataset to GoodData references references = [] diff --git a/converters/gooddata/tests/test_ossie_to_gooddata.py b/converters/gooddata/tests/test_ossie_to_gooddata.py index 73c2f831..1e49a91c 100644 --- a/converters/gooddata/tests/test_ossie_to_gooddata.py +++ b/converters/gooddata/tests/test_ossie_to_gooddata.py @@ -382,6 +382,54 @@ def test_grain_uses_source_column_for_aliased_attribute(field: dict): assert [grain.id for grain in customer.grain] == ["attr.customers.customer_key"] +def test_duplicate_source_columns_are_rejected(): + """Verify ambiguous grain and relationship targets fail instead of being misassigned.""" + model = { + "semantic_model": [ + { + "name": "m", + "datasets": [ + { + "name": "customers", + "primary_key": ["customer_id"], + "fields": [ + _direct_field("customer_id", dimension={}), + { + "name": "customer_key", + "expression": { + "dialects": [ + {"dialect": "ANSI_SQL", "expression": "customer_id"} + ] + }, + "dimension": {}, + }, + ], + }, + { + "name": "orders", + "fields": [_direct_field("customer_id", dimension={})], + }, + ], + "relationships": [ + { + "name": "orders_customer", + "from": "orders", + "to": "customers", + "from_columns": ["customer_id"], + "to_columns": ["customer_id"], + } + ], + } + ] + } + + with pytest.raises( + ValueError, + match="Dataset 'customers': source column 'customer_id' maps to multiple fields", + ): + ossie_to_gooddata(model) + + def test_relationships_become_references(ossie_tpcds_dict: dict): """Verify Ossie relationships become GoodData references.""" result = ossie_to_gooddata(ossie_tpcds_dict)