Skip to content

Commit 5691507

Browse files
authored
Update Rust dependencies (#34)
1 parent 05f5912 commit 5691507

47 files changed

Lines changed: 2319 additions & 895 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ jobs:
5858
- `issundb-mcp`: IssunDB with an MCP server
5959
6060
Note that the binaries need a working OpenMP runtime to work on your system.
61-
That means that typically `libgomp` on glibc Linux, `libomp` on macOS, and `vcomp` (Visual C++ Runtime)
62-
on Windows, should be installed on your system.
61+
That means that typically `libgomp` on Linux, `libomp` on macOS, and `vcomp` on Windows, should be installed on your system.
6362
6463
### Container Image
6564

.github/workflows/tests.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ jobs:
7575
run: make test-cli
7676

7777
- name: Run openCypher TCK conformance tests
78-
# Regression gate: the suite runs every TCK scenario and fails on any
79-
# new failure beyond the known-gap budget. The budget is the current
80-
# count of deferred-work failures (26) plus a margin for the handful of
81-
# rand()-flaky scenarios; lower it as gaps are closed.
78+
# This is for preventing regression. The test suite runs every TCK scenario
79+
# and fails on any new failure beyond the known-gap budget. The budget is
80+
# the current count of deferred-work failures (26) plus a margin for the
81+
# handful of rand()-flaky scenarios; lower it as gaps are closed.
8282
env:
8383
ISSUNDB_CONFORMANCE: '1'
8484
ISSUNDB_CONFORMANCE_MAX_FAILURES: '30'

AGENTS.md

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ IssunDB is an embedded graph database with vector and full-text search, written
88
Priorities, in order:
99

1010
1. Correct storage behavior: ACID transactions, adjacency consistency, and ID uniqueness.
11-
2. Clear boundaries between the storage engine, query layer, vector index, and public facade.
11+
2. Clear boundaries between the storage engine, query layer, vector and text indexes, and public facade.
1212
3. Reproducible, benchmark-backed performance; no premature optimization before correctness is covered.
1313
4. Idiomatic Rust: ownership, zero-cost abstractions, and `unsafe` only where necessary and documented.
1414

@@ -21,7 +21,7 @@ Priorities, in order:
2121
the public facade, and the binding crates (`issundb-rest`, `issundb-mcp`, `issundb-py`) consume only the
2222
`issundb` facade and its extension crates. Do not import across those boundaries in the wrong direction.
2323
- Keep all mutable state inside `Graph` and `Storage`; do not introduce module-level `static mut` or `lazy_static` globals for runtime state.
24-
- Writes are serialized via the `parking_lot::Mutex<()>` write lock on `Graph`; LMDB enforces the same constraint at the storage level. Do not bypass
24+
- Writes are serialized via the `parking_lot::ReentrantMutex<()>` write lock on `Graph`; LMDB enforces the same constraint at the storage level. Do not bypass
2525
either.
2626
- Add comments only when they clarify a non-obvious storage invariant, an LMDB lifetime constraint, or a GraphBLAS semiring choice.
2727
- Maintain the permissive license boundary of the workspace (MIT or Apache-2.0). Do not add dependencies or statically link libraries with copyleft,
@@ -43,13 +43,13 @@ Quick examples:
4343
- Use Oxford commas in inline lists: "a, b, and c" not "a, b, c".
4444
- Do not use em dashes. Restructure the sentence, or use a colon or semicolon instead.
4545
- Avoid colorful adjectives and adverbs. Write "adjacency query" not "blazing adjacency query".
46-
- Use noun phrases for checklist items, not imperative verbs. Write "temp directory teardown" not "tear down the temp directory".
46+
- Prefer noun phrases for checklist items over imperative verbs. Write "temp directory teardown" not "tear down the temp directory".
4747
- Headings in Markdown files must be in title case: "Build from Source" not "Build from source". Minor words (a, an, the, and, but, or, for, in, on,
4848
at, to, by, of) stay lowercase unless they are the first word.
4949

5050
## Repository Layout
5151

52-
The current tree includes storage, CSR snapshots, vector search, hybrid retrieval primitives, Cypher, the CLI, an REST API, language bindings,
52+
The current tree includes storage, CSR snapshots, vector search, hybrid retrieval primitives, Cypher, the CLI, a REST API, language bindings,
5353
and shared test utilities.
5454
This layout describes the current structure and target decoupled crate boundaries.
5555
Do not invent modules that do not yet exist when answering questions, but do place new modules according to this map.
@@ -85,23 +85,28 @@ Do not invent modules that do not yet exist when answering questions, but do pla
8585
(resize plus per-element set and drop) and the self-contained `dense_to_id`/`id_to_dense` mapping the matrix-view consumers read.
8686
- `src/error.rs`: `Error` enum; all storage and serialization errors unify here.
8787
- `crates/issundb-cypher/`: Cypher parser, AST, logical planner, physical planner, optimizer, and executor.
88-
- `src/parser.rs`: hand-written recursive-descent parser for MATCH (including inline relationship property maps and multi-label node patterns
88+
- `src/parser.rs`: Cypher parser built with the `chumsky` parser-combinator library (with a Pratt parser for operator-precedence expressions) for
89+
MATCH (including inline relationship property maps and multi-label node patterns
8990
such as `(n:A:B)`), WHERE, RETURN, CREATE, SET (property and label assignment), REMOVE (label and property), and DELETE/DETACH DELETE over
9091
arbitrary expression targets.
9192
- `src/ast.rs`: AST node types.
9293
- `src/plan/`: logical planner, physical planner, optimizer, and statistics helpers.
9394
- `src/exec/mod.rs`: public entry points (`execute`, `explain`), shared type definitions, and tests.
9495
- `src/exec/read.rs`: `execute_physical` and read-path helpers (`evaluate_where`, `evaluate_sort_key`, `json_to_prop_value`,
9596
`execute_filter_over_expand`).
96-
I - `src/exec/vectorized.rs`: columnar fast path for the final projection or aggregation over a one-hop or two-hop directed expansion. A
97-
structural recognizer matches `[Limit]? [Sort]? [Distinct]? Project [Aggregate]? Filter* Expand(directed single hop) [Filter* Expand]
98-
LabelScan` with single-property expressions, modeling the chain as one id column per node variable (the leaf plus each hop's
99-
destination). It executes column-at-a-time (per-hop bulk expansion with the fan-out preserving the row pipeline's depth-first order,
100-
bulk label membership, bulk property gather via `Graph::node_props_json_table`, and group-by-code aggregation via
101-
`Graph::node_prop_group_codes`), building the result records directly. A two-hop chain is recognized only when the two hops carry
102-
distinct relationship types, so no single edge can fill both hops and relationship uniqueness is vacuous (the column fan-out tracks no
103-
edge identity); same-type or longer chains fall back. The recognizer sees through a `Distinct` operator because the caller deduplicates
104-
the built records. Any unrecognized shape falls back to the row pipeline, so correctness never depends on the recognizer.
97+
- `src/exec/vectorized.rs`: columnar fast path for the final projection or aggregation over a linear chain of up to `MAX_VEC_HOPS`
98+
directed single hops. A structural recognizer matches `[Limit]? [Sort]? [Distinct]? Project [Aggregate]? Stage* (Expand(directed single
99+
hop) Stage*){0,MAX_VEC_HOPS} Leaf` with single-property expressions, modeling the chain as one id column per node variable (the leaf
100+
plus each hop's destination). It executes column-at-a-time (per-hop bulk expansion with the fan-out preserving the row pipeline's
101+
depth-first order, bulk label membership, bulk property gather via `Graph::node_props_json_table`, and group-by-code aggregation via
102+
`Graph::node_prop_group_codes`), building the result records directly. A multi-hop chain is recognized only when every hop carries a
103+
distinct relationship type, so no single edge can fill two hops and relationship uniqueness is vacuous (the column fan-out tracks no
104+
edge identity); a repeated type, or a chain longer than `MAX_VEC_HOPS`, falls back. When the single aggregate is a non-distinct `count`
105+
over the chain's terminal variable and that variable feeds no group key, the executor collapses the final hop: instead of materializing
106+
every terminal row it counts each source's qualifying neighbors once (`execute_collapsed_count`), so a `count` of upstream-grouped
107+
neighbors stays proportional to the edges scanned rather than the rows produced. The recognizer sees through a `Distinct` operator
108+
because the caller deduplicates the built records. Any unrecognized shape falls back to the row pipeline, so correctness never depends
109+
on the recognizer.
105110
- `src/exec/factorize.rs`: `FactorizedRecordGroup` (shared `Arc<PathMap>` prefix plus per-row extensions) and `filter_refs_in_expr`.
106111
- `src/exec/expr.rs`: expression evaluation (`evaluate_expr`, `eval_binary_op`, `eval_arithmetic`, `eval_function_call`).
107112
- `src/exec/write.rs`: mutation execution (`execute_create`, `execute_set`, `execute_delete`, `execute_merge`).
@@ -234,9 +239,9 @@ All graph operations go through `Graph`; do not call `Storage` directly from out
234239

235240
Node and edge CRUD, accessors, and registry lookups have predictable signatures; read them from the source rather than this file. Methods:
236241
`add_node`, `add_node_multi`, `get_node`, `update_node`, `delete_node`, `add_label`, `remove_label`, `node_labels`, `add_edge`, `get_edge`,
237-
`out_neighbors`, `in_neighbors`, `node_has_relationships`, `nodes_by_label`, `edges_by_type`, `all_nodes`, `label_name`, `type_name`,
238-
`list_node_indexes_and_constraints`, `list_edge_indexes_and_constraints`, `node_count_by_label`, `edge_count_by_type`, `put_vector_bytes`,
239-
`vector_bytes`, and `rebuild_csr`.
242+
`update_edge`, `delete_edge`, `out_neighbors`, `in_neighbors`, `node_has_relationships`, `nodes_by_label`, `edges_by_type`, `all_nodes`,
243+
`label_name`, `type_name`, `list_node_indexes_and_constraints`, `list_edge_indexes_and_constraints`, `node_count_by_label`,
244+
`edge_count_by_type`, `put_vector_bytes`, `vector_bytes`, and `rebuild_csr`.
240245

241246
The read-path and statistics methods carry non-obvious semantics:
242247

@@ -288,6 +293,7 @@ It may depend on `issundb-core`; it must not depend on `issundb-text`, `issundb-
288293
populated graph and rebuilds the index from the persisted embeddings under the new configuration. The stored vectors are raw, metric-agnostic
289294
f32, so they re-index under any metric; this is O(n) in the stored vector count and is an administrative operation, not a concurrent one.
290295
- `VectorGraphExt::upsert_vector(n: NodeId, v: &[f32]) -> Result<(), VectorError>`
296+
- `VectorGraphExt::remove_vector(n: NodeId) -> Result<(), VectorError>`: removes the embedding for a node from both memory and storage.
291297
- `VectorGraphExt::vector_search(q: &[f32], k: usize) -> Result<Vec<Hit>, VectorError>`
292298
- `VectorGraphExt::vector_search_with(q: &[f32], opts: &VectorSearchOptions) -> Result<Vec<Hit>, VectorError>`: adds an exact-label filter,
293299
property equality filters (both evaluated during the HNSW traversal), and `rescore_factor`. On a quantized index the search defaults to
@@ -313,16 +319,21 @@ retrieve functions are free functions, not methods on `Graph`, to preserve the c
313319

314320
- `retrieve(graph: &Graph, q: &[f32], k: usize, hops: u8) -> Result<Subgraph, RetrievalError>`
315321
- `retrieve_with(graph: &Graph, q: &[f32], opts: &RetrieveOptions) -> Result<Subgraph, RetrievalError>`
322+
- `retrieve_hybrid(graph: &Graph, q: &[f32], text_query: &str, opts: &HybridRetrieveOptions) -> Result<Subgraph, RetrievalError>`: fuses vector and text search seed relevance scores before running expansion.
316323
- `Subgraph`: `nodes: Vec<NodeId>`, `edges: Vec<EdgeId>`, `scores: HashMap<NodeId, f32>`
317324
- `RetrieveOptions`: `k`, `hops`, `max_distance`, `max_nodes`
325+
- `HybridRetrieveOptions`: `vector_k`, `text_k`, `text_label`, `text_property`, `hops`, `max_distance`, `max_nodes`, `vector_label`, `fusion`
326+
- `FusionStrategy`: reciprocal rank fusion (`Rrf { k }`) or linear combination (`WeightedSum { vector_weight, text_weight }`)
318327

319328
### `issundb_cypher`
320329

321330
Cypher query execution. Exposed through the `issundb` facade via the `GraphQueryExt` trait; do not call `issundb_cypher::execute` directly from
322331
outside `issundb`.
323332

324-
- `query(cypher: &str) -> Result<QueryResult, CypherError>` and
325-
`query_with_params(cypher: &str, params: &HashMap<String, serde_json::Value>) -> Result<QueryResult, CypherError>`
333+
- `query(cypher: &str) -> Result<QueryResult, CypherError>`,
334+
`query_with_params(cypher: &str, params: &HashMap<String, serde_json::Value>) -> Result<QueryResult, CypherError>`,
335+
`query_with_procedures(cypher: &str, params: &HashMap<String, serde_json::Value>, registry: &ProcedureRegistry) -> Result<QueryResult, CypherError>`, and
336+
`explain(cypher: &str) -> Result<String, CypherError>`
326337
- `QueryResult`: `columns: Vec<String>`, `records: Vec<Record>`
327338
- `Record`: `values: Vec<serde_json::Value>`
328339

@@ -332,10 +343,13 @@ Untyped expansion uses GraphBLAS SpMV; typed expansion reads the CSR snapshot in
332343
and the source set is small so a write-then-expand workload never pays a rebuild. The optimizer splits top-level `AND` conjunctions in WHERE so
333344
each conjunct pushes down to its own lowest binder, and rewrites an equality or range filter over a labeled scan into `NodeIndexScan` or
334345
`NodeRangeScan` when the property has a declared index; the rewrite recurses through every single-input operator (including `Aggregate`, `Sort`,
335-
`Limit`, and `Distinct`) and treats a split conjunct's expression form like the structured comparison forms. Bulk label filtering uses `label_idx`
336-
point
346+
`Limit`, and `Distinct`) and treats a split conjunct's expression form like the structured comparison forms. A natural inner `HashJoin` whose one
347+
side merely re-scans a variable the other already binds (the shape a multi-`MATCH` sharing a pivot produces) is rewritten into a linear
348+
"expand into" chain (`rewrite_join_to_expand`), grafting the redundant-scan side's `Filter`/`Expand` chain onto the driver so the full re-scan is
349+
eliminated and the columnar path and closing-join rewrite can both exploit the chain; it fires only when the two sides share exactly the one rooted
350+
variable and never across an `OptionalMatch`. Bulk label filtering uses `label_idx` point
337351
lookups (`Graph::label_filter`), and single-property node reads go through the in-memory property columns (`Graph::node_prop_json`).
338-
A final projection or aggregation over a one-hop or two-hop directed expansion executes column-at-a-time through `exec/vectorized.rs`
352+
A final projection or aggregation over a linear chain of up to `MAX_VEC_HOPS` directed hops executes column-at-a-time through `exec/vectorized.rs`
339353
(`Graph::node_props_json_table` and `Graph::node_prop_group_codes`); every other shape runs the row pipeline.
340354
A grouping-free `count` over a one-hop or two-hop directed expansion lowers instead to the `PathCount` kernel
341355
(`Graph::count_linear_paths`); per-vertex `prop CMP literal` predicates on the path's labeled variables push down into the kernel as
@@ -459,7 +473,6 @@ Additional validation when relevant:
459473
- Public API docs are generated from `rustdoc` on `crates/issundb/src/lib.rs`. Keep that module focused on the deliberate public surface; do not
460474
re-export `Storage` or other internals.
461475
- User workflow changes should update `README.md`.
462-
- Phase progress and completeness changes should update `ROADMAP.md`.
463476
- If you detect stale docs while changing related code, fix them in the same patch.
464477

465478
## Review Guidelines (P0/P1 Focus)

0 commit comments

Comments
 (0)