{"record":{"id":"31305af924311d3e","repo":"usememos/memos","slug":"set-operations-require-a-list-literal-as-the-secon","errorCode":null,"errorMessage":"set operations require a list literal as the second argument","messagePattern":"set operations require a list literal as the second argument","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":726,"sourceCode":"\tif len(call.Args) != 2 {\n\t\treturn nil, errors.Errorf(\"%s expects two arguments\", call.Function)\n\t}\n\n\tfieldName, err := getIdentName(call.Args[0])\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"set operations require a list field as the first argument\")\n\t}\n\tfield, ok := pc.schema.Field(fieldName)\n\tif !ok {\n\t\treturn nil, errors.Errorf(\"unknown identifier %q\", fieldName)\n\t}\n\tif field.Kind != FieldKindJSONList {\n\t\treturn nil, errors.Errorf(\"set operations require a list field, got %q\", fieldName)\n\t}\n\n\tlistExpr := call.Args[1].GetListExpr()\n\tif listExpr == nil {\n\t\treturn nil, errors.New(\"set operations require a list literal as the second argument\")\n\t}\n\tvalues := make([]string, 0, len(listExpr.Elements))\n\tfor _, el := range listExpr.Elements {\n\t\tv, err := getConstValue(el)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"set operations only support literal string elements\")\n\t\t}\n\t\ts, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"set operations require string elements\")\n\t\t}\n\t\tvalues = append(values, s)\n\t}\n\n\tmembership := func(s string) Condition {\n\t\treturn &ElementInCondition{Element: &LiteralValue{Value: s}, Field: fieldName}\n\t}\n\tsizeEquals := func(n int) Condition {","sourceCodeStart":708,"sourceCodeEnd":744,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L708-L744","documentation":"Set operations (in / not-in style membership over JSON list fields) require the second argument to be a CEL list literal, e.g. tag in [\"work\", \"urgent\"]. The parser inspects call.Args[1].GetListExpr(); when it is nil (an identifier, call, or string), compilation fails because only literal lists can be translated to SQL.","triggerScenarios":"tag in \"work\" (bare string instead of list), tag in tag (field on both sides), or tag in someList where someList is an unsupported function result.","commonSituations":"Treating in like SQL's IN with a comma string (\"work,urgent\"); attempting list-to-list containment between two fields; variable substitution that emits a non-list value.","solutions":["Wrap the values in a list literal: tag in [\"work\", \"urgent\"]","For a single value, still use a one-element list: tag in [\"work\"]","Build the list literal by joining pre-quoted strings when generating filters from code"],"exampleFix":"// before\ntag in \"work\"\n\n// after\ntag in [\"work\"]","handlingStrategy":"validation","validationCode":"// TS: build a safe membership filter from a string array\nconst buildIn = (field: string, values: string[]) =>\n  `${field} in [${values.map(v => JSON.stringify(String(v))).join(', ')}]`;\nif (values.length === 0) throw new Error('membership list cannot be empty');","typeGuard":"const isStringArray = (v: unknown): v is string[] => Array.isArray(v) && v.every(x => typeof x === 'string');","tryCatchPattern":null,"preventionTips":["Always wrap membership values in [ ... ] list literal syntax","JSON-encode each element when generating filters programmatically","Never pass a field name or bare string as the in operand"],"tags":["cel","set-operations","lists","filter"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}