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
8 changes: 8 additions & 0 deletions src/arcadedb/_native/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,13 @@ def enabled() -> bool:
return os.environ.get("ARCADEDB_BACKEND", _DEFAULT_BACKEND).lower() == "native"


def needs_jvm(what: str):
"""A call that hands out a Java object of the JVM build. Native has no JVM, by design: the message names the build that has it."""
raise NotOnNativeYet(
f"{what} needs the JVM build (the arcadedb-jvm package); this is the native build (the arcadedb package)"
)


def not_yet(what: str):
"""A feature the native build is still missing (none today). A final release allows no test that ends here."""
raise NotOnNativeYet(f"{what} is not on the native backend yet")
8 changes: 4 additions & 4 deletions src/arcadedb/_native/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ..exceptions import ArcadeDBError
from ..transactions import TransactionContext
from ..type_conversion import json_bulk_dumps
from . import interrupts, lib, not_yet
from . import interrupts, lib, needs_jvm
from .bulk import insert_rows_columnar
from .codec import _prep as _prep_row
from .codec import encode_params, json_native, loads, row_default
Expand Down Expand Up @@ -855,7 +855,7 @@ def _async(self):
# -- not on native yet

def get_java_database(self):
not_yet("get_java_database")
needs_jvm("get_java_database")

def new_vertex(self, type_name: str):
"""A vertex that is created by ``save()`` (``CREATE VERTEX .. CONTENT``)."""
Expand Down Expand Up @@ -1226,7 +1226,7 @@ def __bool__(self):
return True

def __getattr__(self, name):
not_yet(f"the Java object a Schema method returns ({name})")
needs_jvm(f"the Java object a Schema method returns ({name})")


class NativeSchema:
Expand Down Expand Up @@ -1391,7 +1391,7 @@ def list_vector_indexes(self):
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(name)
not_yet(f"Schema.{name} (returns Java objects)")
needs_jvm(f"Schema.{name} (returns Java objects)")


class NativeDatabaseFactory:
Expand Down
4 changes: 2 additions & 2 deletions src/arcadedb/_native/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ..graph import Document as _BaseDocument
from ..graph import Edge as _BaseEdge
from ..graph import Vertex as _BaseVertex
from . import not_yet
from . import needs_jvm
from .codec import RID


Expand Down Expand Up @@ -84,7 +84,7 @@ def get_type_name(self) -> str:
return self._type

def get_java_document(self):
not_yet("get_java_document")
needs_jvm("get_java_document")

# -- writing

Expand Down
6 changes: 3 additions & 3 deletions src/arcadedb/_native/schema_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from ..exceptions import ArcadeDBError
from ..vector import VectorIndex as _BaseVectorIndex
from . import not_yet
from . import needs_jvm

_PROPERTY = re.compile(r"name: (.+?), type: ([A-Z_]+)")

Expand Down Expand Up @@ -46,7 +46,7 @@ def getBuckets(self):
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(name)
not_yet(f"the Java object a Schema method returns ({name})")
needs_jvm(f"the Java object a Schema method returns ({name})")

def __repr__(self):
return f"<Type {self.getName()}>"
Expand Down Expand Up @@ -80,7 +80,7 @@ def isAutomatic(self) -> bool:
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(name)
not_yet(f"the Java object a Schema method returns ({name})")
needs_jvm(f"the Java object a Schema method returns ({name})")

def __repr__(self):
return f"<Index {self.getName()}>"
Expand Down
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The server tests do not skip when the server stack is absent: the wheel always s

The same suite runs against the native `arcadedb` wheel (`native/wheel/parity_wheel.py`). A test is reported as a skip on the native build in two explicit ways, never by what its failure says:

- `NotOnNativeYet`: the native code raised it because the feature needs a Java object API the library does not expose. The report says `NOTYET`, and a final release allows none.
- `NotOnNativeYet`: the native code raised it because the feature needs a Java object API the library does not expose. The report says `NOTYET`, and a final release allows none. A call that is JVM-only by design (`get_java_database`, `get_java_document`, a Java object a `Schema` method returns) raises it with a message that names the `arcadedb-jvm` build; a shared test that reaches one is `NA-JVM`, or reads the value through a statement or the Python API instead.
- A row of `NATIVE_NA_JVM_TESTS` in `tests/conftest.py`, keyed `<file>::[<Class>::]<test>`: the test reaches into the JVM on purpose (`jpype.JClass`, a Java array, `db._java_db`, counted bridge crossings). The report says `NA-JVM`. Add a row only for such a test, and give the reason. When the behaviour is shared, write a native test beside it (`test_native_*.py`, collected on the native build only).

