apache/superset · error · ValueError

Relationship filter on '{col_name}' requires a single-column

Error message

Relationship filter on '{col_name}' requires a single-column primary key on the related model; found {len(pk_cols)} columns.

What it means

Raised by BaseDAO._apply_relationship_filter() when filtering on a collection relationship (many-to-many / one-to-many detected via RelationshipProperty.uselist) whose related model has a composite (multi-column) primary key. The implementation translates the operator into `.any(related_pk ...)` and needs exactly one PK column; it fails loudly with a ValueError rather than silently dropping trailing PK columns.

Source

Thrown at superset/daos/base.py:667

        col_name: str,
        operator_enum: "ColumnOperatorEnum",
        value: Any,
    ) -> Any:
        """Apply a filter on a many-to-many or one-to-many relationship column.

        Translates the caller's operator into a SQLAlchemy ``.any()``
        expression against the related model's primary key. Supports
        eq / ne / in / nin / is_null / is_not_null. Other operators
        (sw, like, gt, etc.) don't make sense on a collection of related
        rows and raise a clear ValueError instead of producing a
        cryptic SQLAlchemy error at query time.
        """
        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."
                )

View on GitHub (pinned to f4587218dd)

Solutions

  1. Filter on a scalar column instead of the collection relationship (e.g. the FK column on this model).
  2. Give the related model a single-column primary key (all stock Superset models have one) — or query the association table directly.
  3. If you control the code path, build the filter manually with tuple `.in_()` handling for composite keys.

Example fix

# before
filters=[{"col": "composite_pk_children", "opr": "eq", "value": 5}]

# after
filters=[{"col": "parent_id", "opr": "eq", "value": 5}]
Defensive patterns

Strategy: validation

Validate before calling

from sqlalchemy.orm import RelationshipProperty, inspect

def relationship_has_single_pk(model_cls, rel_name: str) -> bool:
    col = getattr(model_cls, rel_name, None)
    if col is None or not hasattr(col, "property"):
        return False
    if not isinstance(col.property, RelationshipProperty) or not col.property.uselist:
        return True  # scalar path unaffected
    return len(inspect(col.property.mapper).primary_key) == 1

Try / catch

try:
    dao.find(query=", filters=filters)
except ValueError as ex:
    if 'single-column primary key' in str(ex):
        # rewrite filter against a scalar FK column on this model
        ...

Prevention

When it happens

Trigger: Passing filters like {"col": "<relationship_name>", "opr": "eq", "value": <id>} to any DAO where the relationship target model defines a composite primary key (len(inspect(mapper).primary_key) != 1).

Common situations: Custom Superset forks/plugins that add association models with composite PKs and try to filter them through the generic DAO filter machinery; MCP-driven dynamic filtering that assumes single-column PKs on every related model.

Related errors


AI-assisted analysis of apache/superset@f4587218dd (2026-08-14). Data as JSON: /api/errors/187199f168a2c907. Report an issue: GitHub.