gabotorresruiz commented on code in PR #44454:
URL: https://github.com/apache/superset/pull/44454#discussion_r4262471037


##########
superset/semantic_layers/dimension_resolution.py:
##########
@@ -0,0 +1,118 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Resolve immutable provider dimensions for each query usage."""
+
+from collections.abc import Iterable, Mapping
+from enum import Enum
+
+from flask_babel import gettext as _
+from superset_core.semantic_layers.types import Dimension, Grain
+
+from superset.exceptions import QueryObjectValidationError
+
+# Known grains rank finest to coarsest, ahead of unknown representations.
+_GRAIN_FINENESS: dict[str, int] = {
+    "PT1S": 0,
+    "PT1M": 1,
+    "PT1H": 2,
+    "P1D": 3,
+    "P1W": 4,
+    "P1M": 5,
+    "P3M": 6,
+    "P1Y": 7,
+}
+
+
+class DimensionUsage(Enum):
+    """Keep selection policy explicit at each host/provider boundary."""
+
+    METADATA = "metadata"
+    COMPATIBILITY = "compatibility"
+    FILTER = "filter"
+    TIME_BOUND = "time_bound"

Review Comment:
   Just a NIT, and this is the line my earlier note got wrong, so thanks for 
the pointer.
   
   What is left is that `TIME_BOUND` is the only member of this enum with no 
production reference at this head; the other six have one or two each. Worth 
dropping it until the sorting follow-up needs it, so the enum stays an accurate 
list of the roles the resolver actually serves?



##########
superset/semantic_layers/mapper.py:
##########
@@ -432,59 +439,54 @@ def _map_query_object(
 
     grain = _convert_time_grain(query_object.extras.get("time_grain_sqla"))
     time_axis_column = _get_grain_time_axis_column(query_object, 
all_dimensions)
-    # A semantic view can expose multiple Dimension variants per name (one per
-    # supported time grain). Pick exactly one variant per selected column:
-    # for the time-axis column we honor the user's grain selection, falling
-    # back to the raw / no-grain variant when no exact match exists and then
-    # to any available variant so the axis is never silently dropped; for
-    # every other selected column we prefer the raw variant and otherwise
-    # take any available variant.
+    grouping_grains: dict[str, Grain | None] = (
+        {time_axis_column: grain}
+        if time_axis_column is not None and time_axis_column in 
normalized_columns
+        else {}
+    )
+    grouping_dimensions: dict[str, Dimension] = resolve_dimensions(
+        view_dimensions, usage=DimensionUsage.GROUP, 
grouping_grains=grouping_grains
+    )
     dimensions: list[Dimension] = []
-    seen_non_axis: dict[str, Dimension] = {}
-    axis_variants: list[Dimension] = []
-    axis_match: Dimension | None = None
-    for dimension in view_dimensions:
-        if dimension.name not in normalized_columns:
-            continue
-        if dimension.name == time_axis_column:
-            axis_variants.append(dimension)
-            if axis_match is None and dimension.grain == grain:
-                axis_match = dimension
-            continue
-        existing = seen_non_axis.get(dimension.name)
-        if existing is None or (existing.grain is not None and dimension.grain 
is None):
-            seen_non_axis[dimension.name] = dimension
-
-    if axis_match is not None:
-        dimensions.append(axis_match)
-    elif axis_variants:
-        # No variant matches the requested grain. Prefer the raw (grain=None)
-        # variant; otherwise pick a deterministic fallback so the axis stays
-        # on the query instead of being silently dropped.
-        raw_variant = next((v for v in axis_variants if v.grain is None), None)
-        dimensions.append(
-            raw_variant
-            if raw_variant is not None
-            else min(axis_variants, key=lambda v: v.grain.name if v.grain else 
"")
-        )
-    dimensions.extend(seen_non_axis.values())
-
-    order = _get_order_from_query_object(query_object, all_metrics, 
all_dimensions)
+    if (
+        time_axis_column in normalized_columns
+        and time_axis_column in grouping_dimensions
+    ):
+        dimensions.append(grouping_dimensions[time_axis_column])
+    dimensions.extend(
+        dimension
+        for name, dimension in grouping_dimensions.items()

Review Comment:
   Not a blocker, but worth knowing before you resolve, because this is the 
block that conflicts: master `8ace079d8d` (#42760) rewrote it so that 
`dimensions` follows the caller's deduplicated column order instead of catalog 
order. On this branch `columns=["region", "category"]` comes back as 
`["category", "region"]`, and on master it comes back as `["region", 
"category"]`, so keeping this block as it stands would quietly revert that. 
Master's own 
`test_map_query_object_preserves_order_while_deduplicating_columns` passes a 
single column twice, so it would not catch the revert either.
   
   Since the resolver hands you a dict keyed by name, I think both can survive 
as something like this, once master's `normalized_column_order` is in the file:
   
   ```python
       dimensions: list[Dimension] = [
           grouping_dimensions[name]
           for name in normalized_column_order
           if name in grouping_dimensions
       ]
   ```
   
   That shape keeps the whole `semantic_layers` suite passing here, including 
the grain tests that currently rely on the axis being appended first. Could you 
re-run the 408 resolver and mapper tests after the resolution either way? That 
set is what reassured me nothing had gone missing this time.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to