A test that fails on the native build for any other reason is a failure. Fix the native code, or mark a known engine bug with a strict `xfail`. Do not add a row to hide it.
Expand Down
20 changes: 19 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@
# crossings, or patches a hook of the JPype path. A failure text never puts a test on this list, and a failing test is never turned
# into a skip: a test whose behaviour native shares has to pass on native, and one that fails there is a bug to file (an
# xfail(strict=True) naming the BACKLOG-bindings.md row, so it turns red when fixed), not an entry here.
# A feature the native wheel lacks on purpose raises NotOnNativeYet from the native code, which pytest_runtest_makereport reports as NOTYET.
# A call that hands out a Java object (get_java_database, get_java_document, a Schema method's Java object) raises NotOnNativeYet from needs_jvm in the
# native code, which pytest_runtest_makereport reports as NOTYET. A shared test must not reach one: it goes on this list, or it reads the value
# through a statement or the Python API. Every NOTYET is a gap to close before a final release, which allows none.
JAVA_CLASS = "builds a java.* object through JPype"
JAVA_ARRAY = "builds a Java array through JPype (jpype.JArray)"
JAVA_DB = "reads the Java database behind the wrapper (db._java_db)"
Expand Down Expand Up @@ -138,6 +140,22 @@
"test_transaction_config.py::test_set_wal_flush_is_database_wide": JAVA_DB,
"test_run_in_transaction_retry.py::test_contended_commits_spend_their_budget_before_failing": JAVA_DB,
"test_float_vector_bulk.py::TestAllowedRids::test_rid_objects_and_text_mixed_still_work": "builds a Java RID (db.to_java_rid) and reads the Java RID set of the index",
"test_float_vector_bulk.py::TestDecision::test_a_declared_vector_column_is_taken_out": "decides on the Java database (get_java_database) and builds the bridge's FloatVectors.Matrix objects",
"test_float_vector_bulk.py::TestDecision::test_numpy_rows_and_ints_are_taken_out": "decides on the Java database (get_java_database) and builds the bridge's FloatVectors.Matrix objects",
"test_float_vector_bulk.py::TestDecision::test_the_inherited_declaration_counts": "decides on the Java database (get_java_database) and builds the bridge's FloatVectors.Matrix objects",
"test_float_vector_bulk.py::TestDecision::test_anything_else_stays_in_the_json": "decides on the Java database (get_java_database) and builds the bridge's FloatVectors.Matrix objects",
"test_float_vector_bulk.py::TestDecision::test_an_undeclared_property_stays_in_the_json": "decides on the Java database (get_java_database) and builds the bridge's FloatVectors.Matrix objects",
"test_float_vector_bulk.py::TestDocumentSet::test_convert_python_to_java_float32_is_one_float_array": 'asserts the converted value is a Java float[] (getClass().getName() == "[F")',
"test_float_vector_bulk.py::TestAllowedRids::test_the_set_holds_one_rid_per_string": "reads the Java RID set the index builds (_build_allowed_rids_set)",
"test_percall_calls.py::test_named_scalars_bind_as_the_plain_path_binds_them": "compares with the plain JPype path through db.get_java_database() (Database._java_parameters, core._through_bridge)",
"test_percall_calls.py::test_positional_scalars_bind_as_the_plain_path_binds_them": "compares with the plain JPype path through db.get_java_database() (Database._java_parameters, core._through_bridge)",
"test_percall_calls.py::test_a_lone_null_or_lone_java_array_keeps_the_plain_path": "compares with the plain JPype path through db.get_java_database() (Database._java_parameters, core._through_bridge)",
"test_percall_calls.py::test_other_values_take_the_plain_path": "compares with the plain JPype path through db.get_java_database() (Database._java_parameters, core._through_bridge)",
"test_light_edge_and_view_known_issues.py::test_other_ways_to_count_light_edges_an_older_version_loaded": "builds the light edges of an undeclared edge type with the deprecated Java Vertex.newLightEdge; no statement does (CREATE EDGE writes regular edges there)",
"test_light_edge_and_view_known_issues.py::test_one_hop_count_over_light_edges_an_older_version_loaded": "builds the light edges of an undeclared edge type with the deprecated Java Vertex.newLightEdge; no statement does (CREATE EDGE writes regular edges there)",
"test_vector_sql.py::TestVectorSQL::test_create_index_with_native_int8_encoding_sql": "reads encoding from the Java index metadata, which get_metadata() does not return and the native library does not expose",
"test_vector_sql.py::TestVectorSearchOptionsSql::test_graph_build_settings_reach_the_index": "reads neighborOverflowFactor and alphaDiversityRelaxation from the Java index metadata, which get_metadata() does not return and the native library does not expose",
"test_vector_sql.py::TestVectorSearchOptionsSql::test_sql_graph_settings_default_to_the_engines": "reads neighborOverflowFactor and alphaDiversityRelaxation from the Java index metadata, which get_metadata() does not return and the native library does not expose",
"test_xml_limits.py::test_import_hitting_a_limit_names_the_option": "reads the JDK's system properties through JPype (native: test_native_xml_limits.py)",
"test_xml_limits.py::test_per_call_option_lifts_the_limits_for_that_call_only": "reads the JDK's system properties through JPype (native: test_native_xml_limits.py)",
"test_xml_limits.py::test_per_call_option_restores_a_value_that_was_already_set": "sets and reads a JDK system property through JPype",
Expand Down
17 changes: 17 additions & 0 deletions tests/test_percall_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ def _plain_query(db, command, args):
PROJECTION = "SELECT :i AS i, :big AS big, :f AS f, :s AS s, :b AS b, :n AS n"


