jaegertracing/jaeger · error · ErrFilterUnsupported

%w: it compares a reference against a constant only

Error message

%w: it compares a reference against a constant only

What it means

applyAsLegacyField converts a predicate Call into a legacy TraceQueryParams field. The predicate's first argument must be an AttributeRef or FieldRef; anything else (e.g. a constant on the left, a nested Call) is not a comparable reference and yields ErrFilterUnsupported with 'compares a reference against a constant only'.

Source

Thrown at internal/storage/v2/api/tracestore/shape.go:175

	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 {
		return errUnsupportedOperator(predicate.Op)
	}
	switch ref := predicate.Args[0].(type) {
	case *expression.AttributeRef:
		return applyAttribute(query, predicate.Op, ref, predicate.Args[1])
	case *expression.FieldRef:
		return applyField(query, predicate.Op, ref, predicate.Args[1])
	default:
		return fmt.Errorf("%w: it compares a reference against a constant only", ErrFilterUnsupported)
	}
}

func applyAttribute(query *TraceQueryParams, op expression.Operator, ref *expression.AttributeRef, value expression.Expression) error {
	if op != expression.OpEq {
		return errUnsupportedOperatorOn(op, ref.Key)
	}
	if !legacyFilterLevels.SupportsLevel(ref.Level) {
		return fmt.Errorf("%w: it does not index the %q level", ErrFilterUnsupported, ref.Level)
	}
	text, err := textConstant(ref.Key, value)
	if err != nil {
		return err
	}
	if _, ok := query.Attributes.Get(ref.Key); ok {
		return errRepeatedPredicateOn(ref.Key)
	}
	query.Attributes.PutStr(ref.Key, text)

View on GitHub (pinned to 806f444784)

Solutions

  1. Rewrite the comparison so the reference (attribute or field) is the first operand and the constant the second.
  2. Use a filter-based Reader path instead of the legacy shape if you need general comparisons.
  3. Pre-validate predicate argument order before conversion.

Example fix

// before
expression.Eq(expression.Constant("svc"), expression.SpanAttribute("service.name"))
// after
expression.Eq(expression.SpanAttribute("service.name"), expression.Constant("svc"))
Defensive patterns

Strategy: validation

Validate before calling

func refFirst(c *expression.Call) bool {
  switch c.Args[0].(type) {
  case *expression.AttributeRef, *expression.FieldRef:
    return true
  }
  return false
}
// ensure all predicates pass before conversion

Type guard

func asRef(e expression.Expression) (expression.Expression, bool) {
  switch e.(type) {
  case *expression.AttributeRef, *expression.FieldRef:
    return e, true
  }
  return nil, false
}

Try / catch

shape, err := tracestore.ToLegacyShape(filter)
if err != nil {
  if errors.Is(err, tracestore.ErrFilterUnsupported) && strings.Contains(err.Error(), "constant") {
    filter = normalizeOperandOrder(filter)
  }
}

Prevention

When it happens

Trigger: Calling ToLegacyShape with a predicate whose Args[0] is neither *expression.AttributeRef nor *expression.FieldRef — e.g. constant-first comparisons or function-call operands.

Common situations: Filters built with reversed operand order (constant == attribute) or with computed expressions on the left side, then converted to the legacy query shape which only supports ref-vs-constant comparisons.

Related errors


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/a1671b2dbf052fc2. Report an issue: GitHub.