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/09/23 12:18:54 UTC

(ossie) branch main updated: fix(orionbelt): read OSSIE_SQL_2026 metric expressions (#443)

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 e6fe5208 fix(orionbelt): read OSSIE_SQL_2026 metric expressions (#443)
e6fe5208 is described below

commit e6fe52088e0e8940c219b1d00b529e0e8a3841f1
Author: Ralfo Becher <ra...@users.noreply.github.com>
AuthorDate: Wed Sep 23 14:18:48 2026 +0200

    fix(orionbelt): read OSSIE_SQL_2026 metric expressions (#443)
    
    * fix(orionbelt): read OSSIE_SQL_2026 metric expressions
    
    A metric whose only expression was tagged OSSIE_SQL_2026 was preserved
    as unconverted with a "no SQL-parseable dialect" LOSSY warning, because
    _SQL_PARSEABLE_DIALECTS predates the dialect. It is the spec's portable,
    ANSI-compatible expression language, so it is now read like ANSI_SQL and
    preferred after it, before the SNOWFLAKE / DATABRICKS vendor dialects.
    
    The warning text is built from the tuple so it cannot drift again.
    
    Part of #442.
    
    Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
    
    * docs(orionbelt): list OSSIE_SQL_2026 among the dialects read on import
    
    Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 5 (1M context) <no...@anthropic.com>
---
 converters/orionbelt/README.md                     |  2 +-
 .../orionbelt/ossie_obml_mapping_analysis.md       |  2 +-
 .../orionbelt/src/ossie_orionbelt/_common.py       | 12 +++++----
 .../orionbelt/src/ossie_orionbelt/ossie_to_obml.py | 17 +++++++------
 .../tests/test_ossie_metric_no_silent_loss.py      | 29 ++++++++++++++++++++++
 5 files changed, 48 insertions(+), 14 deletions(-)

diff --git a/converters/orionbelt/README.md b/converters/orionbelt/README.md
index 15e590b8..fb947c95 100644
--- a/converters/orionbelt/README.md
+++ b/converters/orionbelt/README.md
@@ -129,7 +129,7 @@ OBML, but are not interpreted by other Ossie consumers:
   (`obml_unconverted_metrics`) and re-emitted on OBML to Ossie, so the Ossie to OBML
   to Ossie roundtrip stays lossless. A `LOSSY:` warning is raised for each such
   metric because it is **not queryable through OBML**. SQL expressions in the
-  `ANSI_SQL`, `SNOWFLAKE`, and `DATABRICKS` dialects are all read on import.
+  `ANSI_SQL`, `OSSIE_SQL_2026`, `SNOWFLAKE`, and `DATABRICKS` dialects are all read on import.
 
 Ossie v0.1.x inputs are accepted on read via a legacy normalization shim; output
 targets Ossie **v0.2.0.dev0**.
diff --git a/converters/orionbelt/ossie_obml_mapping_analysis.md b/converters/orionbelt/ossie_obml_mapping_analysis.md
index 08df96ce..4fd90d3c 100644
--- a/converters/orionbelt/ossie_obml_mapping_analysis.md
+++ b/converters/orionbelt/ossie_obml_mapping_analysis.md
@@ -149,7 +149,7 @@ These OBML features have no direct Ossie equivalent. Where possible, metadata is
 
 - **`primary_key`** — natively represented: Ossie's dataset-level `primary_key` array maps to per-column `primaryKey: true` on OBML columns (`DataObjectColumn.primaryKey`), and back to the dataset array on export.
 - **`unique_keys`** — no native OBML equivalent; round-trips via an `Ossie`-vendor `customExtension` (`obml_unique_keys`).
-- **Multi-dialect expressions** — on import the converter reads the first available SQL dialect in the order `ANSI_SQL`, `SNOWFLAKE`, `DATABRICKS`; non-SQL dialects (`MDX`, `TABLEAU`, `MAQL`) are not parsed. A metric with no SQL-parseable dialect, or an expression OBML cannot decompose, is preserved verbatim (`obml_unconverted_metrics`) with a `LOSSY:` warning rather than dropped. On export, OBML measures/metrics emit `ANSI_SQL`.
+- **Multi-dialect expressions** — on import the converter reads the first available SQL dialect in the order `ANSI_SQL`, `OSSIE_SQL_2026`, `SNOWFLAKE`, `DATABRICKS`; non-SQL dialects (`MDX`, `TABLEAU`, `MAQL`, `SIGMA`, `THOUGHTSPOT`, `DAX`) are not parsed. A metric with no SQL-parseable dialect, or an expression OBML cannot decompose, is preserved verbatim (`obml_unconverted_metrics`) with a `LOSSY:` warning rather than dropped. On export, OBML measures/metrics emit `ANSI_SQL`.
 - **`ai_context`** — preserved losslessly via `customExtensions` (see Section 2.4).
 - **`custom_extensions`** — mapped to OBML `customExtensions`.
 
diff --git a/converters/orionbelt/src/ossie_orionbelt/_common.py b/converters/orionbelt/src/ossie_orionbelt/_common.py
index 9d4a5412..bfe04922 100644
--- a/converters/orionbelt/src/ossie_orionbelt/_common.py
+++ b/converters/orionbelt/src/ossie_orionbelt/_common.py
@@ -34,11 +34,13 @@ import re
 _OSSIE_VERSION = "0.2.0.dev0"
 
 # SQL dialects (of the Ossie enum) whose aggregation expressions our regex-based
-# metric parser can read, in preference order. ANSI_SQL first; SNOWFLAKE and
-# DATABRICKS are SQL engines OrionBelt also targets, and their simple/expression
-# aggregations (``SUM(t.c)``, ``SUM(t.a * t.b)``) are syntactically identical to
-# ANSI. MDX / TABLEAU / MAQL are non-SQL languages and are never parsed as SQL.
-_SQL_PARSEABLE_DIALECTS = ("ANSI_SQL", "SNOWFLAKE", "DATABRICKS")
+# metric parser can read, in preference order. ANSI_SQL first, then
+# OSSIE_SQL_2026 (the spec's portable, ANSI-compatible expression language);
+# SNOWFLAKE and DATABRICKS are SQL engines OrionBelt also targets, and their
+# simple/expression aggregations (``SUM(t.c)``, ``SUM(t.a * t.b)``) are
+# syntactically identical to ANSI. Non-SQL languages (MDX, TABLEAU, MAQL, SIGMA,
+# THOUGHTSPOT, DAX) are never parsed as SQL.
+_SQL_PARSEABLE_DIALECTS = ("ANSI_SQL", "OSSIE_SQL_2026", "SNOWFLAKE", "DATABRICKS")
 
 # Matches a ``dataset.column`` reference inside a SQL expression, where each
 # side is a bare identifier or a quoted identifier (double quotes, backticks, or
diff --git a/converters/orionbelt/src/ossie_orionbelt/ossie_to_obml.py b/converters/orionbelt/src/ossie_orionbelt/ossie_to_obml.py
index 72bf28be..5f825792 100644
--- a/converters/orionbelt/src/ossie_orionbelt/ossie_to_obml.py
+++ b/converters/orionbelt/src/ossie_orionbelt/ossie_to_obml.py
@@ -724,14 +724,17 @@ class OssietoOBML:
                 measures[name] = delegated
                 continue
 
-            # Prefer ANSI_SQL, but also read SNOWFLAKE / DATABRICKS expressions
-            # (SQL engines OrionBelt targets) — their aggregations are
-            # syntactically ANSI-compatible. Non-SQL dialects (MDX/TABLEAU/MAQL)
-            # are not parsed as SQL.
+            # Prefer ANSI_SQL, but also read OSSIE_SQL_2026 and SNOWFLAKE /
+            # DATABRICKS expressions - their aggregations are syntactically
+            # ANSI-compatible. Non-SQL dialects (MDX/TABLEAU/MAQL/...) are not
+            # parsed as SQL.
             expr_text, _expr_dialect = self._select_sql_expression(m.get("expression", {}))
             if not expr_text:
                 self._preserve_unconverted_metric(
-                    m, "no SQL-parseable dialect (ANSI_SQL / SNOWFLAKE / DATABRICKS) expression"
+                    m,
+                    "no SQL-parseable dialect ("
+                    + " / ".join(_SQL_PARSEABLE_DIALECTS)
+                    + ") expression",
                 )
                 continue
 
@@ -996,8 +999,8 @@ class OssietoOBML:
         """Pick a SQL-parseable expression from an Ossie ``expression`` object.
 
         Returns ``(expression, dialect)`` for the most preferred SQL dialect
-        present (ANSI_SQL > SNOWFLAKE > DATABRICKS), or ``("", "")`` when the
-        metric only carries non-SQL dialects (MDX / TABLEAU / MAQL) or no usable
+        present (ANSI_SQL > OSSIE_SQL_2026 > SNOWFLAKE > DATABRICKS), or
+        ``("", "")`` when the metric only carries non-SQL dialects or no usable
         expression. Catching SNOWFLAKE / DATABRICKS lets third-party models
         whose authors omitted ANSI_SQL still convert, since their aggregation
         syntax is ANSI-compatible.
diff --git a/converters/orionbelt/tests/test_ossie_metric_no_silent_loss.py b/converters/orionbelt/tests/test_ossie_metric_no_silent_loss.py
index e46ad84c..a10e5f2f 100644
--- a/converters/orionbelt/tests/test_ossie_metric_no_silent_loss.py
+++ b/converters/orionbelt/tests/test_ossie_metric_no_silent_loss.py
@@ -93,6 +93,18 @@ class TestDialectCatching:
         assert "Net" in obml.get("measures", {})
         assert not any(w.startswith("LOSSY:") for w in converter.warnings)
 
+    def test_ossie_sql_2026_only_agg_becomes_measure(self) -> None:
+        # OSSIE_SQL_2026 is the spec's portable ANSI-compatible language; a
+        # metric expressed only in it must convert, not be preserved as lossy.
+        ossie = _ossie_model([_metric("Total Amount", "OSSIE_SQL_2026", "SUM(Orders.amount)")])
+        converter = conv.OssietoOBML(ossie)
+        obml = converter.convert()
+
+        measure = obml["measures"]["Total Amount"]
+        assert measure["aggregation"] == "sum"
+        assert measure["columns"][0] == {"dataObject": "Orders", "column": "amount"}
+        assert not any(w.startswith("LOSSY:") for w in converter.warnings)
+
     def test_snowflake_uppercased_identifiers_resolve_to_canonical(self) -> None:
         # Snowflake commonly upper-cases identifiers. They must resolve back to
         # the real Ossie dataset/field names, not produce refs to ORDERS.AMOUNT.
@@ -173,6 +185,23 @@ class TestDialectCatching:
         # ANSI_SQL wins regardless of ordering -> column is `id`, not `amount`.
         assert obml["measures"]["Total Amount"]["columns"][0]["column"] == "id"
 
+    def test_ossie_sql_2026_preferred_over_vendor_dialects(self) -> None:
+        ossie = _ossie_model(
+            [
+                {
+                    "name": "Total Amount",
+                    "expression": {
+                        "dialects": [
+                            {"dialect": "SNOWFLAKE", "expression": "SUM(Orders.amount)"},
+                            {"dialect": "OSSIE_SQL_2026", "expression": "SUM(Orders.id)"},
+                        ]
+                    },
+                }
+            ]
+        )
+        obml = conv.OssietoOBML(ossie).convert()
+        assert obml["measures"]["Total Amount"]["columns"][0]["column"] == "id"
+
 
 class TestNoSilentLoss:
     """Non-convertible metrics are preserved + warned, never dropped."""