jaegertracing/jaeger · error · ErrFilterUnsupported
%w: it evaluates a conjunction of predicates only
Error message
%w: it evaluates a conjunction of predicates only
What it means
flatConjuncts flattens an AND conjunction for ToLegacyShape conversion. Every argument of an OpAnd call must itself be a Call; a non-Call argument (e.g. a bare comparison constant or malformed node) cannot be expressed in the legacy conjunctive shape and yields ErrFilterUnsupported with 'conjunction of predicates only'.
Source
Thrown at internal/storage/v2/api/tracestore/shape.go:148
return query, err
}
}
return rewritten, nil
}
// flatConjuncts returns the leaf predicates of a conjunction. A nested `and` means the same
// as a flat one, so it is flattened rather than refused; `or` and `not` have no legacy form,
// so they are refused as the operators they are.
func flatConjuncts(filter *expression.Call) ([]*expression.Call, error) {
switch filter.Op {
case expression.OpOr, expression.OpNot:
return nil, errUnsupportedOperator(filter.Op)
case expression.OpAnd:
conjuncts := make([]*expression.Call, 0, len(filter.Args))
for _, arg := range filter.Args {
nested, ok := arg.(*expression.Call)
if !ok {
return nil, fmt.Errorf("%w: it evaluates a conjunction of predicates only", ErrFilterUnsupported)
}
inner, err := flatConjuncts(nested)
if err != nil {
return nil, err
}
conjuncts = append(conjuncts, inner...)
}
return conjuncts, nil
default:
return []*expression.Call{filter}, nil
}
}
// applyAsLegacyField writes one predicate into the legacy field that carries it. A legacy field
// holds a key and one value, so a predicate that reads no single value off the span — a
// quantifier over the events, an operator applied to a call — has nowhere to go.
func applyAsLegacyField(query *TraceQueryParams, predicate *expression.Call) error {
if len(predicate.Args) != 2 {View on GitHub (pinned to 806f444784)
Solutions
- Ensure every AND argument is a predicate Call (comparison or nested AND).
- Rebuild the expression using the expression package constructors instead of hand-built nodes.
- Catch this and fall back to the filter-capabilities path instead of legacy shape conversion.
Example fix
// before
expression.Call{Op: expression.OpAnd, Args: []expression.Expression{pred, someConstant}}
// after
expression.Call{Op: expression.OpAnd, Args: []expression.Expression{pred1, pred2}} Defensive patterns
Strategy: validation
Validate before calling
func isPredicate(e expression.Expression) bool {
_, ok := e.(*expression.Call)
return ok
}
// assert every AND arg is a Call before calling ToLegacyShape Type guard
func isCallNode(e expression.Expression) (*expression.Call, bool) {
c, ok := e.(*expression.Call)
return c, ok
} Try / catch
shape, err := tracestore.ToLegacyShape(filter)
if err != nil {
if errors.Is(err, tracestore.ErrFilterUnsupported) {
return queryViaFilterPath(filter) // skip legacy conversion
}
return err
} Prevention
- Build expression trees only with the expression package constructors.
- Never hand-assemble Call nodes with raw arguments.
- Round-trip test filter builders through ToLegacyShape.
When it happens
Trigger: Calling ToLegacyShape on a filter where an OpAnd node has a non-Call argument — i.e. a malformed or non-predicate expression nested directly under AND.
Common situations: Programmatically constructed expression trees with wrong node types; filters built by external tools that don't enforce the predicate-only AND contract.
Related errors
- %w: it compares a reference against a constant only
- %w: it does not index the %q level
- %w: it does not support the operator %q
- %w: it does not index the %q level
- filter argument is empty: a call argument must carry a call
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/7edfb22135cb0d38.
Report an issue: GitHub.