Skip to main content

SqlGraphStore

Struct SqlGraphStore 

Source
pub struct SqlGraphStore { /* private fields */ }
Expand description

A GraphStore backed by SQLite tables.

Implementations§

Source§

impl SqlGraphStore

Source

pub fn new_scoped( pool: Arc<ConnectionPool>, is_file_backed: bool, namespace: impl Into<String>, ) -> Self

Create a new store with a default namespace for multi-record query filtering.

The namespace is a PARAM-ONLY hint (ADR-007 rule 4) — it is used as a WHERE filter in multi-record queries and as the write namespace stamped on upserted edges, but it does NOT enforce isolation: upsert_edge accepts edges from any namespace, and by-ID ops (get_edge, delete_edge) ignore the namespace entirely.

Trait Implementations§

Source§

impl GraphStore for SqlGraphStore

Source§

fn neighbors_both_directions<'life0, 'async_trait>( &'life0 self, node_id: Uuid, query: NeighborQuery, ) -> Pin<Box<dyn Future<Output = Result<Vec<DirectedNeighborHit>, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Single-query both-direction neighbor fetch (ADR-089 context-verb optimization): projects a 'out'/'in' literal from each UNION ALL arm so the caller gets direction labels without a second direction- scoped round trip. query.direction is ignored — always both.

Source§

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 = Result<Option<(Uuid, i64)>, StorageError>> + 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. Read more
Source§

fn upsert_edge<'life0, 'async_trait>( &'life0 self, edge: Edge, ) -> Pin<Box<dyn Future<Output = Result<(), StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Insert or update a single edge.
Source§

fn upsert_edge_observed<'life0, 'async_trait>( &'life0 self, request: EdgeUpsertRequest, ) -> Pin<Box<dyn Future<Output = Result<EdgeUpsertResult, StorageError>> + 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.
Source§

fn insert_edge_if_absent<'life0, 'async_trait>( &'life0 self, edge: Edge, ) -> Pin<Box<dyn Future<Output = Result<bool, StorageError>> + 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. Read more
Source§

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 = Result<bool, StorageError>> + 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. Read more
Source§

fn upsert_edges<'life0, 'async_trait>( &'life0 self, edges: Vec<Edge>, ) -> Pin<Box<dyn Future<Output = Result<BatchWriteSummary, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Insert or update a batch of edges.
Source§

fn upsert_edge_guarded<'life0, 'async_trait>( &'life0 self, edge: Edge, ) -> Pin<Box<dyn Future<Output = Result<GuardedWriteOutcome, StorageError>> + 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). Read more
Source§

fn upsert_edge_guarded_observed<'life0, 'async_trait>( &'life0 self, request: EdgeUpsertRequest, ) -> Pin<Box<dyn Future<Output = Result<GuardedEdgeUpsertOutcome, StorageError>> + 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.
Source§

fn upsert_edges_guarded<'life0, 'async_trait>( &'life0 self, edges: Vec<Edge>, ) -> Pin<Box<dyn Future<Output = Result<GuardedBatchOutcome, StorageError>> + 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. Read more
Source§

fn upsert_edges_guarded_observed<'life0, 'async_trait>( &'life0 self, requests: Vec<EdgeUpsertRequest>, ) -> Pin<Box<dyn Future<Output = Result<GuardedEdgeBatchUpsertOutcome, StorageError>> + 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.
Source§

fn get_edge<'life0, 'async_trait>( &'life0 self, id: LinkId, ) -> Pin<Box<dyn Future<Output = Result<Option<Edge>, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Fetch an edge by link ID, returning None if absent. Filters soft-deleted rows.
Source§

fn get_edge_including_deleted<'life0, 'async_trait>( &'life0 self, id: LinkId, ) -> Pin<Box<dyn Future<Output = Result<Option<Edge>, StorageError>> + 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.
Source§

fn edge_sequence<'life0, 'async_trait>( &'life0 self, id: Uuid, ) -> Pin<Box<dyn Future<Output = Result<Option<i64>, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Resolve an edge id to its immutable insertion sequence.
Source§

fn edge_sequences<'life0, 'life1, 'async_trait>( &'life0 self, ids: &'life1 [Uuid], ) -> Pin<Box<dyn Future<Output = Result<Vec<(Uuid, i64)>, StorageError>> + 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.
Source§

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 = Result<Option<Edge>, StorageError>> + 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. Read more
Source§

fn get_edges<'life0, 'life1, 'async_trait>( &'life0 self, ids: &'life1 [LinkId], ) -> Pin<Box<dyn Future<Output = Result<Vec<Edge>, StorageError>> + 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. Read more
Source§

fn batch_neighbors<'life0, 'life1, 'async_trait>( &'life0 self, sources: &'life1 [Uuid], query: NeighborQuery, ) -> Pin<Box<dyn Future<Output = Result<Vec<(Uuid, NeighborHit)>, StorageError>> + 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. Read more
Source§

fn delete_edge<'life0, 'async_trait>( &'life0 self, id: LinkId, mode: DeleteMode, ) -> Pin<Box<dyn Future<Output = Result<bool, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Delete an edge by link ID using the specified delete mode.
Source§

fn query_edges<'life0, 'async_trait>( &'life0 self, filter: EdgeFilter, sort: Vec<SortOrder<EdgeSortField>>, page: PageRequest, ) -> Pin<Box<dyn Future<Output = Result<Page<Edge>, StorageError>> + 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.
Source§

fn count_edges<'life0, 'async_trait>( &'life0 self, filter: EdgeFilter, ) -> Pin<Box<dyn Future<Output = Result<u64, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Count edges matching the given filter.
Source§

fn count_edges_in_namespaces<'life0, 'life1, 'async_trait>( &'life0 self, namespaces: &'life1 [String], filter: EdgeFilter, ) -> Pin<Box<dyn Future<Output = Result<u64, StorageError>> + 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.
Source§

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 = Result<Page<Edge>, StorageError>> + 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.
Source§

fn count_edges_by_relation<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<Vec<(EdgeRelation, u64)>, StorageError>> + 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).
Source§

fn count_edges_by_relation_in_namespaces<'life0, 'life1, 'async_trait>( &'life0 self, namespaces: &'life1 [String], ) -> Pin<Box<dyn Future<Output = Result<Vec<(EdgeRelation, u64)>, StorageError>> + 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.
Source§

fn count_edges_by_endpoint_base<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<EdgeEndpointBaseCounts, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Count live edges grouped by the base each endpoint resolves against. Read more
Source§

fn count_edges_by_endpoint_base_in_namespaces<'life0, 'life1, 'async_trait>( &'life0 self, namespaces: &'life1 [String], ) -> Pin<Box<dyn Future<Output = Result<EdgeEndpointBaseCounts, StorageError>> + 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.
Source§

fn query_edges_after<'life0, 'async_trait>( &'life0 self, filter: EdgeFilter, after: Option<Uuid>, limit: u32, ) -> Pin<Box<dyn Future<Output = Result<EdgeSeekPage, StorageError>> + 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).
Source§

fn query_edges_sequence_after<'life0, 'async_trait>( &'life0 self, filter: EdgeFilter, after: Option<SeekCursor>, limit: u32, ) -> Pin<Box<dyn Future<Output = Result<SeekPage<Edge>, StorageError>> + 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).
Source§

fn neighbors<'life0, 'async_trait>( &'life0 self, node_id: Uuid, query: NeighborQuery, ) -> Pin<Box<dyn Future<Output = Result<Vec<NeighborHit>, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Return immediate neighbors of a graph node.
Source§

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 = Result<Vec<NeighborHit>, StorageError>> + 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.
Source§

fn traverse<'life0, 'async_trait>( &'life0 self, request: TraversalRequest, ) -> Pin<Box<dyn Future<Output = Result<Vec<GraphPath>, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Bounded multi-hop BFS traversal from the given roots. Read more
Source§

fn purge_incident_edges<'life0, 'async_trait>( &'life0 self, node_id: Uuid, ) -> Pin<Box<dyn Future<Output = Result<u64, StorageError>> + 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).

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more