def test_named_scalars_round_trip(db):
assert db.query("sql", PROJECTION, SCALARS).first().to_dict() == SCALARS


def test_positional_scalars_round_trip(db):
values = (7, 2**40, 1.5, "text", True, None)
row = db.query(
"sql", "SELECT ? AS a, ? AS b, ? AS c, ? AS d, ? AS e, ? AS f", *values
).first()
assert row.to_dict() == dict(zip("abcdef", values))


def test_named_scalars_bind_as_the_plain_path_binds_them(db):
glued = db.query("sql", PROJECTION, SCALARS).first()
plain = _plain_query(db, PROJECTION, (SCALARS,)).first()
Expand Down Expand Up @@ -123,6 +135,11 @@ def test_a_lone_null_or_lone_java_array_keeps_the_plain_path(db, lone):
)
is core._NO_GLUE
)


@pytest.mark.parametrize("lone", [None, "java-array"])
def test_a_lone_null_or_lone_java_array_binds_as_the_value(db, lone):
value = None if lone is None else arcadedb.to_java_float_array([1.0, 2.0])
row = db.query("sql", "SELECT ? AS x", value).first()
assert (
(row.get("x") is None) if lone is None else (list(row.get("x")) == [1.0, 2.0])
Expand Down
25 changes: 11 additions & 14 deletions tests/test_vector_product_quantization.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,7 @@ def _search(db, query, options="{ efSearch: 100 }", k=K):


def _metadata(db):
index = db.schema.get_index_by_name("PqDoc[embedding]")
if "TypeIndex" in index.getClass().getName():
index = index.getSubIndexes().get(0)
return index.getMetadata()
return db.schema.get_vector_index("PqDoc", "embedding").get_metadata()


def _recall(db, vectors, queries, options):
Expand All @@ -137,12 +134,12 @@ def test_index_settings_reach_the_index(self, test_db, corpus, has_pq_rerank):

meta = _metadata(test_db)

assert str(meta.quantizationType) == "PRODUCT"
assert int(meta.rerankOversample) == 30
assert int(meta.pqTrainingLimit) == 1500
assert bool(meta.fusedPQ) is False
assert int(meta.pqSubspaces) == 8
assert int(meta.pqClusters) == 64
assert meta["quantization"] == "PRODUCT"
assert meta["rerank_oversample"] == 30
assert meta["pq_training_limit"] == 1500
assert meta["fused_pq"] is False
assert meta["pq_subspaces"] == 8
assert meta["pq_clusters"] == 64

def test_rerank_oversample_defaults_to_ten_and_fused_pq_to_off(
self, test_db, corpus, has_pq_rerank
Expand All @@ -152,11 +149,11 @@ def test_rerank_oversample_defaults_to_ten_and_fused_pq_to_off(
meta = _metadata(test_db)

if not has_pq_rerank:
assert not hasattr(meta, "rerankOversample")
assert "rerank_oversample" not in meta
return

assert int(meta.rerankOversample) == 10
assert bool(meta.fusedPQ) is False
assert meta["rerank_oversample"] == 10
assert meta["fused_pq"] is False

def test_negative_rerank_oversample_is_refused(self, test_db, has_pq_rerank):
test_db.command("sql", "CREATE DOCUMENT TYPE PqDoc")
Expand Down Expand Up @@ -249,7 +246,7 @@ def test_fused_pq_index_returns_k_results(self, test_db, corpus, has_pq_rerank):

_build(test_db)

assert bool(_metadata(test_db).fusedPQ) is True
assert _metadata(test_db)["fused_pq"] is True
assert len(_search(test_db, queries[0])) == K


Expand Down
41 changes: 22 additions & 19 deletions tests/test_vector_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,17 +597,16 @@ def test_create_index_with_rich_metadata_sql(self, test_db):
""",
)

java_index = self._get_primary_sql_vector_index(test_db, "SqlMetaDoc[vec]")
metadata = java_index.getMetadata()

assert metadata.dimensions == 4
assert str(metadata.similarityFunction) == "COSINE"
assert str(metadata.quantizationType) == "INT8"
assert metadata.idPropertyName == "slug"
assert metadata.storeVectorsInGraph is True
assert metadata.addHierarchy is True
assert metadata.graphBuildCacheSize == 456
assert metadata.mutationsBeforeRebuild == 789
metadata = test_db.schema.get_vector_index("SqlMetaDoc", "vec").get_metadata()

assert metadata["dimensions"] == 4
assert metadata["similarity_function"] == "COSINE"
assert metadata["quantization"] == "INT8"
assert metadata["id_property"] == "slug"
assert metadata["store_vectors_in_graph"] is True
assert metadata["add_hierarchy"] is True
assert metadata["graph_build_cache_size"] == 456
assert metadata["mutations_before_rebuild"] == 789

# locationCacheSize was removed in #5559/#5568 and the engine now
# rejects it outright, so a SQL METADATA block that still carries it
Expand Down Expand Up @@ -1265,12 +1264,10 @@ def test_graph_build_settings_reach_the_index(self, test_db):
def test_add_hierarchy_defaults_differ_between_sql_and_python(self, test_db):
"""SQL follows the engine (flat graph); create_vector_index() builds a hierarchy."""
self._make_docs(test_db)
meta = self._metadata(test_db, "OptDoc[embedding]")
assert bool(meta.addHierarchy) is False
meta = test_db.schema.get_vector_index("OptDoc", "embedding").get_metadata()
assert meta["add_hierarchy"] is False
# The vector guide says the same of quantization: SQL is the engine's NONE.
assert str(meta.quantizationType) == "NONE"
assert float(meta.neighborOverflowFactor) == pytest.approx(1.2)
assert float(meta.alphaDiversityRelaxation) == pytest.approx(1.2)
assert meta["quantization"] == "NONE"

test_db.command("sql", "CREATE VERTEX TYPE PyHier")
test_db.command("sql", "CREATE PROPERTY PyHier.embedding ARRAY_OF_FLOATS")
Expand All @@ -1287,6 +1284,12 @@ def test_add_hierarchy_defaults_differ_between_sql_and_python(self, test_db):
)
assert engine_default_index.get_metadata()["add_hierarchy"] is False

def test_sql_graph_settings_default_to_the_engines(self, test_db):
self._make_docs(test_db)
meta = self._metadata(test_db, "OptDoc[embedding]")
assert float(meta.neighborOverflowFactor) == pytest.approx(1.2)
assert float(meta.alphaDiversityRelaxation) == pytest.approx(1.2)

def test_sql_metadata_gives_the_helper_defaults(self, test_db):
"""The vector guide's quick start sets quantization and addHierarchy in METADATA."""
test_db.command("sql", "CREATE VERTEX TYPE QuickDoc")
Expand All @@ -1304,9 +1307,9 @@ def test_sql_metadata_gives_the_helper_defaults(self, test_db):
}
""",
)
meta = self._metadata(test_db, "QuickDoc[embedding]")
assert str(meta.quantizationType) == "INT8"
assert bool(meta.addHierarchy) is True
meta = test_db.schema.get_vector_index("QuickDoc", "embedding").get_metadata()
assert meta["quantization"] == "INT8"
assert meta["add_hierarchy"] is True


class TestSparseSearchOptionsSql:
Expand Down
Loading