@@ -217,24 +217,46 @@ def _apply_filters(query: Any, filters: dict[str, Any]) -> Any:
217217 query = SupabaseGroongaDocumentStore ._apply_filters (query , cond )
218218 return query
219219
220- if op in ("OR" , "NOT" ):
221- neg_map = {"==" : "neq" , "!=" : "eq" , ">" : "lte" , ">=" : "lt" , "<" : "gte" , "<=" : "gt" }
220+ if op == "OR" :
222221 pg_op_map = {"==" : "eq" , "!=" : "neq" , ">" : "gt" , ">=" : "gte" , "<" : "lt" , "<=" : "lte" }
223- op_map = neg_map if op == "NOT" else pg_op_map
224222 parts = []
225223 for cond in conditions :
226224 if "field" not in cond :
227- msg = f"Nested logical operators inside { op } are not supported."
225+ msg = "Nested logical operators inside OR are not supported."
226+ raise FilterError (msg )
227+ cond_field = cond .get ("field" , "" )
228+ cond_op = cond .get ("operator" , "" )
229+ cond_value = cond .get ("value" )
230+ if cond_op not in pg_op_map :
231+ msg = f"Operator '{ cond_op } ' inside OR filter is not supported."
232+ raise FilterError (msg )
233+ # Use text accessor (->>): PostgREST OR strings don't support JSONB (->) expressions.
234+ col = f"meta->>{ cond_field [len ('meta.' ):]} " if cond_field .startswith ("meta." ) else cond_field
235+ norm = SupabaseGroongaDocumentStore ._normalize_value (cond_value )
236+ parts .append (f"{ col } .{ pg_op_map [cond_op ]} .{ norm } " )
237+ return query .or_ ("," .join (parts ))
238+
239+ if op == "NOT" :
240+ # NOT(A AND B) = NOT_A OR NOT_B, with null-inclusive semantics.
241+ # Use text accessor: PostgREST OR strings don't support JSONB (->) expressions.
242+ neg_map = {"==" : "neq" , "!=" : "eq" , ">" : "lte" , ">=" : "lt" , "<" : "gte" , "<=" : "gt" }
243+ parts = []
244+ for cond in conditions :
245+ if "field" not in cond :
246+ msg = "Nested logical operators inside NOT are not supported."
228247 raise FilterError (msg )
229248 cond_field = cond .get ("field" , "" )
230249 cond_op = cond .get ("operator" , "" )
231250 cond_value = cond .get ("value" )
232- if cond_op not in op_map :
233- msg = f"Operator '{ cond_op } ' inside { op } filter is not supported."
251+ if cond_op not in neg_map :
252+ msg = f"Operator '{ cond_op } ' inside NOT filter is not supported."
234253 raise FilterError (msg )
235- col = SupabaseGroongaDocumentStore . _meta_col ( cond_field , cond_value )
254+ col = f"meta->> { cond_field [ len ( 'meta.' ):] } " if cond_field . startswith ( "meta." ) else cond_field
236255 norm = SupabaseGroongaDocumentStore ._normalize_value (cond_value )
237- parts .append (f"{ col } .{ op_map [cond_op ]} .{ norm } " )
256+ parts .append (f"{ col } .{ neg_map [cond_op ]} .{ norm } " )
257+ if cond_op == "==" and cond_field .startswith ("meta." ):
258+ # NOT(field==value) also covers docs where the field is absent (SQL NULL semantics)
259+ parts .append (f"{ col } .is.null" )
238260 return query .or_ ("," .join (parts ))
239261
240262 msg = f"Filter operator '{ op } ' is not supported. Supported logical operators: AND, OR, NOT."
@@ -262,7 +284,13 @@ def _apply_condition(query: Any, condition: dict[str, Any]) -> Any:
262284 return query .is_ (col , "null" ) if norm is None else query .eq (col , norm )
263285
264286 if op == "!=" :
265- return query .not_ .is_ (col , "null" ) if norm is None else query .neq (col , norm )
287+ if norm is None :
288+ return query .not_ .is_ (col , "null" )
289+ if field .startswith ("meta." ):
290+ # SQL: NULL != value returns NULL (not TRUE), so include docs where the field is absent.
291+ key = field [len ("meta." ):]
292+ return query .or_ (f"{ col } .neq.{ norm } ,meta->>{ key } .is.null" )
293+ return query .neq (col , norm )
266294
267295 if op in (">" , ">=" , "<" , "<=" ):
268296 if isinstance (value , list ):
@@ -294,6 +322,12 @@ def _apply_condition(query: Any, condition: dict[str, Any]) -> Any:
294322 if not isinstance (value , list ):
295323 msg = "Filter operator 'not in' requires a list value."
296324 raise FilterError (msg )
325+ if field .startswith ("meta." ):
326+ # SQL: NULL NOT IN (...) returns NULL, so include docs where the field is absent.
327+ key = field [len ("meta." ):]
328+ non_none = [v for v in value if v is not None ]
329+ vals = "," .join (str (v ) for v in non_none )
330+ return query .or_ (f"{ col } .not.in.({ vals } ),meta->>{ key } .is.null" )
297331 return query .not_ .in_ (col , value )
298332
299333 return query
0 commit comments