Skip to content
Merged
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
22 changes: 2 additions & 20 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}


Expand Down
38 changes: 22 additions & 16 deletions tests/test_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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)."""
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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],
Expand Down
155 changes: 38 additions & 117 deletions tests/test_vector_params_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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."""
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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."""
Expand All @@ -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."""
Expand All @@ -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)."""
Expand All @@ -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.
Expand All @@ -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(
Expand All @@ -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."""
Expand Down
Loading