{"record":{"id":"87a4fb0785a8c1e4","repo":"usememos/memos","slug":"text-match-argument-must-be-a-string","errorCode":null,"errorMessage":"text match argument must be a string","messagePattern":"text match argument must be a string","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":336,"sourceCode":"\t}\n\n\tfield, ok := schema.Field(targetName)\n\tif !ok {\n\t\treturn nil, errors.Errorf(\"unknown identifier %q\", targetName)\n\t}\n\tif !field.SupportsContains {\n\t\treturn nil, errors.Errorf(\"identifier %q does not support text matching\", targetName)\n\t}\n\tif len(call.Args) != 1 {\n\t\treturn nil, errors.New(\"text match expects exactly one argument\")\n\t}\n\tvalue, err := getConstValue(call.Args[0])\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"text match only supports literal arguments\")\n\t}\n\tstr, ok := value.(string)\n\tif !ok {\n\t\treturn nil, errors.New(\"text match argument must be a string\")\n\t}\n\treturn &TextMatchCondition{\n\t\tField: targetName,\n\t\tMode:  mode,\n\t\tValue: str,\n\t}, nil\n}\n\nfunc buildMatchesCondition(call *exprv1.Expr_Call, schema Schema) (Condition, error) {\n\tif call.Target == nil {\n\t\treturn nil, errors.New(\"matches requires a target\")\n\t}\n\ttargetName, err := getIdentName(call.Target)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfield, ok := schema.Field(targetName)","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L318-L354","documentation":"After verifying a text-match call has exactly one argument, the parser requires that argument to be a string literal. getConstValue succeeds for ints, doubles, bools, and strings; anything other than a string cannot be compiled into the SQL text-match predicate, so the parse fails with this error.","triggerScenarios":"Filter strings such as content.contains(42), content.contains(true), or content.contains(someIdent) where the argument is a non-string literal or an identifier instead of a quoted string.","commonSituations":"Programmatically interpolating a number or unquoted variable into the filter template, or assuming CEL will coerce types like JavaScript does.","solutions":["Quote the argument so it is a string literal: content.contains(\"42\")","If interpolating values from code, serialize them as JSON strings (strconv.Quote / JSON.stringify) before embedding","Use a plain comparison (row_status == 1) for non-text fields instead of a text matcher"],"exampleFix":"// before\ncontent.contains(42)\n\n// after\ncontent.contains(\"42\")","handlingStrategy":"validation","validationCode":"// TS: ensure interpolated text-match args are quoted strings\nconst arg = String(value);\nif (!/^\"(?:[^\"\\\\]|\\\\.)*\"$/.test(JSON.stringify(arg).replace(/^\"|\"$/g, '\"'))) {\n  throw new Error('argument must be a quoted string');\n}\nconst filter = `content.contains(${JSON.stringify(String(value))})`;","typeGuard":"const isStringLiteral = (v: unknown): v is string => typeof v === 'string';","tryCatchPattern":"try { const conds = parseFilter(expr); } catch (e) { if (String(e).includes('must be a string')) showUserError('text match needs a quoted string'); else throw e; }","preventionTips":["Always serialize interpolated values with JSON.stringify / strconv.Quote","Never interpolate raw numbers into text-match calls","Keep a fixture suite of valid/invalid filter strings in client tests"],"tags":["cel","filter","type-mismatch","parser"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}