{"record":{"id":"031fff511b662fe3","repo":"usememos/memos","slug":"expression-is-not-a-literal","errorCode":null,"errorMessage":"expression is not a literal","messagePattern":"expression is not a literal","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":468,"sourceCode":"\t\treturn CompareLte, nil\n\tcase \"_>=_\":\n\t\treturn CompareGte, nil\n\tdefault:\n\t\treturn \"\", errors.Errorf(\"unsupported comparison operator %q\", fn)\n\t}\n}\n\nfunc getIdentName(expr *exprv1.Expr) (string, error) {\n\tif ident := expr.GetIdentExpr(); ident != nil {\n\t\treturn ident.GetName(), nil\n\t}\n\treturn \"\", errors.New(\"expression is not an identifier\")\n}\n\nfunc getConstValue(expr *exprv1.Expr) (interface{}, error) {\n\tv, ok := expr.ExprKind.(*exprv1.Expr_ConstExpr)\n\tif !ok {\n\t\treturn nil, errors.New(\"expression is not a literal\")\n\t}\n\tswitch x := v.ConstExpr.ConstantKind.(type) {\n\tcase *exprv1.Constant_StringValue:\n\t\treturn v.ConstExpr.GetStringValue(), nil\n\tcase *exprv1.Constant_Int64Value:\n\t\treturn v.ConstExpr.GetInt64Value(), nil\n\tcase *exprv1.Constant_Uint64Value:\n\t\treturn int64(v.ConstExpr.GetUint64Value()), nil\n\tcase *exprv1.Constant_DoubleValue:\n\t\treturn v.ConstExpr.GetDoubleValue(), nil\n\tcase *exprv1.Constant_BoolValue:\n\t\treturn v.ConstExpr.GetBoolValue(), nil\n\tcase *exprv1.Constant_NullValue:\n\t\treturn nil, nil\n\tdefault:\n\t\treturn nil, errors.Errorf(\"unsupported constant %T\", x)\n\t}\n}","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L450-L486","documentation":"getConstValue requires the expression to be a constant literal (exprv1.Expr_ConstExpr) and unwraps string, int, uint, double, and bool kinds. Any non-literal — identifier, call, list — fails here; it typically surfaces wrapped as 'only supports literal arguments' from contains/matches/timestamp/duration validators.","triggerScenarios":"content.contains(tag) (identifier argument), timestamp(created_ts) (field argument), or duration(30 * 24) where the argument is an expression rather than a single literal.","commonSituations":"Trying to compare two fields via a function argument, or passing computed values where the DSL only accepts literals — the filter compiler intentionally forbids non-constant arguments so predicates can be compiled to SQL.","solutions":["Replace the argument with a literal constant","For field-to-field comparison use the == operator directly, not a function argument","Fold arithmetic yourself and inline the resulting number/string"],"exampleFix":"// before\ncontent.contains(tag)\n\n// after\ncontent.contains(\"work\")","handlingStrategy":"type-guard","validationCode":"// Go (AST level): only pass ConstExpr nodes where literals are required\nif arg.GetConstExpr() == nil {\n    return errors.New(\"argument must be a constant literal\")\n}","typeGuard":"func isLiteral(e *exprv1.Expr) bool { return e.GetConstExpr() != nil }","tryCatchPattern":null,"preventionTips":["Treat function arguments as literal-only by design","Use operators (==) for field-to-field comparisons","Inline computed constants before building the filter"],"tags":["cel","filter","literal","parser"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}