{"record":{"id":"b1ea81ea3ae3735f","repo":"jaegertracing/jaeger","slug":"w-it-compares-q-against-a-string-constant-only","errorCode":null,"errorMessage":"%w: it compares %q against a string constant only","messagePattern":"%w: it compares %q against a string constant only","errorType":"validation","errorClass":"ErrFilterUnsupported","httpStatus":null,"severity":"error","filePath":"internal/storage/v2/api/tracestore/shape.go","lineNumber":270,"sourceCode":"\t\t}\n\t\tquery.DurationMax = constant.Value\n\tdefault:\n\t\treturn errUnsupportedOperatorOn(op, name)\n\t}\n\treturn nil\n}\n\n// textConstant reads the constant a string-valued legacy field can carry: a text constant, or an\n// untyped one, which is what an unqualified tag equality has always been. A constant of any other\n// type asks for a match on a type these fields cannot name.\nfunc textConstant(name string, value expression.Expression) (string, error) {\n\tswitch constant := value.(type) {\n\tcase *expression.StringValue:\n\t\treturn constant.Value, nil\n\tcase *expression.AnyValue:\n\t\treturn constant.Value, nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"%w: it compares %q against a string constant only\", ErrFilterUnsupported, name)\n\t}\n}\n\n// errNotADuration refuses a bound that is not a length of time. An untyped constant reaching here\n// was never read as a duration, which expression.ResolveConstants does on the way in, so the\n// refusal says that rather than blaming the spelling the caller wrote.\nfunc errNotADuration(name string, value expression.Expression) error {\n\tif constant, ok := value.(*expression.AnyValue); ok {\n\t\treturn fmt.Errorf(\"%w: the bound %q on %q was never read as a duration\",\n\t\t\tErrFilterInvalid, constant.Value, name)\n\t}\n\treturn fmt.Errorf(`%w: it compares %q against a duration such as \"2s\" only`,\n\t\tErrFilterUnsupported, name)\n}\n\nfunc errUnsupportedOperator(op expression.Operator) error {\n\treturn fmt.Errorf(\"%w: it does not support the operator %q\", ErrFilterUnsupported, op)\n}","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/jaegertracing/jaeger/blob/806f4447841ecdb60519f408b004a599d515f437/internal/storage/v2/api/tracestore/shape.go#L252-L288","documentation":"The legacy scalar fields (attributes, service name, operation name) are string-valued, so an equality predicate can only carry a string constant (*expression.StringValue) or an untyped constant (*expression.AnyValue) that represents a tag match with no declared type. A typed non-string constant — a number, boolean, or duration literal — names a type these fields cannot match, so textConstant refuses it with ErrFilterUnsupported. This happens while downconverting a filter to the legacy shape for backends without native filter support.","triggerScenarios":"ToLegacyShape() encounters a flat-conjunction predicate of the form <AttributeRef|FieldRef(service|name)> == constant where the constant is neither *expression.StringValue nor *expression.AnyValue — e.g. comparing an attribute against a numeric or duration literal.","commonSituations":"A client builds a typed comparison such as http.status_code == 200 (int constant) or an attribute compared against a bool/duration; the backend only serves string tag equality, so the typed constant is rejected during shape conversion.","solutions":["Send the constant as a string: store/compare the tag value as its string form (e.g. '200' instead of 200).","Use an untyped *expression.AnyValue constant, which matches the attribute regardless of stored form.","Drop the predicate or use a backend with native typed filter support."],"exampleFix":"// before\nArgs: []expression.Expression{&expression.AttributeRef{Key: \"http.status_code\"}, &expression.Int64Value{Value: 200}}\n// after\nArgs: []expression.Expression{&expression.AttributeRef{Key: \"http.status_code\"}, &expression.AnyValue{Value: \"200\"}}","handlingStrategy":"validation","validationCode":"func isStringLikeConstant(v expression.Expression) bool {\n  switch v.(type) {\n  case *expression.StringValue, *expression.AnyValue:\n    return true\n  }\n  return false\n}","typeGuard":"func isStringLikeConstant(v expression.Expression) bool {\n  switch v.(type) {\n  case *expression.StringValue, *expression.AnyValue:\n    return true\n  default:\n    return false\n  }\n}","tryCatchPattern":"legacy, err := params.ToLegacyShape()\nif errors.Is(err, tracestore.ErrFilterUnsupported) && strings.Contains(err.Error(), \"string constant only\") {\n  return fmt.Errorf(\"convert the constant to its string form before querying: %w\", err)\n}","preventionTips":["Store and compare tag values as strings in filters","Use AnyValue for untyped tag equality","Never build numeric/bool constants against attribute or name fields"],"tags":["jaeger","tracestore","filter-unsupported","type-mismatch"],"backgroundTag":"unsupported-comparison-type","analyzedSha":"806f4447841ecdb60519f408b004a599d515f437","analyzedAt":"2026-09-01T02:39:22.140Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}