pub trait GraphStore:
Send
+ Sync
+ 'static {
Show 33 methods
// Required methods
fn upsert_edge<'life0, 'async_trait>(
&'life0 self,
edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn upsert_edges<'life0, 'async_trait>(
&'life0 self,
edges: Vec<Edge>,
) -> Pin<Box<dyn Future<Output = StorageResult<BatchWriteSummary>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn get_edge<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn get_edge_including_deleted<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn get_edge_by_natural_key_including_deleted<'life0, 'life1, 'async_trait>(
&'life0 self,
namespace: &'life1 str,
source_id: Uuid,
target_id: Uuid,
relation: EdgeRelation,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn delete_edge<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
mode: DeleteMode,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn query_edges<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
sort: Vec<SortOrder<EdgeSortField>>,
page: PageRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Page<Edge>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn count_edges<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn count_edges_by_relation<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(EdgeRelation, u64)>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn query_edges_after<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
after: Option<Uuid>,
limit: u32,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeSeekPage>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn neighbors<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<NeighborHit>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn traverse<'life0, 'async_trait>(
&'life0 self,
request: TraversalRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<GraphPath>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn purge_incident_edges<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
// Provided methods
fn latest_annotating_note<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_node_id: Uuid,
_kind: &'life1 str,
_tag: &'life2 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<(Uuid, i64)>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn insert_edge_if_absent<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn upsert_edge_observed<'life0, 'async_trait>(
&'life0 self,
_request: EdgeUpsertRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeUpsertResult>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn replace_edge_if_unchanged<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
_expected_updated_at: DateTime<Utc>,
_expected_deleted_at: Option<DateTime<Utc>>,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn upsert_edge_guarded<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedWriteOutcome>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn upsert_edge_guarded_observed<'life0, 'async_trait>(
&'life0 self,
_request: EdgeUpsertRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedEdgeUpsertOutcome>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn upsert_edges_guarded<'life0, 'async_trait>(
&'life0 self,
_edges: Vec<Edge>,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedBatchOutcome>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn upsert_edges_guarded_observed<'life0, 'async_trait>(
&'life0 self,
_requests: Vec<EdgeUpsertRequest>,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedEdgeBatchUpsertOutcome>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn query_edges_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
filter: EdgeFilter,
sort: Vec<SortOrder<EdgeSortField>>,
page: PageRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Page<Edge>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn count_edges_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
filter: EdgeFilter,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn count_edges_by_relation_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(EdgeRelation, u64)>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn count_edges_by_endpoint_base<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeEndpointBaseCounts>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn count_edges_by_endpoint_base_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeEndpointBaseCounts>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn edge_sequence<'life0, 'async_trait>(
&'life0 self,
_id: Uuid,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<i64>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn edge_sequences<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [Uuid],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(Uuid, i64)>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn query_edges_sequence_after<'life0, 'async_trait>(
&'life0 self,
_filter: EdgeFilter,
_after: Option<SeekCursor>,
_limit: u32,
) -> Pin<Box<dyn Future<Output = StorageResult<SeekPage<Edge>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn neighbors_page<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
after: Option<NeighborCursor>,
neighbor_kinds: Option<Vec<String>>,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<NeighborHit>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn neighbors_both_directions<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<DirectedNeighborHit>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn get_edges<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [LinkId],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<Edge>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn batch_neighbors<'life0, 'life1, 'async_trait>(
&'life0 self,
sources: &'life1 [Uuid],
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(Uuid, NeighborHit)>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
}Expand description
Directed edge CRUD and graph traversal over the knowledge graph.
Required Methods§
Sourcefn upsert_edge<'life0, 'async_trait>(
&'life0 self,
edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn upsert_edge<'life0, 'async_trait>(
&'life0 self,
edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Insert or update a single edge.
Sourcefn upsert_edges<'life0, 'async_trait>(
&'life0 self,
edges: Vec<Edge>,
) -> Pin<Box<dyn Future<Output = StorageResult<BatchWriteSummary>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn upsert_edges<'life0, 'async_trait>(
&'life0 self,
edges: Vec<Edge>,
) -> Pin<Box<dyn Future<Output = StorageResult<BatchWriteSummary>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Insert or update a batch of edges.
Sourcefn get_edge<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_edge<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Fetch an edge by link ID, returning None if absent. Filters soft-deleted rows.
Sourcefn get_edge_including_deleted<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn get_edge_including_deleted<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Fetch an edge by link ID including soft-deleted rows. Used by the runtime hard-delete path to locate and namespace-check an already-soft-deleted edge before purging it.
Sourcefn get_edge_by_natural_key_including_deleted<'life0, 'life1, 'async_trait>(
&'life0 self,
namespace: &'life1 str,
source_id: Uuid,
target_id: Uuid,
relation: EdgeRelation,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_edge_by_natural_key_including_deleted<'life0, 'life1, 'async_trait>(
&'life0 self,
namespace: &'life1 str,
source_id: Uuid,
target_id: Uuid,
relation: EdgeRelation,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Fetch an edge by natural key (namespace, source, target, relation) including
soft-deleted rows. Used by the atomic-apply result renderer for a symmetric-relation
update whose surviving canonical row may be tombstoned (ADR-039 DO NOTHING) — the
normal query_edges/list_edges path filters deleted_at IS NULL and would report
“not found” for exactly that row.
namespace is the natural key’s own namespace column value (part of the
UNIQUE(namespace, source_id, target_id, relation) constraint this method queries by)
— it is passed explicitly rather than implied by whichever store instance self is,
so a caller who resolved the record’s namespace independently of its own ambient token
(the atomic-apply renderer, which knows the committed edge’s namespace from its prepare-
time EdgeNaturalKey, not from the caller’s token) cannot accidentally query the wrong
namespace by relying on implicit store scoping.
Sourcefn delete_edge<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
mode: DeleteMode,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn delete_edge<'life0, 'async_trait>(
&'life0 self,
id: LinkId,
mode: DeleteMode,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Delete an edge by link ID using the specified delete mode.
Sourcefn query_edges<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
sort: Vec<SortOrder<EdgeSortField>>,
page: PageRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Page<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn query_edges<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
sort: Vec<SortOrder<EdgeSortField>>,
page: PageRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Page<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Query edges with filter, sort, and pagination without an implicit
exact count. Implementations should return total: None; callers that
need a count use Self::count_edges explicitly.
Sourcefn count_edges<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn count_edges<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Count edges matching the given filter.
Sourcefn count_edges_by_relation<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(EdgeRelation, u64)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn count_edges_by_relation<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(EdgeRelation, u64)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Count edges grouped by relation, ignoring soft-deleted rows. Cheap
aggregate (GROUP BY relation) used to report the true per-relation
population for full-graph audits (#702.3).
Sourcefn query_edges_after<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
after: Option<Uuid>,
limit: u32,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeSeekPage>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn query_edges_after<'life0, 'async_trait>(
&'life0 self,
filter: EdgeFilter,
after: Option<Uuid>,
limit: u32,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeSeekPage>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Seek-pagination page of edges ordered by id ascending, using an
indexed range scan (id > after) against the (namespace, id)
primary key instead of OFFSET. after is exclusive; None starts
from the beginning of the set. This remains an efficient compatibility
path for a fixed edge set, but random UUIDs inserted concurrently may
sort behind an issued boundary. Public concurrent walks use
Self::query_edges_sequence_after instead (#1424).
Sourcefn neighbors<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<NeighborHit>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn neighbors<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<NeighborHit>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Return immediate neighbors of a graph node.
Sourcefn traverse<'life0, 'async_trait>(
&'life0 self,
request: TraversalRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<GraphPath>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn traverse<'life0, 'async_trait>(
&'life0 self,
request: TraversalRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<GraphPath>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Bounded multi-hop BFS traversal from the given roots.
Implementations must validate TraversalRequest::validate, count
adjacency rows before first-visit de-duplication against the request’s
shared execution budget, stop a root as soon as its effective result
limit is filled, and return an error rather than partial paths when the
work or time budget expires. Minimum-depth BFS selection is normative;
same-depth tie ordering is not.
Sourcefn purge_incident_edges<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn purge_incident_edges<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Hard-delete every incident edge (source or target) for node_id, regardless of soft-delete
state. Used during endpoint hard-delete to prevent dangling graph_edges rows (ADR-002
no-dangling-references contract).
Provided Methods§
Sourcefn latest_annotating_note<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_node_id: Uuid,
_kind: &'life1 str,
_tag: &'life2 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<(Uuid, i64)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn latest_annotating_note<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_node_id: Uuid,
_kind: &'life1 str,
_tag: &'life2 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<(Uuid, i64)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Return the newest live note of kind carrying the exact string tag
in its properties.tags array and connected to node_id by a live
incoming annotates edge in this store’s namespace. Returns its UUID
and creation timestamp; equal timestamps choose the smallest UUID.
Apply all predicates before limiting to one result. Note lookup follows the by-ID contract; visibility is determined by the annotation edge’s namespace, not by introducing a second namespace filter on the note. The note and edge must belong to this backend. Unsupported backends fail explicitly rather than scanning an arbitrary annotation window.
Sourcefn insert_edge_if_absent<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn insert_edge_if_absent<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Insert an edge only when neither its id nor natural key already
exists. Returns true when this call inserted the row and false
when an existing row won the race. The existing row is never updated.
The default returns Unsupported rather than falling back to
GraphStore::upsert_edge, because an upsert would overwrite the
winning row and violate this method’s conditional-insert contract.
Sourcefn upsert_edge_observed<'life0, 'async_trait>(
&'life0 self,
_request: EdgeUpsertRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeUpsertResult>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn upsert_edge_observed<'life0, 'async_trait>(
&'life0 self,
_request: EdgeUpsertRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeUpsertResult>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Insert or replace one edge and return the transaction-observed disposition plus preimage. Tombstone restoration is controlled by the request rather than being an implicit side effect of every upsert.
Sourcefn replace_edge_if_unchanged<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
_expected_updated_at: DateTime<Utc>,
_expected_deleted_at: Option<DateTime<Utc>>,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn replace_edge_if_unchanged<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
_expected_updated_at: DateTime<Utc>,
_expected_deleted_at: Option<DateTime<Utc>>,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Replace an edge only when the persisted row still matches the caller’s read snapshot.
expected_updated_at is the snapshot revision and
expected_deleted_at closes the soft-delete race. The replacement
edge’s updated_at must be strictly greater than that persisted
revision. Returns false when the row disappeared, changed, or was
supplied a non-advancing replacement revision. This is the full-edge
compare-and-swap seam used when a caller derives coupled fields from
that snapshot before persistence — mirrors
crate::NoteStore::replace_note_if_unchanged. The default returns
Unsupported rather than falling back to an unguarded upsert and
reintroducing the stale-snapshot race.
Sourcefn upsert_edge_guarded<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedWriteOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn upsert_edge_guarded<'life0, 'async_trait>(
&'life0 self,
_edge: Edge,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedWriteOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Insert or update a single edge, re-checking that both endpoints still exist (and are not soft-deleted) as part of the same write, not a separate prior read. Closes the TOCTOU window between an async prepare-time existence check and a later, unconditional write: a concurrent hard-delete of an endpoint that lands between the two can otherwise leave a durably dangling edge (#769).
Returns GuardedWriteOutcome::Refused naming exactly which
endpoint(s) were missing, determined by the guard’s own in-transaction
probe — never reconstructed by a caller re-reading the endpoints after
the write already failed, since a concurrent write landing between the
refusal and any such later read could misreport which endpoint was
actually missing at write time.
Default returns StorageError::Unsupported: a backend that does not
override this method cannot honor the endpoint-existence guarantee,
and silently falling back to GraphStore::upsert_edge would
reintroduce the TOCTOU window this method exists to close.
Sourcefn upsert_edge_guarded_observed<'life0, 'async_trait>(
&'life0 self,
_request: EdgeUpsertRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedEdgeUpsertOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn upsert_edge_guarded_observed<'life0, 'async_trait>(
&'life0 self,
_request: EdgeUpsertRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedEdgeUpsertOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Observed form of GraphStore::upsert_edge_guarded. In addition to
the endpoint guard, it distinguishes create, live replacement, and
explicit resurrection without a caller-side read/write race.
Sourcefn upsert_edges_guarded<'life0, 'async_trait>(
&'life0 self,
_edges: Vec<Edge>,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedBatchOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn upsert_edges_guarded<'life0, 'async_trait>(
&'life0 self,
_edges: Vec<Edge>,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedBatchOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Batch form of GraphStore::upsert_edge_guarded. All-or-nothing:
if any edge’s endpoints are missing at write time, no edge from the
batch is persisted, BatchWriteSummary::affected is 0, and
GuardedBatchOutcome::refused names the first failing batch entry and
its missing endpoint(s) — determined by the same in-transaction
pre-check that aborted the batch, not a post-hoc re-read.
Retain the original ordered input to enumerate every aborted write via
GuardedBatchOutcome::refusal_page beyond the default bounded sample.
Default returns StorageError::Unsupported, for the same reason as
GraphStore::upsert_edge_guarded’s default.
Sourcefn upsert_edges_guarded_observed<'life0, 'async_trait>(
&'life0 self,
_requests: Vec<EdgeUpsertRequest>,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedEdgeBatchUpsertOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn upsert_edges_guarded_observed<'life0, 'async_trait>(
&'life0 self,
_requests: Vec<EdgeUpsertRequest>,
) -> Pin<Box<dyn Future<Output = StorageResult<GuardedEdgeBatchUpsertOutcome>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
All-or-nothing observed batch form. Implementations must perform endpoint and tombstone-policy preflight in the same write transaction before applying any row.
Sourcefn query_edges_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
filter: EdgeFilter,
sort: Vec<SortOrder<EdgeSortField>>,
page: PageRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Page<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn query_edges_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
filter: EdgeFilter,
sort: Vec<SortOrder<EdgeSortField>>,
page: PageRequest,
) -> Pin<Box<dyn Future<Output = StorageResult<Page<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Query edges across the given namespaces in one deterministic query
with real SQL paging. The multi-namespace analogue of
Self::query_edges: a single statement with namespace IN (...)
keeps offset continuation coherent, where fetching per-namespace
prefixes and slicing a client-side merge floats the window between
calls (silent duplicate/skip enumeration). Backends without batched
namespace support retain the single-namespace path and reject
multi-namespace requests explicitly. Implementations should return
total: None; callers that need a count use
Self::count_edges_in_namespaces explicitly.
Sourcefn count_edges_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
filter: EdgeFilter,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn count_edges_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
filter: EdgeFilter,
) -> Pin<Box<dyn Future<Output = StorageResult<u64>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Count edges across the given namespaces in one aggregate query. Backends without batched namespace support retain the single-namespace path and reject multi-namespace requests explicitly.
Sourcefn count_edges_by_relation_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(EdgeRelation, u64)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn count_edges_by_relation_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(EdgeRelation, u64)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Count edges grouped by relation across the given namespaces in one aggregate query.
Sourcefn count_edges_by_endpoint_base<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeEndpointBaseCounts>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn count_edges_by_endpoint_base<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeEndpointBaseCounts>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Count live edges grouped by the base each endpoint resolves against.
A relation breakdown cannot answer this: relations do not determine endpoint bases, and the same relation appears on both sides of the structure/provenance line. One aggregate query, same shape and cost as the relation counts.
Sourcefn count_edges_by_endpoint_base_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeEndpointBaseCounts>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn count_edges_by_endpoint_base_in_namespaces<'life0, 'life1, 'async_trait>(
&'life0 self,
namespaces: &'life1 [String],
) -> Pin<Box<dyn Future<Output = StorageResult<EdgeEndpointBaseCounts>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Count live edges by endpoint base across the given namespaces in one aggregate query.
Sourcefn edge_sequence<'life0, 'async_trait>(
&'life0 self,
_id: Uuid,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<i64>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn edge_sequence<'life0, 'async_trait>(
&'life0 self,
_id: Uuid,
) -> Pin<Box<dyn Future<Output = StorageResult<Option<i64>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Resolve an edge id to its immutable insertion sequence.
Sourcefn edge_sequences<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [Uuid],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(Uuid, i64)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn edge_sequences<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [Uuid],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(Uuid, i64)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Resolve edge ids to immutable insertion sequences. Implementations may override this to batch the lookup; the default preserves correctness.
Sourcefn query_edges_sequence_after<'life0, 'async_trait>(
&'life0 self,
_filter: EdgeFilter,
_after: Option<SeekCursor>,
_limit: u32,
) -> Pin<Box<dyn Future<Output = StorageResult<SeekPage<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn query_edges_sequence_after<'life0, 'async_trait>(
&'life0 self,
_filter: EdgeFilter,
_after: Option<SeekCursor>,
_limit: u32,
) -> Pin<Box<dyn Future<Output = StorageResult<SeekPage<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Seek-pagination page ordered by immutable insertion sequence. This is the stable public-list contract for walks overlapping inserts (#1424).
Sourcefn neighbors_page<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
after: Option<NeighborCursor>,
neighbor_kinds: Option<Vec<String>>,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<NeighborHit>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn neighbors_page<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
after: Option<NeighborCursor>,
neighbor_kinds: Option<Vec<String>>,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<NeighborHit>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Return one deterministic neighbor page. after is exclusive and
neighbor_kinds, when present, filters entity and note kinds before
the limit is applied. Backends that do not implement kind-aware paging
retain an explicit unsupported result rather than silently returning a
misleading page.
Sourcefn neighbors_both_directions<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<DirectedNeighborHit>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn neighbors_both_directions<'life0, 'async_trait>(
&'life0 self,
node_id: Uuid,
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<DirectedNeighborHit>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Return neighbors in BOTH directions in a single call, each tagged with
the direction (Out/In) it was found in. query.direction is
ignored — this always fetches both directions.
Exists so a caller that needs both-direction neighbors labeled by
direction (e.g. the context verb) can do so with one storage query
instead of two separate direction-scoped neighbors calls. The
default implementation preserves the original two-call behavior for
backends that don’t override it; SqlGraphStore overrides this with a
single UNION ALL query that projects a direction literal per arm.
Sourcefn get_edges<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [LinkId],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_edges<'life0, 'life1, 'async_trait>(
&'life0 self,
ids: &'life1 [LinkId],
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<Edge>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Fetch multiple edges by their link IDs in a single round-trip.
IDs that are not found (absent or soft-deleted) are silently skipped;
the returned Vec may be shorter than ids. Backends that support
batched IN (...) queries should override this; the default loops
get_edge so non-SQLite backends keep compiling unchanged.
Callers must chunk large ID lists before calling if they need a strict size bound; this method does not enforce a maximum.
Sourcefn batch_neighbors<'life0, 'life1, 'async_trait>(
&'life0 self,
sources: &'life1 [Uuid],
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(Uuid, NeighborHit)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn batch_neighbors<'life0, 'life1, 'async_trait>(
&'life0 self,
sources: &'life1 [Uuid],
query: NeighborQuery,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<(Uuid, NeighborHit)>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Return neighbors for multiple source nodes in a single round-trip,
yielding (source_id, hit) pairs.
The query parameters (direction, relations, min_weight) are applied
uniformly to every source node. query.limit is applied per source:
each source returns at most limit hits. Backends that support batched
source_id IN (...) queries should override this; the default loops
neighbors so non-SQLite backends keep compiling unchanged.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".