Skip to content

COLUMNAR index on both builds: reflection metadata, reopen and the columnar settings (closes #292) - #300

Merged
tae898 merged 2 commits into
mainfrom
feat/292-columnar-index
Nov 7, 2026
Merged

tae898 merged 2 commits into
mainfrom
feat/292-columnar-index

Conversation

@tae898

@tae898 tae898 commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #292. Part of #289.

I added tests/test_columnar_index.py, which runs on both packages with the public API only, and a short docs page, how-to/columnar-index.adoc. I did not change the Java bridge, the native library or reachability-metadata.json, and the reasons for each of the four points of the issue are below.

Create and drop from Python, and the native build. The tests run CREATE INDEX ON Item (category) COLUMNAR, the same on a numeric property and (tags BY ITEM) on a LIST OF STRING, check the index in schema:indexes (Item[category]_columnar, plus one row per bucket), ingest in several commits, update and delete, query FACETS and GROUP BY, and compare both with what Python counts over the rows it inserted. After DROP INDEX the facets come from a scan and GROUP BY runs its normal plan. A test creates the index on records that are already there, so the maintenance pool rebuilds the projection, and one sets flushInterval and applyInterval low and mergeMaxSegments to 2, streams small commits and polls the manifests under columnar/b*/manifest.json until every row is in a published segment. On the native build that is the apply, flush, merge and maintenance threads running inside the shared library. For the reflection metadata I read every class of index/columnar and query/facet in the engine (main at 4a91847) for reflection, ServiceLoader, resources and Class.forName, and found none: the projection is plain JDK, the index handler and facet.searchValues are registered by instance, and the only parser nodes the statements add are FacetsClause, FacetItem and FacetRangeItem, which #299 already listed. Upstream's reachability-metadata.json lists no columnar class either. The entries upstream has and ours lacks (SQLFunctionEnsureLabelTypes, the antlr StatementExpression and SubqueryExpression, three Ratis gRPC classes) are not columnar, so I left them for a change of their own. The dispatched native run is what proves this, not the audit.

Close and reopen. A clean close and reopen keeps the facets exact: from the projection, or from a scan while the engine rebuilds it, and the test then waits (with a deadline) for the projection to serve them again with nobody asking. For the unclean close, DatabaseInternal.kill() (what the engine's ColumnarCrashRecoveryTest uses) is not reachable from the native package and a JVM-only hook would not test both, so the writer is a child process that ends with os._exit after its commits. That leaves database.lck behind (the test asserts it) and the commits in the write-ahead log, which is the state the engine's recovery reads. Two variants: all commits unflushed, and the first batch already in published segments before the second is written. This is a crash of the process, not a power loss, and Python cannot produce the engine's kill points (a kill in the middle of a flush or a merge). Those stay covered by the engine's own tests.

Settings. I added no setter. ALTER DATABASE already takes the DATABASE-scope keys (groupByAcceleration, flushInterval, mergeDeadRatio and the rest of the list in GlobalConfiguration), takes effect at once and is stored with the database. It refuses the JVM and SERVER ones with cannot be set per database, and those are system properties (ARCADEDB_JVM_ARGS, jvm_kwargs, ARCADEDB_NATIVE_ISOLATE_ARGS), as for arcadedb.fileSync. The docs page says so. The tests pin the listing in schema:database, the refusals, the values after a reopen, and that GROUP BY returns the same rows with arcadedb.columnar.groupByAcceleration on and off. PROFILE shows path: columnar or path: normal (reason: disabled by arcadedb.columnar.groupByAcceleration), so the test also proves which path ran. No test sets a system property, and the page says that.

Tests and lanes. Like the smoke test, the file reads SELECT version(): an engine older than 26.12.1 must refuse CREATE INDEX ... COLUMNAR with Index type 'COLUMNAR' is not supported and ALTER DATABASE of the new keys with not found, and a newer one must run everything. The JVM lane takes its jars from the latest image and runs the real tests on this pull request. The native pull request gate links 26.11.1, so it only runs the refusal branches. The real native run is the dispatched proof with engine=26.12.1-SNAPSHOT.

Binding fix found by the first CI run. Database.get_database_path() returned a raw java.lang.String on arcadedb-jvm, which os.path.join refuses, while the native build returned a str (the family of #286 and #287). I wrapped it in str() and did the same for Database.get_name() and Schema.list_vector_indexes(), the only methods I found that document a str and return a Java getter result unconverted (an AST pass over every str-annotated method in src/arcadedb). I left Document.get_identity(), Schema.get_type(), get_types(), get_index_by_name() and the two get_raw() methods alone, because they are documented as returning Java objects. tests/test_str_returns.py asserts type(x) is str on both packages.

Not verified. Nothing ran locally (the laptop queue guard blocks workloads), so CI is the first run of the test file. I could not check on all four platforms that the threads behave the same in the native image, only what the dispatched run on Linux x86-64 shows. I wrote the PROFILE path strings, the schema:indexes names and the facet source values from the engine source on main, not from a run.

🤖 Generated with Claude Code

tae898 and others added 2 commits November 7, 2026 09:54
…#292)

Why no change to reachability-metadata.json: I read every class of the engine's index/columnar and query/facet packages (26.12.1 snapshot, ArcadeData/arcadedb main 4a91847) for reflection, ServiceLoader, resources and Class.forName. There is none: the projection is plain JDK (FileChannel, VarHandle byte-array views, java.util.concurrent pools and a daemon ticker thread), the index handler is registered by instance in LocalSchema, facet.searchValues by instance in DefaultSQLFunctionFactory, and the only parser nodes the COLUMNAR and FACETS statements add are FacetsClause, FacetItem and FacetRangeItem, which PR #299 already listed. Upstream's own reachability-metadata.json lists no columnar class either. The entries upstream has and ours lacks (SQLFunctionEnsureLabelTypes, antlr StatementExpression and SubqueryExpression, three Ratis gRPC classes) are not columnar and are left for a change of their own.

Why a child process for the unclean close: DatabaseInternal.kill(), which the engine's ColumnarCrashRecoveryTest uses, is not reachable from the native package, and a JVM-only hook would not test both. A writer that ends with os._exit after its commits leaves database.lck behind and the commits in the write-ahead log, which is the state the engine's recovery reads (a crash of the process, not a power loss). Two variants: commits unflushed, and commits flushed to published segments before the rest is written.

Why no new setter for arcadedb.columnar.*: ALTER DATABASE already takes the DATABASE-scope keys (immediately, stored with the database) and refuses the JVM and SERVER ones; those are system properties through ARCADEDB_JVM_ARGS or ARCADEDB_NATIVE_ISOLATE_ARGS, as for arcadedb.fileSync. The test pins that, and that GROUP BY returns the same rows with the acceleration on and off (PROFILE names the path).

tests/test_columnar_index.py is version-gated like the smoke test (an older engine must refuse the index and the keys with its own error), runs on both packages with the public API, and polls states with deadlines. Nothing was run locally (the laptop guard blocks workloads); CI is the first run.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… list_vector_indexes() on arcadedb-jvm (refs #292)

The first CI run of tests/test_columnar_index.py failed on arcadedb-jvm only: Database.get_database_path() returned the raw java.lang.String of getDatabasePath(), which os.path.join refuses (TypeError: expected str, bytes or os.PathLike object, not String). The native build returns a str, so the lanes disagreed. Same family as #286 (get_type_name, fixed in #287): the binding wraps the Java getter in str().

Sweep of src/arcadedb/*.py for a method that documents a str and returns a Java getter result unconverted (an AST pass over every def annotated with str or Path, looking at what each return expression is): Database.get_name(), Database.get_database_path() and Schema.list_vector_indexes() (a list of java.lang.String). Fixed with str(). Left alone because they are documented as returning Java objects: Document.get_identity(), Schema.get_type(), get_types(), get_index_by_name(), Document.get_raw(), Result.get_raw(). Result.get_rid(), Document.get_rid(), get_type_name() and the vector index names already convert.

tests/test_str_returns.py asserts type(x) is str on both packages for the four str returns that can be read through the public API (name, path, vector index names, Result.get_rid), plus that the path joins. Nothing was run locally; CI is the first run.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@tae898
tae898 merged commit 99cb558 into main Nov 7, 2026
42 checks passed
@tae898
tae898 deleted the feat/292-columnar-index branch November 7, 2026 02:07
@github-actions github-actions Bot added this to the 26.12.1 milestone Nov 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Columnar faceting: COLUMNAR index and GROUP BY acceleration in the native and JVM builds

1 participant