ArcadeDB 26.7.2
Overview
This release is a large stability and correctness milestone. It ships the results of a deep engine audit (storage, WAL/recovery, transactions/MVCC, LSM indexes and concurrency), hardens High Availability (Raft) recovery, completes the Neo4j Bolt driver compatibility certification, and fixes a broad set of OpenCypher and SQL query bugs.
It also closes five security advisories from an internal audit (see Security Advisories below); upgrading is strongly recommended for server-mode and multi-tenant deployments.
Two changes require attention before upgrading (see Breaking Changes): Raft storage is now durable by default, and the Bolt protocol now carries native temporal types instead of ISO-8601 strings.
Over 130 issues were closed for this milestone.
Security Advisories
This release closes several security advisories found in an internal audit. Upgrading is strongly recommended for any server-mode or multi-tenant deployment.
- RCE via JavaScript triggers (GHSA-x9f9-r4m8-9xc2). A JavaScript trigger could look up
java.lang.*host classes and reachRuntime.getRuntime().exec(...), obtaining OS command execution with onlyUPDATE_SCHEMAprivileges.java.lang.*is removed from the trigger host-class allow-list; only the benign value packages (java.util,java.time,java.math) remain. - Arbitrary JavaScript execution via
DEFINE FUNCTION ... LANGUAGE js(GHSA-vwjc-v7x7-cm6g). The SQL route to JavaScript bypassed the polyglot scripting gate, letting a user with schema (or lesser) access define and run arbitrary JavaScript. Defining ajsfunction now requiresUPDATE_SECURITY, and the polyglot engine runs withIOAccess.NONE(closingload(path|url)host-file read and SSRF) andPolyglotAccess.NONE(no cross-language pivot). SQL and Cypher user functions keep the standard schema-level protection. - Secret disclosure / root impersonation via
GET /api/v1/server(GHSA-46hj-24h4-j8gf). The server-settings export masked only keys literally containing "password", leakingarcadedb.ha.clusterTokenin clear - which, combined with the cluster-forwarded-auth headers, let a low-privilege authenticated user impersonate root. Every setting flaggedisHidden()(clusterToken and all password/secret keys) is now redacted, for both the current and default value. - Cross-database IDOR on time-series / batch / Prometheus / Grafana endpoints (GHSA-x8mg-6r4p-87pf). Fourteen handlers extended the base HTTP handler directly and resolved the path database with no
canAccessToDatabasecheck, so a user authorized for one database could read and write any other database on the server. All affected handlers now enforce database authorization (HTTP 403) before any payload handling and fail closed on a missing database name; the batch handler checks before leader-forwarding. - Read-only identity could mutate persisted schema (GHSA-8vr5-263f-x5r3). Several schema/config mutators reachable from a remote command were missing the
UPDATE_SCHEMAguard:ALTER TYPE ... CUSTOM/BUCKETSELECTIONSTRATEGY,ALTER MATERIALIZED VIEW ... REFRESH,DROP MATERIALIZED VIEW/DROP CONTINUOUS AGGREGATE,ALTER TIMESERIES TYPE ... DOWNSAMPLING POLICY,DEFINE FUNCTION, and the stats-onlyREBUILD INDEXpath. All are now gated (database-settings mutators useUPDATE_DATABASE_SETTINGS); every guard is a no-op for embedded, schema-load and HA-replication paths.
Major Highlights
Engine deep-audit 2026-07 (storage, transactions, LSM, concurrency)
A multi-area audit (#4962) hardened the core engine against data-loss and corruption edge cases under crashes, concurrency and load. Notable outcomes:
- The WAL append is now the transaction's point of no return. Page versions are validated and bumped
before the WAL write, so an aborted transaction can no longer be left in the WAL and partially replayed by
recovery; a post-append failure is resolved by recovery replay and fences the database until reopen
(#4936, #4937). - Torn 64KB page writes are repaired on recovery even when the on-disk version header already advanced (#4926); a clean close whose data fsync failed now
preserves the WAL for recovery instead of deleting it (#4934). - Page-cache correctness: an older page version can no longer overwrite a newer committed one (lost
update) and RAM accounting can no longer drift negative and disable eviction (unbounded growth) (#4925, #4933). - LSM index hardening: compaction no longer loses re-inserted keys or leaks orphaned pages on failure,
range scans are no longer truncated by tombstoned keys, and non-unique point lookups no longer resurrect
deleted RIDs (#4942, #4943, #4944, #4945, #4946, #4947). - Transactions: updating the same indexed record twice in one transaction no longer corrupts the index
(#4935); dead-thread cleanup now rolls back abandoned
transactions and releases their file locks, and is correct for virtual threads (#4941, #4956, #4939, #4940). - Async executor and shutdown: the stall detector no longer false-positives on slow tasks, shutdown no
longer drops queued tasks or hangs completion waiters, andclose()/drop()are now bounded so a wedged
worker cannot hang shutdown forever (#4953, #4954, #5080, #4991).
Parallel query safety
Parallel bucket scans (enabled by default) could self-deadlock under pool saturation and wedge HTTP workers, race on the shared command context, or pin a CPU core at 100% when a consumer stopped draining. Scan producers now run on a dedicated pool, each worker gets its own context, and the native select().parallel() iterator bounds its producer offer and closes cleanly (#4948, #4949, #4950, #4951, #5065).
High Availability (Raft) recovery
- Durable Raft storage by default removes a class of permanent follower divergence after a full-cluster cold restart (see Breaking Changes, #4835).
- Diverged-follower resync no longer floods the logs and starves the snapshot download it triggers; only the first gap logs loudly (#4741 follow-up).
- The leader now auto-recovers a follower's replication channel wedged on stale DNS after a Kubernetes pod-IP change, instead of dropping to bare quorum until a manual leadership transfer (#4696).
- A distinct contract for "committed cluster-wide, local apply failed"
(TransactionCommittedRemotelyException, mapped to HTTP 409) so applications no longer retry and create duplicates (#5064, #5017, #5018). - Offline bootstrap leadership-transfer path now commits its baseline (#5099).
Neo4j Bolt driver compatibility certification
The Bolt epic (#4882) reached certification: a shared conformance spec, per-language driver e2e suites (Java, Python, JS, Go, C#) CI-gated with a nightly run, and broad type-fidelity work. Highlights: native temporal PackStream structs in both directions (see Breaking Changes), native Path/Duration/Point structures, retryable-conflict mapping so drivers auto-retry, HA-aware ROUTE responses, Bolt 5.x negotiation and populated write-result counters (#4883-#4892, #4905, #4907, [#4908(https://github.com//issues/4908), #4922,
#4997-#5002, #5082, #5106, #5123).
Server & gRPC security hardening
A server-side security audit closed authentication, authorization and transport gaps: brute-force lockout and constant-time password compare, WebSocket change-stream per-database authorization, SSRF protection on restore/import, thread-safe security store persistence, and gRPC admin-RPC authorization, transaction-hijack protection and TLS/DoS hardening (#5021, #5022, #5027, #5029, #5030, #5034, #5037, #5039, #5040, #5048).
New Features & Improvements
- User-defined functions are now persisted and distributed across an HA cluster
(#5121). LSM_SPARSE_VECTOR: configurable posting-weight quantization (FP32/FP16/INT8) via index
METADATA, at parity with the dense vector index
(#5143).- Commutative append-merge for super-node edges removes retry storms on high-degree vertices
(#5148). - Extended Cypher write-counter (
QueryStatistics) surfacing over HTTP/gRPC withUNION/CALL
aggregation (#5015). Pointdatatype and spatial index (#3401).- Python bindings: bulk-transport performance, embedded-only packaging and engine-sync fixes
(#5010).
Major Fixes
OpenCypher
- Dynamic bracket property mutations (
SET n[key] = value,REMOVE n[key]) are now applied instead of silently ignored (#5141). - Inline relationship property filters
{prop: value}are now enforced everywhere they were previously ignored: inMATCH(#5093),exists()(#5109), pattern comprehensions (#5111, #5139, #5146) andshortestPath/allShortestPaths(#5096). exists()now enforces multi-label conjunctions on a bound start variable (#5095);count(n)on a multi-label pattern is no longer inflated (#5084).count(*)afterOPTIONAL MATCHno longer drops null-preserving rows (#5094);count(*)with an inline-filtered start node and anonymous endpoint no longer returns the global relationship count (#5071); the stale cached bucket counter behindcount(*)drift is fixed (#5149, #5152).- Relationship writes inside
FOREACH,CALLsubqueries and top-levelREMOVEnow take effect (edge variable was being dropped by a planner optimization) (#4912, #4913, #5013);FOREACHupdates are now visible to a laterRETURN(#5114);FOREACHwithMERGE/CASE WHEN exists()no longer dropsSETor ignores conditions (#4993, #4994). CASEfixes: simpleCASE expr WHEN valueinWHERE(#5083) andCASE WHEN r IS NOT NULLon anOPTIONAL MATCHrelationship (#5137).- 2-hop pattern comprehension with an anonymous middle node now returns matches (#5007);
COUNT {}inside a pattern-comprehensionWHEREis now evaluated (#5140). - Correlated subquery fixes:
EXISTS { ... WHERE node IN collected_nodes }now matches (#5138);EXISTS ... AND NOT EXISTSreturns the correct result (#4995);CALLsubqueryORDER BY LIMITis correct (#5005). - Nested parameter field access (
$param.field) resolves inside aMATCHpattern property map (#4909); optional path withwhile/@ridfilter no longer NPEs on a missing prior segment (#4919). - Query planning/perf: independent
MATCHpattern ordering no longer causes large runtime gaps (#5116, #5117);MATCH...CREATEnow uses the unique index instead of a full scan (#5107);
NodeIndexRangeScannow applies the temporal normalization it was bypassing (#5008).
SQL
FROMcan now be used as a property name in DDL, indexes, projections and inserts (#5092).- Native
select()ORDER BYon a non-index-served property no longer returns an empty result set (#5079). TRAVERSE ... MAXDEPTHno longer under-approximates the d-hop ball (#5159) andSTRATEGY BREADTH_FIRSTnow performs a breadth-first traversal (#5158).- Map string-key indexing
$map["key"]now works insideINSERT ... CONTENT(#4915);Result.toJSON()serializes primitive arrays as JSON arrays (#4967);Type.convert()no longer wraps an empty array in a single-item list (#5091).
Indexes, vectors & encryption
FULL_TEXT(and everyLSM_TREE) index now returns results on an encrypted database (#5142). Note: rebuildLSM_TREE/FULL_TEXTindexes (REBUILD INDEX *) on databases encrypted before this fix.- LSM compaction no longer loses entries on non-unique composite indexes with many duplicate keys (#5119, #5120);
SEARCH_INDEX(..., 'term~')no longer NPEs after repeated deletes/updates (#5118). CREATE INDEX ... BY KEY/BY VALUEnow works, automatic-index series creation succeeds, andBY ITEM ... IF NOT EXISTSreports the correctcreatedflag (#4879, #4880, #4881).- In-transaction iteration on a non-unique index no longer drops a committed RID sharing a key with an uncommitted entry (#5055).
Import / Export
IMPORT DATABASEno longer crashes on a JSONL dump that contains anLSM_VECTORindex; the index is rebuilt and repopulated from the exported embeddings (#5069).
Remote & Client
RemoteVertex.isConnectedTo()now accepts aVertexargument, not only aRID(#5122).- Studio: re-login no longer adds a duplicate CodeMirror element (#4893).
Storage, transactions & concurrency (audit follow-ups)
- HASH index: a cyclic overflow chain no longer spins a CPU core forever; scan walkers are bounded and throw
an actionable error (#4743). - PageManager lifecycle is now refcounted, removing a check-then-act race on concurrent open/close of
different databases (#4927). - Flush suspension is refcounted so overlapping backup/verify/snapshot each own their window without torn
reads (#5068). - The
fastasync queue maps to a multi-producerDisruptorBlockingQueue(the previous SPSC queue could
silently lose tasks) (#5066). - Additional low-severity storage/WAL/LSM/transaction/concurrency cleanups from the audit (#4924, #4928-#4932, #4938, #4952, #4955, #4957-#4961, #5067).
Breaking Changes (migration notes)
1. raftPersistStorage now defaults to true (durable Raft storage)
arcadedb.ha.raftPersistStorage now defaults to true (previously false/ephemeral outside Kubernetes).
Wiping the Raft log on restart could permanently diverge a lagging follower after a full-cluster cold restart
(WALVersionGapException) or silently re-form a fresh single-node cluster. Durable storage lets a restarted
node rejoin by replaying its persisted log. Ensure arcadedb.ha.raftStorageDirectory lives on durable
storage. A throwaway/test cluster can opt out explicitly with arcadedb.ha.raftPersistStorage=false
(#4835).
2. Bolt: temporal values now use native PackStream structures (not ISO-8601 strings)
The Neo4j Bolt protocol now carries date, time, localtime, datetime and localdatetime as native
PackStream temporal structures in both directions. Inbound datetime query parameters are decoded to
java.time values (previously silently dropped), and outbound temporal properties are returned as native
structs. A Bolt client that previously read a temporal property as an ISO String must now read the native
temporal type (e.g. Value.asZonedDateTime() / asLocalDate()), the same as against Neo4j
(#4905,
#4907).
Dependencies
Runtime and library updates in this release:
- Netty
4.2.16.Final - Jackson Databind
2.22.1 - PostgreSQL JDBC
42.7.13 - GraalVM
25.1.3 - JLine
4.3.1 - Neo4j Java driver
6.2.0 - lz4-java
1.11.1 - Jedis
7.5.3 - proto-google-common-protos
2.73.0 - protobuf-maven-plugin
5.1.7 - Studio: apexcharts
5.16.0
Toolchain, build and CI updates: GitHub Actions (setup-go, github-script and grouped action bumps),
Prettier mirror, Testcontainers, Tomcat JDBC (test), and assorted Studio dev-dependency and e2e (JS/Go/Python)
driver bumps.
Full Changelog: 26.7.1...26.7.2