You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ossie.apache.org by jb...@apache.org on 2026/10/04 12:44:26 UTC

(ossie) branch main updated: fix(dbt): unqualify compound aggregate arguments per column (#388)

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ossie.git


The following commit(s) were added to refs/heads/main by this push:
     new 86854d46 fix(dbt): unqualify compound aggregate arguments per column (#388)
86854d46 is described below

commit 86854d46f931da99034847df59542b1fd161fc8e
Author: Poorva Barve <po...@gmail.com>
AuthorDate: Sun Oct 4 05:44:21 2026 -0700

    fix(dbt): unqualify compound aggregate arguments per column (#388)
    
    * fix(dbt): unqualify compound aggregate arguments per column
    
    `_col_name` rendered a non-column aggregate argument to text and kept
    only what followed the last dot, so `SUM(orders.gross - orders.tax)`
    became `expr: tax` and `SUM(COALESCE(orders.tax, 0))` became `tax, 0)`.
    Strip the dataset qualifier from each column reference on the parsed
    tree instead, keeping the surrounding expression intact.
    
    Fixes #385
    
    * test(dbt): use flat document in compound round-trip test
---
 converters/dbt/src/ossie_dbt/expression_utils.py | 22 ++++++--
 converters/dbt/tests/test_ossie_to_msi.py        | 70 ++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 6 deletions(-)

diff --git a/converters/dbt/src/ossie_dbt/expression_utils.py b/converters/dbt/src/ossie_dbt/expression_utils.py
index 340e883f..ea975844 100644
--- a/converters/dbt/src/ossie_dbt/expression_utils.py
+++ b/converters/dbt/src/ossie_dbt/expression_utils.py
@@ -31,12 +31,21 @@ def _strip_qualifier(col: str) -> str:
     return col.rsplit(".", 1)[-1] if "." in col else col
 
 
+def _unqualify_column(node: exp.Expression) -> exp.Expression:
+    """Drop the table/schema/database parts of a column reference; other nodes pass through."""
+    return exp.Column(this=node.this) if isinstance(node, exp.Column) else node
+
+
 def _col_name(node: exp.Expression) -> str:
-    """Return the bare (unqualified) column name from a sqlglot expression node."""
+    """Return an aggregate argument with the dataset qualifier stripped from every column reference.
+
+    MSI evaluates a metric's ``expr`` inside its own semantic model, so column
+    references must be unqualified: ``orders.amount`` → ``amount`` and
+    ``orders.gross - orders.tax`` → ``gross - tax``.
+    """
     if isinstance(node, exp.Column):
         return node.name
-    rendered = node.sql()
-    return _strip_qualifier(rendered)
+    return node.transform(_unqualify_column).sql()
 
 
 def _is_row_count_argument(node: exp.Expression) -> bool:
@@ -95,11 +104,12 @@ def _contains_distinct_row_count(expression: str) -> bool:
 def _extract_agg_info(expression: str) -> Optional[Tuple[AggregationType, str, Optional[float], bool]]:
     """Parse a SQL aggregation expression using sqlglot.
 
-    Returns ``(agg_type, bare_col, percentile, use_discrete_percentile)`` for recognised patterns,
+    Returns ``(agg_type, expr, percentile, use_discrete_percentile)`` for recognised patterns,
     ``None`` otherwise. ``percentile`` is only set for ``PERCENTILE`` aggregations; it is ``None``
     for all others. ``use_discrete_percentile`` is ``True`` only for ``PERCENTILE_DISC``.
-    The returned column name has any dataset qualifier stripped. ``COUNT`` of ``*`` or of any non-null
-    constant (``COUNT(1)``, ``COUNT(TRUE)``, ...) returns ``ROW_COUNT_EXPR`` instead of a column name;
+    ``expr`` is the aggregate argument with the dataset qualifier stripped from every column
+    reference (a bare column name in the common case). ``COUNT`` of ``*`` or of any non-null constant
+    (``COUNT(1)``, ``COUNT(TRUE)``, ...) returns ``ROW_COUNT_EXPR`` instead of a column name;
     ``SUM`` of a constant returns the constant itself (``SUM(2)`` → ``'2'``). ``COUNT(DISTINCT ...)`` of a
     row-count argument, and multi-argument ``COUNT``, return ``None``.
     """
diff --git a/converters/dbt/tests/test_ossie_to_msi.py b/converters/dbt/tests/test_ossie_to_msi.py
index 099bb9c5..1caa65a8 100644
--- a/converters/dbt/tests/test_ossie_to_msi.py
+++ b/converters/dbt/tests/test_ossie_to_msi.py
@@ -629,6 +629,54 @@ class TestOssieToMSIMetricConversion:
         assert metric.type_params.metric_aggregation_params is not None
         assert metric.type_params.metric_aggregation_params.semantic_model == "analytics.orders"
 
+    @pytest.mark.parametrize(
+        ("expression", "expected_agg", "expected_expr"),
+        [
+            ("SUM(orders.gross - orders.tax)", AggregationType.SUM, "gross - tax"),
+            ("SUM(COALESCE(orders.tax, 0))", AggregationType.SUM, "COALESCE(tax, 0)"),
+            ("MAX(orders.gross - orders.tax)", AggregationType.MAX, "gross - tax"),
+            ("SUM(amount * 0.5)", AggregationType.SUM, "amount * 0.5"),
+            ("AVG(CAST(orders.tax AS DOUBLE))", AggregationType.AVERAGE, "CAST(tax AS DOUBLE)"),
+            (
+                "COUNT(DISTINCT orders.status || orders.region)",
+                AggregationType.COUNT_DISTINCT,
+                "status || region",
+            ),
+            (
+                "PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY orders.gross - orders.tax)",
+                AggregationType.PERCENTILE,
+                "gross - tax",
+            ),
+        ],
+    )
+    def test_compound_aggregate_argument_is_unqualified_as_a_whole(
+        self, expression: str, expected_agg: AggregationType, expected_expr: str
+    ) -> None:
+        """Every column reference inside the aggregate argument loses its dataset qualifier;
+        the surrounding expression is kept intact rather than sliced at the last dot."""
+        doc = _ossie_doc(
+            datasets=[
+                _ossie_dataset(
+                    "orders",
+                    fields=[
+                        _ossie_field("amount"),
+                        _ossie_field("gross"),
+                        _ossie_field("tax"),
+                        _ossie_field("status"),
+                        _ossie_field("region"),
+                    ],
+                )
+            ],
+            metrics=[_ossie_metric("m", expression)],
+        )
+        result = OssieToMSIConverter().convert(doc).output
+
+        m = result.metrics[0]
+        assert m.type_params.metric_aggregation_params is not None
+        assert m.type_params.metric_aggregation_params.agg == expected_agg
+        assert m.type_params.metric_aggregation_params.semantic_model == "orders"
+        assert m.type_params.expr == expected_expr
+
     def test_percentile_cont_0_5_produces_median(self) -> None:
         doc = _ossie_doc(
             datasets=[_ossie_dataset("orders", fields=[_ossie_field("amount")])],
@@ -922,3 +970,25 @@ class TestOssieToMSIRoundTrip:
         assert m.type_params.metric_aggregation_params.agg == AggregationType.PERCENTILE
         assert m.type_params.metric_aggregation_params.agg_params is not None
         assert m.type_params.metric_aggregation_params.agg_params.use_discrete_percentile is True
+
+    def test_compound_aggregate_argument_survives_round_trip(self) -> None:
+        """Ossie → MSI → Ossie keeps a compound aggregate argument intact."""
+        original = _ossie_doc(
+            datasets=[_ossie_dataset("orders", fields=[_ossie_field("gross"), _ossie_field("tax")])],
+            metrics=[
+                _ossie_metric("net_sales", "SUM(orders.gross - orders.tax)"),
+                _ossie_metric("tax_or_zero", "SUM(COALESCE(orders.tax, 0))"),
+            ],
+        )
+
+        msi = OssieToMSIConverter().convert(original).output
+        ossie_doc = MSIToOssieConverter().convert(msi).output
+
+        metrics = ossie_doc.metrics or []
+        expressions = {m.name: m.expression.dialects[0].expression for m in metrics}
+        # msi_to_ossie._qualify_col re-qualifies only bare identifiers,
+        # so the exported argument comes back unqualified.
+        assert expressions == {
+            "net_sales": "SUM(gross - tax)",
+            "tax_or_zero": "SUM(COALESCE(tax, 0))",
+        }