mikebridge commented on code in PR #45133:
URL: https://github.com/apache/superset/pull/45133#discussion_r4253304945


##########
superset/semantic_layers/mapper.py:
##########
@@ -771,30 +774,94 @@ def _convert_query_object_filter(
             )
         return filters or None
 
-    value = _coerce_filter_value(value, dimension)
-
-    operator = OPERATOR_MAP.get(operator_str)
+    operator: Operator | None = OPERATOR_MAP.get(operator_str)
     if not operator:
         # Unknown operator - raise error to prevent unauthorized access
         raise ValueError(f"Unsupported filter operator: {operator_str}")
 
-    return {
+    value: FilterValues | frozenset[FilterValues]
+    if operator in {Operator.IN, Operator.NOT_IN}:
+        # Coerce before deduplicating: equal raw values (0 and False) may 
differ
+        # in validity for the selected dimension's type.
+        value = (
+            frozenset(
+                _coerce_scalar_filter_value(item, dimension) for item in 
raw_value
+            )
+            if isinstance(raw_value, (list, tuple))
+            else _coerce_scalar_filter_value(raw_value, dimension)
+        )
+    elif operator in {Operator.IS_NULL, Operator.IS_NOT_NULL}:
+        value = None
+    else:
+        # Match the native datasource's scalar handling of collection inputs.
+        scalar: FilterValues = (
+            (raw_value[0] if raw_value else None)
+            if isinstance(raw_value, (list, tuple))
+            else raw_value
+        )
+        value = _coerce_scalar_filter_value(scalar, dimension)
+        if value is None and operator not in {Operator.EQUALS, 
Operator.NOT_EQUALS}:
+            raise QueryObjectValidationError(
+                "Must specify a value for this comparison."
+            )
+
+    return _normalize_membership_filter(
         Filter(
             type=PredicateType.WHERE,
             column=dimension,
             operator=operator,
             value=value,
         )
-    }
+    )
 
 
-def _coerce_filter_value(
-    value: FilterValues | frozenset[FilterValues],
-    dimension: Dimension,
-) -> FilterValues | frozenset[FilterValues]:
-    if isinstance(value, frozenset):
-        return frozenset(_coerce_scalar_filter_value(v, dimension) for v in 
value)
-    return _coerce_scalar_filter_value(value, dimension)
+def _normalize_membership_filter(filter_: Filter) -> set[FilterExpression]:
+    """Normalize NULL equality/membership and reject empty membership 
selections."""
+    negated: bool = filter_.operator in {Operator.NOT_EQUALS, Operator.NOT_IN}
+    null_filter: Filter = replace(
+        filter_,
+        operator=Operator.IS_NOT_NULL if negated else Operator.IS_NULL,
+        value=None,
+    )
+    if filter_.operator in {Operator.EQUALS, Operator.NOT_EQUALS}:
+        return {null_filter} if filter_.value is None else {filter_}
+    if filter_.operator not in {Operator.IN, Operator.NOT_IN}:
+        return {filter_}
+
+    values: frozenset[FilterValues] = (
+        frozenset(filter_.value)
+        if isinstance(filter_.value, (tuple, frozenset))
+        else frozenset({filter_.value})
+    )
+    if not values:
+        raise QueryObjectValidationError("Semantic IN/NOT IN filters cannot be 
empty.")
+    if None not in values:
+        return {filter_}
+
+    non_null_values: frozenset[FilterValues] = values - {None}
+    if not non_null_values:
+        return {null_filter}
+    comparison: Filter = replace(filter_, value=non_null_values)
+    if negated:
+        return {comparison, null_filter}

Review Comment:
   Following the contract decision, 
[f2c1cd00da](https://github.com/apache/superset/commit/f2c1cd00da2964d108a075a245b45e6ac990d2b5)
 removes OR_FILTERS and the host refusal path. OrFilter is mandatory SDK 
vocabulary: providers must render it, including main and group-limit paths. 
NULL normalization and the combined cache keys are unchanged.
   
   The three dispatch controls failed on the old refusal and pass after 
removal; the full semantic directory has 929 passing tests, branch-file hooks 
and the normal pre-push gate passed. Implementation used Codex, with 
independent Claude Fable 5.1 review of the full Python branch and final 
semantic/DDIA delta.
   
   The paired provider rollout is SC-125752, before or atomically with the 
shell pin bump. I am leaving this thread unresolved until #5484 renders 
OrFilter; this Apache change alone does not establish provider support.



-- 
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