apache/superset · error · ValueError
Operator '{operator_enum.value}' on relationship column '{co
Error message
Operator '{operator_enum.value}' on relationship column '{col_name}' requires a scalar value, got {type(value).__name__}. Use '{counterpart}' to match multiple related ids. What it means
Raised by BaseDAO._apply_relationship_filter() when eq or ne is used on a collection relationship with a list/tuple value. `column.any(pk == [..])` would compile to nonsense and behave unpredictably across backends, so the code fails fast and tells you to use `in` (for eq) or `nin` (for ne) to match multiple related ids.
Source
Thrown at superset/daos/base.py:680
pk_cols = inspect(column.property.mapper).primary_key
if len(pk_cols) != 1:
# Composite PKs would need a tuple `.in_()` and per-operator
# tuple handling; no Superset model uses one today, so we
# fail loudly rather than silently drop the trailing columns.
raise ValueError(
f"Relationship filter on '{col_name}' requires a "
f"single-column primary key on the related model; "
f"found {len(pk_cols)} columns."
)
related_pk = pk_cols[0]
if operator_enum in (ColumnOperatorEnum.eq, ColumnOperatorEnum.ne):
# `value` must be scalar for both eq and ne: a list/tuple would
# silently compile to `related_pk == [...]` (or `!= [...]`),
# which behaves unpredictably across backends instead of
# failing fast. Use `in`/`nin` to match multiple related ids.
if isinstance(value, (list, tuple)):
counterpart = "in" if operator_enum == ColumnOperatorEnum.eq else "nin"
raise ValueError(
f"Operator '{operator_enum.value}' on relationship "
f"column '{col_name}' requires a scalar value, got "
f"{type(value).__name__}. Use '{counterpart}' to match "
f"multiple related ids."
)
if operator_enum == ColumnOperatorEnum.eq:
return query.filter(column.any(related_pk == value))
return query.filter(~column.any(related_pk == value))
if operator_enum == ColumnOperatorEnum.in_:
values = value if isinstance(value, (list, tuple)) else [value]
return query.filter(column.any(related_pk.in_(values)))
if operator_enum == ColumnOperatorEnum.nin:
values = value if isinstance(value, (list, tuple)) else [value]
return query.filter(~column.any(related_pk.in_(values)))
if operator_enum == ColumnOperatorEnum.is_null:
# "has no related rows at all"
return query.filter(~column.any())
if operator_enum == ColumnOperatorEnum.is_not_null:View on GitHub (pinned to f4587218dd)
Solutions
- Switch the operator: use {"opr": "in", "value": [1,2,3]} to match any of the ids, or {"opr": "nin", "value": [...]} to exclude them.
- Pass a scalar with eq/ne when you mean exactly one id.
- For negated membership remember nin means 'has NO related row with these ids'.
Example fix
# before
filters=[{"col": "roles", "opr": "eq", "value": [1, 2]}]
# after
filters=[{"col": "roles", "opr": "in", "value": [1, 2]}] Defensive patterns
Strategy: validation
Validate before calling
REL_SCALAR_OPS = {"eq", "ne", "in", "nin", "is_null", "is_not_null"}
def normalize_rel_filter(f: dict) -> dict:
if f["opr"] == "eq" and isinstance(f.get("value"), (list, tuple)):
return {**f, "opr": "in"}
if f["opr"] == "ne" and isinstance(f.get("value"), (list, tuple)):
return {**f, "opr": "nin"}
return f Type guard
def is_scalar_relationship_filter(f: dict) -> bool:
return not (f["opr"] in {"eq", "ne"} and isinstance(f.get("value"), (list, tuple))) Try / catch
try:
dao.find(query=", filters=filters)
except ValueError as ex:
if 'requires a scalar value' in str(ex):
filters = [normalize_rel_filter(f) for f in filters]
results = dao.find(query=", filters=filters) Prevention
- Map 'match any of these ids' to in and 'match none of these' to nin from the start.
- Keep value types consistent: scalar for eq/ne, list for in/nin.
- Validate filter payloads against get_filterable_columns_and_operators() output.
When it happens
Trigger: filters=[{"col": "<uselist relationship>", "opr": "eq", "value": [1,2,3]}] or the same with "opr": "ne" against any DAO; scalar eq/ne values are fine, only list/tuple values trip it.
Common situations: Reusing a scalar-column filter payload (where eq + list sometimes 'works') against a relationship column; MCP/tool clients that always wrap values in lists.
Related errors
- Invalid filter: column '%s' does not exist on %s
- Relationship filter on '{col_name}' requires a single-column
- Operator '{operator_enum.value}' is not supported on relatio
- created_by_fk_or_editor only supports 'eq'; got '{c.opr}'
- created_by_fk_or_editor only supports 'eq'; got '{c.opr}'
AI-assisted analysis of apache/superset@f4587218dd (2026-08-14).
Data as JSON: /api/errors/ee6fbb72b2e00765.
Report an issue: GitHub.