pub struct TraversalBuilder {
pub start_node: String,
pub max_depth: usize,
pub edge_types: Vec<String>,
pub min_weight: f64,
pub attribute_mode: Option<AttributeMode>,
pub as_of: Option<String>,
}Expand description
Recursive CTE traversal query builder (§5.2).
Fields§
§start_node: String§max_depth: usize§edge_types: Vec<String>§min_weight: f64§attribute_mode: Option<AttributeMode>None means defaulted, not Current (T3.2, D-085).
The distinction is the whole mechanism. Current chosen by a caller who
knows what it means is a legitimate, fast answer; Current arrived at by
never touching the setting, on a query about the past, is a wrong answer
nobody asked for. Those two produce identical behaviour and must not be
stored identically, so the field records which happened.
Public for construction-by-struct-literal, which is why it is an Option
here rather than a private bool beside the mode: a caller building the
struct directly should have to write down the same thing the builder
method records.
as_of: Option<String>The valid-time instant to traverse at, if it is not the present.
Added in 0.6.0 so “as of Tuesday” is expressible. Before this, the
instant arrived as execute’s now_ts parameter and a historical
traversal was indistinguishable from a live one — which is why the
mismatch with AttributeMode::Current could only ever be a warn!:
nothing in the call had the information needed to raise an error.
Implementations§
Source§impl TraversalBuilder
impl TraversalBuilder
pub fn new(start_node: impl Into<String>) -> Self
pub fn max_depth(self, depth: usize) -> Self
pub fn edge_types(self, types: Vec<String>) -> Self
pub fn min_weight(self, weight: f64) -> Self
Sourcepub fn attribute_mode(self, mode: AttributeMode) -> Self
pub fn attribute_mode(self, mode: AttributeMode) -> Self
State the attribute mode explicitly.
Calling this is what turns Current from a default into a decision, and
Self::execute treats the two differently on a historical traversal —
see Self::as_of.
Sourcepub fn as_of(self, ts: impl Into<String>) -> Self
pub fn as_of(self, ts: impl Into<String>) -> Self
Traverse the graph as it was at ts rather than at the present (§5.2).
§Setting this makes the attribute mode a required decision (T3.2, D-085)
A historical traversal has two independent temporal questions, and until
0.6.0 only one of them was asked. The topology comes from ts. The node
attributes — titles, content — come from wherever
AttributeMode says, and the default said Current, which is live
text. So as_of(Tuesday) returned Tuesday’s graph wearing today’s
titles, and reported that through a tracing::warn! — invisible in any
application that has not configured a subscriber, which is most of them
at first run.
So: with as_of set and no Self::attribute_mode call,
Self::execute returns [DbError::AttributeModeUnstated] rather than
guessing. Both answers stay available and neither is silent:
// Tuesday's graph with Tuesday's titles — usually what was meant.
let then = TraversalBuilder::new("a")
.as_of("2026-01-06T00:00:00.000000Z")
.attribute_mode(AttributeMode::AtTime)
.execute(conn, now)
.await?;
// Tuesday's topology with today's titles — legitimate, and now stated.
let mixed = TraversalBuilder::new("a")
.as_of("2026-01-06T00:00:00.000000Z")
.attribute_mode(AttributeMode::Current)
.execute(conn, now)
.await?;A traversal with no as_of is a query about now, where Current and
AtTime agree about which text to return, so the default stands and no
caller has to change.
Sourcepub fn build_sql(&self) -> String
pub fn build_sql(&self) -> String
Compile the recursive CTE query string as specified in §5.2.
Edge types become bind placeholders, not quoted literals. An earlier
version spliced them in with format!("'{t}'"), which made any caller
string a SQL fragment on the read path — and the only validation in the
crate, super::edge::validate_edge_type, runs in
super::EdgeAssertion::normalized on the write path, so a traversal
never passed through it. Binding removes the question rather than
answering it: unlike a table name, an edge type is a value, and values
can be parameters.
Sourcepub async fn execute_ids(
&self,
conn: &Connection,
now_ts: &str,
) -> Result<Vec<String>>
pub async fn execute_ids( &self, conn: &Connection, now_ts: &str, ) -> Result<Vec<String>>
Node ids reachable under this traversal, in id order (§5.2).
Reads at Self::as_of when set, else at now_ts. No attribute mode is
involved, so this never returns [DbError::AttributeModeUnstated]:
topology at an instant is unambiguous, and it is only the pairing with
live attributes that needed a decision.
Sourcepub async fn execute(
&self,
conn: &Connection,
now_ts: &str,
) -> Result<Vec<NodeAttributes>>
pub async fn execute( &self, conn: &Connection, now_ts: &str, ) -> Result<Vec<NodeAttributes>>
Execute the traversal and hydrate attributes per Self::attribute_mode
(§5.2).
The hydration is a second step rather than a join in the CTE because the
three modes read from two different places: Current and Omit from
concepts, AtTime from transaction_log. The previous version always
emitted the concepts join, so attribute_mode was stored, exposed by a
builder method, and never read — a caller asking for AtTime got live
attributes with no indication that the mode had been ignored. That is the
exact failure Doctrine II exists to prevent, arriving as a silent wrong
answer rather than as an error.
AttributeMode::Omit returns Ok(vec![]) here, which is
indistinguishable from a traversal that reached nothing. That is a
limitation of this method’s return type rather than of the mode; callers
wanting topology only should use Self::execute_ids, which says what it
found.
§Errors
[DbError::AttributeModeUnstated] when Self::as_of is set and
Self::attribute_mode is not — see as_of for why that combination is
a question rather than a default (T3.2, D-085).
now_ts is the caller’s present. A traversal with as_of set reads
topology at that instant instead; now_ts is still what an
AttributeMode::Current hydrate means by “current”.
Trait Implementations§
Source§impl Clone for TraversalBuilder
impl Clone for TraversalBuilder
Source§fn clone(&self) -> TraversalBuilder
fn clone(&self) -> TraversalBuilder
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for TraversalBuilder
impl RefUnwindSafe for TraversalBuilder
impl Send for TraversalBuilder
impl Sync for TraversalBuilder
impl Unpin for TraversalBuilder
impl UnsafeUnpin for TraversalBuilder
impl UnwindSafe for TraversalBuilder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request