@@ -240,12 +240,18 @@ def execute(self, node: ASTNode) -> ExecutionResult:
240240
241241 # ── Statement executors ───────────────────────────────────────────────
242242
243+ @staticmethod
244+ def _is_grpc_not_found_error (error : BaseException ) -> bool :
245+ """Return True if *error* is a gRPC NOT_FOUND status."""
246+ from grpc import RpcError , StatusCode
247+ return isinstance (error , RpcError ) and error .code () == StatusCode .NOT_FOUND
248+
243249 def _fetch_collection_info (self , name : str ):
244250 """Fetch full CollectionInfo for *name* in a single API call.
245251
246252 Returns the CollectionInfo object when the collection exists, or
247- ``None`` when the collection is not found (HTTP 404). Any other
248- Qdrant error is re-raised as :class:`QQLRuntimeError`.
253+ ``None`` when the collection is not found (HTTP 404 or gRPC NOT_FOUND).
254+ Any other Qdrant error is re-raised as :class:`QQLRuntimeError`.
249255 """
250256 try :
251257 return self ._client .get_collection (name )
@@ -255,6 +261,18 @@ def _fetch_collection_info(self, name: str):
255261 raise QQLRuntimeError (
256262 f"Qdrant error fetching collection '{ name } ': { e } "
257263 ) from e
264+ except ValueError as e :
265+ if f"Collection { name } not found" in str (e ):
266+ return None
267+ raise QQLRuntimeError (
268+ f"Qdrant error fetching collection '{ name } ': { e } "
269+ ) from e
270+ except Exception as e :
271+ if self ._is_grpc_not_found_error (e ):
272+ return None
273+ raise QQLRuntimeError (
274+ f"Qdrant error fetching collection '{ name } ': { e } "
275+ ) from e
258276
259277 def _topology_from_collection_info (self , info : Any ) -> CollectionTopology :
260278 """Parse a CollectionInfo object into a :class:`CollectionTopology`.
@@ -1333,8 +1351,8 @@ def _build_search_params(self, with_clause: SearchWith | None) -> SearchParams |
13331351 hnsw_ef = with_clause .hnsw_ef ,
13341352 exact = with_clause .exact ,
13351353 quantization = quantization ,
1336- indexed_only = True if with_clause .indexed_only else None ,
1337- acorn = AcornSearchParams (enable = True ) if with_clause .acorn else None ,
1354+ indexed_only = with_clause . indexed_only if with_clause .indexed_only is not None else None ,
1355+ acorn = AcornSearchParams (enable = with_clause . acorn ) if with_clause .acorn is not None else None ,
13381356 )
13391357
13401358 def _build_hnsw_config (self , config : CollectionConfig | None ) -> HnswConfigDiff | None :
@@ -1835,6 +1853,15 @@ def _build_qdrant_filter(self, expr: FilterExpr) -> Any:
18351853
18361854 # ── Comparison ────────────────────────────────────────────────────
18371855 if isinstance (expr , CompareExpr ):
1856+ if expr .value is None :
1857+ null_condition = IsNullCondition (is_null = PayloadField (key = expr .field ))
1858+ if expr .op == "=" :
1859+ return null_condition
1860+ if expr .op == "!=" :
1861+ return Filter (must_not = [null_condition ])
1862+ raise QQLRuntimeError (
1863+ f"Cannot use operator '{ expr .op } ' with null for field '{ expr .field } '"
1864+ )
18381865 if expr .op == "=" :
18391866 return FieldCondition (
18401867 key = expr .field , match = MatchValue (value = expr .value )
@@ -1858,14 +1885,34 @@ def _build_qdrant_filter(self, expr: FilterExpr) -> Any:
18581885
18591886 # ── IN / NOT IN ───────────────────────────────────────────────────
18601887 if isinstance (expr , InExpr ):
1861- return FieldCondition (
1862- key = expr .field , match = MatchAny (any = list (expr .values ))
1888+ non_nulls = [v for v in expr .values if v is not None ]
1889+ if len (non_nulls ) == len (expr .values ):
1890+ return FieldCondition (
1891+ key = expr .field , match = MatchAny (any = non_nulls )
1892+ )
1893+ null_condition = IsNullCondition (is_null = PayloadField (key = expr .field ))
1894+ if not non_nulls :
1895+ return null_condition
1896+ return Filter (
1897+ should = [
1898+ null_condition ,
1899+ FieldCondition (key = expr .field , match = MatchAny (any = non_nulls )),
1900+ ]
18631901 )
18641902
18651903 if isinstance (expr , NotInExpr ):
1904+ non_nulls = [v for v in expr .values if v is not None ]
1905+ null_condition = IsNullCondition (is_null = PayloadField (key = expr .field ))
1906+ if len (non_nulls ) != len (expr .values ):
1907+ must_not = [null_condition ]
1908+ if non_nulls :
1909+ must_not .append (
1910+ FieldCondition (key = expr .field , match = MatchAny (any = non_nulls ))
1911+ )
1912+ return Filter (must_not = must_not )
18661913 return FieldCondition (
18671914 key = expr .field ,
1868- match = MatchExcept (** {"except" : list ( expr . values ) }),
1915+ match = MatchExcept (** {"except" : non_nulls }),
18691916 )
18701917
18711918 # ── IS NULL / IS NOT NULL ─────────────────────────────────────────
0 commit comments