Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions test/collection/test_config_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,58 @@ def test_updating_vector_when_none_left() -> None:

with pytest.raises(WeaviateInvalidInputError, match="does not exist"):
update.merge_with_existing({"class": "Test", "properties": []})


def _single_vector_schema(vector_index_type: str) -> dict:
"""A legacy collection with a single, unnamed vector."""
return {
"class": "SingleVector",
"vectorizer": "none",
"vectorIndexType": vector_index_type,
"vectorIndexConfig": {"distance": "cosine", "vectorCacheMaxObjects": 1000},
}


@pytest.mark.parametrize("argument", ["vector_index_config", "vectorizer_config"])
def test_changing_single_vector_index_type_raises(argument: str) -> None:
"""Changing the index type used to be silently dropped for a single vector (#1277)."""
update = _CollectionConfigUpdate(**{argument: Reconfigure.VectorIndex.dynamic(threshold=1000)})

with pytest.raises(WeaviateInvalidInputError, match="from 'flat' to 'dynamic'"):
update.merge_with_existing(_single_vector_schema("flat"))


@pytest.mark.parametrize("use_deprecated_syntax", [False, True])
def test_changing_named_vector_index_type_raises(use_deprecated_syntax: bool) -> None:
"""Changing the index type of a named vector is rejected before reaching the server (#1277)."""
flat = Reconfigure.VectorIndex.flat(vector_cache_max_objects=1000)
update = (
_CollectionConfigUpdate(
vectorizer_config=[
Reconfigure.NamedVectors.update(name="boi", vector_index_config=flat)
]
)
if use_deprecated_syntax
else _CollectionConfigUpdate(
vector_config=Reconfigure.Vectors.update(name="boi", vector_index_config=flat)
)
)

with pytest.raises(WeaviateInvalidInputError, match="vector 'boi' from 'hnsw' to 'flat'"):
update.merge_with_existing(multi_vector_schema())


def test_updating_with_same_vector_index_type_still_works() -> None:
single = _CollectionConfigUpdate(
vectorizer_config=Reconfigure.VectorIndex.flat(vector_cache_max_objects=5)
).merge_with_existing(_single_vector_schema("flat"))
assert single["vectorIndexType"] == "flat"
assert single["vectorIndexConfig"]["vectorCacheMaxObjects"] == 5

named = _CollectionConfigUpdate(
vector_config=Reconfigure.Vectors.update(
name="boi", vector_index_config=Reconfigure.VectorIndex.hnsw(ef=128)
)
).merge_with_existing(multi_vector_schema())
assert named["vectorConfig"]["boi"]["vectorIndexType"] == "hnsw"
assert named["vectorConfig"]["boi"]["vectorIndexConfig"]["ef"] == 128
28 changes: 28 additions & 0 deletions weaviate/collections/classes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1674,6 +1674,22 @@ def __existing_vector_index_config(schema: Dict[str, Any], name: str) -> Dict[st
)
return cast(Dict[str, Any], existing["vectorIndexConfig"])

@staticmethod
def __check_vector_index_type(
update: VectorIndexConfigUpdate, existing_type: Optional[str], name: Optional[str] = None
) -> None:
# The vector index type is immutable server-side. Without this check, the change is either
# silently dropped (single unnamed vector) or the server fails with an internal error
# (named vectors).
new_type = update.vector_index_type().value
if existing_type and existing_type != new_type:
target = "the collection" if name is None else f"vector '{name}'"
raise WeaviateInvalidInputError(
f"Cannot update the vector index type of {target} from '{existing_type}' to "
f"'{new_type}', the vector index type is immutable. To use a different vector "
"index type, you must recreate the collection"
)

def __check_quantizers(
self,
quantizer: Optional[_QuantizerConfigUpdate],
Expand Down Expand Up @@ -1750,6 +1766,7 @@ def merge_with_existing(self, schema: Dict[str, Any]) -> Dict[str, Any]:
schema.get("objectTTLConfig", {})
)
if self.vectorIndexConfig is not None:
self.__check_vector_index_type(self.vectorIndexConfig, schema.get("vectorIndexType"))
self.__check_quantizers(self.vectorIndexConfig.quantizer, schema["vectorIndexConfig"])
schema["vectorIndexConfig"] = self.vectorIndexConfig.merge_with_existing(
schema["vectorIndexConfig"]
Expand Down Expand Up @@ -1778,6 +1795,7 @@ def merge_with_existing(self, schema: Dict[str, Any]) -> Dict[str, Any]:
)
if self.vectorizerConfig is not None:
if isinstance(self.vectorizerConfig, VectorIndexConfigUpdate):
self.__check_vector_index_type(self.vectorizerConfig, schema.get("vectorIndexType"))
self.__check_quantizers(
self.vectorizerConfig.quantizer, schema["vectorIndexConfig"]
)
Expand All @@ -1787,6 +1805,11 @@ def merge_with_existing(self, schema: Dict[str, Any]) -> Dict[str, Any]:
else:
for vc in self.vectorizerConfig:
existing = self.__existing_vector_index_config(schema, vc.name)
self.__check_vector_index_type(
vc.vectorIndexConfig,
schema["vectorConfig"][vc.name].get("vectorIndexType"),
vc.name,
)
self.__check_quantizers(vc.vectorIndexConfig.quantizer, existing)
schema["vectorConfig"][vc.name]["vectorIndexConfig"] = (
vc.vectorIndexConfig.merge_with_existing(existing)
Expand All @@ -1802,6 +1825,11 @@ def merge_with_existing(self, schema: Dict[str, Any]) -> Dict[str, Any]:
)
for vc in vcs:
existing = self.__existing_vector_index_config(schema, vc.name)
self.__check_vector_index_type(
vc.vectorIndexConfig,
schema["vectorConfig"][vc.name].get("vectorIndexType"),
vc.name,
)
self.__check_quantizers(vc.vectorIndexConfig.quantizer, existing)
schema["vectorConfig"][vc.name]["vectorIndexConfig"] = (
vc.vectorIndexConfig.merge_with_existing(existing)
Expand Down
Loading