{"record":{"id":"2509c70980942fe1","repo":"usememos/memos","slug":"comparison-must-start-with-a-field-reference-or-su","errorCode":null,"errorMessage":"comparison must start with a field reference or supported function","messagePattern":"comparison must start with a field reference or supported function","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/render.go","lineNumber":181,"sourceCode":"\t\t}\n\t\tswitch field.Kind {\n\t\tcase FieldKindBoolColumn:\n\t\t\treturn r.renderBoolColumnComparison(field, cond.Operator, cond.Right)\n\t\tcase FieldKindJSONBool:\n\t\t\treturn r.renderJSONBoolComparison(field, cond.Operator, cond.Right)\n\t\tcase FieldKindJSONExists:\n\t\t\treturn r.renderJSONExistsComparison(field, cond.Operator, cond.Right)\n\t\tcase FieldKindScalar:\n\t\t\treturn r.renderScalarComparison(field, cond.Operator, cond.Right)\n\t\tdefault:\n\t\t\treturn renderResult{}, errors.Errorf(\"field %q does not support comparison\", field.Name)\n\t\t}\n\tcase *FunctionValue:\n\t\treturn r.renderFunctionComparison(left, cond.Operator, cond.Right)\n\tcase *FieldAccessorValue:\n\t\treturn r.renderAccessorComparison(left, cond.Operator, cond.Right)\n\tdefault:\n\t\treturn renderResult{}, errors.New(\"comparison must start with a field reference or supported function\")\n\t}\n}\n\n// accessorSpec maps a CEL timestamp accessor to per-dialect SQL date-part tokens\n// and the offset to subtract so the result matches CEL's base (e.g. CEL months\n// are 0-based but every dialect reports 1-based, so off=1). off is indexed\n// [sqlite, postgres, mysql].\ntype accessorSpec struct {\n\tsqlite string // strftime format specifier\n\tpg     string // EXTRACT field\n\tmysql  string // function name\n\toff    [3]int\n}\n\nvar accessorSpecs = map[string]accessorSpec{\n\t\"getFullYear\":   {\"%Y\", \"YEAR\", \"YEAR\", [3]int{0, 0, 0}},\n\t\"getMonth\":      {\"%m\", \"MONTH\", \"MONTH\", [3]int{1, 1, 1}},\n\t\"getDate\":       {\"%d\", \"DAY\", \"DAYOFMONTH\", [3]int{0, 0, 0}},","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/render.go#L163-L199","documentation":"The renderer translates a ComparisonCondition by switching on the left-hand expression type: FieldRef, FunctionValue, or FieldAccessorValue. Anything else — literals, arithmetic results, or custom ValueExpr implementations — cannot start an SQL comparison, so this error is thrown.","triggerScenarios":"A filter like 42 < content.size or \"abc\" == tag — starting a comparison with a literal. Also triggered when user code constructs a ComparisonCondition with a custom ValueExpr implementation the renderer does not know.","commonSituations":"Reversed comparisons written by users; library extensions adding new ValueExpr types without extending renderComparison; programmatic construction of filter conditions.","solutions":["Put the field or function on the left: content.size() > 42 instead of 42 < content.size()","If extending the library, add a case for your custom ValueExpr type in renderComparison or convert it to a FieldRef before rendering","Fold literal-vs-literal comparisons in the parser instead of sending them to the renderer"],"exampleFix":"// before\n`42 < content.size()`\n\n// after\n`content.size() > 42`","handlingStrategy":"validation","validationCode":"// In Go, before rendering, verify the comparison left side is a supported type\nfunc isRenderableLeft(v filter.ValueExpr) bool {\n    switch v.(type) {\n    case *filter.FieldRef, *filter.FunctionValue, *filter.FieldAccessorValue:\n        return true\n    }\n    return false\n}\n\n// ok := isRenderableLeft(cond.Left)","typeGuard":"func isRenderableLeft(v filter.ValueExpr) bool {\n    switch v.(type) {\n    case *filter.FieldRef, *filter.FunctionValue, *filter.FieldAccessorValue:\n        return true\n    default:\n        return false\n    }\n}","tryCatchPattern":"if _, err := r.Render(cond); err != nil {\n    if strings.Contains(err.Error(), \"must start with a field reference\") {\n        return userErr(\"put the field or function on the left of the comparison\")\n    }\n    return err\n}","preventionTips":["Write comparisons field-first: content.size() > 42, not 42 < content.size()","Validate left-hand expression type before constructing ComparisonCondition programmatically","Extend renderComparison if you introduce new ValueExpr types"],"tags":["cel","filter","renderer","sql"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}