From b2d7624271003e46daf814c819bb673c8962c44c Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Sat, 31 Oct 2026 02:48:23 +0900 Subject: [PATCH 1/5] docs: fix the three broken cross-references and two anchor names that fail Documentation Validation - postgres.adoc: <> pointed at a section title; the section gets [[postgres-column-type-mapping]]. - settings.adoc: <<_a_statement_that_fails_halfway>> relied on the generated id; the section in transactions.adoc gets [[statement-fails-halfway]]. - sql/chapter.adoc: <> -> the anchor is vector-neighbors. - sql-functions.adoc: [[cchShortestPath]] and [[duanSSSP]] -> cch-shortest-path and duan-sssp (lowercase with hyphens), with the three references updated. docs-validator.py passes locally (it failed on main with these five). Co-Authored-By: Claude Opus 5.5 --- src/main/asciidoc/concepts/transactions.adoc | 1 + src/main/asciidoc/how-to/connectivity/postgres.adoc | 3 ++- src/main/asciidoc/how-to/data-modeling/graph-olap.adoc | 2 +- .../asciidoc/reference/graph-algorithms/path-finding.adoc | 2 +- src/main/asciidoc/reference/settings.adoc | 2 +- src/main/asciidoc/reference/sql/chapter.adoc | 2 +- src/main/asciidoc/reference/sql/sql-functions.adoc | 6 +++--- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/asciidoc/concepts/transactions.adoc b/src/main/asciidoc/concepts/transactions.adoc index bda40c00a..6d9affb3c 100644 --- a/src/main/asciidoc/concepts/transactions.adoc +++ b/src/main/asciidoc/concepts/transactions.adoc @@ -364,6 +364,7 @@ A `ConcurrentModificationException` always means another transaction wrote the r In particular, updating a record that the same transaction has already deleted is not a conflict, it simply has no effect - the delete wins, since the record will not exist once the transaction commits. The order of the two operations does not matter. +[[statement-fails-halfway]] ==== A statement that fails halfway A statement that fails inside a transaction you keep is taken back on its own, and the transaction goes on as if the statement had never run, the way SQLite undoes a failed statement. diff --git a/src/main/asciidoc/how-to/connectivity/postgres.adoc b/src/main/asciidoc/how-to/connectivity/postgres.adoc index 037755e13..536128f05 100644 --- a/src/main/asciidoc/how-to/connectivity/postgres.adoc +++ b/src/main/asciidoc/how-to/connectivity/postgres.adoc @@ -229,7 +229,7 @@ dropping it, finds the views that depend on it. Dependencies are listed per type _Since v26.10.1_, `pg_type` can also be queried on its own, and not only as part of a column list. Some clients build a map of every type the server has before they run a single query, and refuse to connect if that map comes back empty - the Apache Arrow ADBC PostgreSQL driver is one of them. Such a query is answered -with the types this protocol can actually produce (see <> below), +with the types this protocol can actually produce (see <> below), each described the way PostgreSQL describes it. A filter the server cannot interpret returns no rows rather than every type, so a client asking whether an extension type such as `hstore` exists is correctly told it does not. @@ -407,6 +407,7 @@ PROGRAM`, which would let a wire client write files or run commands on the serve without a binary encoding on this server (an array) is refused the same way; export it in text or CSV format, or project it as a string. +[[postgres-column-type-mapping]] ===== Column type mapping A column's advertised PostgreSQL type must not depend on whether the query happens to return any rows - a diff --git a/src/main/asciidoc/how-to/data-modeling/graph-olap.adoc b/src/main/asciidoc/how-to/data-modeling/graph-olap.adoc index 27c9df496..033cb9d4f 100644 --- a/src/main/asciidoc/how-to/data-modeling/graph-olap.adoc +++ b/src/main/asciidoc/how-to/data-modeling/graph-olap.adoc @@ -456,7 +456,7 @@ GraphAnalyticalView roads = GraphAnalyticalView.builder(database) .build(); ---- -Queries go through <> (Cypher) or <> (SQL). A query is answered by the hierarchy when its weight property and relationship types are exactly those of a hierarchy on a ready view; otherwise, and whenever the hierarchy cannot answer for the view's current state, it is answered by bidirectional Dijkstra. Answers are exact either way, and a transaction holding uncommitted changes always sees them. +Queries go through <> (Cypher) or <> (SQL). A query is answered by the hierarchy when its weight property and relationship types are exactly those of a hierarchy on a ready view; otherwise, and whenever the hierarchy cannot answer for the view's current state, it is answered by bidirectional Dijkstra. Answers are exact either way, and a transaction holding uncommitted changes always sees them. *How it works* diff --git a/src/main/asciidoc/reference/graph-algorithms/path-finding.adoc b/src/main/asciidoc/reference/graph-algorithms/path-finding.adoc index a68c07e0c..c44f275f0 100644 --- a/src/main/asciidoc/reference/graph-algorithms/path-finding.adoc +++ b/src/main/asciidoc/reference/graph-algorithms/path-finding.adoc @@ -179,7 +179,7 @@ RETURN path, weight - Dibbelt, Strasser, Wagner: _Customizable Contraction Hierarchies_, ACM Journal of Experimental Algorithmics 21 (2016) -See also: <> for the SQL function, and <> for defining and monitoring the hierarchy. +See also: <> for the SQL function, and <> for defining and monitoring the hierarchy. ''' diff --git a/src/main/asciidoc/reference/settings.adoc b/src/main/asciidoc/reference/settings.adoc index e88d30b68..c4a54cabe 100644 --- a/src/main/asciidoc/reference/settings.adoc +++ b/src/main/asciidoc/reference/settings.adoc @@ -566,7 +566,7 @@ The following plugins are available for ArcadeDB Server: |`txRetryDelay`|Cap, in milliseconds, on the random wait before the next transaction retry: an exponential backoff with full jitter, drawn from `1` to `min(txRetryDelay, txRetryDelayBase * 2^attempt)`. Helpful in case of high concurrency on the same pages (multi-thread insertion over the same bucket). Applied by the embedded `transaction()`, by `COMMIT RETRY` and, since v26.10.1, by the remote `RemoteDatabase.transaction()` too. Since v26.10.2 it is also applied between the replays of an asynchronous batch commit that hit a conflict. 0 disables the delay|Integer|100 |`txRetryDelayBase`|Starting size, in milliseconds, of the transaction retry backoff window, doubled on each further attempt up to `txRetryDelay`|Integer|2 |`txStaleReadCheck`|Refuses, with a retryable `ConcurrentModificationException`, a property write on a record read in the current transaction when another transaction committed a change to that record in between, so a value computed from the older read can never overwrite the concurrent change (a lost update under `READ_COMMITTED`). Adding or removing edges on a vertex is never refused. Only direct property assignment and removal is checked, not an in-place change to a list, map or embedded document got from the record. SQL `UPDATE` and Cypher `SET`/`MERGE`/`REMOVE` already compute their values from the latest data and are not affected. Set to false to restore the previous last-writer-wins behavior _(since v26.10.1)_|Boolean|true -|`txStatementSavepointMaxEntries`|A statement that fails inside a transaction you keep is taken back on its own, and the transaction goes on, instead of being refused its commit. To do it the engine journals what the statement changes in the transaction, up to this many entries; a statement that changes more is not taken back when it fails, and the transaction can no longer be committed, as before statement savepoints. A bulk statement thus pays no memory for a journal it could not use, while the many small statements of an interactive transaction are all undone exactly. 0 disables statement savepoints. See <<_a_statement_that_fails_halfway>> _(since v26.11.1)_|Integer|100000 +|`txStatementSavepointMaxEntries`|A statement that fails inside a transaction you keep is taken back on its own, and the transaction goes on, instead of being refused its commit. To do it the engine journals what the statement changes in the transaction, up to this many entries; a statement that changes more is not taken back when it fails, and the transaction can no longer be committed, as before statement savepoints. A bulk statement thus pays no memory for a journal it could not use, while the many small statements of an interactive transaction are all undone exactly. 0 disables statement savepoints. See <> _(since v26.11.1)_|Integer|100000 |`txWAL`|Uses the WAL|Boolean|true |`txWalFiles`|Maximum number of concurrent files to use for tx log. 0 or a negative value = available cores, which is also the default. Since v26.11.1 a WAL file is opened on the first commit that needs it, instead of all of them when the database opens, and `txWalIdleTimeout` closes the ones left idle (see <>)|Integer|(machine dependent) |`txWalFlush`|Flushes the WAL on disk at commit time. It can be 0 = no flush, 1 = flush without metadata and 2 = full flush (fsync). In `production` server mode, this is automatically set to `1` if not explicitly configured. See <>|Integer|0 diff --git a/src/main/asciidoc/reference/sql/chapter.adoc b/src/main/asciidoc/reference/sql/chapter.adoc index 91f1c703d..5f813149f 100644 --- a/src/main/asciidoc/reference/sql/chapter.adoc +++ b/src/main/asciidoc/reference/sql/chapter.adoc @@ -63,7 +63,7 @@ | | | | | <> | | | | | | <> | | | | | | <> | -| | | | | <> | +| | | | | <> | |=== *Extended Functions (APOC Compatible)* diff --git a/src/main/asciidoc/reference/sql/sql-functions.adoc b/src/main/asciidoc/reference/sql/sql-functions.adoc index 9ba2f5720..ed42628fa 100644 --- a/src/main/asciidoc/reference/sql/sql-functions.adoc +++ b/src/main/asciidoc/reference/sql/sql-functions.adoc @@ -262,7 +262,7 @@ SELECT bothV() FROM Friend ''' -[[cchShortestPath]] +[[cch-shortest-path]] ====== cchShortestPath() pass:[graph] Returns the cheapest path between two vertices, answered by a <> when a Graph Analytical View keeps one for the weight property and edge types asked for, and by bidirectional Dijkstra otherwise. @@ -531,10 +531,10 @@ SELECT duration(start, 'year') FROM Employees ''' -[[duanSSSP]] +[[duan-sssp]] ====== duanSSSP() pass:[graph] -Returns the cheapest path between two vertices, through the same engine as <> (a Customizable Contraction Hierarchy when a Graph Analytical View keeps one, bidirectional Dijkstra otherwise). The weight property is optional and defaults to `weight`. +Returns the cheapest path between two vertices, through the same engine as <> (a Customizable Contraction Hierarchy when a Graph Analytical View keeps one, bidirectional Dijkstra otherwise). The weight property is optional and defaults to `weight`. Direction can be OUT (default), IN or BOTH. The resulting path is returned as list of RIDs, empty when the destination cannot be reached. From 5d388caf5c2a18199ec721cee472c75c139de9da Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Thu, 5 Nov 2026 10:56:41 +0900 Subject: [PATCH 2/5] docs: write inline code that holds *, _, #, ~ or a as literal monospace The "Validate HTML output" step (html5validator on the single-page build, target/generated-docs/index.html) failed with 21 errors, on main as well. Asciidoctor applies strong, emphasis, mark and subscript before monospace, so a pair of `*` (or `_`, `#`, `~`) in two different backtick spans pairs up across them: `wal.group.*` and `commit.lock.*` rendered as wal.group. and commit.lock., which is a stray "code" end tag, and `=~`/`!~` and `#` did the same with sub and mark. `file://exports/` was turned into a bare link with an illegal href. The same markup renders the live pages wrong (bold running over the text between two code spans), so the fix is in the sources. Each affected span is now `+...+` literal monospace, the form sql-projections.adoc already used. Only spans in the paragraphs that rendered wrongly changed. Checked by rendering index.adoc with Asciidoctor 3 and scanning for misnested code/strong/em/mark/sub tags (80 before, 0 after); the real html5validator run is the CI step. Co-Authored-By: Claude Opus 5.5 --- src/main/asciidoc/concepts/transactions.adoc | 2 +- .../how-to/data-modeling/graph-olap.adoc | 10 +++++----- .../asciidoc/how-to/integrations/grafana.adoc | 2 +- .../asciidoc/how-to/migration/importer.adoc | 2 +- src/main/asciidoc/how-to/operations/ha.adoc | 2 +- .../how-to/operations/observability.adoc | 2 +- .../asciidoc/how-to/operations/upgrade.adoc | 6 +++--- .../reference/cypher/cypher-clauses.adoc | 8 ++++---- .../reference/cypher/cypher-compatibility.adoc | 4 ++-- .../graph-algorithms/path-finding.adoc | 2 +- .../asciidoc/reference/graphql/graphql.adoc | 4 ++-- src/main/asciidoc/reference/grpc-api.adoc | 6 +++--- src/main/asciidoc/reference/http-api/http.adoc | 4 ++-- .../reference/java-api/java-select-api.adoc | 4 ++-- .../asciidoc/reference/lightweight-edges.adoc | 2 +- src/main/asciidoc/reference/settings.adoc | 18 +++++++++--------- .../reference/sql/sql-database-admin.adoc | 12 ++++++------ .../asciidoc/reference/sql/sql-functions.adoc | 2 +- .../reference/sql/sql-projections.adoc | 4 ++-- .../asciidoc/reference/sql/sql-triggers.adoc | 2 +- src/main/asciidoc/reference/sql/sql-types.adoc | 8 ++++---- src/main/asciidoc/tools/studio/ai.adoc | 2 +- src/main/asciidoc/tools/studio/security.adoc | 4 ++-- 23 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/main/asciidoc/concepts/transactions.adoc b/src/main/asciidoc/concepts/transactions.adoc index 6d9affb3c..f6f44f4cd 100644 --- a/src/main/asciidoc/concepts/transactions.adoc +++ b/src/main/asciidoc/concepts/transactions.adoc @@ -255,7 +255,7 @@ flushing on its own (group commit, setting `arcadedb.txWalGroupCommit`, on by de on disk, but it no longer holds the locks of the files it wrote while its WAL is written and flushed, so the commits that arrive meanwhile are written and flushed together. On an Apple silicon laptop, 50 clients writing into the same type went from about 230 to about 4,000 commits per second with `txWalFlush=2`. A single writer sees no difference. The -<> `wal.group.*` and `commit.lock.*` show how many commits each flush served and how long +<> `+wal.group.*+` and `+commit.lock.*+` show how many commits each flush served and how long a commit holds its locks. What changes compared to the previous versions: diff --git a/src/main/asciidoc/how-to/data-modeling/graph-olap.adoc b/src/main/asciidoc/how-to/data-modeling/graph-olap.adoc index 033cb9d4f..b424e4231 100644 --- a/src/main/asciidoc/how-to/data-modeling/graph-olap.adoc +++ b/src/main/asciidoc/how-to/data-modeling/graph-olap.adoc @@ -579,14 +579,14 @@ counted on its own, by its own push-down or plan, and the counts are multiplied. least one row. Two parts of one `MATCH` clause are multiplied only when the schema proves their relationships cannot be the same edge, since a clause binds every relationship once. A product that does not fit a 64-bit integer is an error (issue #9596). -* a `WITH` that only passes variables on, `*` and aliases included (`WITH message, creator`, `WITH m AS x`), with no +* a `WITH` that only passes variables on, `+*+` and aliases included (`WITH message, creator`, `WITH m AS x`), with no aggregation, `DISTINCT`, `WHERE`, `ORDER BY`, `SKIP` or `LIMIT`: it leaves the number of rows alone, so the statement is counted as if the clauses on both sides were one query part (issue #9597). -* `count(x)` of a node or relationship variable a non-optional `MATCH` binds, which counts the same rows as `count(*)`; +* `count(x)` of a node or relationship variable a non-optional `MATCH` binds, which counts the same rows as `+count(*)+`; a single relationship between two unconstrained nodes, `MATCH ()-[e:KNOWS]->() RETURN count(e)` (untyped, with several types or undirected too), read from the view's per-type totals, or without a view in one walk of the edge lists that loads no edge; and `MATCH (n) RETURN count(n)`, the sum of the vertex types' counters (issue #9600). Unlike the SQL -`SELECT count(*) FROM KNOWS`, which counts edge records, the relationship count includes lightweight edges, which keep no +`+SELECT count(*) FROM KNOWS+`, which counts edge records, the relationship count includes lightweight edges, which keep no record. * the same pattern however it is written: the comma-separated parts of a `MATCH` are read as the graph they describe, so a chain cut into parts is counted as the chain, and a cycle such as LSQB Q2 (a `KNOWS` hop closed by a three-hop chain, or @@ -608,9 +608,9 @@ must be undirected, anonymous and of one type, and the three variables carry one `COUNT TRIANGLES (CSR sorted intersection)` with the order it counts. *Multi-hop chains on the view.* A chain of labeled hops such as `MATCH (a:P)-[:K]->(b:P)-[:K]->(c:P)` is walked on the -view (`EXPLAIN` shows `GAVFusedChain`). Since v26.10.2 a `count(*)`, or `count(x)` of a variable of the chain, is counted +view (`EXPLAIN` shows `GAVFusedChain`). Since v26.10.2 a `+count(*)+`, or `count(x)` of a variable of the chain, is counted during the walk with up to two grouping keys that are chain variables, in a `RETURN` or a `WITH` -(`RETURN a, count(*) AS n`); `EXPLAIN` shows it on the chain as `fused count(*) AS n`. When the statement returns the +(`+RETURN a, count(*) AS n+`); `EXPLAIN` shows it on the chain as `+fused count(*) AS n+`. When the statement returns the paths' rows instead, they are produced in bounded batches, so memory no longer grows with the number of paths and a `LIMIT` stops the walk early (issue #9738). If the view is rebuilt while such a result is still being read, the query fails with a retryable error rather than mixing rows from two builds: run it again. diff --git a/src/main/asciidoc/how-to/integrations/grafana.adoc b/src/main/asciidoc/how-to/integrations/grafana.adoc index 1723977d8..92c63f374 100644 --- a/src/main/asciidoc/how-to/integrations/grafana.adoc +++ b/src/main/asciidoc/how-to/integrations/grafana.adoc @@ -12,7 +12,7 @@ The https://github.com/ArcadeData/arcadedb-grafana-datasource[ArcadeDB Grafana p *Features:* * **Time Series mode** - visual query builder with auto-discovered types, fields, tags, and aggregation functions (SUM, AVG, MIN, MAX, COUNT) -* **SQL mode** - ArcadeDB SQL with macro expansion (`$__timeFrom`, `$__timeTo`, `$__timeFilter(col)`, `$__interval`) +* **SQL mode** - ArcadeDB SQL with macro expansion (`+$__timeFrom+`, `+$__timeTo+`, `+$__timeFilter(col)+`, `+$__interval+`) * **Cypher mode** - OpenCypher queries with optional Node Graph visualization * **Gremlin mode** - Apache Gremlin traversals with optional Node Graph visualization * **Alerting** - full Grafana alerting support via the Go backend diff --git a/src/main/asciidoc/how-to/migration/importer.adoc b/src/main/asciidoc/how-to/migration/importer.adoc index f84b5257e..ff8b4ca23 100644 --- a/src/main/asciidoc/how-to/migration/importer.adoc +++ b/src/main/asciidoc/how-to/migration/importer.adoc @@ -80,7 +80,7 @@ Parsed 362000 (54256/sec) - 0 documents (0/sec) - 164118 vertices (5260/sec) - 3 ---- An RDF source is recognised from the shape of its first statement - `subject predicate object` with an optional -closing `.` - so quoted literal objects, language tags (`@en`), typed literals (`^^`), blank nodes (`_:b1`) +closing `.` - so quoted literal objects, language tags (`@en`), typed literals (`^^`), blank nodes (`+_:b1+`) and Windows (CRLF) line endings are all read as RDF, and the character separating the terms is used as the field delimiter. _(Since v26.10.1: a file using any of those was analysed as delimited text instead and the import failed with a number-parsing error naming an IRI. If a file still cannot be recognised, set the separator explicitly with diff --git a/src/main/asciidoc/how-to/operations/ha.adoc b/src/main/asciidoc/how-to/operations/ha.adoc index 097fa4888..7188cbb27 100644 --- a/src/main/asciidoc/how-to/operations/ha.adoc +++ b/src/main/asciidoc/how-to/operations/ha.adoc @@ -692,7 +692,7 @@ With ephemeral Raft storage, wiping the log on restart could leave the cluster i Durable storage lets a restarted node rejoin by *replaying its own persisted log* rather than always requiring a full snapshot resync from the leader. -*Migration:* the per-node directory is named `raft-storage-` dynamically, which container runtimes and Kubernetes cannot mount as a volume (a wildcard like `raft-storage-*` is created as a literal directory, not expanded). Set `arcadedb.ha.raftStorageDirectory` to a *static* path (e.g. `/home/arcadedb/raft-storage`) and mount *that* path on durable storage before upgrading. On Kubernetes, back it with a `PersistentVolumeClaim` — see <>. On a plain filesystem you can instead copy the existing `raft-storage-` directory across the upgrade. A throwaway or test cluster that intentionally wants ephemeral behavior can opt out with `arcadedb.ha.raftPersistStorage=false`. +*Migration:* the per-node directory is named `raft-storage-` dynamically, which container runtimes and Kubernetes cannot mount as a volume (a wildcard like `+raft-storage-*+` is created as a literal directory, not expanded). Set `arcadedb.ha.raftStorageDirectory` to a *static* path (e.g. `/home/arcadedb/raft-storage`) and mount *that* path on durable storage before upgrading. On Kubernetes, back it with a `PersistentVolumeClaim` — see <>. On a plain filesystem you can instead copy the existing `raft-storage-` directory across the upgrade. A throwaway or test cluster that intentionally wants ephemeral behavior can opt out with `arcadedb.ha.raftPersistStorage=false`. [[ha-remote-commit-contract]] ===== Committed-remotely response contract diff --git a/src/main/asciidoc/how-to/operations/observability.adoc b/src/main/asciidoc/how-to/operations/observability.adoc index 15588732a..74f414e72 100644 --- a/src/main/asciidoc/how-to/operations/observability.adoc +++ b/src/main/asciidoc/how-to/operations/observability.adoc @@ -171,7 +171,7 @@ grouped under `db="unknown"` rather than appearing one series per name. | `arcadedb.engine.commit.lock.handoffs` / `.commit.lock.handoff.seconds` | Those locks passed to a waiting commit, and the total time from the release to that commit running _(Since v26.11.1)_ |=== -The `commit.lock.*` and `wal.group.*` counters are read as averages. `rate(...hold.seconds) / rate(...holds)` is how long +The `+commit.lock.*+` and `+wal.group.*+` counters are read as averages. `rate(...hold.seconds) / rate(...holds)` is how long a commit keeps the files it writes locked, and with many clients writing into the same type it is what limits the commits per second: about 30 microseconds is normal, milliseconds mean the commits are waiting for the disk while they hold the locks (`arcadedb.txWalFlush` set to 1 or 2 with `arcadedb.txWalGroupCommit=false`). `rate(wal.group.commits) / rate(wal.group.commit.syncs)` diff --git a/src/main/asciidoc/how-to/operations/upgrade.adoc b/src/main/asciidoc/how-to/operations/upgrade.adoc index 164656f96..b4e5ab5ef 100644 --- a/src/main/asciidoc/how-to/operations/upgrade.adoc +++ b/src/main/asciidoc/how-to/operations/upgrade.adoc @@ -796,7 +796,7 @@ Every entry of `list queries` and `SHOW TRANSACTIONS` now carries the client tha [discrete] ====== 26.10.2 - Graph algorithms answer the incoming side of unidirectional edge types -The `algo.*` and `path.*` procedures, the path-finding SQL functions (`dijkstra()`, `astar()`, `bellmanFord()`, `cchShortestPath()`, `duanSSSP()`) and the `node.degree*` / `node.relationship.*` functions now follow the edges of a `UNIDIRECTIONAL` type from their target too, as a Graph Analytical View already did (issue #8629). Before, they saw such an edge from its source only, so for example a walk with direction `IN` or `BOTH` missed it. `refactor.mergeNodes`, `refactor.cloneNodesWithRelationships` and changing a node's labels in Cypher no longer drop the incoming unidirectional edges of the node. +The `+algo.*+` and `+path.*+` procedures, the path-finding SQL functions (`dijkstra()`, `astar()`, `bellmanFord()`, `cchShortestPath()`, `duanSSSP()`) and the `+node.degree*+` / `+node.relationship.*+` functions now follow the edges of a `UNIDIRECTIONAL` type from their target too, as a Graph Analytical View already did (issue #8629). Before, they saw such an edge from its source only, so for example a walk with direction `IN` or `BOTH` missed it. `refactor.mergeNodes`, `refactor.cloneNodesWithRelationships` and changing a node's labels in Cypher no longer drop the incoming unidirectional edges of the node. *Impact:* results over unidirectional edge types can change (more paths, higher in-degrees, different scores), and now match the results with a Graph Analytical View. @@ -805,7 +805,7 @@ The `algo.*` and `path.*` procedures, the path-finding SQL functions (`dijkstra( [discrete] ====== 26.10.2 - Cypher counts match an undirected self loop once -An undirected relationship pattern such as `(n)-[:KNOWS]-(m)` matches a self loop (an edge from a vertex to itself) once, as the openCypher specification and Neo4j do. The rows of such a pattern always did, but the counts computed without materializing the rows counted it twice: `MATCH (a)-[:KNOWS]-(b) RETURN count(*)`, a `count(*)` grouped by the start node, `OPTIONAL MATCH ... WITH n, count(m)` and the `COUNT { }` subquery, with and without a Graph Analytical View (issues #8750, #9540). The same release corrects the count of a chain with an inequality such as `WHERE a <> c` when the chain has a labelled or multi-hop part before or after the two positions the inequality compares. +An undirected relationship pattern such as `(n)-[:KNOWS]-(m)` matches a self loop (an edge from a vertex to itself) once, as the openCypher specification and Neo4j do. The rows of such a pattern always did, but the counts computed without materializing the rows counted it twice: `+MATCH (a)-[:KNOWS]-(b) RETURN count(*)+`, a `+count(*)+` grouped by the start node, `OPTIONAL MATCH ... WITH n, count(m)` and the `COUNT { }` subquery, with and without a Graph Analytical View (issues #8750, #9540). The same release corrects the count of a chain with an inequality such as `WHERE a <> c` when the chain has a labelled or multi-hop part before or after the two positions the inequality compares. *Impact:* those counts can be lower than before on graphs with self loops, or on such chains, and now equal the number of rows the same `MATCH` returns. @@ -949,7 +949,7 @@ A named Cypher constraint now records what its `CREATE CONSTRAINT` created, and *Action:* drop an old constraint by its index name, or through SQL (`DROP INDEX`, `ALTER PROPERTY ... MANDATORY false`). See <>. [discrete] -====== 26.10.2 - The `path.*` procedures answer as `apoc.path.*` does +====== 26.10.2 - The `+path.*+` procedures answer as `+apoc.path.*+` does `path.expandconfig` now reads its `uniqueness` option, with the APOC default `RELATIONSHIP_PATH`, and `path.expand` is breadth-first with `RELATIONSHIP_PATH` (issue #10049). Before, the breadth-first walk was always `NODE_GLOBAL` and the depth-first walk and `path.expand` always `NODE_PATH`. `path.subgraphall` returns every relationship between the returned nodes, whatever the `relationshipFilter` (issue #10048). `relationshipFilter` and `labelFilter` read the APOC syntax, and an unknown relationship type or label is refused (issue #10025). diff --git a/src/main/asciidoc/reference/cypher/cypher-clauses.adoc b/src/main/asciidoc/reference/cypher/cypher-clauses.adoc index e38859097..420e6fbfb 100644 --- a/src/main/asciidoc/reference/cypher/cypher-clauses.adoc +++ b/src/main/asciidoc/reference/cypher/cypher-clauses.adoc @@ -134,7 +134,7 @@ RETURN p, length(p) `WALK` mode without an explicit maximum hop bound will throw an error. Always specify an upper bound, e.g., `+[*1..10]+`. ==== -The path mode applies to a variable-length pattern predicate too: `WHERE (a)-[*]->(a)` is true when a cycle leads back to `a`, as `MATCH (a)-[*]->(a)` finds it, and `WHERE (a)-[*0..]->(a)` is true through the zero-length path. +The path mode applies to a variable-length pattern predicate too: `+WHERE (a)-[*]->(a)+` is true when a cycle leads back to `a`, as `+MATCH (a)-[*]->(a)+` finds it, and `+WHERE (a)-[*0..]->(a)+` is true through the zero-length path. --- @@ -158,7 +158,7 @@ RETURN first, second // one row: (:A), (:C) Inside a pattern comprehension, the relationship variable of a variable-length hop is the list of relationships walked, as in `MATCH`: `[(a)-[r*]->(x) | size(r)]` returns the length of each path, and a zero-length hop binds the empty list. -A variable-length hop reaches a vertex only through a relationship whose record exists. When the record is gone but the edge lists of its two endpoints still name it (a dangling entry, left behind by an incomplete replication or a partially rolled-back transaction), the entry is skipped and reported in the log, whatever the spelling of the hop (`*`, `*2..4`, undirected) and the path mode. Another, live, relationship to the same vertex still reaches it. The check asks the bucket whether the record exists, once per vertex reached, and reads no record. Before 26.11.1 such an entry was followed and returned a vertex that no relationship reached. An undirected hop over an edge type declared unidirectional skips it too (_since v26.11.1_; before, the outgoing list of such a hop was followed, and an undirected walk that read the relationships failed with a `RecordNotFoundException` instead of skipping the entry). A fixed-length hop (`-[:R]->`), `out()` and `in()` still follow a dangling entry, as does everything that counts the entries of a vertex (its degree, `count(*)` over a hop): they answer from the edge lists without reading an edge, and probing each entry would make a count a walk of the edge pages. The way to remove the anomaly is `CHECK DATABASE FIX`, which prunes the dangling entries; after it every spelling of a hop agrees. +A variable-length hop reaches a vertex only through a relationship whose record exists. When the record is gone but the edge lists of its two endpoints still name it (a dangling entry, left behind by an incomplete replication or a partially rolled-back transaction), the entry is skipped and reported in the log, whatever the spelling of the hop (`+*+`, `+*2..4+`, undirected) and the path mode. Another, live, relationship to the same vertex still reaches it. The check asks the bucket whether the record exists, once per vertex reached, and reads no record. Before 26.11.1 such an entry was followed and returned a vertex that no relationship reached. An undirected hop over an edge type declared unidirectional skips it too (_since v26.11.1_; before, the outgoing list of such a hop was followed, and an undirected walk that read the relationships failed with a `RecordNotFoundException` instead of skipping the entry). A fixed-length hop (`-[:R]->`), `out()` and `in()` still follow a dangling entry, as does everything that counts the entries of a vertex (its degree, `+count(*)+` over a hop): they answer from the edge lists without reading an edge, and probing each entry would make a count a walk of the edge pages. The way to remove the anomaly is `CHECK DATABASE FIX`, which prunes the dangling entries; after it every spelling of a hop agrees. --- @@ -176,7 +176,7 @@ RETURN count(DISTINCT b) The answer holds the same rows and values as the one path enumeration gives, in every direction and every <>; only their order can differ, see below. The shortcut applies when the statement is nothing but one non-optional `MATCH` with one hop, followed by `RETURN`, and: -* the lower bound of the hop is 0 or 1 (`*`, `*..n`, `*0..n` and `*1..n`); +* the lower bound of the hop is 0 or 1 (`+*+`, `+*..n+`, `+*0..n+` and `+*1..n+`); * there is no named path and the relationship variable is not used anywhere (not in `WHERE`, `RETURN`, `ORDER BY` or an inline `WHERE`); * the `RETURN` is either `DISTINCT` over the start or end vertices or their properties, or it groups by such values and aggregates them with `count(DISTINCT ...)`, `collect(DISTINCT ...)`, `sum(DISTINCT ...)`, `avg(DISTINCT ...)`, `min(...)` or `max(...)`. `ORDER BY` may use those values or the output names. @@ -230,7 +230,7 @@ even though `(x)` on its own would always match. *Returning a path.* A path returned as it is reaches the client as one path value, whether a `MATCH` pattern, `shortestPath()`, `allShortestPaths()`, a path procedure or `apoc.path.create`/`slice`/`combine` built it. A Bolt driver receives a `Path`. Over HTTP, gRPC and the PostgreSQL protocol, which have no path type, it is the object -`{"_type": "path", "nodes": [...], "relationships": [...], "length": n}`, with every node and relationship written whole, +`+{"_type": "path", "nodes": [...], "relationships": [...], "length": n}+`, with every node and relationship written whole, and Studio's graph view draws its nodes and relationships. _(Since v26.10.2)_ In 26.10.1 and earlier a `MATCH` path was sent over HTTP, gRPC and PostgreSQL as its text form. See <>. diff --git a/src/main/asciidoc/reference/cypher/cypher-compatibility.adoc b/src/main/asciidoc/reference/cypher/cypher-compatibility.adoc index 74a6aa7bd..11f7fe600 100644 --- a/src/main/asciidoc/reference/cypher/cypher-compatibility.adoc +++ b/src/main/asciidoc/reference/cypher/cypher-compatibility.adoc @@ -323,12 +323,12 @@ property `STRING` when its values may look like a time or a duration but are pla converted. The `millisecond`, `microsecond` and `nanosecond` fields of `time()`, `localtime()`, `datetime()`, `localdatetime()` -and the `*.truncate()` functions must each fit in their own range, as in Neo4j: `millisecond` 0-999, `microsecond` +and the `+*.truncate()+` functions must each fit in their own range, as in Neo4j: `millisecond` 0-999, `microsecond` 0-999 when `millisecond` is also given (else 0-999999), `nanosecond` 0-999 when a coarser field is given (else up to 999999999). A value out of range, such as `time({hour: 12, millisecond: 5000})`, is an error rather than being carried into the seconds. Given with a base value (`time:`, `datetime:`, ...), these fields replace its whole fraction of second: `localtime({time: localtime('12:30:45.123456789'), nanosecond: 5})` is `12:30:45.000000005`. The adjustment map of a -`*.truncate()` keeps the units the truncation left: `localtime.truncate('millisecond', t, {nanosecond: 5})` keeps the +`+*.truncate()+` keeps the units the truncation left: `localtime.truncate('millisecond', t, {nanosecond: 5})` keeps the milliseconds of `t`. `datetime({epochSeconds: ...})` and `datetime({epochMillis: ...})` take a `timezone` like any other `datetime()` map, diff --git a/src/main/asciidoc/reference/graph-algorithms/path-finding.adoc b/src/main/asciidoc/reference/graph-algorithms/path-finding.adoc index c44f275f0..8be591552 100644 --- a/src/main/asciidoc/reference/graph-algorithms/path-finding.adoc +++ b/src/main/asciidoc/reference/graph-algorithms/path-finding.adoc @@ -18,7 +18,7 @@ RETURN [n IN nodes(path) | n.name] AS route, length(path) AS hops, weight ---- A Bolt driver receives it as a `Path`. Over HTTP, gRPC and the PostgreSQL protocol, which have no path type, it is the -object `{"_type": "path", "nodes": [...], "relationships": [...], "length": n}`, with every node and relationship written +object `+{"_type": "path", "nodes": [...], "relationships": [...], "length": n}+`, with every node and relationship written whole. This shape is not limited to these procedures: since v26.10.2 every Cypher path (a `MATCH p = ...` pattern, `shortestPath()`, `allShortestPaths()`, `apoc.path.create`, `apoc.path.slice` and `apoc.path.combine`) is the same path value and reaches the client the same way, and Studio's graph view draws it. In 26.10.1 and earlier a `MATCH` path was diff --git a/src/main/asciidoc/reference/graphql/graphql.adoc b/src/main/asciidoc/reference/graphql/graphql.adoc index 813e185e6..6040c6589 100644 --- a/src/main/asciidoc/reference/graphql/graphql.adoc +++ b/src/main/asciidoc/reference/graphql/graphql.adoc @@ -97,7 +97,7 @@ In 26.10.2 and earlier a query running while types were being defined could fail [[graphql-introspection-types]] ==== Introspection of database types -Introspection (`__type(name:)` and `__schema { types }`) also describes the database types that have no GraphQL type definition, deriving each field from the property type. +Introspection (`+__type(name:)+` and `+__schema { types }+`) also describes the database types that have no GraphQL type definition, deriving each field from the property type. _(Since v26.10.2)_ Every field is described as the GraphQL result serializes it: [cols="2,3", options="header"] @@ -117,7 +117,7 @@ _(Since v26.10.2)_ Every field is described as the GraphQL result serializes it: | `DATE`, `DATETIME` and the other temporal types, `STRING` | `SCALAR String` |=== -`JSON` and `BigDecimal` are custom scalars, listed by `__schema { types }` and resolved by `__type(name:)` like `Long`. +`JSON` and `BigDecimal` are custom scalars, listed by `+__schema { types }+` and resolved by `+__type(name:)+` like `Long`. They describe the value only: no coercion is applied to a result or to an argument. In 26.10.1 and earlier, `MAP`, `EMBEDDED`, `LINK`, `DECIMAL` and `BINARY` properties were described as `String`. diff --git a/src/main/asciidoc/reference/grpc-api.adoc b/src/main/asciidoc/reference/grpc-api.adoc index bf2798d8c..379d65642 100644 --- a/src/main/asciidoc/reference/grpc-api.adoc +++ b/src/main/asciidoc/reference/grpc-api.adoc @@ -19,7 +19,7 @@ Enable it by adding the plugin to the `arcadedb.server.plugins` setting, then st The format is `:` (comma-separated when registering several plugins). Once enabled, the server listens on port `50051` by default. Confirm it started by looking for a `gRPC server started` line in the server log, or with `grpcurl -plaintext localhost:50051 list`. -Host, port, TLS, message size and the other gRPC options are the `arcadedb.grpc.*` settings - see <> for the full list. Since v26.10.2 every one of them is a registered server setting, read like `arcadedb.server.plugins` from the server configuration file, from environment variables and from JVM system properties (`-Darcadedb.grpc.*`). In v26.10.1 and earlier only `arcadedb.grpc.port` was; the others were read as JVM system properties only, so a `grpc.tls.enabled` written in the configuration file was ignored. +Host, port, TLS, message size and the other gRPC options are the `+arcadedb.grpc.*+` settings - see <> for the full list. Since v26.10.2 every one of them is a registered server setting, read like `arcadedb.server.plugins` from the server configuration file, from environment variables and from JVM system properties (`+-Darcadedb.grpc.*+`). In v26.10.1 and earlier only `arcadedb.grpc.port` was; the others were read as JVM system properties only, so a `grpc.tls.enabled` written in the configuration file was ignored. _(Since v26.10.2.)_ `arcadedb.grpc.host` binds the standard gRPC server to every local address the name resolves to (in v26.10.1 and earlier it was read but never applied, so the server listened on every interface); the xDS server (`grpc.mode` `xds` or `both`) has no host setting and still listens on every interface. @@ -165,10 +165,10 @@ granted the named database; every other RPC requires the `root` user and otherwi *Leader routing.* On a replicated cluster, `CreateDatabase`, `DropDatabase`, `CreateUser`, `UpdateUser`, `DeleteUser`, `SaveGroup`, `DeleteGroup`, `CreateApiToken`, `DeleteApiToken`, `RestoreBackup`, `RestoreDatabase`, and `ImportDatabase` are refused on a follower with `FAILED_PRECONDITION` and the leader's address in the -`arcadedb-leader-*` trailers. gRPC has no request proxy, so the client redirects itself. _(Since v26.10.2.)_ The +`+arcadedb-leader-*+` trailers. gRPC has no request proxy, so the client redirects itself. _(Since v26.10.2.)_ The streaming inserts of `ArcadeDbService` (`InsertStream` and `InsertBidirectional`, and the rows of a `BulkInsert`) report a write a follower refused differently: the refused row or commit comes back as an `InsertError` with `code` -`NOT_LEADER` and the leader's address in `leader_address`, rather than through the `arcadedb-leader-*` trailers (see +`NOT_LEADER` and the leader's address in `leader_address`, rather than through the `+arcadedb-leader-*+` trailers (see <>). *Server info and databases* diff --git a/src/main/asciidoc/reference/http-api/http.adoc b/src/main/asciidoc/reference/http-api/http.adoc index 16b79c5ca..953056e00 100644 --- a/src/main/asciidoc/reference/http-api/http.adoc +++ b/src/main/asciidoc/reference/http-api/http.adoc @@ -113,7 +113,7 @@ statements, or an edit to `server-users.jsonl`. Since version 26.10.1 a token issued by one server of a cluster is honoured by every other server, so a load balancer in front of the cluster needs no sticky sessions. The token names the server that issued it, `AU--`, where the server name is the value of `arcadedb.server.name` (any character outside -letters, digits, `.`, `_` and `-` is replaced with `_`). A server that receives a token it has never seen asks +letters, digits, `.`, `+_+` and `-` is replaced with `+_+`). A server that receives a token it has never seen asks the issuer once, through the cluster-internal channel protected by the cluster token, and keeps a local copy for the following requests. The copy is confirmed with the issuer periodically (a third of `server.httpAuthSessionExpireTimeout`, at most every 60 seconds), which also counts as activity on the issuer, @@ -567,7 +567,7 @@ running on in `value` and the built-in default in `default`, with `overridden` t because the setting was given an explicit value - in the server configuration file, by an embedding application, or at runtime with `set server setting`. Secrets are masked: settings that are secrets in their entirety (the HA cluster token, any key containing -`password`) report `*****`, and `arcadedb.server.defaultDatabases`, whose value merely *embeds* credentials, keeps +`password`) report `+*****+`, and `arcadedb.server.defaultDatabases`, whose value merely *embeds* credentials, keeps its database names, user names and groups while each password is replaced _(since v26.10.1)_. Responses: diff --git a/src/main/asciidoc/reference/java-api/java-select-api.adoc b/src/main/asciidoc/reference/java-api/java-select-api.adoc index b60fac8a5..d46f9bbff 100644 --- a/src/main/asciidoc/reference/java-api/java-select-api.adoc +++ b/src/main/asciidoc/reference/java-api/java-select-api.adoc @@ -343,9 +343,9 @@ literal value. A nested array **is a parenthesis**: it is evaluated as one unit, re-associated `(a = 2 AND b = 1) OR b = 3`. This is the only way to hand the native API an explicitly grouped condition: the fluent builder appends conditions left to right and applies operator precedence. -A literal value that itself starts with `:` or `#` is escaped by doubling that leading character (e.g. the literal +A literal value that itself starts with `:` or `+#+` is escaped by doubling that leading character (e.g. the literal `":alice"` is written as `"::alice"`), so it is never confused with a property or parameter reference. This -happens automatically on serialization; a hand-written literal starting with `:` or `#` must be doubled the same +happens automatically on serialization; a hand-written literal starting with `:` or `+#+` must be doubled the same way. The right operand of `between` and `in` is a plain array of values -- `[":age", "between", [18, 65]]` -- which is diff --git a/src/main/asciidoc/reference/lightweight-edges.adoc b/src/main/asciidoc/reference/lightweight-edges.adoc index 469f52fc8..a7870ab64 100644 --- a/src/main/asciidoc/reference/lightweight-edges.adoc +++ b/src/main/asciidoc/reference/lightweight-edges.adoc @@ -89,7 +89,7 @@ DELETE FROM Follows Because a lightweight edge has no record, none of them can read the edge type's own storage: they read the vertices that hold the edges. Two consequences are worth knowing before you rely on them: -* *`count(*)` is not instant.* On a regular type it answers from a stored counter; on a lightweight type no such counter can be correct, so the count reads the graph. A throttled `WARNING` in the server log tells you when this happens on a large database. +* *`+count(*)+` is not instant.* On a regular type it answers from a stored counter; on a lightweight type no such counter can be correct, so the count reads the graph. A throttled `WARNING` in the server log tells you when this happens on a large database. * *`LIMIT` does not make it cheap.* There is nothing to seek into, so a small `LIMIT` may still read a large part of the vertex set. Anchoring the query on the vertices stays the cheapest way to reach these edges, and is unchanged: diff --git a/src/main/asciidoc/reference/settings.adoc b/src/main/asciidoc/reference/settings.adoc index c4a54cabe..8716ccca5 100644 --- a/src/main/asciidoc/reference/settings.adoc +++ b/src/main/asciidoc/reference/settings.adoc @@ -447,7 +447,7 @@ The following plugins are available for ArcadeDB Server: |`backup.maxMBPerSecond`|Cap, in MB/s, on the rate a full backup reads the database files, so it cannot saturate the disk. A slower backup keeps writers throttled for longer. `0` means no limit|Integer|0 |`checkDatabaseAdjacencyCacheEntries`|Maximum number of adjacency-list entries `CHECK DATABASE` keeps in memory while it checks back references, so the edge list of a super node is walked once instead of once per edge (about 20MB at the default). The least recently probed list is evicted first. `0` disables the cache|Integer|1000000 |`checkDatabaseRepairBatchPages`|Number of modified pages `CHECK DATABASE ... FIX` accumulates before it commits its repair and opens the next transaction, so an HA Raft entry stays below the replicated entry limit. Also bounds `CHECK DATABASE ... COMPRESS`. A soft limit, checked between units of work. `0` disables batching: the whole repair is one transaction|Integer|256 -|`command.regexTimeout`|Maximum time in milliseconds a regular expression evaluation may run before it is aborted, against catastrophic backtracking. Covers SQL `MATCHES`, `LIKE` and `ILIKE`, openCypher `=~`, `text.regexReplace()`, `.normalize()`, full-text regexp and wildcard queries, PromQL `=~`/`!~` and `REGEXP` property validation. One deadline is shared by a whole command (or by a whole scan outside a command), not one per row. Applies even when `command.timeout` is `0`. `0` disables it (not recommended)|Long|1000 +|`command.regexTimeout`|Maximum time in milliseconds a regular expression evaluation may run before it is aborted, against catastrophic backtracking. Covers SQL `MATCHES`, `LIKE` and `ILIKE`, openCypher `+=~+`, `text.regexReplace()`, `.normalize()`, full-text regexp and wildcard queries, PromQL `+=~+`/`+!~+` and `REGEXP` property validation. One deadline is shared by a whole command (or by a whole scan outside a command), not one per row. Applies even when `command.timeout` is `0`. `0` disables it (not recommended)|Long|1000 |`externalPropertyBucketDefaultPageSize`|Default page size in bytes of the paired buckets that hold `EXTERNAL` property values (embeddings, large strings, JSON). Larger than the primary bucket default to reduce multi-page records. Minimum is 1048 bytes _(since v26.11.1)_. See <>|Integer|262144 |`externalPropertyBucketPath`|Directory where new paired external-property buckets are created. Empty (default) keeps them next to the primary buckets. Set it to cheaper or slower storage to tier the heavy values away from the rest of the database. The directory must exist and be writable; existing buckets are not moved. Since v26.10.2 an `ALTER DATABASE` of it applies on every node of an HA cluster, so the path must exist on all of them|String| |`flushAllPagesTimeout`|Milliseconds without flush progress after which waiting for all the pages of a database to reach the disk (on close, rename, or when a backup suspends flushing) gives up with a SEVERE log instead of hanging on a wedged flush. The window restarts whenever the number of pending pages goes down, so a slow but healthy backlog never trips it. A close that gives up keeps the WAL files and the lock file, so the next open runs recovery and replays the unflushed pages. `0` waits forever|Long|60000 @@ -459,7 +459,7 @@ The following plugins are available for ArcadeDB Server: |`index.buildChunkSizeMB`|Size in MB of the transaction chunks of a bulk index build with the WAL disabled. Larger chunks reduce commit overhead and use more memory|Long|50 |`index.buildSortedNonUniqueMinRecords`|Minimum number of records (subtypes included) a type must hold for a `NOTUNIQUE` LSM index created with the default build mode to use the sorted build instead of adding records one at a time, which pays the transaction bookkeeping of every key. When the keys fit the memory budget of the sorted build it takes about two thirds of the time. A build on a replicated database or inside a transaction with pending changes always adds records one at a time. `0` turns the switch off. _Since v26.11.1_|Long|1000000 |`index.buildSortedUniqueMinRecords`|Minimum number of records (subtypes included) a type must hold for a `UNIQUE` LSM index created with the default build mode to use the sorted build instead of adding records one at a time, whose duplicate check made the build time grow with the square of the record count. A build on a replicated database or inside a transaction with pending writes to the buckets it indexes always adds records one at a time, but from this many records on it no longer checks every key against the index it is filling: it checks the keys once, over the finished index, and refuses the first duplicate then, so its time grows with the record count, and a duplicate costs the whole build before it is reported. `0` turns the switch off, and that deferred check with it. _Since v26.11.1_|Long|10000 -|`indexBloomFilterRate`|Target false-positive rate of the bloom filter an index compaction writes for each compacted series of an LSM index, so a point lookup skips a series that cannot hold the key. Helps most when the series overlap in key range (UUIDs, emails, a bulk load into a `UNIQUE` index); range scans never use it. Costs about 1.2 bytes per key on disk at 1%, in an `_bf.bfidx` file. Existing indexes get filters at their next compaction. `0` disables it|Float|0.01 +|`indexBloomFilterRate`|Target false-positive rate of the bloom filter an index compaction writes for each compacted series of an LSM index, so a point lookup skips a series that cannot hold the key. Helps most when the series overlap in key range (UUIDs, emails, a bulk load into a `UNIQUE` index); range scans never use it. Costs about 1.2 bytes per key on disk at 1%, in an `+_bf.bfidx+` file. Existing indexes get filters at their next compaction. `0` disables it|Float|0.01 |`indexCompactionFullSeriesThreshold`|Number of compacted series at which an index compaction becomes a full compaction, merging every series and the mutable pages into one and dropping deleted entries. A full compaction can also start earlier, see `indexCompactionFullDeadEntriesRatio`. `0` disables it|Integer|10 |`indexCompactionFullDeadEntriesRatio`|Starts a full compaction of an LSM index as soon as the deleted keys its range scans have skipped since the last full compaction reach this multiple of the entries of its compacted part, whatever the series count. Incremental compactions keep the deleted entries, so under a sliding window (insert the newest ids, `DELETE ... WHERE id < ?` the oldest) every scan from the low end used to walk every key deleted since the last full compaction until `indexCompactionFullSeriesThreshold` was reached. An index whose deleted keys no scan crosses is left to the series threshold. In a cluster the leader compacts, so the scans counted are the ones the leader served. Watch `deadEntriesSkippedSinceFullCompaction` in the index statistics. `0` disables it. _Since v26.11.1_|Float|1 |`indexDefaultPageSize`|Default page size in bytes for new plain LSM-tree indexes (not full-text, geo or vector ones) created without an explicit page size (SQL `CREATE INDEX` has no page-size clause). A transaction copies every page it changes, so smaller pages make small write transactions cheaper while larger ones make lookups slightly faster. Existing indexes keep their page size. Minimum is 8192. In a cluster, keep it identical on all the servers. _Since v26.10.1_|Integer|262144 @@ -471,7 +471,7 @@ The following plugins are available for ArcadeDB Server: |`command.warningsEvery`|Reduce warnings in commands to print in console only every X occurrences. Use 0 to disable warnings with commands|Integer|100 |`commitLockTimeout`|Timeout in ms to lock resources during commit. A bulk index build uses `index.buildCommitLockTimeout` instead for its own commits _(since v26.10.1)_|Long|5000 |`explicitLockTimeout`|Timeout in ms to acquire the resources of an explicit `LOCK`. Separate from `commitLockTimeout`, which bounds the locking the engine does for you at commit time _(honoured since v26.10.1)_|Long|5000 -|`cypher.algoMaxWorkingMemory`|Maximum heap, in bytes, that a single `algo.*` procedure call may reserve for its working set: the graph it loads without a view (vertex list, RID index), the adjacency lists, weighted adjacency lists and degree buffers, the edge arrays of `algo.mst`/`algo.msa`, the visited sets and traversal state of the walks, the neighbour bitsets of the clique, triangle and truss algorithms, walk buffers, label memory, embedding and feature matrices, `nodeCount x nodeCount` distance/similarity/capacity matrices, and terminal tables and terminal-pair arrays. A call over budget is refused before allocating, naming the offending component. Negative means no limit, a positive value is a fixed budget. 0 (the default since v26.11.1) sizes the budget at each call from the heap the call can get: half of the heap free after the last collection plus the page cache, which the call takes back from the cache before it allocates, and never less than 1/8 of the max heap (64MB at least), the fixed default it replaces. Before v26.11.1 the default was that fixed 1/8, which on a 2GB heap refused `algo.wcc()` on a graph of 34M edges whose 280MB adjacency fitted (issue #11011). _Graph load coverage added in v26.9.1_|Long|0 (sized from the heap) +|`cypher.algoMaxWorkingMemory`|Maximum heap, in bytes, that a single `+algo.*+` procedure call may reserve for its working set: the graph it loads without a view (vertex list, RID index), the adjacency lists, weighted adjacency lists and degree buffers, the edge arrays of `algo.mst`/`algo.msa`, the visited sets and traversal state of the walks, the neighbour bitsets of the clique, triangle and truss algorithms, walk buffers, label memory, embedding and feature matrices, `nodeCount x nodeCount` distance/similarity/capacity matrices, and terminal tables and terminal-pair arrays. A call over budget is refused before allocating, naming the offending component. Negative means no limit, a positive value is a fixed budget. 0 (the default since v26.11.1) sizes the budget at each call from the heap the call can get: half of the heap free after the last collection plus the page cache, which the call takes back from the cache before it allocates, and never less than 1/8 of the max heap (64MB at least), the fixed default it replaces. Before v26.11.1 the default was that fixed 1/8, which on a 2GB heap refused `algo.wcc()` on a graph of 34M edges whose 280MB adjacency fitted (issue #11011). _Graph load coverage added in v26.9.1_|Long|0 (sized from the heap) |`opencypher.bulkCreateBatchSize`|Batch size of bulk `CREATE` after an `UNWIND`: the records are created in batches to reduce transaction overhead. `0` disables batching|Integer|20000 |`opencypher.foreachEagerRead`|Runs `FOREACH` to completion before a later clause of the same query reads the graph (`MATCH`, `MERGE`, `CALL`), so every row sees the complete result of the `FOREACH`. The input rows are kept in memory meanwhile. `false` restores the streaming behavior for very large inputs, at the price of the later read seeing a partly applied `FOREACH`|Boolean|true |`opencypher.labelWriteDegreeLimit`|Maximum degree of a vertex a Cypher label change may rewrite; above it the command fails. `0` (default) disables the limit|Integer|0 @@ -493,8 +493,8 @@ The following plugins are available for ArcadeDB Server: |`gavPersistCsr`|When true, a Graph Analytical View (GAV/CSR) that is READY (with no pending overlay changes) when the database closes cleanly writes its CSR to disk alongside a freshness certificate (the database's last committed transaction id at build time). If nothing was committed to the database between that close and the next open, the certificate still matches and the persisted CSR is reused as-is instead of being rebuilt by a full graph scan. Any commit in between invalidates the certificate and falls back to the previous behavior: an async rebuild triggered on open. Set to false to disable persisting the CSR file (e.g. to avoid its disk footprint or the extra write at close). See <>. _Since v26.9.1_|Boolean|true |`gavBuildReleasesPages`|When true, the pages a Graph Analytical View build reads from disk leave the page cache when the build ends, unless another reader used them meanwhile: the view answers what they hold, and the page cache is on the heap. While the build runs they are the first pages evicted. Pages the cache held before the build stay. Set to false to keep them, for a workload that reads the same records through the record API right after the build. See <>. _Since v26.11.1_|Boolean|true |`gavRestoreAwaitTimeout`|Milliseconds `database.open()` blocks waiting for Graph Analytical Views (GAV/CSR) restored from persisted definitions to reach READY before returning. 0 (default) does not wait: when no persisted CSR plausibly applies, the full rebuild is still triggered immediately in the background, but `open()` returns before it completes and queries issued right after run unaccelerated until it does; when a persisted CSR does plausibly apply (see `gavPersistCsr`), nothing is even started at `open()` time as of v26.9.1 - it is deferred to whichever query touches the view first (see <>), so the fast path costs nothing for a session that never queries it. A positive value here forces the wait either way, trading a slower `open()` for the restored/rebuilt view being usable by the query that triggered the reopen. See <>|Long|0 -|`gavQueryRestoreAwaitTimeout`|Milliseconds a query waits, while it is planned or starts, for a Graph Analytical View (GAV/CSR) whose deferred restore from disk is still in flight, so the first count push-down, `MATCH`, shortest path or other graph function after a reopen runs on the restored view instead of reading every record. Covers the SQL and OpenCypher planners and the SQL graph functions; the `algo.*` procedures have their own budget (`gavAlgoRestoreAwaitTimeout`). The wait ends early when the restore finishes, and a view that is only rebuilding after a commit is never waited for. `0` does not wait: the call takes the record path. The wait is not tied to the command timeout, so keep it small. Since v26.10.2.|Long|5000 -|`gavAlgoRestoreAwaitTimeout`|Milliseconds a whole-graph algorithm (`algo.wcc`, `algo.pagerank`, `algo.bfs` and the other `algo.*` procedures) waits for a Graph Analytical View whose deferred restore from disk is still in flight, so the first call after a reopen runs on the restored view instead of scanning every record. The wait ends early when the restore finishes or the command timeout expires. `0` does not wait. A command without its own timeout can block for the whole budget, so lower it if such commands must answer quickly. Separate from `gavRestoreAwaitTimeout`, which makes `open()` itself block. Since v26.10.1.|Long|60000 +|`gavQueryRestoreAwaitTimeout`|Milliseconds a query waits, while it is planned or starts, for a Graph Analytical View (GAV/CSR) whose deferred restore from disk is still in flight, so the first count push-down, `MATCH`, shortest path or other graph function after a reopen runs on the restored view instead of reading every record. Covers the SQL and OpenCypher planners and the SQL graph functions; the `+algo.*+` procedures have their own budget (`gavAlgoRestoreAwaitTimeout`). The wait ends early when the restore finishes, and a view that is only rebuilding after a commit is never waited for. `0` does not wait: the call takes the record path. The wait is not tied to the command timeout, so keep it small. Since v26.10.2.|Long|5000 +|`gavAlgoRestoreAwaitTimeout`|Milliseconds a whole-graph algorithm (`algo.wcc`, `algo.pagerank`, `algo.bfs` and the other `+algo.*+` procedures) waits for a Graph Analytical View whose deferred restore from disk is still in flight, so the first call after a reopen runs on the restored view instead of scanning every record. The wait ends early when the restore finishes or the command timeout expires. `0` does not wait. A command without its own timeout can block for the whole budget, so lower it if such commands must answer quickly. Separate from `gavRestoreAwaitTimeout`, which makes `open()` itself block. Since v26.10.1.|Long|60000 |`graph.edgeAppendMerge`|At commit, when the only conflict on an edge-list page is concurrent in-chunk edge appends (which commute), re-apply the appends on top of the newer page version instead of failing the whole transaction with a retryable conflict. Removes the retry storm on super-node (hot vertex) edge insertion. See <>.|Boolean|true |`graph.edgeListInitialChunkSize`|Size in bytes of the first chunk of a vertex's edge list. Each further chunk doubles the previous one up to 8192, so the space a vertex allocates is the sum of that series - a smaller first chunk does not necessarily use less space, it just takes more chunks (each with its own header) to reach the same capacity. The best value follows the degree distribution: around 128 suits an average degree near 10, the default suits very sparse graphs, and above degree 100 the setting barely matters. Values below 32 are clamped. See <>.|Integer|64 |`graph.ghostEdgeProbe`|Whether the walks that follow adjacency entries without loading the edge record (variable-length Cypher hops, the BFS, DFS and path walks, `path.subgraphNodes` and `path.subgraphAll`) check once per vertex reached that the edge of the entry still has its record, and skip a dangling entry. The probe costs about a third of a variable-length hop query. Set to `false` only on a database known to hold no dangling entries, for example after `CHECK DATABASE FIX`: a database that does hold some can then answer differently depending on the projection. _Since v26.11.1_|Boolean|true @@ -545,10 +545,10 @@ The following plugins are available for ArcadeDB Server: |`pageSnapshotTrackOpeners`|Debug aid: record the stack of the code that opens each point-in-time snapshot window, so that a database close still waiting for a window after 60 seconds names the code path that opened it. Off by default, the window is still listed with its opening thread, age and status. _Since v26.11.1_|Boolean|false |`pageSnapshotSpillPath`|Directory for the scratch file a copy-on-write shadow spills into once `pageSnapshotMaxRAM` is exhausted. Empty (the default) uses the database directory, where the shadow competes for space with the very files it is protecting. The file is pure scratch: created on demand, deleted when the window closes, and never read by recovery|String| |`polyglotCommand.timeout`|Default timeout for polyglot commands (in ms)|Long|10000 -|`queryMaxHeapElementsAllowedPerOp`|Maximum number of elements (records) allowed in a single query for memory-intensive operations (eg. ORDER BY in heap). If exceeded, the query fails with an OCommandExecutionException. Negative number means no limit. In OpenCypher it also bounds the rows a Cartesian product or a hash join of disconnected patterns buffers, `ORDER BY`, `DISTINCT`, `UNION`, the groups of an aggregation, `collect()` and the eager read ahead of a write _(since v26.10.1)_. This setting is intended as a safety measure against excessive resource consumption from a single query (eg. prevent OutOfMemory). A `GROUP BY` computed by a parallel scan checks the limit on each thread and on the final groups, so its peak memory can reach the limit times the number of threads. A SQL time series aggregation (`ts.timeBucket()` with `GROUP BY`) that returns more buckets than this fails instead of exhausting the heap, as the HTTP, gRPC and Grafana time series endpoints already did _(since v26.10.1)_. It also bounds the index the delete of a vertex keeps in heap to find the incoming edges of unidirectional edge types: past it, each delete scans the types instead. Since v26.10.2 it also bounds the result of a SQL `LET` subquery, the records a SQL `MATCH` prefetches for a small root, and what an aggregate that keeps its values holds: `list()`, `set()`, `map()`, `unionAll()`, `intersect()`, `symmetricDifference()`, `percentile()`, `median()`, `mode()` and the `ts.*` window functions, and in OpenCypher `percentileCont()`, `percentileDisc()`, `count(DISTINCT ...)` and the same SQL aggregates called as `sql.*`. An aggregate is counted by what it holds, not by the rows it is given: a `set()` of a few distinct values over millions of rows stays far below the limit. Such a query used to run past the limit, so one that holds more than it may now fail where it completed before: raise the limit for it if the heap allows. Since v26.11.1 it bounds a subquery written inline in an expression too (a projection, a function argument, a `SET` value, the endpoints of `CREATE EDGE`) and the rows of a user defined SQL function, as it does the same subquery in a `LET`: one that returned more rows than the limit now fails where it completed before. In OpenCypher it also bounds, since v26.11.1, the list a `COLLECT { ... }` subquery or a pattern comprehension builds and the rows of `apoc.do.when()`, and the refusal of either heap limit is no longer turned into a `null` row by `OPTIONAL CALL`. This limit bounds one operation of one query; `queryMaxHeapRAM` bounds the heap the buffers of all the running queries hold together|Long|500000 +|`queryMaxHeapElementsAllowedPerOp`|Maximum number of elements (records) allowed in a single query for memory-intensive operations (eg. ORDER BY in heap). If exceeded, the query fails with an OCommandExecutionException. Negative number means no limit. In OpenCypher it also bounds the rows a Cartesian product or a hash join of disconnected patterns buffers, `ORDER BY`, `DISTINCT`, `UNION`, the groups of an aggregation, `collect()` and the eager read ahead of a write _(since v26.10.1)_. This setting is intended as a safety measure against excessive resource consumption from a single query (eg. prevent OutOfMemory). A `GROUP BY` computed by a parallel scan checks the limit on each thread and on the final groups, so its peak memory can reach the limit times the number of threads. A SQL time series aggregation (`ts.timeBucket()` with `GROUP BY`) that returns more buckets than this fails instead of exhausting the heap, as the HTTP, gRPC and Grafana time series endpoints already did _(since v26.10.1)_. It also bounds the index the delete of a vertex keeps in heap to find the incoming edges of unidirectional edge types: past it, each delete scans the types instead. Since v26.10.2 it also bounds the result of a SQL `LET` subquery, the records a SQL `MATCH` prefetches for a small root, and what an aggregate that keeps its values holds: `list()`, `set()`, `map()`, `unionAll()`, `intersect()`, `symmetricDifference()`, `percentile()`, `median()`, `mode()` and the `+ts.*+` window functions, and in OpenCypher `percentileCont()`, `percentileDisc()`, `count(DISTINCT ...)` and the same SQL aggregates called as `+sql.*+`. An aggregate is counted by what it holds, not by the rows it is given: a `set()` of a few distinct values over millions of rows stays far below the limit. Such a query used to run past the limit, so one that holds more than it may now fail where it completed before: raise the limit for it if the heap allows. Since v26.11.1 it bounds a subquery written inline in an expression too (a projection, a function argument, a `SET` value, the endpoints of `CREATE EDGE`) and the rows of a user defined SQL function, as it does the same subquery in a `LET`: one that returned more rows than the limit now fails where it completed before. In OpenCypher it also bounds, since v26.11.1, the list a `COLLECT { ... }` subquery or a pattern comprehension builds and the rows of `apoc.do.when()`, and the refusal of either heap limit is no longer turned into a `null` row by `OPTIONAL CALL`. This limit bounds one operation of one query; `queryMaxHeapRAM` bounds the heap the buffers of all the running queries hold together|Long|500000 |`parallelScanAbandonedTimeout`|Milliseconds a parallel scan waits for a result set that is neither read nor closed before giving up: the scan then frees its threads and the next read of that result set fails instead of silently returning fewer rows. A query whose `LIMIT` is reached releases its scan at once, without waiting for this timeout, and a result set left open never stalls other queries: they only run less in parallel until it is closed. Raise it for clients that keep cursors open with long pauses. 0 disables the timeout|Long|600000 |`queryIndexMaxSelectivity`|Share of a type's records above which an index search gives way to a full scan of the type, when the scan runs on one thread. Before loading any record, the matching index entries are read alone: when more of them match than this share of the records the type holds, the rows come from a scan filtered by the same condition, otherwise the matching records are loaded in physical order rather than in key order. A scan split across W workers of a parallel scan gives way sooner, at this share divided by (1 + W) / 2, because the index entries are read by one thread whatever the parallelism: with the default, 60% of the type for a sequential scan, 24% on 4 workers, 6% on 18. Applies, in SQL and OpenCypher, only where the order the rows arrive in cannot show in the result - the statement aggregates them or sorts them with an `ORDER BY` the index does not serve - and where the scan answers the same rows. 0 disables it _(since v26.10.1)_. Since v26.11.1 a change on an open database (`ALTER DATABASE`) reaches queries already run once: every database setting change clears the cached query plans|Float|0.6 -|`queryLiteralParameterization`|When true, the literal values written in a SQL or OpenCypher query are extracted into generated parameters before the statement and plan caches are looked up, so queries that differ only in those values (`... WHERE id = 12345`, `... WHERE id = 67890`) share one parsed statement and one execution plan instead of being parsed and planned again for every value. This is what queries generated by an application, a query builder or an LLM (text-to-SQL, text-to-Cypher, GraphRAG tools) typically look like. A literal is extracted only where a parameter means exactly what the literal meant, so the result, its column names and the Java types of its values are the same: the literals of a projection without an alias (the column is named after its text), of `ORDER BY`, `GROUP BY`, `SKIP`, `LIMIT` and `TIMEOUT`, of a SQL `FROM` target, of OpenCypher procedure arguments and of schema statements stay in the text, as do booleans, `null` and empty strings. A query that already uses parameters, or that is run again with the same text, costs the same as before, and the first query of a shape runs as written, so a workload that never repeats a shape pays one parse per query as before. The shared plan is the one built for the second query of the shape, as a plan with parameters is built for the first values it is run with: where values are so skewed that some need a plan of their own, turn the setting off for that database. `EXPLAIN` and `PROFILE` plan the text as written, so they show the values you wrote. A query that itself mentions a name starting with `__lit_` is not parameterized, and names with that prefix are reserved for the generated parameters: a parameter named `__lit_...` reads only the value bound under its plain name, never one bound as `:__lit_...`, so a caller cannot replace a literal of the query text. Set to false to key the caches on the exact query text _(since v26.10.2)_|Boolean|true +|`queryLiteralParameterization`|When true, the literal values written in a SQL or OpenCypher query are extracted into generated parameters before the statement and plan caches are looked up, so queries that differ only in those values (`... WHERE id = 12345`, `... WHERE id = 67890`) share one parsed statement and one execution plan instead of being parsed and planned again for every value. This is what queries generated by an application, a query builder or an LLM (text-to-SQL, text-to-Cypher, GraphRAG tools) typically look like. A literal is extracted only where a parameter means exactly what the literal meant, so the result, its column names and the Java types of its values are the same: the literals of a projection without an alias (the column is named after its text), of `ORDER BY`, `GROUP BY`, `SKIP`, `LIMIT` and `TIMEOUT`, of a SQL `FROM` target, of OpenCypher procedure arguments and of schema statements stay in the text, as do booleans, `null` and empty strings. A query that already uses parameters, or that is run again with the same text, costs the same as before, and the first query of a shape runs as written, so a workload that never repeats a shape pays one parse per query as before. The shared plan is the one built for the second query of the shape, as a plan with parameters is built for the first values it is run with: where values are so skewed that some need a plan of their own, turn the setting off for that database. `EXPLAIN` and `PROFILE` plan the text as written, so they show the values you wrote. A query that itself mentions a name starting with `+__lit_+` is not parameterized, and names with that prefix are reserved for the generated parameters: a parameter named `+__lit_...+` reads only the value bound under its plain name, never one bound as `+:__lit_...+`, so a caller cannot replace a literal of the query text. Set to false to key the caches on the exact query text _(since v26.10.2)_|Boolean|true |`queryParallelScan`|When true, a full scan of a type is read by several threads, for more throughput on multi-core machines: with or without a `WHERE` clause, and with `count`, `sum`, `avg`, `min`, `max` and their `GROUP BY` computed on those threads too, in SQL and in OpenCypher, as is a plain `SELECT DISTINCT` of fields over a type of at least 10,000 records _(since v26.10.1)_. Rows and groups come back in the same order as a sequential scan, but the sum or average of floating-point values may differ in the last digits from one run to the next, as the threads add in no fixed order. A `GROUP BY` of many keys, where every thread would otherwise build its own group for nearly every key, switches to a key exchange once a thread has created a few thousand groups: from then on the rows of the keys that thread does not hold are aggregated in one set of groups shared by the threads and partitioned by key, so each of those keys is held and aggregated once rather than once per thread. This roughly halves the memory such a query reserves from `queryMaxHeapRAM` and makes it faster, and it changes nothing for a `GROUP BY` of few keys. `EXPLAIN`/`PROFILE` reports it on the aggregation step as `N groups by key exchange` _(since v26.10.2)_. Used only outside an explicit transaction|Boolean|true |`queryParallelScanMinBuckets`|Minimum number of buckets a type needs for its full scans to run in parallel. A type with fewer buckets is scanned sequentially, unless one of its buckets is large enough to be split in page ranges (see `queryParallelScanPagesPerUnit`)|Integer|2 |`queryParallelScanPagesPerUnit`|Minimum number of pages of the unit of work a parallel scan cuts a bucket in: a bucket of at least twice as many pages is read by several threads, each on a range of its pages, so a type with a single bucket (the default) is scanned in parallel too. 0 disables the split: each bucket is then read by one thread _(since v26.10.1)_|Integer|32 @@ -563,7 +563,7 @@ The following plugins are available for ArcadeDB Server: |`sqlStatementCache`|Maximum number of parsed statements to keep in cache. A statement must be hit twice to be protected from a burst of one-off query texts. Statements that differ only in the values they embed share one entry (see `queryLiteralParameterization`, _since v26.10.2_), so raise this value only if you generate many statements of different shapes. It also sizes the cache of SQL execution plans. Since v26.11.1 a change on an open database applies at once|Integer|300 |`timeSeriesDecodedBlockCacheRAM`|Memory budget, in MB and *per shard*, for keeping decoded <> data in memory so that reading the same compacted block twice decodes it once. This is what makes a repeated query - a dashboard polling the latest point of a host, for example - answer without re-reading and re-decoding the block it already read. Since the budget is per shard, the worst case for one type is this value times its `SHARDS`, and it is only reached by queries that actually touch that many distinct blocks. A shard sizes its cache when it is opened, so changing this on a running database applies to shards opened afterwards; reopen the database to apply it everywhere. Set to 0 to disable caching. Left at the default it scales with the JVM maximum heap (heap/512, never below 4MB)|Long|(auto) |`txRetries`|Number of retries in case of MVCC exception. A follower's refusal of leader-only work that names the leader (a schema change, for example) is not retried, since every attempt on that node would be refused the same way: it reaches the caller on the first attempt _(since v26.10.2)_|Integer|3 -|`txRetryDelay`|Cap, in milliseconds, on the random wait before the next transaction retry: an exponential backoff with full jitter, drawn from `1` to `min(txRetryDelay, txRetryDelayBase * 2^attempt)`. Helpful in case of high concurrency on the same pages (multi-thread insertion over the same bucket). Applied by the embedded `transaction()`, by `COMMIT RETRY` and, since v26.10.1, by the remote `RemoteDatabase.transaction()` too. Since v26.10.2 it is also applied between the replays of an asynchronous batch commit that hit a conflict. 0 disables the delay|Integer|100 +|`txRetryDelay`|Cap, in milliseconds, on the random wait before the next transaction retry: an exponential backoff with full jitter, drawn from `1` to `+min(txRetryDelay, txRetryDelayBase * 2^attempt)+`. Helpful in case of high concurrency on the same pages (multi-thread insertion over the same bucket). Applied by the embedded `transaction()`, by `COMMIT RETRY` and, since v26.10.1, by the remote `RemoteDatabase.transaction()` too. Since v26.10.2 it is also applied between the replays of an asynchronous batch commit that hit a conflict. 0 disables the delay|Integer|100 |`txRetryDelayBase`|Starting size, in milliseconds, of the transaction retry backoff window, doubled on each further attempt up to `txRetryDelay`|Integer|2 |`txStaleReadCheck`|Refuses, with a retryable `ConcurrentModificationException`, a property write on a record read in the current transaction when another transaction committed a change to that record in between, so a value computed from the older read can never overwrite the concurrent change (a lost update under `READ_COMMITTED`). Adding or removing edges on a vertex is never refused. Only direct property assignment and removal is checked, not an in-place change to a list, map or embedded document got from the record. SQL `UPDATE` and Cypher `SET`/`MERGE`/`REMOVE` already compute their values from the latest data and are not affected. Set to false to restore the previous last-writer-wins behavior _(since v26.10.1)_|Boolean|true |`txStatementSavepointMaxEntries`|A statement that fails inside a transaction you keep is taken back on its own, and the transaction goes on, instead of being refused its commit. To do it the engine journals what the statement changes in the transaction, up to this many entries; a statement that changes more is not taken back when it fails, and the transaction can no longer be committed, as before statement savepoints. A bulk statement thus pays no memory for a journal it could not use, while the many small statements of an interactive transaction are all undone exactly. 0 disables statement savepoints. See <> _(since v26.11.1)_|Integer|100000 @@ -598,7 +598,7 @@ The gRPC server is implemented by the `GrpcServerPlugin`, which is bundled in th -Darcadedb.server.plugins=gRPC:com.arcadedb.server.grpc.GrpcServerPlugin ---- -Once enabled, the server listens on port `50051` by default. Since v26.10.2 every `grpc.*` setting below is a registered server setting, so it can be supplied in the server configuration file, as an environment variable or as a JVM system property (`-Darcadedb.grpc.*`). Before that, only `grpc.port` was, and the others worked as system properties only: a `grpc.tls.enabled` written in the configuration file was ignored and the endpoint stayed in cleartext. An invalid `grpc.mode` now makes the server refuse to start instead of starting no listener. The `server.plugins` setting itself is a standard server setting and can be supplied the same ways. +Once enabled, the server listens on port `50051` by default. Since v26.10.2 every `+grpc.*+` setting below is a registered server setting, so it can be supplied in the server configuration file, as an environment variable or as a JVM system property (`+-Darcadedb.grpc.*+`). Before that, only `grpc.port` was, and the others worked as system properties only: a `grpc.tls.enabled` written in the configuration file was ignored and the endpoint stayed in cleartext. An invalid `grpc.mode` now makes the server refuse to start instead of starting no listener. The `server.plugins` setting itself is a standard server setting and can be supplied the same ways. [%header,cols="25%,50%,10%,15%",stripes=even] |=== diff --git a/src/main/asciidoc/reference/sql/sql-database-admin.adoc b/src/main/asciidoc/reference/sql/sql-database-admin.adoc index 08827fd4d..0d9ec9ce9 100644 --- a/src/main/asciidoc/reference/sql/sql-database-admin.adoc +++ b/src/main/asciidoc/reference/sql/sql-database-admin.adoc @@ -461,13 +461,13 @@ cached record count of bucket 'Order_0' is 10869 but the bucket holds 10906 reco count(*) on this bucket under-reports by 37; run CHECK DATABASE FIX to recompute the counter ---- -`FIX` recomputes it - the counter is invalidated, and the next `count(*)` rescans and republishes it. A read-only run +`FIX` recomputes it - the counter is invalidated, and the next `+count(*)+` rescans and republishes it. A read-only run only reports, and the report carries `staleRecordCounters` and, under `FIX`, `staleRecordCountersFixed`. Both count *buckets*, not records: one per bucket whose counter disagreed, whatever the size of the disagreement. They sit next to record tallies such as `totalActiveRecords` in the same JSON, so the distinction is worth reading twice. -This is the symptom to look for when `count(*)` disagrees with a forced full scan such as -`SELECT count(*) FROM WHERE @rid IS NOT NULL`, in *either* direction. On a replicated database the drift is +This is the symptom to look for when `+count(*)+` disagrees with a forced full scan such as +`+SELECT count(*) FROM WHERE @rid IS NOT NULL+`, in *either* direction. On a replicated database the drift is per node and per bucket, so the replicas disagree with each other while holding identical data, which looks exactly like <> state divergence until a full scan rules it out. @@ -483,9 +483,9 @@ Releases up to and including `26.9.1` could fold a replicated transaction's reco <> entry was replayed on a node that had been restarted *cleanly*, which includes every rolling upgrade and every Kubernetes pod rotation. Nothing in `statistics.json` says which counters that happened to, so `26.10.1` does not trust any of them: the file now carries a format version, counts written without one are -discarded when the database is opened, and each affected bucket recomputes on its next `count(*)`. That is one extra +discarded when the database is opened, and each affected bucket recomputes on its next `+count(*)+`. That is one extra bucket scan, once, and the marker is written back on the next clean close. A `WARNING` in the server log says how many -buckets it applied to, so the first `count(*)` that changes after an upgrade is not a mystery. +buckets it applied to, so the first `+count(*)+` that changes after an upgrade is not a mystery. [[sql-check-database-timeseries]] *TimeSeries integrity* @@ -707,7 +707,7 @@ IMPORT DATABASE [WITH ( = [,] )* ] ---- * *`<url>`* Defines the location of the file to import. Use: - ** `file://` as prefix for files located on the same file system where ArcadeDB is running. The path is resolved against the directory where ArcadeDB is running, so a file written by `EXPORT DATABASE` (which writes to `exports/`) is read back with `file://exports/`. + ** `file://` as prefix for files located on the same file system where ArcadeDB is running. The path is resolved against the directory where ArcadeDB is running, so a file written by `EXPORT DATABASE` (which writes to `exports/`) is read back with `+file://exports/+`. ** `https://` and `http://` as prefix for remote files. *Examples* diff --git a/src/main/asciidoc/reference/sql/sql-functions.adoc b/src/main/asciidoc/reference/sql/sql-functions.adoc index ed42628fa..2752dd0c5 100644 --- a/src/main/asciidoc/reference/sql/sql-functions.adoc +++ b/src/main/asciidoc/reference/sql/sql-functions.adoc @@ -1430,7 +1430,7 @@ SELECT variance(salary) FROM Account Every vector function has two equivalent names: the namespaced `vector.()` form and a camelCase alias (`vectorXXX()`) kept for backward compatibility. Both resolve to the same function, are case-insensitive, and can be called without back-ticks. -*Use the namespaced `vector.()` form* in new queries: it is consistent with the other extended namespaces (`geo.*`, `math.*`, ...) and reads more clearly. The full reference is in <>. The headings below keep the legacy `vectorXXX()` spelling for continuity; the mapping is: +*Use the namespaced `vector.()` form* in new queries: it is consistent with the other extended namespaces (`+geo.*+`, `+math.*+`, ...) and reads more clearly. The full reference is in <>. The headings below keep the legacy `vectorXXX()` spelling for continuity; the mapping is: [%header,cols="2,2"] |=== diff --git a/src/main/asciidoc/reference/sql/sql-projections.adoc b/src/main/asciidoc/reference/sql/sql-projections.adoc index f0ca89a34..205767416 100644 --- a/src/main/asciidoc/reference/sql/sql-projections.adoc +++ b/src/main/asciidoc/reference/sql/sql-projections.adoc @@ -151,8 +151,8 @@ Result: `[{"items[4]": "John Smith"}]` `":{" ( * | (["!"] <identifier> ["*"] (<comma> ["!"] <identifier> ["*"])* ) ) "}"` -Inside the braces, `*` projects every property, `` includes one property and `!` excludes it. -A trailing `*` turns the identifier into a prefix: `+:{s*}+` includes every property whose name starts with `s`, each +Inside the braces, `+*+` projects every property, `` includes one property and `!` excludes it. +A trailing `+*+` turns the identifier into a prefix: `+:{s*}+` includes every property whose name starts with `s`, each under its own name, and `+:{!s*}+` excludes them (see the prefix wildcard examples below). A projection can refer to a link or to a collection of links, eg. a LIST or MAP. diff --git a/src/main/asciidoc/reference/sql/sql-triggers.adoc b/src/main/asciidoc/reference/sql/sql-triggers.adoc index aee2995dc..1f0267df3 100644 --- a/src/main/asciidoc/reference/sql/sql-triggers.adoc +++ b/src/main/asciidoc/reference/sql/sql-triggers.adoc @@ -637,7 +637,7 @@ DROP TRIGGER user_audit; 5. *Transaction Context*: Triggers execute within the same transaction as the triggering operation. If a trigger fails, the entire transaction rolls back. -6. *JavaScript Sandboxing*: JavaScript triggers run in a sandboxed GraalVM environment with a restricted host-class allow-list. The allow-list is limited to the benign value packages `java.util.*`, `java.time.*` and `java.math.*`; `java.lang.*` was removed so a trigger can no longer look up `java.lang.Runtime` and reach `Runtime.getRuntime().exec(...)` for OS command execution (advisory `GHSA-x9f9-r4m8-9xc2`). JavaScript's native `String`, `Math` and `Number` cover most trigger logic, so dropping `java.lang.*` rarely affects real triggers. +6. *JavaScript Sandboxing*: JavaScript triggers run in a sandboxed GraalVM environment with a restricted host-class allow-list. The allow-list is limited to the benign value packages `+java.util.*+`, `+java.time.*+` and `+java.math.*+`; `+java.lang.*+` was removed so a trigger can no longer look up `java.lang.Runtime` and reach `Runtime.getRuntime().exec(...)` for OS command execution (advisory `GHSA-x9f9-r4m8-9xc2`). JavaScript's native `String`, `Math` and `Number` cover most trigger logic, so dropping `+java.lang.*+` rarely affects real triggers. ===== See Also diff --git a/src/main/asciidoc/reference/sql/sql-types.adoc b/src/main/asciidoc/reference/sql/sql-types.adoc index 57d649444..a26c5fa3c 100644 --- a/src/main/asciidoc/reference/sql/sql-types.adoc +++ b/src/main/asciidoc/reference/sql/sql-types.adoc @@ -36,12 +36,12 @@ and a read) pays that scan every time, and is better served by a bidirectional t + Graph algorithms and path procedures ask about the graph rather than about what a vertex stores, so they always answer the incoming side of a unidirectional edge type, the same as with a <>: the Cypher -`algo.*` procedures, the `path.*` procedures (`path.expand`, `path.expandConfig`, `path.spanningTree`, +`+algo.*+` procedures, the `+path.*+` procedures (`path.expand`, `path.expandConfig`, `path.spanningTree`, `path.subgraphNodes`, `path.subgraphAll`), the SQL functions `dijkstra()`, `astar()`, `bellmanFord()`, `cchShortestPath()` and `duanSSSP()`, and the functions `node.degree`, `node.degree.in`, `node.relationship.exists` and -`node.relationship.types`. An `algo.*` procedure that loads the whole graph derives the incoming side from the outgoing -lists of the vertices it loaded, with no scan. A walk that goes vertex by vertex (the `path.*` procedures, the SQL -path-finding functions, the `node.*` functions) builds one in-heap index per unidirectional type the first time it needs +`node.relationship.types`. An `+algo.*+` procedure that loads the whole graph derives the incoming side from the outgoing +lists of the vertices it loaded, with no scan. A walk that goes vertex by vertex (the `+path.*+` procedures, the SQL +path-finding functions, the `+node.*+` functions) builds one in-heap index per unidirectional type the first time it needs it, the scan described above, bounded by `arcadedb.queryMaxHeapElementsAllowedPerOp`. `refactor.mergeNodes`, `refactor.cloneNodesWithRelationships` and adding or removing a node's labels in Cypher (which moves the vertex to another type) carry the incoming unidirectional edges of the node over too, instead of dropping them. The SQL functions diff --git a/src/main/asciidoc/tools/studio/ai.adoc b/src/main/asciidoc/tools/studio/ai.adoc index 307d1de2b..de0c1ede0 100644 --- a/src/main/asciidoc/tools/studio/ai.adoc +++ b/src/main/asciidoc/tools/studio/ai.adoc @@ -59,7 +59,7 @@ While a response is being generated, the *Send* button turns into a *Stop* butto [[studio-ai-markdown]] The assistant renders answers in a fixed subset of Markdown. _(Since v26.10.2.)_ Supported are paragraphs and line -breaks, headings (`#` to `######`), bullet and numbered lists, block quotes, horizontal rules, bold, italic and inline +breaks, headings (`+#+` to `+######+`), bullet and numbered lists, block quotes, horizontal rules, bold, italic and inline code, fenced code blocks (shown with their language as a label and a copy button, without syntax highlighting), pipe tables, and links whose URL is `http` or `https`. Everything else is shown as text: raw HTML, links with any other scheme, and autolinks. An image is shown as a plain link, so nothing is loaded from its URL. Nested lists and nested diff --git a/src/main/asciidoc/tools/studio/security.adoc b/src/main/asciidoc/tools/studio/security.adoc index c3f7f60bc..331d74b1d 100644 --- a/src/main/asciidoc/tools/studio/security.adoc +++ b/src/main/asciidoc/tools/studio/security.adoc @@ -22,7 +22,7 @@ The *Create User* modal asks for: * *Username* and *Password*. * *Database Access* — a table mapping each database to a group. -The wildcard `*` row represents the default group used for any database not listed. +The wildcard `+*+` row represents the default group used for any database not listed. Use the *Add Database* dropdown to grant access to additional databases with a specific group. // TODO: screenshot of the Users sub-tab with the Create User modal open. @@ -43,7 +43,7 @@ The *Create / Edit Group* modal exposes: * *Result Set Limit* — cap the number of rows returned per query. * *Read Timeout* — cap on the duration of read queries (ms). * *Type Permissions* — per-type *Create*, *Read*, *Update*, *Delete* checkboxes. -The `*` row is the default applied to types not explicitly listed; use the *Add Type* dropdown to override the default for specific types. +The `+*+` row is the default applied to types not explicitly listed; use the *Add Type* dropdown to override the default for specific types. * *Database Permissions* — *Schema Changes*, *Database Settings*, *Security*. // TODO: screenshot of the User Groups sub-tab. From 457e662b01ff366653e70f0d14ef00de60d9076b Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Thu, 5 Nov 2026 10:59:20 +0900 Subject: [PATCH 3/5] docs: fix what main gained since this PR was opened main moved by about 130 commits; two new things would fail the same two CI steps again. The anchor [[util-validatePredicate]] (utility.adoc) is renamed util-validate-predicate, the lowercase-with-hyphens form the validator requires (nothing refers to it). In redis.adoc, `*1\r\n:1\r\n` and `'*'` paired as strong across the two code spans, giving a stray "code" end tag in the single-page build; both are literal monospace now. Co-Authored-By: Claude Opus 5.5 --- src/main/asciidoc/reference/extended-functions/utility.adoc | 2 +- src/main/asciidoc/reference/redis-ql/redis.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/asciidoc/reference/extended-functions/utility.adoc b/src/main/asciidoc/reference/extended-functions/utility.adoc index 5e9d9dbe9..3ed7c6e01 100644 --- a/src/main/asciidoc/reference/extended-functions/utility.adoc +++ b/src/main/asciidoc/reference/extended-functions/utility.adoc @@ -162,7 +162,7 @@ follows APOC: it throws when the predicate is *true*. A query written for the ol --- -[[util-validatePredicate]] +[[util-validate-predicate]] ===== util.validatePredicate() The function form of `util.validate()`, usable in a `WHERE` or a `RETURN`: throws when the predicate is true, returns diff --git a/src/main/asciidoc/reference/redis-ql/redis.adoc b/src/main/asciidoc/reference/redis-ql/redis.adoc index 7bf3c3027..16a7e7e7e 100644 --- a/src/main/asciidoc/reference/redis-ql/redis.adoc +++ b/src/main/asciidoc/reference/redis-ql/redis.adoc @@ -289,7 +289,7 @@ _(Since v26.11.1)_ A malformed message gets one `-ERR Protocol error` reply and * A command that does not start with a RESP type marker is read as an inline command, as Redis reads it, so `PING` typed in `nc` or `telnet`, or sent by a health probe, is answered. Quotes and the escapes `\n`, `\r`, `\t`, `\b`, `\a` and `\xHH` are honored, the line is capped at 64 KB, and the argument count at `arcadedb.redis.maxMultiBulkLength`. A blank line gets no reply, and an HTTP request sent to the Redis port is refused at its first line. * A top-level simple string, integer or bulk string (`+`, `:` or `$`) is refused with `-ERR Protocol error` and the connection is closed. This is stricter than Redis, which reads it as an inline command. -* An array whose first element is not a string (`*1\r\n:1\r\n`) is answered with `-ERR Protocol error: expected '$', got ':'` (`'*'` for a nested array, `the command name is a null bulk string` for `$-1`), and the connection stays open. Redis sends the same error and then closes the connection. +* An array whose first element is not a string (`+*1\r\n:1\r\n+`) is answered with `-ERR Protocol error: expected '$', got ':'` (`+'*'+` for a nested array, `the command name is a null bulk string` for `$-1`), and the connection stays open. Redis sends the same error and then closes the connection. [[redis-pipelining]] ===== Pipelining From 931c4cad2cb6d9af6fdc994409da15cf4df9710b Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Thu, 29 Oct 2026 07:46:59 +0900 Subject: [PATCH 4/5] docs(ui): readable tables and an "On this page" menu clear of its rule, at every width; link the Python docs - Right TOC: the aside and the menu both drew the left rule and the links started 1px from it; now one rule on the menu and 0.75rem before the links. - Tables: as wide as the content needs up to the content column, scrolling sideways beyond it (display:block, width:max-content, max-width:100%, overflow-x:auto) instead of splitting words; no automatic hyphenation; inline code no longer word-break:break-all (still overflow-wrap:anywhere for long setting names); 12px cell padding; at <= 768px a minimum column width so tables scroll instead of squeezing. - python-embedded page: link the arcadedb-python component, now published on this site. Co-Authored-By: Claude Opus 5.5 --- docs/ui/supplemental/css/arcadedb.css | 37 ++++++++++++------- .../how-to/connectivity/python-embedded.adoc | 5 ++- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/docs/ui/supplemental/css/arcadedb.css b/docs/ui/supplemental/css/arcadedb.css index 9b42f3bb6..5ac31c3c1 100644 --- a/docs/ui/supplemental/css/arcadedb.css +++ b/docs/ui/supplemental/css/arcadedb.css @@ -196,8 +196,10 @@ a:hover, a:focus { min-width: 360px; } -/* Antora right TOC ("On this page") */ -aside.toc.sidebar, .toc-menu { background: transparent; border-left: 1px solid var(--color-border-light); } +/* Antora right TOC ("On this page"). One rule line, on the menu, with room before the links: + the aside and the menu both drew it, and the links started 1px from it. */ +aside.toc.sidebar { background: transparent; } +.toc-menu { background: transparent; border-left: 1px solid var(--color-border-light); padding-left: 0.75rem; } .toc-menu .title, aside.toc .title { font-family: var(--font-heading); color: var(--color-text-primary); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.75em; } .toc-menu a { color: var(--color-text-secondary); } .toc-menu a:hover, .toc-menu li.is-active > a { color: var(--color-primary); } @@ -279,36 +281,45 @@ aside.toc.sidebar, .toc-menu { background: transparent; border-left: 1px solid v border-radius: 8px; border-collapse: separate; border-spacing: 0; - overflow: hidden; - width: 100%; + /* As wide as the content needs, up to the content column; wider content scrolls sideways + (the GitHub markdown pattern) instead of running under the "On this page" sidebar. */ + display: block; + width: max-content; max-width: 100%; + overflow-x: auto; } -/* Allow long identifiers and inline code to wrap inside table cells. - Without this, names like `arcadedb.ha.stopServerOnReplicationFailure` - force a column wider than the content area and the table overflows - under the right-side "On this page" sidebar. */ +/* Text wraps at spaces and splits a word only when it cannot fit on its own line; no automatic + hyphenation (it split words such as "Pack-age"). Long inline code, such as + `arcadedb.ha.stopServerOnReplicationFailure`, may still wrap anywhere. */ .doc table.tableblock td, .doc table.tableblock th { - word-break: break-word; - overflow-wrap: anywhere; + word-break: normal; + overflow-wrap: break-word; + hyphens: manual; } .doc table.tableblock td code, .doc table.tableblock th code { white-space: normal; - word-break: break-all; + word-break: normal; overflow-wrap: anywhere; } +/* On a phone or a small tablet a multi-column table scrolls sideways instead of squeezing + every column to a few characters. */ +@media (max-width: 768px) { + .doc table.tableblock td, + .doc table.tableblock th { min-width: 7.5rem; } +} .doc table.tableblock thead tr th { background: var(--color-bg-secondary); color: var(--color-text-primary); font-weight: 600; font-family: var(--font-body); - padding: 12px 16px; + padding: 12px; border-bottom: 1px solid var(--color-border); } .doc table.tableblock tbody tr td, .doc table.tableblock tbody tr th { - padding: 12px 16px; + padding: 12px; border-bottom: 1px solid var(--color-border); color: var(--color-text-secondary); } diff --git a/src/main/asciidoc/how-to/connectivity/python-embedded.adoc b/src/main/asciidoc/how-to/connectivity/python-embedded.adoc index c3ab380e3..389b1331f 100644 --- a/src/main/asciidoc/how-to/connectivity/python-embedded.adoc +++ b/src/main/asciidoc/how-to/connectivity/python-embedded.adoc @@ -8,7 +8,7 @@ The embedded bindings run the ArcadeDB engine inside your Python process, so the Install one of them, not both. With both installed, `import arcadedb` raises an `ImportError` that names the two. -The bindings live in the https://github.com/ArcadeData/arcadedb-python[ArcadeData/arcadedb-python] repository. This page covers the first steps; the repository holds the full guides and the API reference. +The bindings live in the https://github.com/ArcadeData/arcadedb-python[ArcadeData/arcadedb-python] repository, and their guides are in the https://docs.arcadedb.com/arcadedb-python/[ArcadeDB Python] docs. This page covers the first steps. ===== Install @@ -101,6 +101,7 @@ You can also combine the two. The bindings can start an in-process HTTP server ( ===== Where to go next -* https://github.com/ArcadeData/arcadedb-python[Bindings repository]: guides for vectors, graphs, time series, import and server mode, plus the API reference and examples. +* https://docs.arcadedb.com/arcadedb-python/[ArcadeDB Python docs]: installation, which package to pick, databases and queries, concurrency, bulk loads and vectors. +* https://github.com/ArcadeData/arcadedb-python[Bindings repository]: the source, the examples and the issue tracker. * xref:how-to/connectivity/drivers/native-drivers.adoc[Native drivers]: the HTTP and gRPC clients, if you would rather talk to a server. * xref:tutorials/python-quickstart.adoc[Python quickstart]: the PostgreSQL wire protocol route. From a706e7d693c94352fc919943050d1a647ee3be2a Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Thu, 5 Nov 2026 11:29:56 +0900 Subject: [PATCH 5/5] docs(ui): on a phone the last table column (the prose) gets a 14rem reading width Measured on a local build in frames of exact device widths: at 360 and 393 px the cells over 8 lines drop from 33 to 24 (which-package), 32 to 19 (http) and 62 to 47 (timeseries), and the longest cell from 27 to 15 and 69 to 31 lines; the table scrolls sideways instead (GitHub's pattern). Tablet and wider are unchanged by this rule. Co-Authored-By: Claude Opus 5.5 --- docs/ui/supplemental/css/arcadedb.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ui/supplemental/css/arcadedb.css b/docs/ui/supplemental/css/arcadedb.css index 5ac31c3c1..0e54bc16c 100644 --- a/docs/ui/supplemental/css/arcadedb.css +++ b/docs/ui/supplemental/css/arcadedb.css @@ -308,6 +308,10 @@ aside.toc.sidebar { background: transparent; } @media (max-width: 768px) { .doc table.tableblock td, .doc table.tableblock th { min-width: 7.5rem; } + /* The last column usually holds the prose (description, notes): give it a reading width, so a phone + scrolls the table sideways instead of folding the text into a column a few words wide. */ + .doc table.tableblock td:last-child, + .doc table.tableblock th:last-child { min-width: 14rem; } } .doc table.tableblock thead tr th { background: var(--color-bg-secondary);