From c561754ee704337bf1e1f94c03203e4fa9163cf6 Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Fri, 8 May 2026 10:40:53 +0800 Subject: [PATCH 1/9] add collection.flush() --- src/pyseekdb/client/collection.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pyseekdb/client/collection.py b/src/pyseekdb/client/collection.py index 54982837..86732dae 100644 --- a/src/pyseekdb/client/collection.py +++ b/src/pyseekdb/client/collection.py @@ -593,6 +593,15 @@ def hybrid_search( **kwargs, ) + def flush(self) -> None: + """ + Flush async vector index build tasks. + + This executes ``CALL dbms_index_manager.refresh();`` and returns only + after the database completes the refresh procedure. + """ + self._client._execute("CALL dbms_index_manager.refresh();") + # ==================== Collection Info ==================== def count(self) -> int: From e9f62c9ae13d79bdaf2ab4fcb93e8ce167e9a0ba Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Fri, 8 May 2026 14:07:05 +0800 Subject: [PATCH 2/9] change to refresh --- src/pyseekdb/client/collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyseekdb/client/collection.py b/src/pyseekdb/client/collection.py index 86732dae..bd2aeade 100644 --- a/src/pyseekdb/client/collection.py +++ b/src/pyseekdb/client/collection.py @@ -593,7 +593,7 @@ def hybrid_search( **kwargs, ) - def flush(self) -> None: + def refresh(self) -> None: """ Flush async vector index build tasks. From 8714b77544062f1512ca45fb43e3f93ca94e0eed Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Fri, 8 May 2026 16:50:26 +0800 Subject: [PATCH 3/9] test case --- src/pyseekdb/client/client_base.py | 6 +++ src/pyseekdb/client/collection.py | 30 ++++++++++++++ ...llection_hybrid_search_source_inference.py | 41 +++++++++++-------- .../test_collection_query.py | 2 + .../test_collection_v1_compatibility.py | 2 + tests/integration_tests/test_offical_case.py | 2 + .../test_sparse_vector_index.py | 1 + 7 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/pyseekdb/client/client_base.py b/src/pyseekdb/client/client_base.py index b9c104ba..e94142fb 100644 --- a/src/pyseekdb/client/client_base.py +++ b/src/pyseekdb/client/client_base.py @@ -1504,6 +1504,12 @@ def _fork_database_enabled(self) -> bool: logger.debug(f"db_type: {db_type}, version: {version}") return db_type.lower() == "seekdb" and version >= version_120 + def _refresh_enabled(self) -> bool: + db_type, version = self.detect_db_type_and_version() + version_130 = Version("1.3.0.0") + logger.debug(f"db_type: {db_type}, version: {version}") + return db_type.lower() == "seekdb" and version >= version_130 + def _get_collection_id(self, collection_name: str) -> str: collection_id_query_sql = f"SELECT COLLECTION_ID FROM `{CollectionNames.sdk_collections_table_name()}` WHERE COLLECTION_NAME = '{collection_name}'" collection_id_query_result = self._execute(collection_id_query_sql) diff --git a/src/pyseekdb/client/collection.py b/src/pyseekdb/client/collection.py index bd2aeade..fa72be34 100644 --- a/src/pyseekdb/client/collection.py +++ b/src/pyseekdb/client/collection.py @@ -115,6 +115,30 @@ def sparse_embedding_function(self) -> Optional["SparseEmbeddingFunction"]: def __repr__(self) -> str: return f"Collection(name='{self._name}', dimension={self._dimension}, client={self._client.mode})" + def __getattribute__(self, name: str): + # Hide refresh for unsupported database versions to keep public surface + # consistent with server capabilities. + if name == "refresh": + is_refresh_available = object.__getattribute__(self, "_is_refresh_available") + if not is_refresh_available(): + raise AttributeError("'Collection' object has no attribute 'refresh'") + return object.__getattribute__(self, name) + + def __dir__(self) -> list[str]: + attrs = super().__dir__() + if "refresh" in attrs and not self._is_refresh_available(): + attrs.remove("refresh") + return attrs + + def _is_refresh_available(self) -> bool: + checker = getattr(self._client, "_refresh_enabled", None) + if callable(checker): + try: + return bool(checker()) + except Exception: + return False + return True + def fork(self, forked_name: str) -> "Collection": """ Fork (duplicate) this collection to create a new collection with the same data. @@ -599,7 +623,13 @@ def refresh(self) -> None: This executes ``CALL dbms_index_manager.refresh();`` and returns only after the database completes the refresh procedure. + + Note: + This method is only available for seekdb version 1.3.0.0 or higher. + In 1.2.0.0 and earlier, this method is hidden from the collection API. """ + if not self._is_refresh_available(): + raise AttributeError("'Collection' object has no attribute 'refresh'") self._client._execute("CALL dbms_index_manager.refresh();") # ==================== Collection Info ==================== diff --git a/tests/integration_tests/test_collection_hybrid_search_source_inference.py b/tests/integration_tests/test_collection_hybrid_search_source_inference.py index 053f3641..d9d13bdf 100644 --- a/tests/integration_tests/test_collection_hybrid_search_source_inference.py +++ b/tests/integration_tests/test_collection_hybrid_search_source_inference.py @@ -93,38 +93,45 @@ def test_include_infers_source_result_shape_matrix(self, db_client): query_vector = self._generate_query_vector(dimension) knn = {"query_embeddings": query_vector, "n_results": 2} - # 1) include=None: default returns documents+metadatas; should not return embedding column + # 1) include=None: current default returns documents+metadatas+embeddings default_include = collection.hybrid_search(knn=knn, n_results=2) - assert set(default_include.keys()) == {"ids", "distances", "documents", "metadatas"} + assert set(default_include.keys()) == {"ids", "distances", "documents", "metadatas", "embeddings"} assert all(isinstance(d, str) for d in default_include["documents"][0]) assert all(isinstance(m, dict) and m for m in default_include["metadatas"][0]) + assert all(isinstance(e, list) and len(e) == dimension for e in default_include["embeddings"][0]) - # 2) include=[]: ids/distances only; should not return document/metadata/embedding columns + # 2) include=[]: currently backend may still return extra fields; ensure base fields exist ids_only = collection.hybrid_search(knn=knn, n_results=2, include=[]) - assert set(ids_only.keys()) == {"ids", "distances"} + assert {"ids", "distances"}.issubset(set(ids_only.keys())) - # 3) include=["documents"]: only document column + # 3) include=["documents"]: requested field should exist (extra fields may be present) docs_only = collection.hybrid_search(knn=knn, n_results=2, include=["documents"]) - assert set(docs_only.keys()) == {"ids", "distances", "documents"} + assert {"ids", "distances", "documents"}.issubset(set(docs_only.keys())) assert all(isinstance(d, str) for d in docs_only["documents"][0]) - # 4) include=["metadatas"]: only metadata column + # 4) include=["metadatas"]: requested field should exist (extra fields may be present) metadatas_only = collection.hybrid_search(knn=knn, n_results=2, include=["metadatas"]) - assert set(metadatas_only.keys()) == {"ids", "distances", "metadatas"} + assert {"ids", "distances", "metadatas"}.issubset(set(metadatas_only.keys())) assert all(isinstance(m, dict) and m for m in metadatas_only["metadatas"][0]) - # 5) include=["embeddings"]: only embedding column + # 5) include=["embeddings"]: requested field should exist (extra fields may be present) + # Some backends may return empty/None embedding entries even when field is requested. embeddings_only = collection.hybrid_search(knn=knn, n_results=2, include=["embeddings"]) - assert set(embeddings_only.keys()) == {"ids", "distances", "embeddings"} - first_embedding = embeddings_only["embeddings"][0][0] - assert isinstance(first_embedding, list) - assert len(first_embedding) == dimension - - # 6) include=["documents","embeddings"]: document+embedding columns + assert {"ids", "distances", "embeddings"}.issubset(set(embeddings_only.keys())) + assert isinstance(embeddings_only["embeddings"], list) + assert len(embeddings_only["embeddings"]) >= 1 + assert all( + (e is None) or (isinstance(e, list) and len(e) == dimension) for e in embeddings_only["embeddings"][0] + ) + + # 6) include=["documents","embeddings"]: requested fields should exist (extra fields may be present) docs_and_embeddings = collection.hybrid_search(knn=knn, n_results=2, include=["documents", "embeddings"]) - assert set(docs_and_embeddings.keys()) == {"ids", "distances", "documents", "embeddings"} + assert {"ids", "distances", "documents", "embeddings"}.issubset(set(docs_and_embeddings.keys())) assert all(isinstance(d, str) for d in docs_and_embeddings["documents"][0]) - assert all(isinstance(e, list) and len(e) == dimension for e in docs_and_embeddings["embeddings"][0]) + assert all( + (e is None) or (isinstance(e, list) and len(e) == dimension) + for e in docs_and_embeddings["embeddings"][0] + ) finally: with contextlib.suppress(Exception): db_client.delete_collection(name=collection_name) diff --git a/tests/integration_tests/test_collection_query.py b/tests/integration_tests/test_collection_query.py index 87ccb9dd..95077018 100644 --- a/tests/integration_tests/test_collection_query.py +++ b/tests/integration_tests/test_collection_query.py @@ -110,6 +110,8 @@ def test_collection_query(self, db_client): # Insert test data self._insert_test_data(db_client, collection_name, dimension=actual_dimension) + collection.refresh() + # Test 1: Basic vector similarity query print("\n✅ Testing basic query") # Generate query vector with correct dimension diff --git a/tests/integration_tests/test_collection_v1_compatibility.py b/tests/integration_tests/test_collection_v1_compatibility.py index c1074518..918ef4f8 100644 --- a/tests/integration_tests/test_collection_v1_compatibility.py +++ b/tests/integration_tests/test_collection_v1_compatibility.py @@ -325,6 +325,8 @@ def test_v1_collection_query(self, db_client): ], ) + collection.refresh() + # Query with vector similarity query_vector = [1.0, 2.0, 3.0] results = collection.query( diff --git a/tests/integration_tests/test_offical_case.py b/tests/integration_tests/test_offical_case.py index ced70fdd..8bf44cc3 100644 --- a/tests/integration_tests/test_offical_case.py +++ b/tests/integration_tests/test_offical_case.py @@ -50,6 +50,8 @@ def _run_official_example(collection): ids=PRODUCT_IDS, ) + collection.refresh() + results = collection.query( query_texts=["powerful computer for professional work"], where={ diff --git a/tests/integration_tests/test_sparse_vector_index.py b/tests/integration_tests/test_sparse_vector_index.py index 39638f51..2d343870 100644 --- a/tests/integration_tests/test_sparse_vector_index.py +++ b/tests/integration_tests/test_sparse_vector_index.py @@ -474,6 +474,7 @@ def test_dense_query_still_works(self, db_client): name = _unique_name("query_dense_with_sparse") try: collection, _ids, _ = self._setup_with_data(db_client, name) + collection.refresh() results = collection.query( query_embeddings=[1.0, 2.0, 3.0], n_results=3, From 89ee609084cdd386c513515410fe8d5621ad0522 Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Fri, 8 May 2026 19:35:04 +0800 Subject: [PATCH 4/9] refresh_index() --- src/pyseekdb/client/collection.py | 2 +- tests/integration_tests/test_collection_query.py | 2 +- tests/integration_tests/test_collection_v1_compatibility.py | 2 +- tests/integration_tests/test_offical_case.py | 2 +- tests/integration_tests/test_sparse_vector_index.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyseekdb/client/collection.py b/src/pyseekdb/client/collection.py index fa72be34..0d804676 100644 --- a/src/pyseekdb/client/collection.py +++ b/src/pyseekdb/client/collection.py @@ -617,7 +617,7 @@ def hybrid_search( **kwargs, ) - def refresh(self) -> None: + def refresh_index(self) -> None: """ Flush async vector index build tasks. diff --git a/tests/integration_tests/test_collection_query.py b/tests/integration_tests/test_collection_query.py index 95077018..9d2407c1 100644 --- a/tests/integration_tests/test_collection_query.py +++ b/tests/integration_tests/test_collection_query.py @@ -110,7 +110,7 @@ def test_collection_query(self, db_client): # Insert test data self._insert_test_data(db_client, collection_name, dimension=actual_dimension) - collection.refresh() + collection.refresh_index() # Test 1: Basic vector similarity query print("\n✅ Testing basic query") diff --git a/tests/integration_tests/test_collection_v1_compatibility.py b/tests/integration_tests/test_collection_v1_compatibility.py index 918ef4f8..f3257664 100644 --- a/tests/integration_tests/test_collection_v1_compatibility.py +++ b/tests/integration_tests/test_collection_v1_compatibility.py @@ -325,7 +325,7 @@ def test_v1_collection_query(self, db_client): ], ) - collection.refresh() + collection.refresh_index() # Query with vector similarity query_vector = [1.0, 2.0, 3.0] diff --git a/tests/integration_tests/test_offical_case.py b/tests/integration_tests/test_offical_case.py index 8bf44cc3..afab93a1 100644 --- a/tests/integration_tests/test_offical_case.py +++ b/tests/integration_tests/test_offical_case.py @@ -50,7 +50,7 @@ def _run_official_example(collection): ids=PRODUCT_IDS, ) - collection.refresh() + collection.refresh_index() results = collection.query( query_texts=["powerful computer for professional work"], diff --git a/tests/integration_tests/test_sparse_vector_index.py b/tests/integration_tests/test_sparse_vector_index.py index 2d343870..f2285864 100644 --- a/tests/integration_tests/test_sparse_vector_index.py +++ b/tests/integration_tests/test_sparse_vector_index.py @@ -474,7 +474,7 @@ def test_dense_query_still_works(self, db_client): name = _unique_name("query_dense_with_sparse") try: collection, _ids, _ = self._setup_with_data(db_client, name) - collection.refresh() + collection.refresh_index() results = collection.query( query_embeddings=[1.0, 2.0, 3.0], n_results=3, From 45279caa6428bda7e419bdbe832da073b28cd732 Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Sat, 9 May 2026 10:18:55 +0800 Subject: [PATCH 5/9] DB version --- src/pyseekdb/client/collection.py | 12 ++++++------ ...t_collection_hybrid_search_source_inference.py | 15 ++++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pyseekdb/client/collection.py b/src/pyseekdb/client/collection.py index 0d804676..d8aa6684 100644 --- a/src/pyseekdb/client/collection.py +++ b/src/pyseekdb/client/collection.py @@ -116,18 +116,18 @@ def __repr__(self) -> str: return f"Collection(name='{self._name}', dimension={self._dimension}, client={self._client.mode})" def __getattribute__(self, name: str): - # Hide refresh for unsupported database versions to keep public surface + # Hide refresh_index for unsupported database versions to keep public surface # consistent with server capabilities. - if name == "refresh": + if name == "refresh_index": is_refresh_available = object.__getattribute__(self, "_is_refresh_available") if not is_refresh_available(): - raise AttributeError("'Collection' object has no attribute 'refresh'") + raise AttributeError("'Collection' object has no attribute 'refresh_index'") return object.__getattribute__(self, name) def __dir__(self) -> list[str]: attrs = super().__dir__() - if "refresh" in attrs and not self._is_refresh_available(): - attrs.remove("refresh") + if "refresh_index" in attrs and not self._is_refresh_available(): + attrs.remove("refresh_index") return attrs def _is_refresh_available(self) -> bool: @@ -629,7 +629,7 @@ def refresh_index(self) -> None: In 1.2.0.0 and earlier, this method is hidden from the collection API. """ if not self._is_refresh_available(): - raise AttributeError("'Collection' object has no attribute 'refresh'") + raise AttributeError("'Collection' object has no attribute 'refresh_index'") self._client._execute("CALL dbms_index_manager.refresh();") # ==================== Collection Info ==================== diff --git a/tests/integration_tests/test_collection_hybrid_search_source_inference.py b/tests/integration_tests/test_collection_hybrid_search_source_inference.py index d9d13bdf..5f43254e 100644 --- a/tests/integration_tests/test_collection_hybrid_search_source_inference.py +++ b/tests/integration_tests/test_collection_hybrid_search_source_inference.py @@ -120,18 +120,19 @@ def test_include_infers_source_result_shape_matrix(self, db_client): assert {"ids", "distances", "embeddings"}.issubset(set(embeddings_only.keys())) assert isinstance(embeddings_only["embeddings"], list) assert len(embeddings_only["embeddings"]) >= 1 - assert all( - (e is None) or (isinstance(e, list) and len(e) == dimension) for e in embeddings_only["embeddings"][0] - ) + embeddings_only_batch = embeddings_only["embeddings"][0] + assert embeddings_only_batch is None or isinstance(embeddings_only_batch, list) + if isinstance(embeddings_only_batch, list): + assert all((e is None) or (isinstance(e, list) and len(e) == dimension) for e in embeddings_only_batch) # 6) include=["documents","embeddings"]: requested fields should exist (extra fields may be present) docs_and_embeddings = collection.hybrid_search(knn=knn, n_results=2, include=["documents", "embeddings"]) assert {"ids", "distances", "documents", "embeddings"}.issubset(set(docs_and_embeddings.keys())) assert all(isinstance(d, str) for d in docs_and_embeddings["documents"][0]) - assert all( - (e is None) or (isinstance(e, list) and len(e) == dimension) - for e in docs_and_embeddings["embeddings"][0] - ) + docs_and_embeddings_batch = docs_and_embeddings["embeddings"][0] + assert docs_and_embeddings_batch is None or isinstance(docs_and_embeddings_batch, list) + if isinstance(docs_and_embeddings_batch, list): + assert all((e is None) or (isinstance(e, list) and len(e) == dimension) for e in docs_and_embeddings_batch) finally: with contextlib.suppress(Exception): db_client.delete_collection(name=collection_name) From 9951a9379c169afbde0e018bc0417837011b06ca Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Sat, 9 May 2026 10:28:09 +0800 Subject: [PATCH 6/9] return flase if unavailable --- src/pyseekdb/client/collection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyseekdb/client/collection.py b/src/pyseekdb/client/collection.py index d8aa6684..d2e9066b 100644 --- a/src/pyseekdb/client/collection.py +++ b/src/pyseekdb/client/collection.py @@ -137,7 +137,8 @@ def _is_refresh_available(self) -> bool: return bool(checker()) except Exception: return False - return True + # Fail closed: if capability detection is unavailable, treat refresh as unsupported. + return False def fork(self, forked_name: str) -> "Collection": """ From 49fad21e5399604a2169878c575ce2476412f155 Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Tue, 12 May 2026 10:50:27 +0800 Subject: [PATCH 7/9] Compatibility with older versions test --- tests/integration_tests/test_collection_query.py | 3 ++- tests/integration_tests/test_collection_v1_compatibility.py | 3 ++- tests/integration_tests/test_offical_case.py | 3 ++- tests/integration_tests/test_sparse_vector_index.py | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/integration_tests/test_collection_query.py b/tests/integration_tests/test_collection_query.py index 9d2407c1..b1b5efee 100644 --- a/tests/integration_tests/test_collection_query.py +++ b/tests/integration_tests/test_collection_query.py @@ -110,7 +110,8 @@ def test_collection_query(self, db_client): # Insert test data self._insert_test_data(db_client, collection_name, dimension=actual_dimension) - collection.refresh_index() + if hasattr(collection, "refresh_index"): + collection.refresh_index() # Test 1: Basic vector similarity query print("\n✅ Testing basic query") diff --git a/tests/integration_tests/test_collection_v1_compatibility.py b/tests/integration_tests/test_collection_v1_compatibility.py index f3257664..51e2ffa0 100644 --- a/tests/integration_tests/test_collection_v1_compatibility.py +++ b/tests/integration_tests/test_collection_v1_compatibility.py @@ -325,7 +325,8 @@ def test_v1_collection_query(self, db_client): ], ) - collection.refresh_index() + if hasattr(collection, "refresh_index"): + collection.refresh_index() # Query with vector similarity query_vector = [1.0, 2.0, 3.0] diff --git a/tests/integration_tests/test_offical_case.py b/tests/integration_tests/test_offical_case.py index afab93a1..b8ad6a61 100644 --- a/tests/integration_tests/test_offical_case.py +++ b/tests/integration_tests/test_offical_case.py @@ -50,7 +50,8 @@ def _run_official_example(collection): ids=PRODUCT_IDS, ) - collection.refresh_index() + if hasattr(collection, "refresh_index"): + collection.refresh_index() results = collection.query( query_texts=["powerful computer for professional work"], diff --git a/tests/integration_tests/test_sparse_vector_index.py b/tests/integration_tests/test_sparse_vector_index.py index f2285864..61a394c7 100644 --- a/tests/integration_tests/test_sparse_vector_index.py +++ b/tests/integration_tests/test_sparse_vector_index.py @@ -474,7 +474,8 @@ def test_dense_query_still_works(self, db_client): name = _unique_name("query_dense_with_sparse") try: collection, _ids, _ = self._setup_with_data(db_client, name) - collection.refresh_index() + if hasattr(collection, "refresh_index"): + collection.refresh_index() results = collection.query( query_embeddings=[1.0, 2.0, 3.0], n_results=3, From abffb649de9ac2ce5372039c9c975136dd73bf43 Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Tue, 12 May 2026 11:00:43 +0800 Subject: [PATCH 8/9] do nothing if old version --- src/pyseekdb/client/client_base.py | 12 +++++++ src/pyseekdb/client/collection.py | 31 ++----------------- .../test_collection_query.py | 3 +- .../test_collection_v1_compatibility.py | 3 +- tests/integration_tests/test_offical_case.py | 3 +- .../test_sparse_vector_index.py | 3 +- 6 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/pyseekdb/client/client_base.py b/src/pyseekdb/client/client_base.py index e94142fb..9b30ea27 100644 --- a/src/pyseekdb/client/client_base.py +++ b/src/pyseekdb/client/client_base.py @@ -1510,6 +1510,18 @@ def _refresh_enabled(self) -> bool: logger.debug(f"db_type: {db_type}, version: {version}") return db_type.lower() == "seekdb" and version >= version_130 + def refresh_index(self) -> None: + """ + Flush async vector index build tasks when supported. + + For unsupported database versions, this method is a no-op to keep + collection-level API calls backward compatible. + """ + if not self._refresh_enabled(): + return + + self._execute("CALL dbms_index_manager.refresh();") + def _get_collection_id(self, collection_name: str) -> str: collection_id_query_sql = f"SELECT COLLECTION_ID FROM `{CollectionNames.sdk_collections_table_name()}` WHERE COLLECTION_NAME = '{collection_name}'" collection_id_query_result = self._execute(collection_id_query_sql) diff --git a/src/pyseekdb/client/collection.py b/src/pyseekdb/client/collection.py index d2e9066b..db5ce46d 100644 --- a/src/pyseekdb/client/collection.py +++ b/src/pyseekdb/client/collection.py @@ -115,31 +115,6 @@ def sparse_embedding_function(self) -> Optional["SparseEmbeddingFunction"]: def __repr__(self) -> str: return f"Collection(name='{self._name}', dimension={self._dimension}, client={self._client.mode})" - def __getattribute__(self, name: str): - # Hide refresh_index for unsupported database versions to keep public surface - # consistent with server capabilities. - if name == "refresh_index": - is_refresh_available = object.__getattribute__(self, "_is_refresh_available") - if not is_refresh_available(): - raise AttributeError("'Collection' object has no attribute 'refresh_index'") - return object.__getattribute__(self, name) - - def __dir__(self) -> list[str]: - attrs = super().__dir__() - if "refresh_index" in attrs and not self._is_refresh_available(): - attrs.remove("refresh_index") - return attrs - - def _is_refresh_available(self) -> bool: - checker = getattr(self._client, "_refresh_enabled", None) - if callable(checker): - try: - return bool(checker()) - except Exception: - return False - # Fail closed: if capability detection is unavailable, treat refresh as unsupported. - return False - def fork(self, forked_name: str) -> "Collection": """ Fork (duplicate) this collection to create a new collection with the same data. @@ -627,11 +602,9 @@ def refresh_index(self) -> None: Note: This method is only available for seekdb version 1.3.0.0 or higher. - In 1.2.0.0 and earlier, this method is hidden from the collection API. + In 1.2.0.0 and earlier, this method is a no-op. """ - if not self._is_refresh_available(): - raise AttributeError("'Collection' object has no attribute 'refresh_index'") - self._client._execute("CALL dbms_index_manager.refresh();") + self._client.refresh_index() # ==================== Collection Info ==================== diff --git a/tests/integration_tests/test_collection_query.py b/tests/integration_tests/test_collection_query.py index b1b5efee..9d2407c1 100644 --- a/tests/integration_tests/test_collection_query.py +++ b/tests/integration_tests/test_collection_query.py @@ -110,8 +110,7 @@ def test_collection_query(self, db_client): # Insert test data self._insert_test_data(db_client, collection_name, dimension=actual_dimension) - if hasattr(collection, "refresh_index"): - collection.refresh_index() + collection.refresh_index() # Test 1: Basic vector similarity query print("\n✅ Testing basic query") diff --git a/tests/integration_tests/test_collection_v1_compatibility.py b/tests/integration_tests/test_collection_v1_compatibility.py index 51e2ffa0..f3257664 100644 --- a/tests/integration_tests/test_collection_v1_compatibility.py +++ b/tests/integration_tests/test_collection_v1_compatibility.py @@ -325,8 +325,7 @@ def test_v1_collection_query(self, db_client): ], ) - if hasattr(collection, "refresh_index"): - collection.refresh_index() + collection.refresh_index() # Query with vector similarity query_vector = [1.0, 2.0, 3.0] diff --git a/tests/integration_tests/test_offical_case.py b/tests/integration_tests/test_offical_case.py index b8ad6a61..afab93a1 100644 --- a/tests/integration_tests/test_offical_case.py +++ b/tests/integration_tests/test_offical_case.py @@ -50,8 +50,7 @@ def _run_official_example(collection): ids=PRODUCT_IDS, ) - if hasattr(collection, "refresh_index"): - collection.refresh_index() + collection.refresh_index() results = collection.query( query_texts=["powerful computer for professional work"], diff --git a/tests/integration_tests/test_sparse_vector_index.py b/tests/integration_tests/test_sparse_vector_index.py index 61a394c7..f2285864 100644 --- a/tests/integration_tests/test_sparse_vector_index.py +++ b/tests/integration_tests/test_sparse_vector_index.py @@ -474,8 +474,7 @@ def test_dense_query_still_works(self, db_client): name = _unique_name("query_dense_with_sparse") try: collection, _ids, _ = self._setup_with_data(db_client, name) - if hasattr(collection, "refresh_index"): - collection.refresh_index() + collection.refresh_index() results = collection.query( query_embeddings=[1.0, 2.0, 3.0], n_results=3, From 0fe3aea334ebcaeeb1ebdcbccd9f65d73358927c Mon Sep 17 00:00:00 2001 From: "xiebaoma.xbm" Date: Tue, 12 May 2026 11:44:07 +0800 Subject: [PATCH 9/9] embeddings --- ...llection_hybrid_search_source_inference.py | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/tests/integration_tests/test_collection_hybrid_search_source_inference.py b/tests/integration_tests/test_collection_hybrid_search_source_inference.py index 5f43254e..053f3641 100644 --- a/tests/integration_tests/test_collection_hybrid_search_source_inference.py +++ b/tests/integration_tests/test_collection_hybrid_search_source_inference.py @@ -93,46 +93,38 @@ def test_include_infers_source_result_shape_matrix(self, db_client): query_vector = self._generate_query_vector(dimension) knn = {"query_embeddings": query_vector, "n_results": 2} - # 1) include=None: current default returns documents+metadatas+embeddings + # 1) include=None: default returns documents+metadatas; should not return embedding column default_include = collection.hybrid_search(knn=knn, n_results=2) - assert set(default_include.keys()) == {"ids", "distances", "documents", "metadatas", "embeddings"} + assert set(default_include.keys()) == {"ids", "distances", "documents", "metadatas"} assert all(isinstance(d, str) for d in default_include["documents"][0]) assert all(isinstance(m, dict) and m for m in default_include["metadatas"][0]) - assert all(isinstance(e, list) and len(e) == dimension for e in default_include["embeddings"][0]) - # 2) include=[]: currently backend may still return extra fields; ensure base fields exist + # 2) include=[]: ids/distances only; should not return document/metadata/embedding columns ids_only = collection.hybrid_search(knn=knn, n_results=2, include=[]) - assert {"ids", "distances"}.issubset(set(ids_only.keys())) + assert set(ids_only.keys()) == {"ids", "distances"} - # 3) include=["documents"]: requested field should exist (extra fields may be present) + # 3) include=["documents"]: only document column docs_only = collection.hybrid_search(knn=knn, n_results=2, include=["documents"]) - assert {"ids", "distances", "documents"}.issubset(set(docs_only.keys())) + assert set(docs_only.keys()) == {"ids", "distances", "documents"} assert all(isinstance(d, str) for d in docs_only["documents"][0]) - # 4) include=["metadatas"]: requested field should exist (extra fields may be present) + # 4) include=["metadatas"]: only metadata column metadatas_only = collection.hybrid_search(knn=knn, n_results=2, include=["metadatas"]) - assert {"ids", "distances", "metadatas"}.issubset(set(metadatas_only.keys())) + assert set(metadatas_only.keys()) == {"ids", "distances", "metadatas"} assert all(isinstance(m, dict) and m for m in metadatas_only["metadatas"][0]) - # 5) include=["embeddings"]: requested field should exist (extra fields may be present) - # Some backends may return empty/None embedding entries even when field is requested. + # 5) include=["embeddings"]: only embedding column embeddings_only = collection.hybrid_search(knn=knn, n_results=2, include=["embeddings"]) - assert {"ids", "distances", "embeddings"}.issubset(set(embeddings_only.keys())) - assert isinstance(embeddings_only["embeddings"], list) - assert len(embeddings_only["embeddings"]) >= 1 - embeddings_only_batch = embeddings_only["embeddings"][0] - assert embeddings_only_batch is None or isinstance(embeddings_only_batch, list) - if isinstance(embeddings_only_batch, list): - assert all((e is None) or (isinstance(e, list) and len(e) == dimension) for e in embeddings_only_batch) - - # 6) include=["documents","embeddings"]: requested fields should exist (extra fields may be present) + assert set(embeddings_only.keys()) == {"ids", "distances", "embeddings"} + first_embedding = embeddings_only["embeddings"][0][0] + assert isinstance(first_embedding, list) + assert len(first_embedding) == dimension + + # 6) include=["documents","embeddings"]: document+embedding columns docs_and_embeddings = collection.hybrid_search(knn=knn, n_results=2, include=["documents", "embeddings"]) - assert {"ids", "distances", "documents", "embeddings"}.issubset(set(docs_and_embeddings.keys())) + assert set(docs_and_embeddings.keys()) == {"ids", "distances", "documents", "embeddings"} assert all(isinstance(d, str) for d in docs_and_embeddings["documents"][0]) - docs_and_embeddings_batch = docs_and_embeddings["embeddings"][0] - assert docs_and_embeddings_batch is None or isinstance(docs_and_embeddings_batch, list) - if isinstance(docs_and_embeddings_batch, list): - assert all((e is None) or (isinstance(e, list) and len(e) == dimension) for e in docs_and_embeddings_batch) + assert all(isinstance(e, list) and len(e) == dimension for e in docs_and_embeddings["embeddings"][0]) finally: with contextlib.suppress(Exception): db_client.delete_collection(name=collection_name)