{"record":{"id":"f301c1a5c2bb63a1","repo":"Tencent/WeKnora","slug":"milvus-filter-condition-is-nil","errorCode":null,"errorMessage":"milvus filter condition is nil","messagePattern":"milvus filter condition is nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/repository/retriever/milvus/filter.go","lineNumber":82,"sourceCode":"type convertResult struct {\n\texprStr string\n\tparams  map[string]any\n}\n\ntype filter struct{}\n\nfunc (c *filter) Convert(cond *universalFilterCondition) (*convertResult, error) {\n\tvar counter int\n\treturn c.convertCondition(cond, &counter)\n}\n\nfunc (c *filter) convertComparisonCondition(\n\tcond *universalFilterCondition,\n\tcounter *int,\n) (*convertResult, error) {\n\tcondField := cond.Field\n\tif condField == \"\" || cond.Value == nil {\n\t\treturn nil, fmt.Errorf(\"milvus filter condition is nil\")\n\t}\n\toperator, ok := comparisonOperators[cond.Operator]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported comparison operator: %s\", cond.Operator)\n\t}\n\n\tparamName := c.convertParamName(cond.Field, counter)\n\treturn &convertResult{\n\t\texprStr: fmt.Sprintf(\"%s %s {%s}\", condField, operator, paramName),\n\t\tparams:  map[string]any{paramName: cond.Value},\n\t}, nil\n}\n\nfunc (c *filter) convertLogicalCondition(\n\tcond *universalFilterCondition,\n\tcounter *int,\n) (*convertResult, error) {\n\tif cond.Value == nil {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/repository/retriever/milvus/filter.go#L64-L100","documentation":"In the Milvus universal filter converter, convertComparisonCondition rejects any condition whose Field is empty or whose Value is nil, returning the sentinel error 'milvus filter condition is nil'. A comparison condition needs both a field name and a value to render into a Milvus boolean expression like \"field == {param}\". This is a caller-side filter construction bug.","triggerScenarios":"Building a filter condition with Field left as \"\" or Value left as nil — e.g. optional fields omitted from a struct being added as conditions with nil values, or JSON filter input where a key exists but its value is null.","commonSituations":"Unmarshaled filter JSON with null values; conditional Go code that appends a condition before checking whether the variable is set; struct-tag driven filter builders that don't skip nil pointer fields.","solutions":["Validate each condition before conversion: skip conditions with empty Field or nil Value instead of adding them.","In filter-building helpers, dereference pointer fields only when non-nil.","Sanitize incoming JSON filters to drop keys with null values before mapping to conditions.","Return a clear validation error to the upstream caller identifying which field was empty."],"exampleFix":"// before\nconds = append(conds, newCond(\"category\", categoryValue)) // categoryValue may be nil\n// after\nif categoryValue != nil && categoryField != \"\" {\n\tconds = append(conds, newCond(categoryField, categoryValue))\n}","handlingStrategy":"validation","validationCode":"func validComparison(c *universalFilterCondition) bool {\n\treturn c != nil && c.Field != \"\" && c.Value != nil\n}\nconds := filterSlice(validComparison)","typeGuard":"func hasFieldAndValue(c *universalFilterCondition) bool {\n\treturn c != nil && c.Field != \"\" && c.Value != nil\n}","tryCatchPattern":null,"preventionTips":["Skip nil-valued and empty-field conditions when building filters.","For JSON filters, drop keys whose value is null during decoding.","Use pointer-aware builders that omit unset optional fields.","Return a field-specific validation error upstream instead of relying on the converter."],"tags":["milvus","go","filter","validation"],"backgroundTag":"nil-filter-condition","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}