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

beto pushed a commit to branch semantic-layer-implementation
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/semantic-layer-implementation 
by this push:
     new b9ab0ced77 Fix order
b9ab0ced77 is described below

commit b9ab0ced773dff245858c60e61bfb7a2daa5ae33
Author: Beto Dealmeida <[email protected]>
AuthorDate: Mon Jan 26 18:47:44 2026 -0500

    Fix order
---
 .../superset-ui-core/src/query/DatasourceKey.ts    |  7 +--
 superset/semantic_layers/mapper.py                 | 41 +++++++++++++---
 .../semantic_layers/snowflake/semantic_view.py     | 56 ++++++++++++++++++++--
 3 files changed, 91 insertions(+), 13 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts 
b/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts
index 07b296c6d0..5eb22613b4 100644
--- a/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts
+++ b/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts
@@ -35,9 +35,10 @@ export default class DatasourceKey {
 
   constructor(key: string) {
     const [idStr, typeStr] = key.split('__');
-    // Try to parse as integer, fall back to string (UUID) if NaN
-    const parsedId = parseInt(idStr, 10);
-    this.id = Number.isNaN(parsedId) ? idStr : parsedId;
+    // Only parse as integer if the entire string is numeric
+    // (parseInt would incorrectly parse "85d3139f..." as 85)
+    const isNumeric = /^\d+$/.test(idStr);
+    this.id = isNumeric ? parseInt(idStr, 10) : idStr;
     this.type = DATASOURCE_TYPE_MAP[typeStr] ?? DatasourceType.Table;
   }
 
diff --git a/superset/semantic_layers/mapper.py 
b/superset/semantic_layers/mapper.py
index f6bfbeb0fb..2bc485fbae 100644
--- a/superset/semantic_layers/mapper.py
+++ b/superset/semantic_layers/mapper.py
@@ -270,6 +270,27 @@ def map_semantic_result_to_query_result(
     )
 
 
+def _normalize_column(column: str | dict, dimension_names: set[str]) -> str:
+    """
+    Normalize a column to its dimension name.
+
+    Columns can be either:
+    - A string (dimension name directly)
+    - A dict with isColumnReference=True and sqlExpression containing the 
dimension name
+    """
+    if isinstance(column, str):
+        return column
+
+    if isinstance(column, dict):
+        # Handle column references (e.g., from time-series charts)
+        if column.get("isColumnReference") and column.get("sqlExpression"):
+            sql_expr = column["sqlExpression"]
+            if sql_expr in dimension_names:
+                return sql_expr
+
+    raise ValueError("Adhoc dimensions are not supported in Semantic Views.")
+
+
 def map_query_object(query_object: ValidatedQueryObject) -> 
list[SemanticQuery]:
     """
     Convert a `QueryObject` into a list of `SemanticQuery`.
@@ -277,8 +298,6 @@ def map_query_object(query_object: ValidatedQueryObject) -> 
list[SemanticQuery]:
     This function maps the `QueryObject` into query objects that focus less on
     visualization and more on semantics.
     """
-    print("BETO")
-    print(query_object)
     semantic_view = query_object.datasource.implementation
 
     all_metrics = {metric.name: metric for metric in semantic_view.metrics}
@@ -286,6 +305,12 @@ def map_query_object(query_object: ValidatedQueryObject) 
-> list[SemanticQuery]:
         dimension.name: dimension for dimension in semantic_view.dimensions
     }
 
