From bcf223f0fe6bf4157e38dbde47c3c49eabb21bb3 Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Sun, 1 Nov 2026 20:38:37 +0900 Subject: [PATCH] tests: read the vector index settings through get_metadata() so the NA-JVM tests run on both builds The pq, id_property, quantization, store_vectors_in_graph, add_hierarchy and cache tests, the two TypeIndex checks of the approximate searches and the two max_connections default tests read a Java index through _java_index or the JPype classes. get_metadata() returns the same values on the native build, and an index SQL creates without maxConnections shows the engine default on both. Their NATIVE_NA_JVM_TESTS entries go. test_encoding_param stays (encoding is not in get_metadata()); the WAL tests stay (no public read of the WAL setting on either build). Against the 26.11.1.dev0 dev wheels (old pair and the new dd45b53947 pair) all 13 rewritten tests pass on native and JVM. Closes #167 Co-Authored-By: Claude Opus 5.5 --- tests/conftest.py | 22 +--- tests/test_vector.py | 38 +++--- tests/test_vector_params_verification.py | 155 ++++++----------------- 3 files changed, 62 insertions(+), 153 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7bed79c6..c5961c27 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -126,8 +126,6 @@ "test_server_wire_protocols.py::test_a_protocol_host_overrides_the_server_host_for_that_protocol_only": "reads the server's GlobalConfiguration through the JVM", "test_sql_constraints_and_maintenance.py::test_backup_check_and_rebuild_index_statements": "sets GlobalConfiguration.SERVER_BACKUP_DIRECTORY through the JVM", "test_vector_params_verification.py::TestVectorParams::test_jvm_heap_check": "reads java.lang.Runtime through JPype", - "test_vector_params_verification.py::TestVectorParams::test_wrapper_default_matches_engine_default": "reads the engine default through java.lang.String and GlobalConfiguration", - "test_vector_params_verification.py::TestVectorParams::test_omitted_max_connections_reaches_the_index": "reads the engine default through java.lang.String and GlobalConfiguration", # Java objects built or read by the test "test_conversion_fixes.py::test_java_instants_convert_without_losing_microseconds": JAVA_CLASS, "test_type_conversion.py::test_offset_datetime_conversion": JAVA_CLASS, @@ -197,24 +195,8 @@ "test_resultset.py::TestSmallResultsCostOneBridgeCall::test_the_bridge_closes_what_it_drains_and_only_that": BRIDGE_COUNT, "test_vector.py::TestLSMVectorIndex::test_lsm_vector_search_looks_up_all_hits_in_one_bridge_call": BRIDGE_COUNT, "test_vector.py::TestLSMVectorIndex::test_lsm_vector_search_uses_database_rid_conversion": "intercepts Database.to_java_rid, the JPype path's RID conversion", - # the Java index behind the wrapper - "test_vector.py::TestLSMVectorIndex::test_create_vector_index_with_pq_params": JAVA_INDEX, - "test_vector.py::TestLSMVectorIndex::test_create_vector_index_with_custom_id_property": JAVA_INDEX, - "test_vector.py::TestLSMVectorIndex::test_lsm_vector_search_approximate_product": JAVA_INDEX, - "test_vector.py::TestLSMVectorIndex::test_lsm_vector_search_approximate_typeindex": JAVA_INDEX, - **{ - f"test_vector_params_verification.py::TestVectorParams::{name}": JAVA_INDEX - for name in ( - "test_quantization_param", - "test_store_vectors_in_graph_param", - "test_encoding_param", - "test_add_hierarchy_param", - "test_per_index_cache_params", - "test_quantization_none", - "test_quantization_binary", - "test_quantization_product", - ) - }, + # the Java index behind the wrapper: encoding is a setting get_metadata() does not return and the native library does not expose + "test_vector_params_verification.py::TestVectorParams::test_encoding_param": JAVA_INDEX, } diff --git a/tests/test_vector.py b/tests/test_vector.py index c3cf8682..366f8f2e 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -220,15 +220,12 @@ def test_create_vector_index_with_pq_params(self, test_db): pq_training_limit=5000, ) - java_idx = index._java_index - if "TypeIndex" in java_idx.getClass().getName(): - java_idx = java_idx.getSubIndexes().get(0) - - meta = java_idx.getMetadata() - assert meta.pqSubspaces == 4 - assert meta.pqClusters == 128 - assert meta.pqCenterGlobally is False - assert meta.pqTrainingLimit == 5000 + meta = index.get_metadata() + assert meta["quantization"] == "PRODUCT" + assert meta["pq_subspaces"] == 4 + assert meta["pq_clusters"] == 128 + assert meta["pq_center_globally"] is False + assert meta["pq_training_limit"] == 5000 def test_create_vector_index_with_custom_id_property(self, test_db): """Custom id_property should propagate to vector metadata.""" @@ -244,11 +241,10 @@ def test_create_vector_index_with_custom_id_property(self, test_db): id_property="slug", ) - java_idx = index._java_index - if "TypeIndex" in java_idx.getClass().getName(): - java_idx = java_idx.getSubIndexes().get(0) - - assert str(java_idx.getMetadata().idPropertyName) == "slug" + assert index.get_metadata()["id_property"] == "slug" + # an index read back from the schema carries it too + reread = test_db.schema.get_vector_index("Doc", "embedding") + assert reread.get_metadata()["id_property"] == "slug" def test_lsm_vector_search(self, test_db): """Test searching in vector index (JVector implementation).""" @@ -398,7 +394,12 @@ def test_lsm_vector_search_approximate_product(self, test_db): pq_clusters=3, ) - assert "TypeIndex" in index._java_index.getClass().getName() + # the wrapper holds the index of the type (the JVM build's TypeIndex); + # the vectors live in its bucket index, which has a name of its own + meta = index.get_metadata() + assert meta["index_name"] == "Doc[embedding]" + assert meta["bucket_index_name"] != meta["index_name"] + assert index.get_quantization() == "PRODUCT" vectors = [ [1.0, 0.0, 0.0], @@ -437,7 +438,12 @@ def test_lsm_vector_search_approximate_typeindex(self, test_db): pq_clusters=2, ) - assert "TypeIndex" in index._java_index.getClass().getName() + # the wrapper holds the index of the type (the JVM build's TypeIndex); + # the vectors live in its bucket index, which has a name of its own + meta = index.get_metadata() + assert meta["index_name"] == "Doc[embedding]" + assert meta["bucket_index_name"] != meta["index_name"] + assert index.get_quantization() == "PRODUCT" vectors = [ [1.0, 0.0, 0.0], diff --git a/tests/test_vector_params_verification.py b/tests/test_vector_params_verification.py index 483dc0ce..66694937 100644 --- a/tests/test_vector_params_verification.py +++ b/tests/test_vector_params_verification.py @@ -36,20 +36,8 @@ def test_quantization_param(self, test_db): # Verify via wrapper convenience method assert index.get_quantization() == "INT8" - # Verify by inspecting Java object directly - # Accessing the underlying Java object's metadata - java_index = index._java_index - - # Depending on if it's a TypeIndex or LSMVectorIndex directly - idx_to_check = java_index - if "TypeIndex" in java_index.getClass().getName(): - idx_to_check = java_index.getSubIndexes().get(0) - - # In Java: index.getMetadata().quantizationType - # JPype allows attribute access for getters or fields - # Note: the exact field name depends on the Java class implementation of metadata - # Given src/arcadedb/vector.py uses .quantizationType: - assert str(idx_to_check.getMetadata().quantizationType) == "INT8" + # Verify through the engine's own metadata of the index + assert index.get_metadata()["quantization"] == "INT8" def test_store_vectors_in_graph_param(self, test_db): """Test sending store_vectors_in_graph parameter.""" @@ -61,43 +49,13 @@ def test_store_vectors_in_graph_param(self, test_db): "StoreDoc", "embedding", dimensions=3, store_vectors_in_graph=True ) - # Accessing the underlying Java object's metadata - java_index = index._java_index - idx_to_check = java_index - if "TypeIndex" in java_index.getClass().getName(): - idx_to_check = java_index.getSubIndexes().get(0) + assert index.get_metadata()["store_vectors_in_graph"] is True - metadata = idx_to_check.getMetadata() - - # We need to find where "storeVectorsInGraph" is stored. - # It might be a field, or it might be in map-like structure if it was passed via JSON. - # Let's inspect what we can. - - print(f"\nMetadata Class: {metadata.getClass().getName()}") - print(f"Metadata String: {metadata.toString()}") - - # Attempt to check property directly if it's exposed as a field matching the JSON key - # Or check via getter if available - - val = None - try: - # Try field access - val = metadata.storeVectorsInGraph - except Exception: - try: - # Try getter - val = metadata.isStoreVectorsInGraph() - except Exception: - val = None # neither accessor: the string fallback below decides - - if val is None: - # Try inspecting the string representation as a fallback for verification - assert ( - "storeVectorsInGraph" in metadata.toString() - or "storeVectorsInGraph=true" in metadata.toString() - ) - else: - assert val is True + # and the default stays off + test_db.command("sql", "CREATE VERTEX TYPE NoStoreDoc") + test_db.command("sql", "CREATE PROPERTY NoStoreDoc.embedding ARRAY_OF_FLOATS") + plain = test_db.create_vector_index("NoStoreDoc", "embedding", dimensions=3) + assert plain.get_metadata()["store_vectors_in_graph"] is False def test_encoding_param(self, test_db): """Test sending encoding parameter for native INT8 storage.""" @@ -141,20 +99,14 @@ def test_add_hierarchy_param(self, test_db): "HierDoc", "embedding", dimensions=3, add_hierarchy=True ) - java_index = index._java_index - idx_to_check = java_index - if "TypeIndex" in java_index.getClass().getName(): - idx_to_check = java_index.getSubIndexes().get(0) - - metadata = idx_to_check.getMetadata() + assert index.get_metadata()["add_hierarchy"] is True - try: - assert metadata.addHierarchy is True - except AttributeError: - assert ( - "addHierarchy" in metadata.toString() - or "addHierarchy=true" in metadata.toString() - ) + test_db.command("sql", "CREATE VERTEX TYPE FlatDoc") + test_db.command("sql", "CREATE PROPERTY FlatDoc.embedding ARRAY_OF_FLOATS") + flat = test_db.create_vector_index( + "FlatDoc", "embedding", dimensions=3, add_hierarchy=False + ) + assert flat.get_metadata()["add_hierarchy"] is False def test_params_persistence(self, tmp_path): """Verify parameters persist after reload.""" @@ -210,27 +162,9 @@ def test_per_index_cache_params(self, test_db): mutations_before_rebuild=789, ) - java_index = index._java_index - idx_to_check = java_index - if "TypeIndex" in java_index.getClass().getName(): - idx_to_check = java_index.getSubIndexes().get(0) - - metadata = idx_to_check.getMetadata() - - # Direct field access is available on LSMVectorIndexMetadata; fall back to string inspection if not. - try: - assert metadata.graphBuildCacheSize == 456 - assert metadata.mutationsBeforeRebuild == 789 - except AttributeError: - meta_str = metadata.toString() - assert ( - "graphBuildCacheSize=456" in meta_str - or "graphBuildCacheSize: 456" in meta_str - ) - assert ( - "mutationsBeforeRebuild=789" in meta_str - or "mutationsBeforeRebuild: 789" in meta_str - ) + metadata = index.get_metadata() + assert metadata["graph_build_cache_size"] == 456 + assert metadata["mutations_before_rebuild"] == 789 def test_quantization_none(self, test_db): """Test sending quantization parameter NONE.""" @@ -243,12 +177,7 @@ def test_quantization_none(self, test_db): assert index.get_quantization() == "NONE" - java_index = index._java_index - idx_to_check = java_index - if "TypeIndex" in java_index.getClass().getName(): - idx_to_check = java_index.getSubIndexes().get(0) - - assert str(idx_to_check.getMetadata().quantizationType) == "NONE" + assert index.get_metadata()["quantization"] == "NONE" def test_quantization_binary(self, test_db): """Test sending quantization parameter BINARY.""" @@ -263,12 +192,7 @@ def test_quantization_binary(self, test_db): assert index.get_quantization() == "BINARY" - java_index = index._java_index - idx_to_check = java_index - if "TypeIndex" in java_index.getClass().getName(): - idx_to_check = java_index.getSubIndexes().get(0) - - assert str(idx_to_check.getMetadata().quantizationType) == "BINARY" + assert index.get_metadata()["quantization"] == "BINARY" def test_quantization_product(self, test_db): """Test sending quantization parameter PRODUCT (PQ).""" @@ -283,28 +207,26 @@ def test_quantization_product(self, test_db): assert index.get_quantization() == "PRODUCT" - java_index = index._java_index - idx_to_check = java_index - if "TypeIndex" in java_index.getClass().getName(): - idx_to_check = java_index.getSubIndexes().get(0) - - assert str(idx_to_check.getMetadata().quantizationType) == "PRODUCT" + assert index.get_metadata()["quantization"] == "PRODUCT" @staticmethod - def _engine_default_max_connections() -> int: - """Read maxConnections off a freshly constructed engine metadata object. + def _engine_default_max_connections(db) -> int: + """Read maxConnections off an index the engine configured by itself. - The field initializer is the engine default, so the value tracks the - bundled jars rather than a number copied into the Python layer. + create_vector_index always sets maxConnections, so the engine default + shows only on an index SQL creates without it. The value tracks the + engine the wheel carries rather than a number copied into the Python + layer, and it reads the same on both builds. """ - import jpype - - java_string = jpype.JClass("java.lang.String") - property_names = jpype.JArray(java_string)(["embedding"]) - metadata = jpype.JPackage("com").arcadedb.schema.LSMVectorIndexMetadata( - "DefaultProbe", property_names, 0 + db.command("sql", "CREATE VERTEX TYPE DefaultProbe") + db.command("sql", "CREATE PROPERTY DefaultProbe.embedding ARRAY_OF_FLOATS") + db.command( + "sql", + "CREATE INDEX ON DefaultProbe (embedding) LSM_VECTOR METADATA " + '{"dimensions": 4, "similarity": "EUCLIDEAN"}', ) - return int(metadata.maxConnections) + probe = db.schema.get_vector_index("DefaultProbe", "embedding") + return int(probe.get_metadata()["max_connections"]) def test_wrapper_default_matches_engine_default(self, test_db): """The Python default must equal the engine default, not a copy of it. @@ -317,11 +239,11 @@ def test_wrapper_default_matches_engine_default(self, test_db): signature = inspect.signature(type(test_db).create_vector_index) wrapper_default = signature.parameters["max_connections"].default - assert wrapper_default == self._engine_default_max_connections() + assert wrapper_default == self._engine_default_max_connections(test_db) def test_omitted_max_connections_reaches_the_index(self, test_db): """Omitting the argument must land the engine default on the index.""" - engine_default = self._engine_default_max_connections() + engine_default = self._engine_default_max_connections(test_db) test_db.command("sql", "CREATE VERTEX TYPE DefaultDegreeDoc") test_db.command( @@ -332,8 +254,7 @@ def test_omitted_max_connections_reaches_the_index(self, test_db): "DefaultDegreeDoc", "embedding", dimensions=4 ) - metadata = self._get_primary_metadata(index) - assert int(metadata.maxConnections) == engine_default + assert index.get_metadata()["max_connections"] == engine_default def test_jvm_heap_check(self): """Verify JVM memory settings from Java level."""