From 67ed7daf9c8ca27b5929850151d38881ca8fc7c1 Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Sun, 25 Oct 2026 23:31:14 +0900 Subject: [PATCH 1/3] Native tests: no NOTYET left, every JVM-only test is NA-JVM or reads through the Python API The 31 NOTYET skips of the native parity run were calls that are JVM-only by design (get_java_database, a Java object a Schema method returns) reached by shared tests, so a final release (--max-notyet 0) would have been blocked. Raise site: needs_jvm() replaces not_yet() for those calls; the message says the call needs the JVM build (arcadedb-jvm) instead of "not yet". not_yet() stays for a real gap. Tests: the ones whose purpose is the Java object (the bridge's float-vector decision, the plain JPype path, a Java RID set, the deprecated Java newLightEdge, metadata fields only the Java index metadata carries) get NA-JVM rows. The ones that used the Java object only to read a value now read it through VectorIndex.get_metadata() (test_create_index_with_rich_metadata_sql, test_sql_metadata_gives_the_helper_defaults, the hierarchy and quantization part of test_add_hierarchy_defaults_differ_between_sql_and_python, both product quantization metadata tests), and scalar and lone-null binding get shared round-trip tests beside the NA-JVM comparisons with the plain path. Co-Authored-By: Claude Sonnet 5.5 --- src/arcadedb/_native/__init__.py | 8 +++++ src/arcadedb/_native/database.py | 15 ++++----- src/arcadedb/_native/records.py | 4 +-- src/arcadedb/_native/schema_objects.py | 6 ++-- tests/README.md | 2 +- tests/conftest.py | 20 ++++++++++- tests/test_percall_calls.py | 17 ++++++++++ tests/test_vector_product_quantization.py | 23 ++++++------- tests/test_vector_sql.py | 41 ++++++++++++----------- 9 files changed, 88 insertions(+), 48 deletions(-) diff --git a/src/arcadedb/_native/__init__.py b/src/arcadedb/_native/__init__.py index 47939e30..731c5bdc 100644 --- a/src/arcadedb/_native/__init__.py +++ b/src/arcadedb/_native/__init__.py @@ -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") diff --git a/src/arcadedb/_native/database.py b/src/arcadedb/_native/database.py index c8695aae..dc74d8d4 100644 --- a/src/arcadedb/_native/database.py +++ b/src/arcadedb/_native/database.py @@ -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 .codec import _prep as _prep_row from .codec import encode_params, json_native, loads, row_default @@ -850,7 +850,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``).""" @@ -1003,11 +1003,8 @@ def import_documents( disable_xml_limits=False, ): """Import document-shaped files (CSV, JSON, ...) through the engine's importer; same arguments as the JPype path.""" - from ..importer import ( - ImportResult, - _build_import_settings, - normalize_import_source, - ) + from ..importer import (ImportResult, _build_import_settings, + normalize_import_source) from . import extras self._check_not_closed() @@ -1221,7 +1218,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: @@ -1386,7 +1383,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: diff --git a/src/arcadedb/_native/records.py b/src/arcadedb/_native/records.py index dcdbd1b2..8f8b6227 100644 --- a/src/arcadedb/_native/records.py +++ b/src/arcadedb/_native/records.py @@ -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 @@ -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 diff --git a/src/arcadedb/_native/schema_objects.py b/src/arcadedb/_native/schema_objects.py index 3bd90465..3ad1a684 100644 --- a/src/arcadedb/_native/schema_objects.py +++ b/src/arcadedb/_native/schema_objects.py @@ -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_]+)") @@ -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"" @@ -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"" diff --git a/tests/README.md b/tests/README.md index 723c95e6..ef1c32de 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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 `::[::]`: 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. diff --git a/tests/conftest.py b/tests/conftest.py index 4e3f5dc5..a03ac5b8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -89,7 +89,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)" @@ -136,6 +138,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", diff --git a/tests/test_percall_calls.py b/tests/test_percall_calls.py index e42e55cd..80cc132a 100644 --- a/tests/test_percall_calls.py +++ b/tests/test_percall_calls.py @@ -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() @@ -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]) diff --git a/tests/test_vector_product_quantization.py b/tests/test_vector_product_quantization.py index 07d72391..e3688de6 100644 --- a/tests/test_vector_product_quantization.py +++ b/tests/test_vector_product_quantization.py @@ -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): @@ -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 @@ -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") diff --git a/tests/test_vector_sql.py b/tests/test_vector_sql.py index 54f10a76..84771d93 100644 --- a/tests/test_vector_sql.py +++ b/tests/test_vector_sql.py @@ -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 @@ -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") @@ -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") @@ -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: From 95e1350df5b961567848bcc69c736ff761dc7d02 Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Sun, 25 Oct 2026 23:32:49 +0900 Subject: [PATCH 2/3] Product quantization test: read fused_pq through get_metadata(), black-format database.py The fused PQ build test still read the Java field (.fusedPQ) from the dict that _metadata() returns now, which failed on the JVM build. black re-wraps the import line that needs_jvm lengthened. Co-Authored-By: Claude Sonnet 5.5 --- tests/test_vector_product_quantization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_vector_product_quantization.py b/tests/test_vector_product_quantization.py index e3688de6..5d4ac312 100644 --- a/tests/test_vector_product_quantization.py +++ b/tests/test_vector_product_quantization.py @@ -246,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 From 20e7a010b4c6b6593fc5b74f7662d5775713845c Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Sun, 25 Oct 2026 23:33:39 +0900 Subject: [PATCH 3/3] Restore the black import style in _native/database.py A bare isort run (no --profile black) had re-wrapped the importer import; only the needs_jvm changes remain in the file's diff. Co-Authored-By: Claude Sonnet 5.5 --- src/arcadedb/_native/database.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/arcadedb/_native/database.py b/src/arcadedb/_native/database.py index dc74d8d4..43fb9249 100644 --- a/src/arcadedb/_native/database.py +++ b/src/arcadedb/_native/database.py @@ -1003,8 +1003,11 @@ def import_documents( disable_xml_limits=False, ): """Import document-shaped files (CSV, JSON, ...) through the engine's importer; same arguments as the JPype path.""" - from ..importer import (ImportResult, _build_import_settings, - normalize_import_source) + from ..importer import ( + ImportResult, + _build_import_settings, + normalize_import_source, + ) from . import extras self._check_not_closed()