+    # Normalize columns (may be dicts with isColumnReference=True for 
time-series)
+    dimension_names = set(all_dimensions.keys())
+    normalized_columns = {
+        _normalize_column(column, dimension_names) for column in 
query_object.columns
+    }
+
     metrics = [all_metrics[metric] for metric in (query_object.metrics or [])]
 
     grain = (
@@ -296,7 +321,7 @@ def map_query_object(query_object: ValidatedQueryObject) -> 
list[SemanticQuery]:
     dimensions = [
         dimension
         for dimension in semantic_view.dimensions
-        if dimension.name in query_object.columns
+        if dimension.name in normalized_columns
         and (
             # if a grain is specified, only include the time dimension if its 
grain
             # matches the requested grain
@@ -794,12 +819,14 @@ def _validate_dimensions(query_object: 
ValidatedQueryObject) -> None:
     Make sure all dimensions are defined in the semantic view.
     """
     semantic_view = query_object.datasource.implementation
+    dimension_names = {dimension.name for dimension in 
semantic_view.dimensions}
 
-    if any(not isinstance(column, str) for column in query_object.columns):
-        raise ValueError("Adhoc dimensions are not supported in Semantic 
Views.")
+    # Normalize all columns to dimension names
+    normalized_columns = [
+        _normalize_column(column, dimension_names) for column in 
query_object.columns
+    ]
 
-    dimension_names = {dimension.name for dimension in 
semantic_view.dimensions}
-    if not set(query_object.columns) <= dimension_names:
+    if not set(normalized_columns) <= dimension_names:
         raise ValueError("All dimensions must be defined in the Semantic 
View.")
 
 
diff --git a/superset/semantic_layers/snowflake/semantic_view.py 
b/superset/semantic_layers/snowflake/semantic_view.py
index 4c7ae770e0..9f34e6f202 100644
--- a/superset/semantic_layers/snowflake/semantic_view.py
+++ b/superset/semantic_layers/snowflake/semantic_view.py
@@ -51,6 +51,7 @@ from superset.semantic_layers.types import (
     NUMBER,
     OBJECT,
     Operator,
+    OrderDirection,
     OrderTuple,
     PredicateType,
     SemanticRequest,
@@ -479,6 +480,49 @@ class SnowflakeSemanticView(SemanticViewImplementation):
             for element, direction in order
         )
 
+    def _get_temporal_dimension(
+        self,
+        dimensions: list[Dimension],
+    ) -> Dimension | None:
+        """
+        Find the first temporal dimension in the list.
+
+        Returns the first dimension with a temporal type (DATE, DATETIME, 
TIME),
+        or None if no temporal dimension is found.
+        """
+        temporal_types = {DATE, DATETIME, TIME}
+        for dimension in dimensions:
+            if dimension.type in temporal_types:
+                return dimension
+        return None
+
+    def _get_default_order(
+        self,
+        dimensions: list[Dimension],
+        order: list[OrderTuple] | None,
+    ) -> list[OrderTuple] | None:
+        """
+        Get the order to use, prepending temporal sort if needed.
+
+        If there's a temporal dimension in the query and it's not already
+        in the order, prepends an ascending sort by that dimension.
+        This ensures time-series data is always sorted chronologically first.
+        """
+        temporal_dimension = self._get_temporal_dimension(dimensions)
+        if not temporal_dimension:
+            return order
+
+        # Check if temporal dimension is already in the order
+        if order:
+            for element, _ in order:
+                if isinstance(element, Dimension) and element.id == 
temporal_dimension.id:
+                    return order
+            # Prepend temporal dimension to existing order
+            return [(temporal_dimension, OrderDirection.ASC)] + list(order)
+
+        # No order specified, use temporal dimension
+        return [(temporal_dimension, OrderDirection.ASC)]
+
     def _build_simple_query(
         self,
         metrics: list[Metric],
@@ -495,7 +539,9 @@ class SnowflakeSemanticView(SemanticViewImplementation):
             self._alias_element(dimension) for dimension in dimensions
         )
         metric_arguments = ", ".join(self._alias_element(metric) for metric in 
metrics)
-        order_clause = self._build_order_clause(order)
+        # Use default temporal ordering if no explicit order is provided
+        effective_order = self._get_default_order(dimensions, order)
+        order_clause = self._build_order_clause(effective_order)
 
         return dedent(
             f"""
@@ -740,7 +786,9 @@ class SnowflakeSemanticView(SemanticViewImplementation):
         select_clause = ",\n    ".join(select_columns)
 
         # Build ORDER BY clause (need to reference the aliased columns)
-        order_clause = self._build_order_clause(order)
+        # Use default temporal ordering if no explicit order is provided
+        effective_order = self._get_default_order(dimensions, order)
+        order_clause = self._build_order_clause(effective_order)
 
         query = dedent(
             f"""
@@ -794,7 +842,9 @@ class SnowflakeSemanticView(SemanticViewImplementation):
             self._alias_element(dimension) for dimension in dimensions
         )
         metric_arguments = ", ".join(self._alias_element(metric) for metric in 
metrics)
-        order_clause = self._build_order_clause(order)
+        # Use default temporal ordering if no explicit order is provided
+        effective_order = self._get_default_order(dimensions, order)
+        order_clause = self._build_order_clause(effective_order)
 
         top_groups_cte, cte_params = self._build_top_groups_cte(
             group_limit,

Reply via email to