@@ -131,6 +131,18 @@ class CollectionTopology:
131131 has_unnamed_dense : bool = False
132132 dense_names : tuple [str , ...] = ()
133133 sparse_names : tuple [str , ...] = ()
134+ # Sizes fetched once in _resolve_topology() so _ensure_collection() never
135+ # needs a second get_collection() call.
136+ dense_sizes : tuple [tuple [str , int ], ...] = ()
137+
138+ def dense_size_map (self ) -> dict [str , int ]:
139+ """Return {vector_name: size} for every dense vector whose size was fetched.
140+
141+ Unnamed single-vector collections appear under the ``""`` key, matching
142+ ``dense_config_key()``. Returns an empty dict when ``exists`` is False or
143+ sizes were not available (e.g. when a mock omits the size attribute).
144+ """
145+ return dict (self .dense_sizes )
134146
135147 @property
136148 def has_dense (self ) -> bool :
@@ -239,14 +251,23 @@ def _resolve_topology(self, name: str) -> CollectionTopology:
239251
240252 if isinstance (vectors , dict ):
241253 dense_names = tuple (vectors .keys ())
254+ dense_sizes : tuple [tuple [str , int ], ...] = tuple (
255+ (k , v .size )
256+ for k , v in vectors .items ()
257+ if getattr (v , "size" , None ) is not None
258+ )
242259 has_unnamed_dense = False
243260 is_named_dense = True
244261 elif vectors is None :
245262 dense_names = ()
263+ dense_sizes = ()
246264 has_unnamed_dense = False
247265 is_named_dense = False
248266 else :
267+ # Single unnamed dense vector
249268 dense_names = ()
269+ unnamed_size = getattr (vectors , "size" , None )
270+ dense_sizes = (("" , unnamed_size ),) if unnamed_size is not None else ()
250271 has_unnamed_dense = True
251272 is_named_dense = False
252273
@@ -259,6 +280,7 @@ def _resolve_topology(self, name: str) -> CollectionTopology:
259280 has_unnamed_dense = has_unnamed_dense ,
260281 dense_names = dense_names ,
261282 sparse_names = sparse_names ,
283+ dense_sizes = dense_sizes ,
262284 )
263285
264286 def _default_dense_vector_name (self ) -> str :
@@ -565,9 +587,9 @@ def _execute_create(self, node: CreateCollectionStmt) -> ExecutionResult:
565587 )
566588
567589 def _execute_alter_collection (self , node : AlterCollectionStmt ) -> ExecutionResult :
568- if not self ._client .collection_exists (node .collection ):
569- raise QQLRuntimeError (f"Collection '{ node .collection } ' does not exist" )
570590 topology = self ._resolve_topology (node .collection )
591+ if not topology .exists :
592+ raise QQLRuntimeError (f"Collection '{ node .collection } ' does not exist" )
571593
572594 update_kwargs : dict [str , Any ] = {"collection_name" : node .collection }
573595 vectors_config = self ._build_vectors_config_diff (topology , node .config )
@@ -828,9 +850,9 @@ def _execute_select(self, node: SelectStmt) -> ExecutionResult:
828850 )
829851
830852 def _execute_search (self , node : SearchStmt ) -> ExecutionResult :
831- if not self ._client .collection_exists (node .collection ):
832- raise QQLRuntimeError (f"Collection '{ node .collection } ' does not exist" )
833853 topology = self ._resolve_topology (node .collection )
854+ if not topology .exists :
855+ raise QQLRuntimeError (f"Collection '{ node .collection } ' does not exist" )
834856
835857 # Build WHERE filter (shared by both hybrid and dense-only paths)
836858 qdrant_filter : Filter | None = None
@@ -1711,9 +1733,9 @@ def _execute_search_groups(
17111733
17121734 def _execute_update_vector (self , node : UpdateVectorStmt ) -> ExecutionResult :
17131735 """Execute UPDATE ... SET VECTOR using update_vectors()."""
1714- if not self ._client .collection_exists (node .collection ):
1715- raise QQLRuntimeError (f"Collection '{ node .collection } ' does not exist" )
17161736 topology = self ._resolve_topology (node .collection )
1737+ if not topology .exists :
1738+ raise QQLRuntimeError (f"Collection '{ node .collection } ' does not exist" )
17171739 vector_name = topology .dense_payload_name (node .vector_name )
17181740 vector_struct : Any = (
17191741 {vector_name : list (node .vector )} if vector_name else list (node .vector )
@@ -1915,33 +1937,33 @@ def _ensure_collection(
19151937 topology : CollectionTopology ,
19161938 explicit_vector : str | None ,
19171939 ) -> None :
1918- """Create the collection if it doesn't exist. Raises on dimension mismatch .
1940+ """Create the collection if needed, or validate dimension compatibility .
19191941
19201942 QQL-created dense collections use the configured dense vector name.
19211943 Externally created unnamed collections still accept plain dense vectors.
1944+ All validation is done against pre-fetched ``topology`` data; no extra
1945+ Qdrant API calls are made.
19221946 """
19231947 if topology .exists :
1924- info = self ._client .get_collection (name )
1925- vectors = info .config .params .vectors # type: ignore[union-attr]
1926- if isinstance (vectors , dict ):
1948+ sizes = topology .dense_size_map ()
1949+ if topology .is_named_dense :
1950+ # dense_using() raises QQLRuntimeError on bad/ambiguous names,
1951+ # and always returns a non-None string in the named-dense branch.
19271952 vector_name = topology .dense_using (explicit_vector )
1928- if vector_name is None :
1929- raise QQLRuntimeError ("Collection has no dense vector" )
1930- vector_config = vectors [vector_name ]
1931- expected_size = getattr (vector_config , "size" , None )
1953+ expected_size = sizes .get (vector_name ) # type: ignore[arg-type]
19321954 if expected_size is not None and expected_size != vector_size :
19331955 raise QQLRuntimeError (
19341956 f"Vector dimension mismatch: collection '{ name } ' vector "
19351957 f"'{ vector_name } ' expects { expected_size } dims, but "
19361958 f"model produces { vector_size } dims. Specify a compatible "
19371959 "model with USING MODEL '<model>'."
19381960 )
1939- elif vectors is not None :
1940- # Unnamed single-vector collection: validate dimensions
1941- if vectors . size != vector_size :
1961+ elif topology . has_unnamed_dense :
1962+ expected_size = sizes . get ( "" )
1963+ if expected_size is not None and expected_size != vector_size :
19421964 raise QQLRuntimeError (
19431965 f"Vector dimension mismatch: collection '{ name } ' expects "
1944- f"{ vectors . size } dims, but model produces { vector_size } dims. "
1966+ f"{ expected_size } dims, but model produces { vector_size } dims. "
19451967 f"Specify a compatible model with USING MODEL '<model>'."
19461968 )
19471969 else :
0 commit comments