pub struct GraphStore { /* private fields */ }Implementations§
Source§impl GraphStore
impl GraphStore
pub fn open_file(path: impl AsRef<Path>) -> Result<Self, GraphError>
pub fn open_memory() -> Result<Self, GraphError>
pub fn backup_to(&self, path: impl AsRef<Path>) -> Result<(), GraphError>
Sourcepub fn check_integrity(&mut self) -> Result<IntegrityReport, GraphError>
pub fn check_integrity(&mut self) -> Result<IntegrityReport, GraphError>
Check physical storage plus MarsDB’s graph invariants. This requires exclusive mutable access because redb may repair physical metadata.
Sourcepub fn begin_write(&self) -> Result<WriteTransaction, GraphError>
pub fn begin_write(&self) -> Result<WriteTransaction, GraphError>
Open a write transaction spanning multiple graph operations. Callers
(e.g. the query executor) drive an entire Cypher statement through
the *_in_txn methods below using this one transaction, then call
write_txn.commit() themselves — this is the crash-safety boundary
from the plan: one statement = one transaction, not one transaction
per individual node/edge write.
v1 uses a write transaction even for pure-read statements (rather than a separate read-only path) to keep one code path and guarantee every statement — reads included — sees one consistent snapshot. Trade-off: this serializes concurrent readers behind redb’s single-writer lock instead of allowing true concurrent reads; a read-only transaction path is the natural follow-up if read concurrency becomes a bottleneck.
Sourcepub fn begin_read(&self) -> Result<ReadTransaction, GraphError>
pub fn begin_read(&self) -> Result<ReadTransaction, GraphError>
Open a read transaction for a statement that never mutates
anything (MATCH ... RETURN) — a consistent point-in-time
snapshot that runs alongside any concurrent readers or a
concurrent writer without contending for redb’s single-writer
lock. No commit/abort: a read transaction has nothing to roll
back, it just releases on drop.
Sourcepub fn commit(write_txn: WriteTransaction) -> Result<(), GraphError>
pub fn commit(write_txn: WriteTransaction) -> Result<(), GraphError>
Commit a transaction obtained from begin_write.
Sourcepub fn abort(write_txn: WriteTransaction) -> Result<(), GraphError>
pub fn abort(write_txn: WriteTransaction) -> Result<(), GraphError>
Abort (roll back) a transaction obtained from
begin_write, discarding any writes made
through it.
pub fn create_node( &self, labels: &[&str], props: BTreeMap<String, PropertyValue>, ) -> Result<NodeId, GraphError>
pub fn create_node_in_txn( write_txn: &WriteTransaction, labels: &[&str], props: BTreeMap<String, PropertyValue>, ) -> Result<NodeId, GraphError>
pub fn get_node(&self, id: NodeId) -> Result<Option<Node>, GraphError>
pub fn get_node_in_txn( txn: Txn<'_>, id: NodeId, ) -> Result<Option<Node>, GraphError>
Sourcepub fn lookup_prop_id_in_txn(
txn: Txn<'_>,
prop: &str,
) -> Result<Option<u32>, GraphError>
pub fn lookup_prop_id_in_txn( txn: Txn<'_>, prop: &str, ) -> Result<Option<u32>, GraphError>
The id interned for a property name, if any – None means the
name has never been written anywhere, so no record can hold it.
Exposed for the query layer’s per-property read path: names resolve
to ids once per statement there, then every row access goes through
get_node_prop_in_txn/get_edge_prop_in_txn by id.
Sourcepub fn get_node_prop_in_txn(
txn: Txn<'_>,
id: NodeId,
prop_id: u32,
) -> Result<Option<Option<PropertyValue>>, GraphError>
pub fn get_node_prop_in_txn( txn: Txn<'_>, id: NodeId, prop_id: u32, ) -> Result<Option<Option<PropertyValue>>, GraphError>
One property of one node, by interned prop id, without decoding the rest of the record or resolving any names — a directory binary search plus one value decode (the v2 read fast path; the codec mechanism measured 79x over whole-record decode at 1-of-20 props).
Nested Option distinguishes the two kinds of missing the executor
must not collapse (lookup_prop’s own docs): outer None = the
node record doesn’t exist (deleted-entity error at the call site),
inner None = node exists, property absent (legal null).
Sourcepub fn get_edge_prop_in_txn(
txn: Txn<'_>,
id: EdgeId,
prop_id: u32,
) -> Result<Option<Option<PropertyValue>>, GraphError>
pub fn get_edge_prop_in_txn( txn: Txn<'_>, id: EdgeId, prop_id: u32, ) -> Result<Option<Option<PropertyValue>>, GraphError>
Edge counterpart of get_node_prop_in_txn, same nested-Option
contract.
Sourcepub fn node_prop_reader(
txn: Txn<'_>,
) -> Result<impl FnMut(NodeId, u32) -> Result<Option<Option<PropertyValue>>, GraphError> + '_, GraphError>
pub fn node_prop_reader( txn: Txn<'_>, ) -> Result<impl FnMut(NodeId, u32) -> Result<Option<Option<PropertyValue>>, GraphError> + '_, GraphError>
Per-property reader over ONE pre-opened NODES handle – for a
caller probing many nodes’ properties in a loop, where
get_node_prop_in_txn’s per-call table open would dominate (the
mars-3va lesson: opens measured 23.67% of a bulk load). Same
nested-Option contract as get_node_prop_in_txn.
Sourcepub fn node_exists_in_txn(txn: Txn<'_>, id: NodeId) -> Result<bool, GraphError>
pub fn node_exists_in_txn(txn: Txn<'_>, id: NodeId) -> Result<bool, GraphError>
Record-existence check without any decoding — for the per-property read path when the property name was never interned (the value is necessarily absent on every record, but a deleted node must still error, not read as null).
Sourcepub fn edge_exists_in_txn(txn: Txn<'_>, id: EdgeId) -> Result<bool, GraphError>
pub fn edge_exists_in_txn(txn: Txn<'_>, id: EdgeId) -> Result<bool, GraphError>
Edge counterpart of node_exists_in_txn.
pub fn create_edge( &self, label: &str, src: NodeId, dst: NodeId, props: BTreeMap<String, PropertyValue>, ) -> Result<EdgeId, GraphError>
pub fn create_edge_in_txn( write_txn: &WriteTransaction, label: &str, src: NodeId, dst: NodeId, props: BTreeMap<String, PropertyValue>, ) -> Result<EdgeId, GraphError>
pub fn get_edge(&self, id: EdgeId) -> Result<Option<Edge>, GraphError>
pub fn get_edge_in_txn( txn: Txn<'_>, id: EdgeId, ) -> Result<Option<Edge>, GraphError>
Sourcepub fn neighbors(
&self,
node: NodeId,
dir: Direction,
label_filter: Option<&str>,
) -> Result<Vec<AdjEntry>, GraphError>
pub fn neighbors( &self, node: NodeId, dir: Direction, label_filter: Option<&str>, ) -> Result<Vec<AdjEntry>, GraphError>
Neighbors of node in dir, optionally filtered by edge label.
Reads directly from the adjacency multimap without touching edges.
pub fn neighbors_in_txn( txn: Txn<'_>, node: NodeId, dir: Direction, label_filter: Option<&str>, ) -> Result<Vec<AdjEntry>, GraphError>
pub fn delete_edge(&self, id: EdgeId) -> Result<bool, GraphError>
pub fn delete_edge_in_txn( write_txn: &WriteTransaction, id: EdgeId, ) -> Result<bool, GraphError>
Sourcepub fn delete_edges_in_txn(
write_txn: &WriteTransaction,
ids: &[EdgeId],
) -> Result<Vec<(EdgeId, String)>, GraphError>
pub fn delete_edges_in_txn( write_txn: &WriteTransaction, ids: &[EdgeId], ) -> Result<Vec<(EdgeId, String)>, GraphError>
Batch form of delete_edge_in_txn: one WriteCtx across every
id instead of a fresh one (and its table opens) per edge, and the
deleted edges’ label names resolved here — once per distinct
label id, while the ctx is already open — instead of a separate
whole-edge fetch per id on the caller’s side. Measured ~neutral
on wall time vs the per-edge path (a scattered bulk delete’s cost
lives in the executor’s match phase, not here — sorting the ids
into per-table passes was tried too and moved nothing), so this
exists for the API shape: one call for a DELETE r statement’s
whole edge set, doing strictly less redundant work. Returns
(id, label name) for each edge that actually existed — an id
already gone (a duplicate in ids, or deleted by an earlier
statement) is silently skipped, same contract as the single-edge
form’s false.
Sourcepub fn delete_node(&self, id: NodeId, detach: bool) -> Result<bool, GraphError>
pub fn delete_node(&self, id: NodeId, detach: bool) -> Result<bool, GraphError>
Delete a node. If detach is false and the node has incident edges,
returns GraphError::NodeHasEdges instead of deleting anything.
Sourcepub fn delete_node_in_txn(
write_txn: &WriteTransaction,
id: NodeId,
detach: bool,
) -> Result<Option<u64>, GraphError>
pub fn delete_node_in_txn( write_txn: &WriteTransaction, id: NodeId, detach: bool, ) -> Result<Option<u64>, GraphError>
Returns None when the node didn’t exist, else
Some(incident edges actually deleted) — the caller-visible
count a DETACH DELETE needs for its statement stats (a
self-loop appears in both adjacency directions but deletes
once, so the count comes from the deletions, not the scan).
Sourcepub fn create_index(
&self,
label: &str,
prop: &str,
unique: bool,
) -> Result<(), GraphError>
pub fn create_index( &self, label: &str, prop: &str, unique: bool, ) -> Result<(), GraphError>
Declares an index on (label, prop), backfilling it from every
existing node with label — see index::create_index’s own docs
for the exact semantics (idempotency, unique-violation behavior).
Sourcepub fn create_index_in_txn(
write_txn: &WriteTransaction,
label: &str,
prop: &str,
unique: bool,
) -> Result<(), GraphError>
pub fn create_index_in_txn( write_txn: &WriteTransaction, label: &str, prop: &str, unique: bool, ) -> Result<(), GraphError>
Same as create_index, but against an already-open
WriteTransaction — for a caller (CREATE INDEX as a Cypher
statement) that’s already inside one transaction and must commit
or abort it as a whole, not open a second one (redb allows only one
writer at a time; opening a second would deadlock).
Sourcepub fn index_def(
&self,
label: &str,
prop: &str,
) -> Result<Option<IndexDef>, GraphError>
pub fn index_def( &self, label: &str, prop: &str, ) -> Result<Option<IndexDef>, GraphError>
None means no index is declared on (label, prop).
Sourcepub fn index_def_in_txn(
txn: Txn<'_>,
label: &str,
prop: &str,
) -> Result<Option<IndexDef>, GraphError>
pub fn index_def_in_txn( txn: Txn<'_>, label: &str, prop: &str, ) -> Result<Option<IndexDef>, GraphError>
Same as index_def, but against an already-open Txn — for a
caller (the query planner/executor) that’s already inside one
transaction and needs a consistent view, not a fresh snapshot.
Sourcepub fn lookup_by_index_in_txn(
txn: Txn<'_>,
label: &str,
prop: &str,
value: &PropertyValue,
) -> Result<Vec<NodeId>, GraphError>
pub fn lookup_by_index_in_txn( txn: Txn<'_>, label: &str, prop: &str, value: &PropertyValue, ) -> Result<Vec<NodeId>, GraphError>
Same as lookup_by_index, but against an already-open Txn.
Sourcepub fn lookup_by_index_range_in_txn(
txn: Txn<'_>,
label: &str,
prop: &str,
lo: Option<(&PropertyValue, bool)>,
hi: Option<(&PropertyValue, bool)>,
limit: Option<usize>,
) -> Result<Vec<NodeId>, GraphError>
pub fn lookup_by_index_range_in_txn( txn: Txn<'_>, label: &str, prop: &str, lo: Option<(&PropertyValue, bool)>, hi: Option<(&PropertyValue, bool)>, limit: Option<usize>, ) -> Result<Vec<NodeId>, GraphError>
Same as lookup_by_index_in_txn, but stops once limit nodes are
found — the storage-level end of LIMIT push-down through an
IndexSeek (see marsdb_query::planner/executor::stream_index_seek).
Range counterpart of lookup_by_index_in_txn — every node whose
indexed value falls within the bounds ((value, inclusive) per
side, either side open). Returns a SUPERSET for numeric bounds
(int/float regions both scanned, lossy conversions widened
outward) — callers must re-check the original predicate; see
index::lookup_range.
Sourcepub fn index_range_cursor_in_txn(
txn: Txn<'_>,
label: &str,
prop: &str,
lo: Option<(&PropertyValue, bool)>,
hi: Option<(&PropertyValue, bool)>,
) -> Result<Option<IndexRangeCursor>, GraphError>
pub fn index_range_cursor_in_txn( txn: Txn<'_>, label: &str, prop: &str, lo: Option<(&PropertyValue, bool)>, hi: Option<(&PropertyValue, bool)>, ) -> Result<Option<IndexRangeCursor>, GraphError>
Resumable form of lookup_by_index_range_in_txn — see
index::IndexRangeCursor for the demand-driven contract.
pub fn lookup_by_index_limited_in_txn( txn: Txn<'_>, label: &str, prop: &str, value: &PropertyValue, limit: usize, ) -> Result<Vec<NodeId>, GraphError>
Sourcepub fn index_match_count_in_txn(
txn: Txn<'_>,
label: &str,
prop: &str,
value: &PropertyValue,
) -> Result<u64, GraphError>
pub fn index_match_count_in_txn( txn: Txn<'_>, label: &str, prop: &str, value: &PropertyValue, ) -> Result<u64, GraphError>
Cheap, exact count of nodes under (label, prop) = value — for the
query planner to compare selectivity between several indexed
equality candidates, not for fetching the nodes themselves (see
lookup_by_index_in_txn). O(1), same contract as lookup_by_index
re: “no index” vs “index, no match” both reading as 0.
Sourcepub fn lookup_by_index(
&self,
label: &str,
prop: &str,
value: &PropertyValue,
) -> Result<Vec<NodeId>, GraphError>
pub fn lookup_by_index( &self, label: &str, prop: &str, value: &PropertyValue, ) -> Result<Vec<NodeId>, GraphError>
Every node currently indexed under (label, prop) = value. Empty
(not an error) if no such index exists — check index_def first if
the caller needs to distinguish “no index” from “index, no match”.
pub fn set_node_prop( &self, id: NodeId, key: &str, value: PropertyValue, ) -> Result<bool, GraphError>
pub fn set_node_prop_in_txn( write_txn: &WriteTransaction, id: NodeId, key: &str, value: PropertyValue, ) -> Result<bool, GraphError>
pub fn set_edge_prop( &self, id: EdgeId, key: &str, value: PropertyValue, ) -> Result<bool, GraphError>
pub fn set_edge_prop_in_txn( write_txn: &WriteTransaction, id: EdgeId, key: &str, value: PropertyValue, ) -> Result<bool, GraphError>
pub fn remove_node_prop_in_txn( write_txn: &WriteTransaction, id: NodeId, key: &str, ) -> Result<bool, GraphError>
pub fn remove_edge_prop_in_txn( write_txn: &WriteTransaction, id: EdgeId, key: &str, ) -> Result<bool, GraphError>
Sourcepub fn add_node_label_in_txn(
write_txn: &WriteTransaction,
id: NodeId,
label: &str,
) -> Result<bool, GraphError>
pub fn add_node_label_in_txn( write_txn: &WriteTransaction, id: NodeId, label: &str, ) -> Result<bool, GraphError>
Adds label to id’s label set – a no-op (not an error) if it’s
already there, same idempotent-add semantics real Cypher’s SET n:Label has.
Sourcepub fn remove_node_label_in_txn(
write_txn: &WriteTransaction,
id: NodeId,
label: &str,
) -> Result<bool, GraphError>
pub fn remove_node_label_in_txn( write_txn: &WriteTransaction, id: NodeId, label: &str, ) -> Result<bool, GraphError>
Removes label from id’s label set – a no-op (not an error) if
it’s not there (label unknown entirely, or known but not on this
node), same as real Cypher’s REMOVE n:Label.
Sourcepub fn node_count_in_txn(txn: Txn<'_>) -> Result<u64, GraphError>
pub fn node_count_in_txn(txn: Txn<'_>) -> Result<u64, GraphError>
Total node count — O(1) (redb tracks table entry counts). For the
query planner’s start-point cardinality comparison: the cost of an
AllNodesScan leaf, never for fetching anything.
Sourcepub fn label_count_in_txn(txn: Txn<'_>, label: &str) -> Result<u64, GraphError>
pub fn label_count_in_txn(txn: Txn<'_>, label: &str) -> Result<u64, GraphError>
Number of nodes carrying label — O(1) via the label index’s
per-key entry count (same mechanism as index_match_count_in_txn).
An unknown label reads as 0, same as everywhere else. Planner
cardinality use only, like node_count_in_txn.
Sourcepub fn list_node_labels_in_txn(
txn: Txn<'_>,
) -> Result<Vec<(String, u64)>, GraphError>
pub fn list_node_labels_in_txn( txn: Txn<'_>, ) -> Result<Vec<(String, u64)>, GraphError>
Every interned name currently carried by at least one node, as
(label, node count) sorted by label — the substance behind
CALL db.labels(). Node labels and relationship types share one
intern namespace (intern_label serves both), so membership here
is decided by live use (a nonzero label-index count), not by
interning: a name whose nodes were all deleted drops out, same as
a name only ever used as a relationship type never appears.
O(interned names), each with an O(1) count read.
Sourcepub fn list_rel_types_in_txn(
txn: Txn<'_>,
) -> Result<Vec<(String, u64)>, GraphError>
pub fn list_rel_types_in_txn( txn: Txn<'_>, ) -> Result<Vec<(String, u64)>, GraphError>
Relationship-type counterpart of list_node_labels_in_txn:
(type, live edge count) sorted by type, counts from
REL_TYPE_COUNTS. Same live-use membership rule.
Sourcepub fn list_property_keys_in_txn(
txn: Txn<'_>,
) -> Result<Vec<String>, GraphError>
pub fn list_property_keys_in_txn( txn: Txn<'_>, ) -> Result<Vec<String>, GraphError>
Every interned property name, sorted — CALL db.propertyKeys().
Interning is permanent (there is no un-intern on last use, unlike
the liveness rule above, which has cheap per-name counts to
consult), so this lists every key that has ever appeared.
Sourcepub fn list_indexes_in_txn(
txn: Txn<'_>,
) -> Result<Vec<(String, String, bool)>, GraphError>
pub fn list_indexes_in_txn( txn: Txn<'_>, ) -> Result<Vec<(String, String, bool)>, GraphError>
Every declared index as (label, property, unique) —
CALL db.indexes(). Full INDEX_DEFS scan; the number of
declared indexes is small by nature.
Sourcepub fn label_id_for(txn: Txn<'_>, name: &str) -> Result<Option<u32>, GraphError>
pub fn label_id_for(txn: Txn<'_>, name: &str) -> Result<Option<u32>, GraphError>
Resolve a label/relationship-type name to its interned id —
None if never interned. Scan-support API (EdgeScanCursor
consumers pre-resolve type names once per scan).
Sourcepub fn edge_record_header(bytes: &[u8]) -> Result<(u32, u64, u64), GraphError>
pub fn edge_record_header(bytes: &[u8]) -> Result<(u32, u64, u64), GraphError>
Header fields (label_id, src, dst) of a raw edge record as
returned by EdgeScanCursor — no property decode.
Sourcepub fn edge_record_prop(
bytes: &[u8],
prop_id: u32,
) -> Result<Option<PropertyValue>, GraphError>
pub fn edge_record_prop( bytes: &[u8], prop_id: u32, ) -> Result<Option<PropertyValue>, GraphError>
One property’s value from a raw edge record, by interned prop
id — a directory-entry read from in-hand bytes, no storage
access. Ok(None) = property absent on this edge.
Sourcepub fn edge_scan_cursor() -> EdgeScanCursor
pub fn edge_scan_cursor() -> EdgeScanCursor
Resumable chunked sweep over the whole EDGES table in id
order — the sequential-scan primitive behind the planner’s
EdgeTypeScan. Same demand-driven shape as IndexRangeCursor:
each next_chunk re-seeks past the last returned id (O(log n))
and copies at most chunk_size raw records out, so a LIMITed
consumer that stops early never pays for the rest of the table.
Sourcepub fn edge_count_in_txn(txn: Txn<'_>) -> Result<u64, GraphError>
pub fn edge_count_in_txn(txn: Txn<'_>) -> Result<u64, GraphError>
Total edge count — O(1) (redb tracks table entry counts), the
edge counterpart of node_count_in_txn. Planner cardinality use
only.
Sourcepub fn rel_type_count_in_txn(
txn: Txn<'_>,
rel_type: &str,
) -> Result<u64, GraphError>
pub fn rel_type_count_in_txn( txn: Txn<'_>, rel_type: &str, ) -> Result<u64, GraphError>
Number of live edges of relationship type rel_type — O(1) via
REL_TYPE_COUNTS (see its definition in tables.rs for the
maintenance/backfill story). An unknown type reads as 0, same as
label_count_in_txn. Planner cardinality use only.
Sourcepub fn all_nodes(
&self,
label_filter: Option<&str>,
) -> Result<Vec<Node>, GraphError>
pub fn all_nodes( &self, label_filter: Option<&str>, ) -> Result<Vec<Node>, GraphError>
Full scan of all nodes, optionally filtered by label. v1 has no secondary index on label, so this is a linear scan of the table.
Sourcepub fn all_node_ids_limited_in_txn(
txn: Txn<'_>,
label_filter: Option<&str>,
limit: usize,
) -> Result<Vec<NodeId>, GraphError>
pub fn all_node_ids_limited_in_txn( txn: Txn<'_>, label_filter: Option<&str>, limit: usize, ) -> Result<Vec<NodeId>, GraphError>
Scan only graph identities, without decoding node records. Query pipelines use this to defer record/property loading until a filter or projection actually needs it.
pub fn all_nodes_in_txn( txn: Txn<'_>, label_filter: Option<&str>, ) -> Result<Vec<Node>, GraphError>
Sourcepub fn all_nodes_limited_in_txn(
txn: Txn<'_>,
label_filter: Option<&str>,
limit: usize,
) -> Result<Vec<Node>, GraphError>
pub fn all_nodes_limited_in_txn( txn: Txn<'_>, label_filter: Option<&str>, limit: usize, ) -> Result<Vec<Node>, GraphError>
Same as all_nodes_in_txn, but stops once limit nodes are found –
the storage-level end of LIMIT push-down (see the executor’s
scan()/eval_plan docs for the query-level half): a query whose
entire plan is a bare scan feeding straight into a LIMIT doesn’t
need to touch rows past the first limit, whether or not a label
filter narrows it first.