Skip to main content

helix_ast/
traversal.rs

1//! Typed traversal states and operations for the public query AST.
2
3use std::marker::PhantomData;
4
5use serde::{Deserialize, Serialize};
6
7use crate::expr::{Predicate, SourcePredicate, StreamBound};
8use crate::graph::{EdgeRef, NodeRef};
9use crate::index::IndexSpec;
10use crate::projection::{
11    validate_binding_name, validate_binding_projections, BindingProjection, Projection,
12};
13use crate::value::{PropertyInput, PropertyValue};
14/// Marker trait for traversal states.
15pub trait TraversalState: private::Sealed {}
16
17mod private {
18    pub trait Sealed {}
19    impl Sealed for super::Empty {}
20    impl Sealed for super::OnNodes {}
21    impl Sealed for super::OnEdges {}
22    impl Sealed for super::Terminal {}
23    impl Sealed for super::ReadOnly {}
24    impl Sealed for super::WriteEnabled {}
25}
26
27/// Initial state with no root node.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct Empty;
30
31/// Traversal currently yields nodes.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct OnNodes;
34
35/// Traversal currently yields edges.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct OnEdges;
38
39/// Traversal is terminal.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct Terminal;
42
43impl TraversalState for Empty {}
44impl TraversalState for OnNodes {}
45impl TraversalState for OnEdges {}
46impl TraversalState for Terminal {}
47
48/// Marker trait for mutation capability.
49pub trait MutationMode: private::Sealed {}
50
51/// Read-only traversal.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct ReadOnly;
54
55/// Traversal containing write operations.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct WriteEnabled;
58
59impl MutationMode for ReadOnly {}
60impl MutationMode for WriteEnabled {}
61/// Sort order.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
63#[serde(rename_all = "snake_case")]
64pub enum Order {
65    /// Ascending.
66    #[default]
67    Asc,
68    /// Descending.
69    Desc,
70}
71
72/// Direction used by shortest-path traversal.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
74#[serde(rename_all = "snake_case")]
75pub enum ShortestPathDirection {
76    /// Follow outgoing edges from the source.
77    #[default]
78    Out,
79    /// Follow incoming edges from the source.
80    In,
81    /// Follow both incoming and outgoing edges.
82    Both,
83}
84
85/// Repeat emit behavior.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
87#[serde(rename_all = "snake_case")]
88pub enum EmitBehavior {
89    /// Do not emit intermediate results.
90    #[default]
91    None,
92    /// Emit before each iteration.
93    Before,
94    /// Emit after each iteration.
95    After,
96    /// Emit before and after.
97    All,
98}
99
100/// Aggregate function.
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case")]
103pub enum AggregateFunction {
104    /// Count.
105    Count,
106    /// Sum.
107    Sum,
108    /// Min.
109    Min,
110    /// Max.
111    Max,
112    /// Mean.
113    Mean,
114}
115/// Query AST node.
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117#[serde(rename_all = "snake_case")]
118pub enum AstNode {
119    /// Implicit branch input for sub-traversals.
120    Context,
121    /// Start from nodes.
122    Nodes { reference: NodeRef },
123    /// Start from nodes matching a predicate.
124    NodesWhere { predicate: SourcePredicate },
125    /// Start from edges.
126    Edges { reference: EdgeRef },
127    /// Start from edges matching a predicate.
128    EdgesWhere { predicate: SourcePredicate },
129    /// Vector search on nodes.
130    VectorSearchNodes {
131        /// Label scope.
132        label: String,
133        /// Vector property.
134        property: String,
135        /// Optional tenant value.
136        #[serde(default, skip_serializing_if = "Option::is_none")]
137        tenant_value: Option<PropertyInput>,
138        /// Query vector input.
139        query_vector: PropertyInput,
140        /// Result count.
141        k: StreamBound,
142    },
143    /// Text search on nodes.
144    TextSearchNodes {
145        /// Label scope.
146        label: String,
147        /// Text property.
148        property: String,
149        /// Optional tenant value.
150        #[serde(default, skip_serializing_if = "Option::is_none")]
151        tenant_value: Option<PropertyInput>,
152        /// Query text input.
153        query_text: PropertyInput,
154        /// Result count.
155        k: StreamBound,
156    },
157    /// Vector search on edges.
158    VectorSearchEdges {
159        /// Label scope.
160        label: String,
161        /// Vector property.
162        property: String,
163        /// Optional tenant value.
164        #[serde(default, skip_serializing_if = "Option::is_none")]
165        tenant_value: Option<PropertyInput>,
166        /// Query vector input.
167        query_vector: PropertyInput,
168        /// Result count.
169        k: StreamBound,
170    },
171    /// Text search on edges.
172    TextSearchEdges {
173        /// Label scope.
174        label: String,
175        /// Text property.
176        property: String,
177        /// Optional tenant value.
178        #[serde(default, skip_serializing_if = "Option::is_none")]
179        tenant_value: Option<PropertyInput>,
180        /// Query text input.
181        query_text: PropertyInput,
182        /// Result count.
183        k: StreamBound,
184    },
185    /// Rank the current node stream by one vector index.
186    VectorSearchNodesWithin {
187        /// Input node stream whose IDs are the exact candidate filter.
188        input: Box<AstNode>,
189        /// Label scope.
190        label: String,
191        /// Vector property.
192        property: String,
193        /// Optional tenant value.
194        #[serde(default, skip_serializing_if = "Option::is_none")]
195        tenant_value: Option<PropertyInput>,
196        /// Query vector input.
197        query_vector: PropertyInput,
198        /// Result count.
199        k: StreamBound,
200    },
201    /// Rank the current edge stream by one vector index.
202    VectorSearchEdgesWithin {
203        /// Input edge stream whose IDs are the exact candidate filter.
204        input: Box<AstNode>,
205        /// Label scope.
206        label: String,
207        /// Vector property.
208        property: String,
209        /// Optional tenant value.
210        #[serde(default, skip_serializing_if = "Option::is_none")]
211        tenant_value: Option<PropertyInput>,
212        /// Query vector input.
213        query_vector: PropertyInput,
214        /// Result count.
215        k: StreamBound,
216    },
217    /// Node-to-node outgoing traversal.
218    Out {
219        /// Input stream.
220        input: Box<AstNode>,
221        /// Optional edge label.
222        #[serde(default, skip_serializing_if = "Option::is_none")]
223        label: Option<String>,
224    },
225    /// Node-to-node incoming traversal.
226    In {
227        /// Input stream.
228        input: Box<AstNode>,
229        /// Optional edge label.
230        #[serde(default, skip_serializing_if = "Option::is_none")]
231        label: Option<String>,
232    },
233    /// Node-to-node both-direction traversal.
234    Both {
235        /// Input stream.
236        input: Box<AstNode>,
237        /// Optional edge label.
238        #[serde(default, skip_serializing_if = "Option::is_none")]
239        label: Option<String>,
240    },
241    /// Node-to-edge outgoing traversal.
242    OutE {
243        /// Input stream.
244        input: Box<AstNode>,
245        /// Optional edge label.
246        #[serde(default, skip_serializing_if = "Option::is_none")]
247        label: Option<String>,
248    },
249    /// Node-to-edge incoming traversal.
250    InE {
251        /// Input stream.
252        input: Box<AstNode>,
253        /// Optional edge label.
254        #[serde(default, skip_serializing_if = "Option::is_none")]
255        label: Option<String>,
256    },
257    /// Node-to-edge both-direction traversal.
258    BothE {
259        /// Input stream.
260        input: Box<AstNode>,
261        /// Optional edge label.
262        #[serde(default, skip_serializing_if = "Option::is_none")]
263        label: Option<String>,
264    },
265    /// Edge-to-target-node traversal.
266    OutN { input: Box<AstNode> },
267    /// Edge-to-source-node traversal.
268    InN { input: Box<AstNode> },
269    /// Edge-to-other-node traversal.
270    OtherN { input: Box<AstNode> },
271    /// Property equality filter.
272    Has {
273        /// Input stream.
274        input: Box<AstNode>,
275        /// Property name.
276        property: String,
277        /// Literal value.
278        value: PropertyValue,
279    },
280    /// Label filter.
281    HasLabel {
282        /// Input stream.
283        input: Box<AstNode>,
284        /// Label.
285        label: String,
286    },
287    /// Property existence filter.
288    HasKey {
289        /// Input stream.
290        input: Box<AstNode>,
291        /// Property.
292        property: String,
293    },
294    /// Predicate filter.
295    Where {
296        /// Input stream.
297        input: Box<AstNode>,
298        /// Predicate.
299        predicate: Predicate,
300    },
301    /// Deduplicate stream.
302    Dedup { input: Box<AstNode> },
303    /// Keep elements within a variable.
304    Within {
305        /// Input stream.
306        input: Box<AstNode>,
307        /// Variable name.
308        variable: String,
309    },
310    /// Keep elements outside a variable.
311    Without {
312        /// Input stream.
313        input: Box<AstNode>,
314        /// Variable name.
315        variable: String,
316    },
317    /// Edge property filter.
318    EdgeHas {
319        /// Input stream.
320        input: Box<AstNode>,
321        /// Property.
322        property: String,
323        /// Value or expression.
324        value: PropertyInput,
325    },
326    /// Edge label filter.
327    EdgeHasLabel {
328        /// Input stream.
329        input: Box<AstNode>,
330        /// Label.
331        label: String,
332    },
333    /// Limit.
334    Limit {
335        /// Input stream.
336        input: Box<AstNode>,
337        /// Bound.
338        count: StreamBound,
339    },
340    /// Skip.
341    Skip {
342        /// Input stream.
343        input: Box<AstNode>,
344        /// Bound.
345        count: StreamBound,
346    },
347    /// Range.
348    Range {
349        /// Input stream.
350        input: Box<AstNode>,
351        /// Start bound.
352        start: StreamBound,
353        /// End bound.
354        end: StreamBound,
355    },
356    /// Store current stream.
357    As { input: Box<AstNode>, name: String },
358    /// Store current stream.
359    Store { input: Box<AstNode>, name: String },
360    /// Select named stream.
361    Select { input: Box<AstNode>, name: String },
362    /// Capture current element as row binding.
363    Bind { input: Box<AstNode>, name: String },
364    /// Inject variable stream.
365    Inject {
366        /// Optional input stream. `None` means source inject.
367        #[serde(default, skip_serializing_if = "Option::is_none")]
368        input: Option<Box<AstNode>>,
369        /// Variable name.
370        variable: String,
371    },
372    /// Count terminal.
373    Count { input: Box<AstNode> },
374    /// Exists terminal.
375    Exists { input: Box<AstNode> },
376    /// ID terminal.
377    Id { input: Box<AstNode> },
378    /// Label terminal.
379    Label { input: Box<AstNode> },
380    /// Values terminal.
381    Values {
382        /// Input stream.
383        input: Box<AstNode>,
384        /// Properties.
385        properties: Vec<String>,
386    },
387    /// Value-map terminal.
388    ValueMap {
389        /// Input stream.
390        input: Box<AstNode>,
391        /// Optional property filter.
392        #[serde(default, skip_serializing_if = "Option::is_none")]
393        properties: Option<Vec<String>>,
394    },
395    /// Project terminal.
396    Project {
397        /// Input stream.
398        input: Box<AstNode>,
399        /// Projection list.
400        projections: Vec<Projection>,
401    },
402    /// Row-binding projection terminal.
403    ProjectBindings {
404        /// Input stream.
405        input: Box<AstNode>,
406        /// Projection list.
407        projections: Vec<BindingProjection>,
408        /// Deduplicate projected rows.
409        distinct: bool,
410    },
411    /// Edge properties terminal.
412    EdgeProperties { input: Box<AstNode> },
413    /// Create index.
414    CreateIndex {
415        /// Index specification.
416        spec: IndexSpec,
417        /// Ignore existing matching index.
418        if_not_exists: bool,
419    },
420    /// Drop index.
421    DropIndex {
422        /// Index specification.
423        spec: IndexSpec,
424    },
425    /// Read one retained index operation in the request scope.
426    GetIndexOperation {
427        /// Canonical lowercase operation UUID.
428        operation_id: String,
429    },
430    /// Ensure one retained operation is runnable in the request scope.
431    RetryIndexOperation {
432        /// Canonical lowercase operation UUID.
433        operation_id: String,
434    },
435    /// Convert one constructing BUILD into abort cleanup.
436    AbortIndexOperation {
437        /// Canonical lowercase operation UUID.
438        operation_id: String,
439    },
440    /// Add node.
441    AddN {
442        /// Optional prior input.
443        #[serde(default, skip_serializing_if = "Option::is_none")]
444        input: Option<Box<AstNode>>,
445        /// Node label.
446        label: String,
447        /// Properties.
448        properties: Vec<(String, PropertyInput)>,
449    },
450    /// Add edge.
451    AddE {
452        /// Input node stream.
453        input: Box<AstNode>,
454        /// Edge label.
455        label: String,
456        /// Target nodes.
457        to: NodeRef,
458        /// Properties.
459        properties: Vec<(String, PropertyInput)>,
460    },
461    /// Set property.
462    SetProperty {
463        /// Input stream.
464        input: Box<AstNode>,
465        /// Property name.
466        name: String,
467        /// Value.
468        value: PropertyInput,
469    },
470    /// Remove property.
471    RemoveProperty {
472        /// Input stream.
473        input: Box<AstNode>,
474        /// Property name.
475        name: String,
476    },
477    /// Drop nodes.
478    Drop { input: Box<AstNode> },
479    /// Drop edges between current nodes and targets.
480    DropEdge {
481        /// Input node stream.
482        input: Box<AstNode>,
483        /// Target nodes.
484        to: NodeRef,
485    },
486    /// Drop labeled edges.
487    DropEdgeLabeled {
488        /// Input node stream.
489        input: Box<AstNode>,
490        /// Target nodes.
491        to: NodeRef,
492        /// Edge label.
493        label: String,
494    },
495    /// Drop edges by ID.
496    DropEdgeById {
497        /// Optional input stream.
498        #[serde(default, skip_serializing_if = "Option::is_none")]
499        input: Option<Box<AstNode>>,
500        /// Edge references.
501        edges: EdgeRef,
502    },
503    /// Order by one property.
504    OrderBy {
505        /// Input stream.
506        input: Box<AstNode>,
507        /// Property.
508        property: String,
509        /// Order.
510        order: Order,
511    },
512    /// Order by multiple properties.
513    OrderByMultiple {
514        /// Input stream.
515        input: Box<AstNode>,
516        /// Ordered keys.
517        orderings: Vec<(String, Order)>,
518    },
519    /// Repeat traversal.
520    Repeat {
521        /// Input stream.
522        input: Box<AstNode>,
523        /// Repeat configuration.
524        config: RepeatConfig,
525    },
526    /// Union branch traversals.
527    Union {
528        /// Input stream.
529        input: Box<AstNode>,
530        /// Branch traversals.
531        traversals: Vec<SubTraversal>,
532    },
533    /// Conditional branch.
534    Choose {
535        /// Input stream.
536        input: Box<AstNode>,
537        /// Condition.
538        condition: Predicate,
539        /// Then branch.
540        then_traversal: SubTraversal,
541        /// Else branch.
542        #[serde(default, skip_serializing_if = "Option::is_none")]
543        else_traversal: Option<SubTraversal>,
544    },
545    /// Coalesce branches.
546    Coalesce {
547        /// Input stream.
548        input: Box<AstNode>,
549        /// Branch traversals.
550        traversals: Vec<SubTraversal>,
551    },
552    /// Optional branch.
553    Optional {
554        /// Input stream.
555        input: Box<AstNode>,
556        /// Branch traversal.
557        traversal: SubTraversal,
558    },
559    /// Group terminal.
560    Group {
561        /// Input stream.
562        input: Box<AstNode>,
563        /// Property.
564        property: String,
565    },
566    /// Group-count terminal.
567    GroupCount {
568        /// Input stream.
569        input: Box<AstNode>,
570        /// Property.
571        property: String,
572    },
573    /// Aggregate terminal.
574    AggregateBy {
575        /// Input stream.
576        input: Box<AstNode>,
577        /// Function.
578        function: AggregateFunction,
579        /// Property.
580        property: String,
581    },
582    /// Fold barrier.
583    Fold { input: Box<AstNode> },
584    /// Unfold barrier.
585    Unfold { input: Box<AstNode> },
586    /// Path operation.
587    Path { input: Box<AstNode> },
588    /// Simple-path operation.
589    SimplePath { input: Box<AstNode> },
590    /// Sack initialization.
591    WithSack {
592        /// Input stream.
593        input: Box<AstNode>,
594        /// Initial value.
595        initial: PropertyValue,
596    },
597    /// Sack set.
598    SackSet {
599        /// Input stream.
600        input: Box<AstNode>,
601        /// Property.
602        property: String,
603    },
604    /// Sack add.
605    SackAdd {
606        /// Input stream.
607        input: Box<AstNode>,
608        /// Property.
609        property: String,
610    },
611    /// Sack get.
612    SackGet { input: Box<AstNode> },
613    /// Unweighted shortest path between two nodes.
614    ShortestPath {
615        /// Source node reference. Must resolve to exactly one node at runtime.
616        source: NodeRef,
617        /// Target node reference. Must resolve to exactly one node at runtime.
618        target: NodeRef,
619        /// Optional edge label.
620        #[serde(default, skip_serializing_if = "Option::is_none")]
621        label: Option<String>,
622        /// Traversal direction.
623        direction: ShortestPathDirection,
624        /// Maximum traversal depth.
625        max_depth: usize,
626    },
627}
628
629impl AstNode {
630    /// Returns true when this AST cannot mutate persistent graph or index state.
631    ///
632    /// The match is intentionally exhaustive so adding an AST operation requires
633    /// an explicit read-safety decision before the crate compiles.
634    pub fn is_read_only(&self) -> bool {
635        match self {
636            Self::Context
637            | Self::Nodes { .. }
638            | Self::NodesWhere { .. }
639            | Self::Edges { .. }
640            | Self::EdgesWhere { .. }
641            | Self::VectorSearchNodes { .. }
642            | Self::TextSearchNodes { .. }
643            | Self::VectorSearchEdges { .. }
644            | Self::TextSearchEdges { .. }
645            | Self::GetIndexOperation { .. }
646            | Self::ShortestPath { .. } => true,
647            Self::CreateIndex { .. }
648            | Self::DropIndex { .. }
649            | Self::RetryIndexOperation { .. }
650            | Self::AbortIndexOperation { .. }
651            | Self::AddN { .. }
652            | Self::AddE { .. }
653            | Self::SetProperty { .. }
654            | Self::RemoveProperty { .. }
655            | Self::Drop { .. }
656            | Self::DropEdge { .. }
657            | Self::DropEdgeLabeled { .. }
658            | Self::DropEdgeById { .. } => false,
659            Self::VectorSearchNodesWithin { input, .. }
660            | Self::VectorSearchEdgesWithin { input, .. }
661            | Self::Out { input, .. }
662            | Self::In { input, .. }
663            | Self::Both { input, .. }
664            | Self::OutE { input, .. }
665            | Self::InE { input, .. }
666            | Self::BothE { input, .. }
667            | Self::OutN { input }
668            | Self::InN { input }
669            | Self::OtherN { input }
670            | Self::Has { input, .. }
671            | Self::HasLabel { input, .. }
672            | Self::HasKey { input, .. }
673            | Self::Where { input, .. }
674            | Self::Dedup { input }
675            | Self::Within { input, .. }
676            | Self::Without { input, .. }
677            | Self::EdgeHas { input, .. }
678            | Self::EdgeHasLabel { input, .. }
679            | Self::Limit { input, .. }
680            | Self::Skip { input, .. }
681            | Self::Range { input, .. }
682            | Self::As { input, .. }
683            | Self::Store { input, .. }
684            | Self::Select { input, .. }
685            | Self::Bind { input, .. }
686            | Self::Count { input }
687            | Self::Exists { input }
688            | Self::Id { input }
689            | Self::Label { input }
690            | Self::Values { input, .. }
691            | Self::ValueMap { input, .. }
692            | Self::Project { input, .. }
693            | Self::ProjectBindings { input, .. }
694            | Self::EdgeProperties { input }
695            | Self::OrderBy { input, .. }
696            | Self::OrderByMultiple { input, .. }
697            | Self::Group { input, .. }
698            | Self::GroupCount { input, .. }
699            | Self::AggregateBy { input, .. }
700            | Self::Fold { input }
701            | Self::Unfold { input }
702            | Self::Path { input }
703            | Self::SimplePath { input }
704            | Self::WithSack { input, .. }
705            | Self::SackSet { input, .. }
706            | Self::SackAdd { input, .. }
707            | Self::SackGet { input } => input.is_read_only(),
708            Self::Inject { input, .. } => input.as_deref().map(Self::is_read_only).unwrap_or(true),
709            Self::Repeat { input, config } => {
710                input.is_read_only() && config.traversal.root.is_read_only()
711            }
712            Self::Union { input, traversals } | Self::Coalesce { input, traversals } => {
713                input.is_read_only()
714                    && traversals
715                        .iter()
716                        .all(|traversal| traversal.root.is_read_only())
717            }
718            Self::Choose {
719                input,
720                then_traversal,
721                else_traversal,
722                ..
723            } => {
724                input.is_read_only()
725                    && then_traversal.root.is_read_only()
726                    && else_traversal
727                        .as_ref()
728                        .map(|traversal| traversal.root.is_read_only())
729                        .unwrap_or(true)
730            }
731            Self::Optional { input, traversal } => {
732                input.is_read_only() && traversal.root.is_read_only()
733            }
734        }
735    }
736
737    /// Returns true when this node is terminal.
738    pub fn is_terminal(&self) -> bool {
739        matches!(
740            self,
741            Self::Count { .. }
742                | Self::Exists { .. }
743                | Self::Id { .. }
744                | Self::Label { .. }
745                | Self::Values { .. }
746                | Self::ValueMap { .. }
747                | Self::Project { .. }
748                | Self::ProjectBindings { .. }
749                | Self::EdgeProperties { .. }
750                | Self::CreateIndex { .. }
751                | Self::DropIndex { .. }
752                | Self::GetIndexOperation { .. }
753                | Self::RetryIndexOperation { .. }
754                | Self::AbortIndexOperation { .. }
755                | Self::Group { .. }
756                | Self::GroupCount { .. }
757                | Self::AggregateBy { .. }
758                | Self::ShortestPath { .. }
759        )
760    }
761}
762
763#[derive(Debug, Clone)]
764enum Operation {
765    Out(Option<String>),
766    In(Option<String>),
767    Both(Option<String>),
768    OutE(Option<String>),
769    InE(Option<String>),
770    BothE(Option<String>),
771    OutN,
772    InN,
773    OtherN,
774    Has(String, PropertyValue),
775    HasLabel(String),
776    HasKey(String),
777    Where(Predicate),
778    Dedup,
779    Within(String),
780    Without(String),
781    EdgeHas(String, PropertyInput),
782    EdgeHasLabel(String),
783    VectorSearchNodesWithin {
784        label: String,
785        property: String,
786        tenant_value: Option<PropertyInput>,
787        query_vector: PropertyInput,
788        k: StreamBound,
789    },
790    VectorSearchEdgesWithin {
791        label: String,
792        property: String,
793        tenant_value: Option<PropertyInput>,
794        query_vector: PropertyInput,
795        k: StreamBound,
796    },
797    Limit(StreamBound),
798    Skip(StreamBound),
799    Range(StreamBound, StreamBound),
800    As(String),
801    Store(String),
802    Select(String),
803    Bind(String),
804    Inject(String),
805    Count,
806    Exists,
807    Id,
808    Label,
809    Values(Vec<String>),
810    ValueMap(Option<Vec<String>>),
811    Project(Vec<Projection>),
812    ProjectBindings {
813        projections: Vec<BindingProjection>,
814        distinct: bool,
815    },
816    EdgeProperties,
817    AddN {
818        label: String,
819        properties: Vec<(String, PropertyInput)>,
820    },
821    AddE {
822        label: String,
823        to: NodeRef,
824        properties: Vec<(String, PropertyInput)>,
825    },
826    SetProperty(String, PropertyInput),
827    RemoveProperty(String),
828    Drop,
829    DropEdge(NodeRef),
830    DropEdgeLabeled {
831        to: NodeRef,
832        label: String,
833    },
834    DropEdgeById(EdgeRef),
835    OrderBy(String, Order),
836    OrderByMultiple(Vec<(String, Order)>),
837    Repeat(RepeatConfig),
838    Union(Vec<SubTraversal>),
839    Choose {
840        condition: Predicate,
841        then_traversal: SubTraversal,
842        else_traversal: Option<SubTraversal>,
843    },
844    Coalesce(Vec<SubTraversal>),
845    Optional(SubTraversal),
846    Group(String),
847    GroupCount(String),
848    AggregateBy(AggregateFunction, String),
849    Fold,
850    Unfold,
851    Path,
852    SimplePath,
853    WithSack(PropertyValue),
854    SackSet(String),
855    SackAdd(String),
856    SackGet,
857}
858
859impl Operation {
860    fn apply(self, input: AstNode) -> AstNode {
861        let input = Box::new(input);
862        match self {
863            Self::Out(label) => AstNode::Out { input, label },
864            Self::In(label) => AstNode::In { input, label },
865            Self::Both(label) => AstNode::Both { input, label },
866            Self::OutE(label) => AstNode::OutE { input, label },
867            Self::InE(label) => AstNode::InE { input, label },
868            Self::BothE(label) => AstNode::BothE { input, label },
869            Self::OutN => AstNode::OutN { input },
870            Self::InN => AstNode::InN { input },
871            Self::OtherN => AstNode::OtherN { input },
872            Self::Has(property, value) => AstNode::Has {
873                input,
874                property,
875                value,
876            },
877            Self::HasLabel(label) => AstNode::HasLabel { input, label },
878            Self::HasKey(property) => AstNode::HasKey { input, property },
879            Self::Where(predicate) => AstNode::Where { input, predicate },
880            Self::Dedup => AstNode::Dedup { input },
881            Self::Within(variable) => AstNode::Within { input, variable },
882            Self::Without(variable) => AstNode::Without { input, variable },
883            Self::EdgeHas(property, value) => AstNode::EdgeHas {
884                input,
885                property,
886                value,
887            },
888            Self::EdgeHasLabel(label) => AstNode::EdgeHasLabel { input, label },
889            Self::VectorSearchNodesWithin {
890                label,
891                property,
892                tenant_value,
893                query_vector,
894                k,
895            } => AstNode::VectorSearchNodesWithin {
896                input,
897                label,
898                property,
899                tenant_value,
900                query_vector,
901                k,
902            },
903            Self::VectorSearchEdgesWithin {
904                label,
905                property,
906                tenant_value,
907                query_vector,
908                k,
909            } => AstNode::VectorSearchEdgesWithin {
910                input,
911                label,
912                property,
913                tenant_value,
914                query_vector,
915                k,
916            },
917            Self::Limit(count) => AstNode::Limit { input, count },
918            Self::Skip(count) => AstNode::Skip { input, count },
919            Self::Range(start, end) => AstNode::Range { input, start, end },
920            Self::As(name) => AstNode::As { input, name },
921            Self::Store(name) => AstNode::Store { input, name },
922            Self::Select(name) => AstNode::Select { input, name },
923            Self::Bind(name) => AstNode::Bind { input, name },
924            Self::Inject(variable) => AstNode::Inject {
925                input: Some(input),
926                variable,
927            },
928            Self::Count => AstNode::Count { input },
929            Self::Exists => AstNode::Exists { input },
930            Self::Id => AstNode::Id { input },
931            Self::Label => AstNode::Label { input },
932            Self::Values(properties) => AstNode::Values { input, properties },
933            Self::ValueMap(properties) => AstNode::ValueMap { input, properties },
934            Self::Project(projections) => AstNode::Project { input, projections },
935            Self::ProjectBindings {
936                projections,
937                distinct,
938            } => AstNode::ProjectBindings {
939                input,
940                projections,
941                distinct,
942            },
943            Self::EdgeProperties => AstNode::EdgeProperties { input },
944            Self::AddN { label, properties } => AstNode::AddN {
945                input: Some(input),
946                label,
947                properties,
948            },
949            Self::AddE {
950                label,
951                to,
952                properties,
953            } => AstNode::AddE {
954                input,
955                label,
956                to,
957                properties,
958            },
959            Self::SetProperty(name, value) => AstNode::SetProperty { input, name, value },
960            Self::RemoveProperty(name) => AstNode::RemoveProperty { input, name },
961            Self::Drop => AstNode::Drop { input },
962            Self::DropEdge(to) => AstNode::DropEdge { input, to },
963            Self::DropEdgeLabeled { to, label } => AstNode::DropEdgeLabeled { input, to, label },
964            Self::DropEdgeById(edges) => AstNode::DropEdgeById {
965                input: Some(input),
966                edges,
967            },
968            Self::OrderBy(property, order) => AstNode::OrderBy {
969                input,
970                property,
971                order,
972            },
973            Self::OrderByMultiple(orderings) => AstNode::OrderByMultiple { input, orderings },
974            Self::Repeat(config) => AstNode::Repeat { input, config },
975            Self::Union(traversals) => AstNode::Union { input, traversals },
976            Self::Choose {
977                condition,
978                then_traversal,
979                else_traversal,
980            } => AstNode::Choose {
981                input,
982                condition,
983                then_traversal,
984                else_traversal,
985            },
986            Self::Coalesce(traversals) => AstNode::Coalesce { input, traversals },
987            Self::Optional(traversal) => AstNode::Optional { input, traversal },
988            Self::Group(property) => AstNode::Group { input, property },
989            Self::GroupCount(property) => AstNode::GroupCount { input, property },
990            Self::AggregateBy(function, property) => AstNode::AggregateBy {
991                input,
992                function,
993                property,
994            },
995            Self::Fold => AstNode::Fold { input },
996            Self::Unfold => AstNode::Unfold { input },
997            Self::Path => AstNode::Path { input },
998            Self::SimplePath => AstNode::SimplePath { input },
999            Self::WithSack(initial) => AstNode::WithSack { input, initial },
1000            Self::SackSet(property) => AstNode::SackSet { input, property },
1001            Self::SackAdd(property) => AstNode::SackAdd { input, property },
1002            Self::SackGet => AstNode::SackGet { input },
1003        }
1004    }
1005}
1006
1007/// Sub-traversal for branching operations.
1008#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1009pub struct SubTraversal {
1010    /// Root node. The default root is [`AstNode::Context`].
1011    pub root: Box<AstNode>,
1012}
1013
1014impl Default for SubTraversal {
1015    fn default() -> Self {
1016        Self::new()
1017    }
1018}
1019
1020impl SubTraversal {
1021    /// Create an empty sub-traversal that starts from parent context.
1022    pub fn new() -> Self {
1023        Self {
1024            root: Box::new(AstNode::Context),
1025        }
1026    }
1027
1028    fn push(mut self, operation: Operation) -> Self {
1029        self.root = Box::new(operation.apply(*self.root));
1030        self
1031    }
1032
1033    /// Traverse outgoing edges.
1034    pub fn out(self, label: Option<impl Into<String>>) -> Self {
1035        self.push(Operation::Out(label.map(Into::into)))
1036    }
1037
1038    /// Traverse incoming edges.
1039    pub fn in_(self, label: Option<impl Into<String>>) -> Self {
1040        self.push(Operation::In(label.map(Into::into)))
1041    }
1042
1043    /// Traverse both directions.
1044    pub fn both(self, label: Option<impl Into<String>>) -> Self {
1045        self.push(Operation::Both(label.map(Into::into)))
1046    }
1047
1048    /// Traverse to outgoing edges.
1049    pub fn out_e(self, label: Option<impl Into<String>>) -> Self {
1050        self.push(Operation::OutE(label.map(Into::into)))
1051    }
1052
1053    /// Traverse to incoming edges.
1054    pub fn in_e(self, label: Option<impl Into<String>>) -> Self {
1055        self.push(Operation::InE(label.map(Into::into)))
1056    }
1057
1058    /// Traverse to both-direction edges.
1059    pub fn both_e(self, label: Option<impl Into<String>>) -> Self {
1060        self.push(Operation::BothE(label.map(Into::into)))
1061    }
1062
1063    /// Edge to target node.
1064    pub fn out_n(self) -> Self {
1065        self.push(Operation::OutN)
1066    }
1067
1068    /// Edge to source node.
1069    pub fn in_n(self) -> Self {
1070        self.push(Operation::InN)
1071    }
1072
1073    /// Edge to other node.
1074    pub fn other_n(self) -> Self {
1075        self.push(Operation::OtherN)
1076    }
1077
1078    /// Property equality filter.
1079    pub fn has(self, property: impl Into<String>, value: impl Into<PropertyValue>) -> Self {
1080        self.push(Operation::Has(property.into(), value.into()))
1081    }
1082
1083    /// Label filter.
1084    pub fn has_label(self, label: impl Into<String>) -> Self {
1085        self.push(Operation::HasLabel(label.into()))
1086    }
1087
1088    /// Property existence filter.
1089    pub fn has_key(self, property: impl Into<String>) -> Self {
1090        self.push(Operation::HasKey(property.into()))
1091    }
1092
1093    /// Predicate filter.
1094    pub fn where_(self, predicate: Predicate) -> Self {
1095        self.push(Operation::Where(predicate))
1096    }
1097
1098    /// Deduplicate stream.
1099    pub fn dedup(self) -> Self {
1100        self.push(Operation::Dedup)
1101    }
1102
1103    /// Keep within variable.
1104    pub fn within(self, var_name: impl Into<String>) -> Self {
1105        self.push(Operation::Within(var_name.into()))
1106    }
1107
1108    /// Keep outside variable.
1109    pub fn without(self, var_name: impl Into<String>) -> Self {
1110        self.push(Operation::Without(var_name.into()))
1111    }
1112
1113    /// Edge property filter.
1114    pub fn edge_has(self, property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
1115        self.push(Operation::EdgeHas(property.into(), value.into()))
1116    }
1117
1118    /// Edge label filter.
1119    pub fn edge_has_label(self, label: impl Into<String>) -> Self {
1120        self.push(Operation::EdgeHasLabel(label.into()))
1121    }
1122
1123    /// Limit stream.
1124    pub fn limit(self, n: impl Into<StreamBound>) -> Self {
1125        self.push(Operation::Limit(n.into()))
1126    }
1127
1128    /// Skip stream.
1129    pub fn skip(self, n: impl Into<StreamBound>) -> Self {
1130        self.push(Operation::Skip(n.into()))
1131    }
1132
1133    /// Range stream.
1134    pub fn range(self, start: impl Into<StreamBound>, end: impl Into<StreamBound>) -> Self {
1135        self.push(Operation::Range(start.into(), end.into()))
1136    }
1137
1138    /// Store stream.
1139    pub fn as_(self, name: impl Into<String>) -> Self {
1140        self.push(Operation::As(name.into()))
1141    }
1142
1143    /// Store stream.
1144    pub fn store(self, name: impl Into<String>) -> Self {
1145        self.push(Operation::Store(name.into()))
1146    }
1147
1148    /// Select stream.
1149    pub fn select(self, name: impl Into<String>) -> Self {
1150        self.push(Operation::Select(name.into()))
1151    }
1152
1153    /// Capture row-local binding.
1154    pub fn bind(self, name: impl Into<String>) -> Self {
1155        self.push(Operation::Bind(validate_binding_name(name)))
1156    }
1157
1158    /// Order by one property.
1159    pub fn order_by(self, property: impl Into<String>, order: Order) -> Self {
1160        self.push(Operation::OrderBy(property.into(), order))
1161    }
1162
1163    /// Order by multiple properties.
1164    pub fn order_by_multiple(self, orderings: Vec<(impl Into<String>, Order)>) -> Self {
1165        self.push(Operation::OrderByMultiple(
1166            orderings
1167                .into_iter()
1168                .map(|(property, order)| (property.into(), order))
1169                .collect(),
1170        ))
1171    }
1172
1173    /// Path operation.
1174    pub fn path(self) -> Self {
1175        self.push(Operation::Path)
1176    }
1177
1178    /// Simple-path operation.
1179    pub fn simple_path(self) -> Self {
1180        self.push(Operation::SimplePath)
1181    }
1182}
1183
1184/// Create a sub-traversal.
1185pub fn sub() -> SubTraversal {
1186    SubTraversal::new()
1187}
1188
1189/// Repeat configuration.
1190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1191pub struct RepeatConfig {
1192    /// Traversal body.
1193    pub traversal: SubTraversal,
1194    /// Optional fixed iteration count.
1195    #[serde(default, skip_serializing_if = "Option::is_none")]
1196    pub times: Option<usize>,
1197    /// Optional stop predicate.
1198    #[serde(default, skip_serializing_if = "Option::is_none")]
1199    pub until: Option<Predicate>,
1200    /// Emit behavior.
1201    pub emit: EmitBehavior,
1202    /// Optional emit predicate.
1203    #[serde(default, skip_serializing_if = "Option::is_none")]
1204    pub emit_predicate: Option<Predicate>,
1205    /// Maximum depth.
1206    pub max_depth: usize,
1207}
1208
1209impl RepeatConfig {
1210    /// Create repeat config.
1211    pub fn new(traversal: SubTraversal) -> Self {
1212        Self {
1213            traversal,
1214            times: None,
1215            until: None,
1216            emit: EmitBehavior::None,
1217            emit_predicate: None,
1218            max_depth: 100,
1219        }
1220    }
1221
1222    /// Set times.
1223    pub fn times(mut self, n: usize) -> Self {
1224        self.times = Some(n);
1225        self
1226    }
1227
1228    /// Set until predicate.
1229    pub fn until(mut self, predicate: Predicate) -> Self {
1230        self.until = Some(predicate);
1231        self
1232    }
1233
1234    /// Emit before and after.
1235    pub fn emit_all(mut self) -> Self {
1236        self.emit = EmitBehavior::All;
1237        self
1238    }
1239
1240    /// Emit before.
1241    pub fn emit_before(mut self) -> Self {
1242        self.emit = EmitBehavior::Before;
1243        self
1244    }
1245
1246    /// Emit after.
1247    pub fn emit_after(mut self) -> Self {
1248        self.emit = EmitBehavior::After;
1249        self
1250    }
1251
1252    /// Emit matching after states.
1253    pub fn emit_if(mut self, predicate: Predicate) -> Self {
1254        self.emit = EmitBehavior::After;
1255        self.emit_predicate = Some(predicate);
1256        self
1257    }
1258
1259    /// Set maximum depth.
1260    pub fn max_depth(mut self, depth: usize) -> Self {
1261        self.max_depth = depth;
1262        self
1263    }
1264}
1265
1266/// A traversal builder with typestate.
1267#[derive(Debug, Clone, PartialEq)]
1268pub struct Traversal<S: TraversalState = OnNodes, M: MutationMode = ReadOnly> {
1269    root: Option<AstNode>,
1270    _state: PhantomData<S>,
1271    _mode: PhantomData<M>,
1272}
1273
1274impl<S: TraversalState, M: MutationMode> Default for Traversal<S, M> {
1275    fn default() -> Self {
1276        Self {
1277            root: None,
1278            _state: PhantomData,
1279            _mode: PhantomData,
1280        }
1281    }
1282}
1283
1284impl<S: TraversalState, M: MutationMode> Traversal<S, M> {
1285    /// Consume this traversal into its root AST node.
1286    pub fn into_ast(self) -> AstNode {
1287        self.root
1288            .expect("traversal must contain at least one AST node before execution")
1289    }
1290
1291    /// Borrow the root AST node.
1292    pub fn root(&self) -> Option<&AstNode> {
1293        self.root.as_ref()
1294    }
1295
1296    /// Returns true if the root node is terminal.
1297    pub fn has_terminal(&self) -> bool {
1298        self.root.as_ref().is_some_and(AstNode::is_terminal)
1299    }
1300
1301    fn from_root<T: TraversalState>(root: AstNode) -> Traversal<T, M> {
1302        Traversal {
1303            root: Some(root),
1304            _state: PhantomData,
1305            _mode: PhantomData,
1306        }
1307    }
1308
1309    fn push<T: TraversalState>(self, operation: Operation) -> Traversal<T, M> {
1310        let root = self
1311            .root
1312            .expect("cannot append traversal operation before a source node");
1313        Traversal::<T, M>::from_root(operation.apply(root))
1314    }
1315
1316    fn push_mutation<T: TraversalState>(self, operation: Operation) -> Traversal<T, WriteEnabled> {
1317        let root = self
1318            .root
1319            .expect("cannot append mutation operation before a source node");
1320        Traversal {
1321            root: Some(operation.apply(root)),
1322            _state: PhantomData,
1323            _mode: PhantomData,
1324        }
1325    }
1326}
1327
1328impl Traversal<Empty, ReadOnly> {
1329    /// Create an empty traversal.
1330    pub fn new() -> Self {
1331        Self::default()
1332    }
1333
1334    fn source<T: TraversalState>(self, root: AstNode) -> Traversal<T, ReadOnly> {
1335        assert!(
1336            self.root.is_none(),
1337            "source operation cannot be appended to an existing traversal"
1338        );
1339        Traversal {
1340            root: Some(root),
1341            _state: PhantomData,
1342            _mode: PhantomData,
1343        }
1344    }
1345
1346    fn mutation_source<T: TraversalState>(self, root: AstNode) -> Traversal<T, WriteEnabled> {
1347        assert!(
1348            self.root.is_none(),
1349            "source mutation cannot be appended to an existing traversal"
1350        );
1351        Traversal {
1352            root: Some(root),
1353            _state: PhantomData,
1354            _mode: PhantomData,
1355        }
1356    }
1357
1358    /// Start from nodes.
1359    pub fn n(self, nodes: impl Into<NodeRef>) -> Traversal<OnNodes> {
1360        self.source(AstNode::Nodes {
1361            reference: nodes.into(),
1362        })
1363    }
1364
1365    /// Start from nodes matching a predicate.
1366    pub fn n_where(self, predicate: SourcePredicate) -> Traversal<OnNodes> {
1367        self.source(AstNode::NodesWhere { predicate })
1368    }
1369
1370    /// Start from nodes with a label.
1371    pub fn n_with_label(self, label: impl Into<String>) -> Traversal<OnNodes> {
1372        self.n_where(Predicate::eq("$label", label.into()))
1373    }
1374
1375    /// Start from nodes with a label and predicate.
1376    pub fn n_with_label_where(
1377        self,
1378        label: impl Into<String>,
1379        predicate: SourcePredicate,
1380    ) -> Traversal<OnNodes> {
1381        self.n_where(Predicate::and(vec![
1382            Predicate::eq("$label", label.into()),
1383            predicate,
1384        ]))
1385    }
1386
1387    /// Start from node vector search.
1388    pub fn vector_search_nodes(
1389        self,
1390        label: impl Into<String>,
1391        property: impl Into<String>,
1392        query_vector: Vec<f32>,
1393        k: usize,
1394        tenant_value: Option<PropertyValue>,
1395    ) -> Traversal<OnNodes> {
1396        self.vector_search_nodes_with(
1397            label,
1398            property,
1399            query_vector,
1400            k,
1401            tenant_value.map(PropertyInput::from),
1402        )
1403    }
1404
1405    /// Start from node vector search with runtime inputs.
1406    pub fn vector_search_nodes_with(
1407        self,
1408        label: impl Into<String>,
1409        property: impl Into<String>,
1410        query_vector: impl Into<PropertyInput>,
1411        k: impl Into<StreamBound>,
1412        tenant_value: Option<PropertyInput>,
1413    ) -> Traversal<OnNodes> {
1414        self.source(AstNode::VectorSearchNodes {
1415            label: label.into(),
1416            property: property.into(),
1417            tenant_value,
1418            query_vector: query_vector.into(),
1419            k: k.into(),
1420        })
1421    }
1422
1423    /// Start from node text search.
1424    pub fn text_search_nodes(
1425        self,
1426        label: impl Into<String>,
1427        property: impl Into<String>,
1428        query_text: impl Into<String>,
1429        k: usize,
1430        tenant_value: Option<PropertyValue>,
1431    ) -> Traversal<OnNodes> {
1432        self.text_search_nodes_with(
1433            label,
1434            property,
1435            PropertyInput::from(query_text.into()),
1436            k,
1437            tenant_value.map(PropertyInput::from),
1438        )
1439    }
1440
1441    /// Start from node text search with runtime inputs.
1442    pub fn text_search_nodes_with(
1443        self,
1444        label: impl Into<String>,
1445        property: impl Into<String>,
1446        query_text: impl Into<PropertyInput>,
1447        k: impl Into<StreamBound>,
1448        tenant_value: Option<PropertyInput>,
1449    ) -> Traversal<OnNodes> {
1450        self.source(AstNode::TextSearchNodes {
1451            label: label.into(),
1452            property: property.into(),
1453            tenant_value,
1454            query_text: query_text.into(),
1455            k: k.into(),
1456        })
1457    }
1458
1459    /// Start from edges.
1460    pub fn e(self, edges: impl Into<EdgeRef>) -> Traversal<OnEdges> {
1461        self.source(AstNode::Edges {
1462            reference: edges.into(),
1463        })
1464    }
1465
1466    /// Start from edges matching a predicate.
1467    pub fn e_where(self, predicate: SourcePredicate) -> Traversal<OnEdges> {
1468        self.source(AstNode::EdgesWhere { predicate })
1469    }
1470
1471    /// Start from edges with a label.
1472    pub fn e_with_label(self, label: impl Into<String>) -> Traversal<OnEdges> {
1473        self.e_where(Predicate::eq("$label", label.into()))
1474    }
1475
1476    /// Start from edges with a label and predicate.
1477    pub fn e_with_label_where(
1478        self,
1479        label: impl Into<String>,
1480        predicate: SourcePredicate,
1481    ) -> Traversal<OnEdges> {
1482        self.e_where(Predicate::and(vec![
1483            Predicate::eq("$label", label.into()),
1484            predicate,
1485        ]))
1486    }
1487
1488    /// Start from edge vector search.
1489    pub fn vector_search_edges(
1490        self,
1491        label: impl Into<String>,
1492        property: impl Into<String>,
1493        query_vector: Vec<f32>,
1494        k: usize,
1495        tenant_value: Option<PropertyValue>,
1496    ) -> Traversal<OnEdges> {
1497        self.vector_search_edges_with(
1498            label,
1499            property,
1500            query_vector,
1501            k,
1502            tenant_value.map(PropertyInput::from),
1503        )
1504    }
1505
1506    /// Start from edge vector search with runtime inputs.
1507    pub fn vector_search_edges_with(
1508        self,
1509        label: impl Into<String>,
1510        property: impl Into<String>,
1511        query_vector: impl Into<PropertyInput>,
1512        k: impl Into<StreamBound>,
1513        tenant_value: Option<PropertyInput>,
1514    ) -> Traversal<OnEdges> {
1515        self.source(AstNode::VectorSearchEdges {
1516            label: label.into(),
1517            property: property.into(),
1518            tenant_value,
1519            query_vector: query_vector.into(),
1520            k: k.into(),
1521        })
1522    }
1523
1524    /// Start from edge text search.
1525    pub fn text_search_edges(
1526        self,
1527        label: impl Into<String>,
1528        property: impl Into<String>,
1529        query_text: impl Into<String>,
1530        k: usize,
1531        tenant_value: Option<PropertyValue>,
1532    ) -> Traversal<OnEdges> {
1533        self.text_search_edges_with(
1534            label,
1535            property,
1536            PropertyInput::from(query_text.into()),
1537            k,
1538            tenant_value.map(PropertyInput::from),
1539        )
1540    }
1541
1542    /// Start from edge text search with runtime inputs.
1543    pub fn text_search_edges_with(
1544        self,
1545        label: impl Into<String>,
1546        property: impl Into<String>,
1547        query_text: impl Into<PropertyInput>,
1548        k: impl Into<StreamBound>,
1549        tenant_value: Option<PropertyInput>,
1550    ) -> Traversal<OnEdges> {
1551        self.source(AstNode::TextSearchEdges {
1552            label: label.into(),
1553            property: property.into(),
1554            tenant_value,
1555            query_text: query_text.into(),
1556            k: k.into(),
1557        })
1558    }
1559
1560    /// Find an unweighted outgoing shortest path between two nodes.
1561    pub fn shortest_path(
1562        self,
1563        source: impl Into<NodeRef>,
1564        target: impl Into<NodeRef>,
1565        max_depth: usize,
1566    ) -> Traversal<Terminal> {
1567        self.shortest_path_with(
1568            source,
1569            target,
1570            None::<String>,
1571            ShortestPathDirection::Out,
1572            max_depth,
1573        )
1574    }
1575
1576    /// Find an unweighted shortest path between two nodes.
1577    pub fn shortest_path_with(
1578        self,
1579        source: impl Into<NodeRef>,
1580        target: impl Into<NodeRef>,
1581        label: Option<impl Into<String>>,
1582        direction: ShortestPathDirection,
1583        max_depth: usize,
1584    ) -> Traversal<Terminal> {
1585        self.source(AstNode::ShortestPath {
1586            source: source.into(),
1587            target: target.into(),
1588            label: label.map(Into::into),
1589            direction,
1590            max_depth,
1591        })
1592    }
1593
1594    /// Create an index if it does not already exist.
1595    pub fn create_index_if_not_exists(self, spec: IndexSpec) -> Traversal<Terminal, WriteEnabled> {
1596        self.mutation_source(AstNode::CreateIndex {
1597            spec,
1598            if_not_exists: true,
1599        })
1600    }
1601
1602    /// Drop an index.
1603    pub fn drop_index(self, spec: IndexSpec) -> Traversal<Terminal, WriteEnabled> {
1604        self.mutation_source(AstNode::DropIndex { spec })
1605    }
1606
1607    /// Read one retained index operation in this request's storage scope.
1608    pub fn get_index_operation(self, operation_id: impl Into<String>) -> Traversal<Terminal> {
1609        self.source(AstNode::GetIndexOperation {
1610            operation_id: operation_id.into(),
1611        })
1612    }
1613
1614    /// Convergently ensure one retained operation is runnable.
1615    pub fn retry_index_operation(
1616        self,
1617        operation_id: impl Into<String>,
1618    ) -> Traversal<Terminal, WriteEnabled> {
1619        self.mutation_source(AstNode::RetryIndexOperation {
1620            operation_id: operation_id.into(),
1621        })
1622    }
1623
1624    /// Convert one constructing BUILD into abort cleanup.
1625    pub fn abort_index_operation(
1626        self,
1627        operation_id: impl Into<String>,
1628    ) -> Traversal<Terminal, WriteEnabled> {
1629        self.mutation_source(AstNode::AbortIndexOperation {
1630            operation_id: operation_id.into(),
1631        })
1632    }
1633
1634    /// Create a node vector index.
1635    pub fn create_vector_index_nodes(
1636        self,
1637        label: impl Into<String>,
1638        property: impl Into<String>,
1639        dimension: std::num::NonZeroUsize,
1640        metric: crate::index::VectorDistanceMetric,
1641        tenant_property: Option<impl Into<String>>,
1642    ) -> Traversal<Terminal, WriteEnabled> {
1643        self.create_index_if_not_exists(IndexSpec::node_vector(
1644            label,
1645            property,
1646            dimension,
1647            metric,
1648            tenant_property,
1649        ))
1650    }
1651
1652    /// Create an edge vector index.
1653    pub fn create_vector_index_edges(
1654        self,
1655        label: impl Into<String>,
1656        property: impl Into<String>,
1657        dimension: std::num::NonZeroUsize,
1658        metric: crate::index::VectorDistanceMetric,
1659        tenant_property: Option<impl Into<String>>,
1660    ) -> Traversal<Terminal, WriteEnabled> {
1661        self.create_index_if_not_exists(IndexSpec::edge_vector(
1662            label,
1663            property,
1664            dimension,
1665            metric,
1666            tenant_property,
1667        ))
1668    }
1669
1670    /// Create a node text index.
1671    pub fn create_text_index_nodes(
1672        self,
1673        label: impl Into<String>,
1674        property: impl Into<String>,
1675        tenant_property: Option<impl Into<String>>,
1676    ) -> Traversal<Terminal, WriteEnabled> {
1677        self.create_index_if_not_exists(IndexSpec::node_text(label, property, tenant_property))
1678    }
1679
1680    /// Create an edge text index.
1681    pub fn create_text_index_edges(
1682        self,
1683        label: impl Into<String>,
1684        property: impl Into<String>,
1685        tenant_property: Option<impl Into<String>>,
1686    ) -> Traversal<Terminal, WriteEnabled> {
1687        self.create_index_if_not_exists(IndexSpec::edge_text(label, property, tenant_property))
1688    }
1689
1690    /// Add a node.
1691    pub fn add_n<K, V>(
1692        self,
1693        label: impl Into<String>,
1694        properties: Vec<(K, V)>,
1695    ) -> Traversal<OnNodes, WriteEnabled>
1696    where
1697        K: Into<String>,
1698        V: Into<PropertyInput>,
1699    {
1700        self.mutation_source(AstNode::AddN {
1701            input: None,
1702            label: label.into(),
1703            properties: collect_properties(properties),
1704        })
1705    }
1706
1707    /// Source-inject a variable.
1708    pub fn inject(self, var_name: impl Into<String>) -> Traversal<OnNodes, ReadOnly> {
1709        self.source(AstNode::Inject {
1710            input: None,
1711            variable: var_name.into(),
1712        })
1713    }
1714
1715    /// Drop edges by ID without a source stream.
1716    pub fn drop_edge_by_id(self, edges: impl Into<EdgeRef>) -> Traversal<OnNodes, WriteEnabled> {
1717        self.mutation_source(AstNode::DropEdgeById {
1718            input: None,
1719            edges: edges.into(),
1720        })
1721    }
1722}
1723
1724fn collect_properties<K, V>(properties: Vec<(K, V)>) -> Vec<(String, PropertyInput)>
1725where
1726    K: Into<String>,
1727    V: Into<PropertyInput>,
1728{
1729    properties
1730        .into_iter()
1731        .map(|(key, value)| (key.into(), value.into()))
1732        .collect()
1733}
1734
1735impl<M: MutationMode> Traversal<OnNodes, M> {
1736    /// Traverse outgoing edges to nodes.
1737    pub fn out(self, label: Option<impl Into<String>>) -> Traversal<OnNodes, M> {
1738        self.push(Operation::Out(label.map(Into::into)))
1739    }
1740
1741    /// Traverse incoming edges to nodes.
1742    pub fn in_(self, label: Option<impl Into<String>>) -> Traversal<OnNodes, M> {
1743        self.push(Operation::In(label.map(Into::into)))
1744    }
1745
1746    /// Traverse both directions to nodes.
1747    pub fn both(self, label: Option<impl Into<String>>) -> Traversal<OnNodes, M> {
1748        self.push(Operation::Both(label.map(Into::into)))
1749    }
1750
1751    /// Traverse to outgoing edges.
1752    pub fn out_e(self, label: Option<impl Into<String>>) -> Traversal<OnEdges, M> {
1753        self.push(Operation::OutE(label.map(Into::into)))
1754    }
1755
1756    /// Traverse to incoming edges.
1757    pub fn in_e(self, label: Option<impl Into<String>>) -> Traversal<OnEdges, M> {
1758        self.push(Operation::InE(label.map(Into::into)))
1759    }
1760
1761    /// Traverse to both-direction edges.
1762    pub fn both_e(self, label: Option<impl Into<String>>) -> Traversal<OnEdges, M> {
1763        self.push(Operation::BothE(label.map(Into::into)))
1764    }
1765
1766    /// Property equality filter.
1767    pub fn has(self, property: impl Into<String>, value: impl Into<PropertyValue>) -> Self {
1768        self.push(Operation::Has(property.into(), value.into()))
1769    }
1770
1771    /// Label filter.
1772    pub fn has_label(self, label: impl Into<String>) -> Self {
1773        self.push(Operation::HasLabel(label.into()))
1774    }
1775
1776    /// Property existence filter.
1777    pub fn has_key(self, property: impl Into<String>) -> Self {
1778        self.push(Operation::HasKey(property.into()))
1779    }
1780
1781    /// Predicate filter.
1782    pub fn where_(self, predicate: Predicate) -> Self {
1783        self.push(Operation::Where(predicate))
1784    }
1785
1786    /// Rank only the current node stream by vector distance.
1787    pub fn vector_search(
1788        self,
1789        label: impl Into<String>,
1790        property: impl Into<String>,
1791        query_vector: Vec<f32>,
1792        k: usize,
1793        tenant_value: Option<PropertyValue>,
1794    ) -> Self {
1795        self.vector_search_with(
1796            label,
1797            property,
1798            query_vector,
1799            k,
1800            tenant_value.map(PropertyInput::from),
1801        )
1802    }
1803
1804    /// Rank only the current node stream with runtime vector inputs.
1805    pub fn vector_search_with(
1806        self,
1807        label: impl Into<String>,
1808        property: impl Into<String>,
1809        query_vector: impl Into<PropertyInput>,
1810        k: impl Into<StreamBound>,
1811        tenant_value: Option<PropertyInput>,
1812    ) -> Self {
1813        self.push(Operation::VectorSearchNodesWithin {
1814            label: label.into(),
1815            property: property.into(),
1816            tenant_value,
1817            query_vector: query_vector.into(),
1818            k: k.into(),
1819        })
1820    }
1821
1822    /// Deduplicate.
1823    pub fn dedup(self) -> Self {
1824        self.push(Operation::Dedup)
1825    }
1826
1827    /// Keep within variable.
1828    pub fn within(self, var_name: impl Into<String>) -> Self {
1829        self.push(Operation::Within(var_name.into()))
1830    }
1831
1832    /// Keep outside variable.
1833    pub fn without(self, var_name: impl Into<String>) -> Self {
1834        self.push(Operation::Without(var_name.into()))
1835    }
1836
1837    /// Limit.
1838    pub fn limit(self, n: impl Into<StreamBound>) -> Self {
1839        self.push(Operation::Limit(n.into()))
1840    }
1841
1842    /// Skip.
1843    pub fn skip(self, n: impl Into<StreamBound>) -> Self {
1844        self.push(Operation::Skip(n.into()))
1845    }
1846
1847    /// Range.
1848    pub fn range(self, start: impl Into<StreamBound>, end: impl Into<StreamBound>) -> Self {
1849        self.push(Operation::Range(start.into(), end.into()))
1850    }
1851
1852    /// Store stream.
1853    pub fn as_(self, name: impl Into<String>) -> Self {
1854        self.push(Operation::As(name.into()))
1855    }
1856
1857    /// Store stream.
1858    pub fn store(self, name: impl Into<String>) -> Self {
1859        self.push(Operation::Store(name.into()))
1860    }
1861
1862    /// Select stream.
1863    pub fn select(self, name: impl Into<String>) -> Self {
1864        self.push(Operation::Select(name.into()))
1865    }
1866
1867    /// Bind current row element.
1868    pub fn bind(self, name: impl Into<String>) -> Self {
1869        self.push(Operation::Bind(validate_binding_name(name)))
1870    }
1871
1872    /// Inject variable stream.
1873    pub fn inject(self, var_name: impl Into<String>) -> Self {
1874        self.push(Operation::Inject(var_name.into()))
1875    }
1876
1877    /// Count terminal.
1878    pub fn count(self) -> Traversal<Terminal, M> {
1879        self.push(Operation::Count)
1880    }
1881
1882    /// Exists terminal.
1883    pub fn exists(self) -> Traversal<Terminal, M> {
1884        self.push(Operation::Exists)
1885    }
1886
1887    /// ID terminal.
1888    pub fn id(self) -> Traversal<Terminal, M> {
1889        self.push(Operation::Id)
1890    }
1891
1892    /// Label terminal.
1893    pub fn label(self) -> Traversal<Terminal, M> {
1894        self.push(Operation::Label)
1895    }
1896
1897    /// Values terminal.
1898    pub fn values(self, properties: Vec<impl Into<String>>) -> Traversal<Terminal, M> {
1899        self.push(Operation::Values(
1900            properties.into_iter().map(Into::into).collect(),
1901        ))
1902    }
1903
1904    /// Value-map terminal.
1905    pub fn value_map(self, properties: Option<Vec<impl Into<String>>>) -> Traversal<Terminal, M> {
1906        self.push(Operation::ValueMap(
1907            properties.map(|items| items.into_iter().map(Into::into).collect()),
1908        ))
1909    }
1910
1911    /// Project terminal.
1912    pub fn project<P>(self, projections: Vec<P>) -> Traversal<Terminal, M>
1913    where
1914        P: Into<Projection>,
1915    {
1916        self.push(Operation::Project(
1917            projections.into_iter().map(Into::into).collect(),
1918        ))
1919    }
1920
1921    /// Project row bindings.
1922    pub fn project_bindings(self, projections: Vec<BindingProjection>) -> Traversal<Terminal, M> {
1923        self.push(Operation::ProjectBindings {
1924            projections: validate_binding_projections(projections),
1925            distinct: false,
1926        })
1927    }
1928
1929    /// Project distinct row bindings.
1930    pub fn project_distinct_bindings(
1931        self,
1932        projections: Vec<BindingProjection>,
1933    ) -> Traversal<Terminal, M> {
1934        self.push(Operation::ProjectBindings {
1935            projections: validate_binding_projections(projections),
1936            distinct: true,
1937        })
1938    }
1939
1940    /// Order by one property.
1941    pub fn order_by(self, property: impl Into<String>, order: Order) -> Self {
1942        self.push(Operation::OrderBy(property.into(), order))
1943    }
1944
1945    /// Order by multiple properties.
1946    pub fn order_by_multiple(self, orderings: Vec<(impl Into<String>, Order)>) -> Self {
1947        self.push(Operation::OrderByMultiple(
1948            orderings
1949                .into_iter()
1950                .map(|(property, order)| (property.into(), order))
1951                .collect(),
1952        ))
1953    }
1954
1955    /// Repeat traversal.
1956    pub fn repeat(self, config: RepeatConfig) -> Self {
1957        self.push(Operation::Repeat(config))
1958    }
1959
1960    /// Union branches.
1961    pub fn union(self, traversals: Vec<SubTraversal>) -> Self {
1962        self.push(Operation::Union(traversals))
1963    }
1964
1965    /// Conditional branch.
1966    pub fn choose(
1967        self,
1968        condition: Predicate,
1969        then_traversal: SubTraversal,
1970        else_traversal: Option<SubTraversal>,
1971    ) -> Self {
1972        self.push(Operation::Choose {
1973            condition,
1974            then_traversal,
1975            else_traversal,
1976        })
1977    }
1978
1979    /// Coalesce branches.
1980    pub fn coalesce(self, traversals: Vec<SubTraversal>) -> Self {
1981        self.push(Operation::Coalesce(traversals))
1982    }
1983
1984    /// Optional branch.
1985    pub fn optional(self, traversal: SubTraversal) -> Self {
1986        self.push(Operation::Optional(traversal))
1987    }
1988
1989    /// Group terminal.
1990    pub fn group(self, property: impl Into<String>) -> Traversal<Terminal, M> {
1991        self.push(Operation::Group(property.into()))
1992    }
1993
1994    /// Group-count terminal.
1995    pub fn group_count(self, property: impl Into<String>) -> Traversal<Terminal, M> {
1996        self.push(Operation::GroupCount(property.into()))
1997    }
1998
1999    /// Aggregate terminal.
2000    pub fn aggregate_by(
2001        self,
2002        function: AggregateFunction,
2003        property: impl Into<String>,
2004    ) -> Traversal<Terminal, M> {
2005        self.push(Operation::AggregateBy(function, property.into()))
2006    }
2007
2008    /// Fold barrier.
2009    pub fn fold(self) -> Self {
2010        self.push(Operation::Fold)
2011    }
2012
2013    /// Unfold barrier.
2014    pub fn unfold(self) -> Self {
2015        self.push(Operation::Unfold)
2016    }
2017
2018    /// Path operation.
2019    pub fn path(self) -> Self {
2020        self.push(Operation::Path)
2021    }
2022
2023    /// Simple-path operation.
2024    pub fn simple_path(self) -> Self {
2025        self.push(Operation::SimplePath)
2026    }
2027
2028    /// Initialize sack.
2029    pub fn with_sack(self, initial: PropertyValue) -> Self {
2030        self.push(Operation::WithSack(initial))
2031    }
2032
2033    /// Set sack.
2034    pub fn sack_set(self, property: impl Into<String>) -> Self {
2035        self.push(Operation::SackSet(property.into()))
2036    }
2037
2038    /// Add to sack.
2039    pub fn sack_add(self, property: impl Into<String>) -> Self {
2040        self.push(Operation::SackAdd(property.into()))
2041    }
2042
2043    /// Get sack.
2044    pub fn sack_get(self) -> Self {
2045        self.push(Operation::SackGet)
2046    }
2047
2048    /// Add a node.
2049    pub fn add_n<K, V>(
2050        self,
2051        label: impl Into<String>,
2052        properties: Vec<(K, V)>,
2053    ) -> Traversal<OnNodes, WriteEnabled>
2054    where
2055        K: Into<String>,
2056        V: Into<PropertyInput>,
2057    {
2058        self.push_mutation(Operation::AddN {
2059            label: label.into(),
2060            properties: collect_properties(properties),
2061        })
2062    }
2063
2064    /// Add edges.
2065    pub fn add_e<K, V>(
2066        self,
2067        label: impl Into<String>,
2068        to: impl Into<NodeRef>,
2069        properties: Vec<(K, V)>,
2070    ) -> Traversal<OnNodes, WriteEnabled>
2071    where
2072        K: Into<String>,
2073        V: Into<PropertyInput>,
2074    {
2075        self.push_mutation(Operation::AddE {
2076            label: label.into(),
2077            to: to.into(),
2078            properties: collect_properties(properties),
2079        })
2080    }
2081
2082    /// Set property.
2083    pub fn set_property(
2084        self,
2085        name: impl Into<String>,
2086        value: impl Into<PropertyInput>,
2087    ) -> Traversal<OnNodes, WriteEnabled> {
2088        self.push_mutation(Operation::SetProperty(name.into(), value.into()))
2089    }
2090
2091    /// Remove property.
2092    pub fn remove_property(self, name: impl Into<String>) -> Traversal<OnNodes, WriteEnabled> {
2093        self.push_mutation(Operation::RemoveProperty(name.into()))
2094    }
2095
2096    /// Drop nodes.
2097    pub fn drop(self) -> Traversal<OnNodes, WriteEnabled> {
2098        self.push_mutation(Operation::Drop)
2099    }
2100
2101    /// Drop edges.
2102    pub fn drop_edge(self, to: impl Into<NodeRef>) -> Traversal<OnNodes, WriteEnabled> {
2103        self.push_mutation(Operation::DropEdge(to.into()))
2104    }
2105
2106    /// Drop labeled edges.
2107    pub fn drop_edge_labeled(
2108        self,
2109        to: impl Into<NodeRef>,
2110        label: impl Into<String>,
2111    ) -> Traversal<OnNodes, WriteEnabled> {
2112        self.push_mutation(Operation::DropEdgeLabeled {
2113            to: to.into(),
2114            label: label.into(),
2115        })
2116    }
2117
2118    /// Drop edges by ID.
2119    pub fn drop_edge_by_id(self, edges: impl Into<EdgeRef>) -> Traversal<OnNodes, WriteEnabled> {
2120        self.push_mutation(Operation::DropEdgeById(edges.into()))
2121    }
2122}
2123
2124impl<M: MutationMode> Traversal<OnEdges, M> {
2125    /// Edge to target node.
2126    pub fn out_n(self) -> Traversal<OnNodes, M> {
2127        self.push(Operation::OutN)
2128    }
2129
2130    /// Edge to source node.
2131    pub fn in_n(self) -> Traversal<OnNodes, M> {
2132        self.push(Operation::InN)
2133    }
2134
2135    /// Edge to other node.
2136    pub fn other_n(self) -> Traversal<OnNodes, M> {
2137        self.push(Operation::OtherN)
2138    }
2139
2140    /// Property equality filter.
2141    pub fn has(self, property: impl Into<String>, value: impl Into<PropertyValue>) -> Self {
2142        self.push(Operation::Has(property.into(), value.into()))
2143    }
2144
2145    /// Label filter.
2146    pub fn has_label(self, label: impl Into<String>) -> Self {
2147        self.push(Operation::HasLabel(label.into()))
2148    }
2149
2150    /// Property existence filter.
2151    pub fn has_key(self, property: impl Into<String>) -> Self {
2152        self.push(Operation::HasKey(property.into()))
2153    }
2154
2155    /// Predicate filter.
2156    pub fn where_(self, predicate: Predicate) -> Self {
2157        self.push(Operation::Where(predicate))
2158    }
2159
2160    /// Rank only the current edge stream by vector distance.
2161    pub fn vector_search(
2162        self,
2163        label: impl Into<String>,
2164        property: impl Into<String>,
2165        query_vector: Vec<f32>,
2166        k: usize,
2167        tenant_value: Option<PropertyValue>,
2168    ) -> Self {
2169        self.vector_search_with(
2170            label,
2171            property,
2172            query_vector,
2173            k,
2174            tenant_value.map(PropertyInput::from),
2175        )
2176    }
2177
2178    /// Rank only the current edge stream with runtime vector inputs.
2179    pub fn vector_search_with(
2180        self,
2181        label: impl Into<String>,
2182        property: impl Into<String>,
2183        query_vector: impl Into<PropertyInput>,
2184        k: impl Into<StreamBound>,
2185        tenant_value: Option<PropertyInput>,
2186    ) -> Self {
2187        self.push(Operation::VectorSearchEdgesWithin {
2188            label: label.into(),
2189            property: property.into(),
2190            tenant_value,
2191            query_vector: query_vector.into(),
2192            k: k.into(),
2193        })
2194    }
2195
2196    /// Edge property filter.
2197    pub fn edge_has(self, property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
2198        self.push(Operation::EdgeHas(property.into(), value.into()))
2199    }
2200
2201    /// Edge label filter.
2202    pub fn edge_has_label(self, label: impl Into<String>) -> Self {
2203        self.push(Operation::EdgeHasLabel(label.into()))
2204    }
2205
2206    /// Set a property on current edges.
2207    pub fn set_property(
2208        self,
2209        name: impl Into<String>,
2210        value: impl Into<PropertyInput>,
2211    ) -> Traversal<OnEdges, WriteEnabled> {
2212        self.push_mutation(Operation::SetProperty(name.into(), value.into()))
2213    }
2214
2215    /// Remove a property from current edges.
2216    pub fn remove_property(self, name: impl Into<String>) -> Traversal<OnEdges, WriteEnabled> {
2217        self.push_mutation(Operation::RemoveProperty(name.into()))
2218    }
2219
2220    /// Deduplicate.
2221    pub fn dedup(self) -> Self {
2222        self.push(Operation::Dedup)
2223    }
2224
2225    /// Limit.
2226    pub fn limit(self, n: impl Into<StreamBound>) -> Self {
2227        self.push(Operation::Limit(n.into()))
2228    }
2229
2230    /// Skip.
2231    pub fn skip(self, n: impl Into<StreamBound>) -> Self {
2232        self.push(Operation::Skip(n.into()))
2233    }
2234
2235    /// Range.
2236    pub fn range(self, start: impl Into<StreamBound>, end: impl Into<StreamBound>) -> Self {
2237        self.push(Operation::Range(start.into(), end.into()))
2238    }
2239
2240    /// Store stream.
2241    pub fn as_(self, name: impl Into<String>) -> Self {
2242        self.push(Operation::As(name.into()))
2243    }
2244
2245    /// Store stream.
2246    pub fn store(self, name: impl Into<String>) -> Self {
2247        self.push(Operation::Store(name.into()))
2248    }
2249
2250    /// Bind current row element.
2251    pub fn bind(self, name: impl Into<String>) -> Self {
2252        self.push(Operation::Bind(validate_binding_name(name)))
2253    }
2254
2255    /// Count terminal.
2256    pub fn count(self) -> Traversal<Terminal, M> {
2257        self.push(Operation::Count)
2258    }
2259
2260    /// Exists terminal.
2261    pub fn exists(self) -> Traversal<Terminal, M> {
2262        self.push(Operation::Exists)
2263    }
2264
2265    /// ID terminal.
2266    pub fn id(self) -> Traversal<Terminal, M> {
2267        self.push(Operation::Id)
2268    }
2269
2270    /// Label terminal.
2271    pub fn label(self) -> Traversal<Terminal, M> {
2272        self.push(Operation::Label)
2273    }
2274
2275    /// Values terminal.
2276    pub fn values(self, properties: Vec<impl Into<String>>) -> Traversal<Terminal, M> {
2277        self.push(Operation::Values(
2278            properties.into_iter().map(Into::into).collect(),
2279        ))
2280    }
2281
2282    /// Value-map terminal.
2283    pub fn value_map(self, properties: Option<Vec<impl Into<String>>>) -> Traversal<Terminal, M> {
2284        self.push(Operation::ValueMap(
2285            properties.map(|items| items.into_iter().map(Into::into).collect()),
2286        ))
2287    }
2288
2289    /// Project terminal.
2290    pub fn project<P>(self, projections: Vec<P>) -> Traversal<Terminal, M>
2291    where
2292        P: Into<Projection>,
2293    {
2294        self.push(Operation::Project(
2295            projections.into_iter().map(Into::into).collect(),
2296        ))
2297    }
2298
2299    /// Project row bindings.
2300    pub fn project_bindings(self, projections: Vec<BindingProjection>) -> Traversal<Terminal, M> {
2301        self.push(Operation::ProjectBindings {
2302            projections: validate_binding_projections(projections),
2303            distinct: false,
2304        })
2305    }
2306
2307    /// Project distinct row bindings.
2308    pub fn project_distinct_bindings(
2309        self,
2310        projections: Vec<BindingProjection>,
2311    ) -> Traversal<Terminal, M> {
2312        self.push(Operation::ProjectBindings {
2313            projections: validate_binding_projections(projections),
2314            distinct: true,
2315        })
2316    }
2317
2318    /// Edge-properties terminal.
2319    pub fn edge_properties(self) -> Traversal<Terminal, M> {
2320        self.push(Operation::EdgeProperties)
2321    }
2322
2323    /// Order by one property.
2324    pub fn order_by(self, property: impl Into<String>, order: Order) -> Self {
2325        self.push(Operation::OrderBy(property.into(), order))
2326    }
2327}
2328
2329/// Create a traversal.
2330pub fn g() -> Traversal<Empty> {
2331    Traversal::new()
2332}