From afeddcebd97ac88c74ad4e5747e25f0e9d9b8250 Mon Sep 17 00:00:00 2001 From: Roi Lipman Date: Tue, 29 Sep 2026 10:43:04 +0300 Subject: [PATCH 1/3] docs: document the CCH (Customizable Contraction Hierarchies) index Add a dedicated Indexing page for the CCH index covering creation via `CREATE CCH INDEX` DDL (including multi-relationship-type indexes), querying with `db.idx.cch.query`, deletion, incremental maintenance, listing in `db.indexes()`, persistence/replication, and best practices. Wire the page into the Indexing navigation group and redirects, list it on the indexing overview, cross-link it from the vector index page, and add CCH-related terms to the spellcheck wordlist. Co-Authored-By: Claude Opus 4.8 (1M context) --- .wordlist.txt | 5 + cypher/indexing/cch-index.mdx | 392 +++++++++++++++++++++++++++++++ cypher/indexing/index.mdx | 8 +- cypher/indexing/vector-index.mdx | 3 +- docs.json | 7 +- 5 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 cypher/indexing/cch-index.mdx diff --git a/.wordlist.txt b/.wordlist.txt index 777ed7b8..119e8040 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -90,6 +90,7 @@ calmcode camelCase camel_case cardinality +CCH CDLP ceil CentOS @@ -101,7 +102,10 @@ checkpointer Checkpointing checkpointing checksums +chordal chunkers +customizable +customization Claude CLI cli @@ -193,6 +197,7 @@ DELUSER DESC destinationGraphName dev +Dijkstra n8n dimensionality directionality diff --git a/cypher/indexing/cch-index.mdx b/cypher/indexing/cch-index.mdx new file mode 100644 index 00000000..72ef054b --- /dev/null +++ b/cypher/indexing/cch-index.mdx @@ -0,0 +1,392 @@ +--- +title: "CCH Indexing: Fast Point-to-Point Shortest Paths on Weighted Graphs" +sidebarTitle: "CCH Index" +description: "CCH (Customizable Contraction Hierarchies) indexes accelerate weighted point-to-point shortest-path queries on relationship graphs such as road and transport networks." +--- + +FalkorDB's CCH index implements **Customizable Contraction Hierarchies**, a routing technique for answering weighted point-to-point shortest-path queries in microseconds. It is designed for graphs where relationships carry a numeric weight — road networks, transit systems, utility grids, or any dataset where you repeatedly ask "what is the cheapest route from A to B?". + +Unlike a range, full-text, or vector index — which accelerate filtering on a single entity — a CCH index precomputes a hierarchy over an entire weighted relationship graph so that shortest-path queries between arbitrary node pairs avoid a full Dijkstra traversal. + +## How CCH Works + +A classic contraction hierarchy bakes topology and edge weights together, making it expensive to rebuild when weights change. CCH decouples the work into three phases: + +1. **Preprocessing** (topology only) — computes a nested-dissection elimination order and a chordal "upward graph". This phase depends only on the graph structure, not on the weights, so it is reused across many weight updates. +2. **Customization** (weights only) — applies the concrete edge weights to the precomputed structure, filling in shortcut weights. This is fast and re-runs whenever weights change. +3. **Query** — a rank-pruned bidirectional Dijkstra over the hierarchy, followed by "unpacking" shortcuts back into the original edges to return a genuine path. + +Because the CCH index lives inside the index object, **nothing is written into your graph** — there are no shortcut edges, rank, or helper properties polluting your data. The index also maintains itself incrementally as the graph changes and is fully persisted to RDB and replicated. + +## Creating a CCH index + +A CCH index is created with DDL over a **relationship pattern**, naming the single edge property that holds the weight: + +```cypher +CREATE CCH INDEX FOR ON () +``` + +For example, to index a road network whose `ROAD` relationships carry a `w` (distance/cost) property: + + + +```python Python +graph.query("CREATE CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)") +``` + +```javascript JavaScript +await graph.query("CREATE CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)"); +``` + +```java Java +graph.query("CREATE CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)"); +``` + +```rust Rust +graph.query("CREATE CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)").execute().await?; +``` + +```bash Shell +GRAPH.QUERY DEMO_GRAPH "CREATE CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)" +``` + + + +`CREATE CCH INDEX` returns `indices_created = 1`. + +### Indexing multiple relationship types + +CCH is the only FalkorDB index type that may span **several relationship types** in a single index. This is useful when a route can traverse more than one kind of edge — for example roads and ferries: + + + +```python Python +graph.query("CREATE CCH INDEX FOR ()-[e:ROAD|FERRY]->() ON (e.w)") +``` + +```javascript JavaScript +await graph.query("CREATE CCH INDEX FOR ()-[e:ROAD|FERRY]->() ON (e.w)"); +``` + +```java Java +graph.query("CREATE CCH INDEX FOR ()-[e:ROAD|FERRY]->() ON (e.w)"); +``` + +```rust Rust +graph.query("CREATE CCH INDEX FOR ()-[e:ROAD|FERRY]->() ON (e.w)").execute().await?; +``` + +```bash Shell +GRAPH.QUERY DEMO_GRAPH "CREATE CCH INDEX FOR ()-[e:ROAD|FERRY]->() ON (e.w)" +``` + + + +The set of relationship types together with the weight property forms the **index key**. The set is order-agnostic and deduplicated, so `ROAD|FERRY` and `FERRY|ROAD` name the same index, and `ROAD|ROAD` is just `ROAD`. Two indexes over the same relationship types but different weight properties are distinct. + +**Notes and constraints:** +- A CCH index is only supported on **relationships** — a node pattern such as `(n:Label)` is rejected. +- Exactly **one property** — the edge weight — may be indexed. +- Relationship types and the weight attribute referenced at creation are created on demand if they do not yet exist (as with any `CREATE INDEX`). +- **Edge direction is respected**: a directed edge is never traversed backwards during build or query. Model bidirectional roads as two directed edges. +- When the weight property is missing on an edge, its weight defaults to `1`. Parallel edges between the same pair collapse to the cheapest one, and self-loops are ignored. +- Creating a second CCH index with the same key returns an error: `a CCH index already exists over these relationship types and weight attribute`. + +## Querying a CCH index + +A CCH index is queried with the `db.idx.cch.query` procedure. Bind the source and target nodes with a `MATCH`, then pass them along with the index key: + +```cypher +CALL db.idx.cch.query({ + sourceNode: NODE, // required, the bound source node + targetNode: NODE, // required, the bound target node + relTypes: [STRING], // must match an existing CCH index key + weightProp: STRING // must match the index's weight property +}) YIELD pathWeight, path +``` + +The procedure yields: +- **`pathWeight`** (DOUBLE) — the total weight of the shortest path. +- **`path`** (PATH) — the shortest path, fully unpacked into the original relationships (no shortcut edges appear in the result). + +For example, to find the cheapest route between two junctions: + + + +```python Python +result = graph.query(""" + MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) + YIELD pathWeight, path + RETURN pathWeight, path +""") +``` + +```javascript JavaScript +const result = await graph.query(` + MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) + YIELD pathWeight, path + RETURN pathWeight, path +`); +``` + +```java Java +ResultSet result = graph.query( + "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) " + + "CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) " + + "YIELD pathWeight, path RETURN pathWeight, path"); +``` + +```rust Rust +let result = graph.query( + "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) \ + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) \ + YIELD pathWeight, path RETURN pathWeight, path").execute().await?; +``` + +```bash Shell +GRAPH.QUERY DEMO_GRAPH "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) YIELD pathWeight, path RETURN pathWeight, path" +``` + + + +When querying an index that spans multiple relationship types, pass the same set in `relTypes` (order and duplicates do not matter): + + + +```python Python +result = graph.query(""" + MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD', 'FERRY'], weightProp: 'w'}) + YIELD pathWeight, path + RETURN pathWeight, path +""") +``` + +```javascript JavaScript +const result = await graph.query(` + MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD', 'FERRY'], weightProp: 'w'}) + YIELD pathWeight, path + RETURN pathWeight, path +`); +``` + +```java Java +ResultSet result = graph.query( + "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) " + + "CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD', 'FERRY'], weightProp: 'w'}) " + + "YIELD pathWeight, path RETURN pathWeight, path"); +``` + +```rust Rust +let result = graph.query( + "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) \ + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD', 'FERRY'], weightProp: 'w'}) \ + YIELD pathWeight, path RETURN pathWeight, path").execute().await?; +``` + +```bash Shell +GRAPH.QUERY DEMO_GRAPH "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD', 'FERRY'], weightProp: 'w'}) YIELD pathWeight, path RETURN pathWeight, path" +``` + + + +The `relTypes` and `weightProp` you pass must match an existing index key **exactly** — a query for `['ROAD']` will not fall back to a `['ROAD', 'FERRY']` index. + +`db.idx.cch.query` is a **read-only** procedure, so it can also be issued through `GRAPH.RO_QUERY`. (`CREATE`/`DROP CCH INDEX` are write operations and are rejected on read-only connections.) + +**Query behavior:** +- When `sourceNode` and `targetNode` are the same node, the result is `pathWeight = 0` and a single-node path. +- When the target is **unreachable** from the source, the procedure returns **no rows** (it is not an error). +- The returned `pathWeight` matches the weight computed by [`algo.SPpaths`](/algorithms/sppath); where the shortest path is unique the paths are identical, and where there are ties either valid shortest path may be returned. + +## Deleting a CCH index + +Drop a CCH index with the matching relationship pattern and weight property: + +```cypher +DROP CCH INDEX FOR ON () +``` + + + +```python Python +graph.query("DROP CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)") +``` + +```javascript JavaScript +await graph.query("DROP CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)"); +``` + +```java Java +graph.query("DROP CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)"); +``` + +```rust Rust +graph.query("DROP CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)").execute().await?; +``` + +```bash Shell +GRAPH.QUERY DEMO_GRAPH "DROP CCH INDEX FOR ()-[e:ROAD]->() ON (e.w)" +``` + + + +Dropping an index that does not exist returns an error: `no CCH index over these relationship types and weight attribute`. + +## Incremental Maintenance + +A CCH index keeps itself up to date as the underlying graph changes — you do not need to rebuild it manually. Pending updates are coalesced and applied automatically when a query commits, so a query always reads the last-committed hierarchy. + +FalkorDB chooses the cheapest maintenance strategy for each kind of change: + +- **Edge weight change** → a scoped re-customization of only the affected shortcuts (fast, weights-only). +- **Edge added** between a pair that already has a shortcut → a weight-only re-customization; an edge that introduces a **new adjacency** triggers a full rebuild. +- **Edge deleted** → the shortcut is retained and re-seeded; deletions accumulate toward a staleness threshold, after which the index rebuilds to reclaim stale structure. +- **Node added or removed** → a full rebuild, since the node id-space changes. + +Because preprocessing is topology-only, weight-only changes stay cheap even on large graphs. + +## Index Management + +### Listing CCH Indexes + +To view all indexes (including CCH) in your graph, use the `db.indexes()` procedure: + +```cypher +CALL db.indexes() +``` + +A CCH index reports: +- **`entitytype`**: `RELATIONSHIP` +- **`types`**: the weight property mapped to `['CCH']` — for example `{w: ['CCH']}` +- **`properties`**: the weight property, e.g. `['w']` +- **`label`**: the comma-joined, sorted set of indexed relationship types, e.g. `"FERRY,ROAD"` + +## Verifying CCH Index Usage + +Because a CCH index is queried explicitly through `db.idx.cch.query`, you can confirm the plan with `GRAPH.EXPLAIN`: + + + +```python Python +result = graph.explain(""" + MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) + YIELD pathWeight, path + RETURN pathWeight, path +""") +print(result) +# Output shows: ProcedureCall | db.idx.cch.query +``` + +```javascript JavaScript +const result = await graph.explain(` + MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) + YIELD pathWeight, path + RETURN pathWeight, path +`); +console.log(result); +// Output shows: ProcedureCall | db.idx.cch.query +``` + +```java Java +String result = graph.explain( + "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) " + + "CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) " + + "YIELD pathWeight, path RETURN pathWeight, path"); +System.out.println(result); +// Output shows: ProcedureCall | db.idx.cch.query +``` + +```rust Rust +let result = graph.explain( + "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) \ + CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) \ + YIELD pathWeight, path RETURN pathWeight, path").execute().await?; +println!("{}", result); +// Output shows: ProcedureCall | db.idx.cch.query +``` + +```bash Shell +GRAPH.EXPLAIN DEMO_GRAPH "MATCH (a:Junction {id: 0}), (b:Junction {id: 42}) CALL db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) YIELD pathWeight, path RETURN pathWeight, path" +# Output shows: ProcedureCall | db.idx.cch.query +``` + + + +## Persistence and Replication + +A CCH index survives restarts and follows replicas automatically: + +- **RDB**: the full hierarchy is serialized with the graph, so a reloaded index answers queries immediately with no rebuild — and still without any shortcut edges in the graph. +- **Replication / AOF**: index creation and drops replicate as definition-only effects; each replica builds and maintains its own hierarchy. Weight changes replicate and re-customize the replica's index. A full replica synchronization ships the complete hierarchy inside the RDB. + +## Performance Tradeoffs and Best Practices + +### When to Use CCH Indexes + +CCH indexes are ideal for: +- **Route planning**: repeated shortest-path queries over road, rail, or transit networks +- **Logistics and delivery**: cheapest-route lookups over weighted distribution graphs +- **Network/utility graphs**: least-cost paths where edges carry a numeric cost or latency +- **Interactive applications**: when point-to-point queries must return in microseconds + +They are less suited to graphs whose topology changes constantly (frequent node/edge additions), since structural changes trigger rebuilds. + +### Performance Considerations + +**Benefits:** +- Microsecond-scale point-to-point shortest-path queries after the index is built +- Cheap re-customization when only edge weights change — topology preprocessing is reused +- Keeps the user graph clean — no shortcut edges or helper properties are written +- Self-maintaining, fully persisted to RDB, and replicated + +**Costs:** +- **Build time**: the initial preprocessing and customization take time proportional to the graph size +- **Memory**: the hierarchy is held in memory alongside the graph (counted under `indices_sz_mb` in `GRAPH.MEMORY USAGE`) +- **Rebuild on topology change**: adding new adjacencies, adding/removing nodes, or crossing the deletion staleness threshold triggers a full rebuild + +**Recommendations:** +- Create a CCH index when the same weighted graph is queried for many source/target pairs +- Model one-way connections as directed edges and bidirectional ones as two directed edges +- Store the weight on the indexed property; edges without it are treated as weight `1` +- Keep separate indexes per metric (e.g. distance vs. travel-time) by using different weight properties +- Batch large topology changes together to amortize the resulting rebuild + +### See Also + +- [Range Index](/cypher/indexing/range-index) — for numeric and string range queries +- [Full-text Index](/cypher/indexing/fulltext-index) — for keyword and text-based search +- [Vector Index](/cypher/indexing/vector-index) — for semantic similarity search + +> **Tip:** Use a CCH index for repeated weighted shortest-path (routing) queries, a range index for exact or range-based property lookups, a full-text index for keyword search, and a vector index for semantic similarity search. + +## Frequently Asked Questions + + + + CCH stands for **Customizable Contraction Hierarchies**, a routing technique that precomputes a hierarchy over a weighted graph so point-to-point shortest-path queries run in microseconds. It separates topology preprocessing from weight customization, so weight changes stay cheap. + + + Bind the source and target nodes with a `MATCH`, then call `db.idx.cch.query({sourceNode: a, targetNode: b, relTypes: ['ROAD'], weightProp: 'w'}) YIELD pathWeight, path`. It returns the total path weight and the full path, unpacked into the original relationships. + + + No. The hierarchy lives inside the index object. Nothing — no shortcut edges, ranks, or helper properties — is written into your graph, and the index maintains itself as the graph changes. + + + Yes. CCH is the only FalkorDB index type that may span multiple relationship types in a single index, e.g. `CREATE CCH INDEX FOR ()-[e:ROAD|FERRY]->() ON (e.w)`. The relationship-type set plus the weight property forms the index key, which is order-agnostic and deduplicated. + + + An unreachable target returns **no rows** (not an error). When the source and target are the same node, the query returns `pathWeight = 0` and a single-node path. + + + A missing weight defaults to `1`. Parallel edges between the same pair collapse to the cheapest one, and self-loops are ignored. + + + No. The index updates incrementally: weight changes trigger a cheap re-customization, while structural changes (new adjacencies, node add/remove, or many deletions) trigger an automatic rebuild. Updates are applied when a query commits. + + diff --git a/cypher/indexing/index.mdx b/cypher/indexing/index.mdx index 75022b49..3cae9a79 100644 --- a/cypher/indexing/index.mdx +++ b/cypher/indexing/index.mdx @@ -22,6 +22,10 @@ Full-text indexes leverage RediSearch capabilities to provide powerful text sear Vector indexes enable similarity search on vector embeddings. These indexes are essential for AI and machine learning applications, supporting operations like nearest neighbor search with configurable similarity functions (euclidean or cosine). +### [CCH Index](/cypher/indexing/cch-index) + +CCH (Customizable Contraction Hierarchies) indexes accelerate weighted point-to-point shortest-path queries over relationship graphs such as road and transport networks. They precompute a routing hierarchy so route lookups between arbitrary node pairs return in microseconds, and support indexing multiple relationship types together. + --- Choose an index type from the navigation menu to learn more about creating, querying, and managing that specific type of index. @@ -30,7 +34,7 @@ Choose an index type from the navigation menu to learn more about creating, quer - FalkorDB supports three index types: **Range indexes** for exact-match and comparison queries, **Full-text indexes** for text search with stemming and scoring, and **Vector indexes** for similarity search on embeddings. + FalkorDB supports four index types: **Range indexes** for exact-match and comparison queries, **Full-text indexes** for text search with stemming and scoring, **Vector indexes** for similarity search on embeddings, and **CCH indexes** for weighted point-to-point shortest-path (routing) queries. Create indexes on properties frequently used in WHERE clause filters. Indexes accelerate lookups but add write overhead, so avoid indexing properties that are rarely queried or have very high write frequency. @@ -39,6 +43,6 @@ Choose an index type from the navigation menu to learn more about creating, quer Yes. FalkorDB supports indexing both node label properties and relationship type properties with range and full-text indexes. - Use the procedure `CALL db.indexes()` which yields all indexes with their label, properties, type (RANGE, FULLTEXT, or VECTOR), entity type, and status. + Use the procedure `CALL db.indexes()` which yields all indexes with their label, properties, type (RANGE, FULLTEXT, VECTOR, or CCH), entity type, and status. diff --git a/cypher/indexing/vector-index.mdx b/cypher/indexing/vector-index.mdx index 4e30f4d4..a9e5092b 100644 --- a/cypher/indexing/vector-index.mdx +++ b/cypher/indexing/vector-index.mdx @@ -116,8 +116,9 @@ These parameters control the HNSW (Hierarchical Navigable Small World) index str - [Full-text Index](/cypher/indexing/fulltext-index) — for keyword and text-based search - [Range Index](/cypher/indexing/range-index) — for numeric and string range queries +- [CCH Index](/cypher/indexing/cch-index) — for weighted shortest-path (routing) queries -> **Tip:** Use a vector index for semantic similarity search, a full-text index for keyword search, and a range index for exact or range-based property lookups. +> **Tip:** Use a vector index for semantic similarity search, a full-text index for keyword search, a range index for exact or range-based property lookups, and a CCH index for weighted shortest-path (routing) queries. ## Inserting vectors diff --git a/docs.json b/docs.json index 0462ce1d..acafbb94 100644 --- a/docs.json +++ b/docs.json @@ -72,7 +72,8 @@ "pages": [ "cypher/indexing/range-index", "cypher/indexing/fulltext-index", - "cypher/indexing/vector-index" + "cypher/indexing/vector-index", + "cypher/indexing/cch-index" ], "root": "cypher/indexing/index" }, @@ -906,6 +907,10 @@ "source": "/cypher/indexing.html", "destination": "/cypher/indexing" }, + { + "source": "/cypher/indexing/cch-index.html", + "destination": "/cypher/indexing/cch-index" + }, { "source": "/cypher/indexing/fulltext-index.html", "destination": "/cypher/indexing/fulltext-index" From d4dbca7c2e8934aa9c86aebb5250ea1f725c1498 Mon Sep 17 00:00:00 2001 From: Roi Lipman Date: Wed, 30 Sep 2026 14:31:33 +0300 Subject: [PATCH 2/3] docs(spellcheck): add CCH index terms to wordlist Add "cch", "precomputes", "precomputed", and "adjacencies" to .wordlist.txt to clear spellcheck CI failures on the CCH index page. The lowercase "cch" entry covers every capitalization (CCH/Cch/cch) under aspell's case rules. Also refines the `label` field description in cch-index.mdx. Co-Authored-By: Claude Opus 4.8 (1M context) --- .wordlist.txt | 6 +++++- cypher/indexing/cch-index.mdx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.wordlist.txt b/.wordlist.txt index 5fc66897..e6e7c510 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -968,4 +968,8 @@ AStar Dijkstra haversine heuristicScale -optimality \ No newline at end of file +optimality +cch +precomputes +precomputed +adjacencies \ No newline at end of file diff --git a/cypher/indexing/cch-index.mdx b/cypher/indexing/cch-index.mdx index 72ef054b..ce13bafc 100644 --- a/cypher/indexing/cch-index.mdx +++ b/cypher/indexing/cch-index.mdx @@ -263,7 +263,7 @@ A CCH index reports: - **`entitytype`**: `RELATIONSHIP` - **`types`**: the weight property mapped to `['CCH']` — for example `{w: ['CCH']}` - **`properties`**: the weight property, e.g. `['w']` -- **`label`**: the comma-joined, sorted set of indexed relationship types, e.g. `"FERRY,ROAD"` +- **`label`**: the deduplicated set of indexed relationship types, comma-joined in the index's internal order (by relationship-type id, which is not necessarily alphabetical), e.g. `"ROAD,FERRY"` ## Verifying CCH Index Usage From 46f26f4272e0367b97e85d178e1d5b5d79faf30f Mon Sep 17 00:00:00 2001 From: Roi Lipman Date: Wed, 30 Sep 2026 14:40:15 +0300 Subject: [PATCH 3/3] docs(spellcheck): add "precompute" to wordlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cypher/indexing/index.mdx uses the base form "precompute", which the previous commit didn't cover. Verified locally with pyspelling + aspell against .spellcheck.yml — the full repo now passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.wordlist.txt b/.wordlist.txt index e6e7c510..1d620326 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -970,6 +970,7 @@ haversine heuristicScale optimality cch +precompute precomputes precomputed adjacencies \ No newline at end of file