use std::marker::PhantomData;
use serde::{Deserialize, Serialize};
use crate::expr::{Predicate, SourcePredicate, StreamBound};
use crate::graph::{EdgeRef, NodeRef};
use crate::index::IndexSpec;
use crate::projection::{
validate_binding_name, validate_binding_projections, BindingProjection, Projection,
};
use crate::value::{PropertyInput, PropertyValue};
pub trait TraversalState: private::Sealed {}
mod private {
pub trait Sealed {}
impl Sealed for super::Empty {}
impl Sealed for super::OnNodes {}
impl Sealed for super::OnEdges {}
impl Sealed for super::Terminal {}
impl Sealed for super::ReadOnly {}
impl Sealed for super::WriteEnabled {}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Empty;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OnNodes;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OnEdges;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Terminal;
impl TraversalState for Empty {}
impl TraversalState for OnNodes {}
impl TraversalState for OnEdges {}
impl TraversalState for Terminal {}
pub trait MutationMode: private::Sealed {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReadOnly;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WriteEnabled;
impl MutationMode for ReadOnly {}
impl MutationMode for WriteEnabled {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum Order {
#[default]
Asc,
Desc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ShortestPathDirection {
#[default]
Out,
In,
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum EmitBehavior {
#[default]
None,
Before,
After,
All,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AggregateFunction {
Count,
Sum,
Min,
Max,
Mean,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AstNode {
Context,
Nodes { reference: NodeRef },
NodesWhere { predicate: SourcePredicate },
Edges { reference: EdgeRef },
EdgesWhere { predicate: SourcePredicate },
VectorSearchNodes {
label: String,
property: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
tenant_value: Option<PropertyInput>,
query_vector: PropertyInput,
k: StreamBound,
},
TextSearchNodes {
label: String,
property: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
tenant_value: Option<PropertyInput>,
query_text: PropertyInput,
k: StreamBound,
},
VectorSearchEdges {
label: String,
property: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
tenant_value: Option<PropertyInput>,
query_vector: PropertyInput,
k: StreamBound,
},
TextSearchEdges {
label: String,
property: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
tenant_value: Option<PropertyInput>,
query_text: PropertyInput,
k: StreamBound,
},
VectorSearchNodesWithin {
input: Box<AstNode>,
label: String,
property: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
tenant_value: Option<PropertyInput>,
query_vector: PropertyInput,
k: StreamBound,
},
VectorSearchEdgesWithin {
input: Box<AstNode>,
label: String,
property: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
tenant_value: Option<PropertyInput>,
query_vector: PropertyInput,
k: StreamBound,
},
Out {
input: Box<AstNode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
In {
input: Box<AstNode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
Both {
input: Box<AstNode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
OutE {
input: Box<AstNode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
InE {
input: Box<AstNode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
BothE {
input: Box<AstNode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
OutN { input: Box<AstNode> },
InN { input: Box<AstNode> },
OtherN { input: Box<AstNode> },
Has {
input: Box<AstNode>,
property: String,
value: PropertyValue,
},
HasLabel {
input: Box<AstNode>,
label: String,
},
HasKey {
input: Box<AstNode>,
property: String,
},
Where {
input: Box<AstNode>,
predicate: Predicate,
},
Dedup { input: Box<AstNode> },
Within {
input: Box<AstNode>,
variable: String,
},
Without {
input: Box<AstNode>,
variable: String,
},
EdgeHas {
input: Box<AstNode>,
property: String,
value: PropertyInput,
},
EdgeHasLabel {
input: Box<AstNode>,
label: String,
},
Limit {
input: Box<AstNode>,
count: StreamBound,
},
Skip {
input: Box<AstNode>,
count: StreamBound,
},
Range {
input: Box<AstNode>,
start: StreamBound,
end: StreamBound,
},
As { input: Box<AstNode>, name: String },
Store { input: Box<AstNode>, name: String },
Select { input: Box<AstNode>, name: String },
Bind { input: Box<AstNode>, name: String },
Inject {
#[serde(default, skip_serializing_if = "Option::is_none")]
input: Option<Box<AstNode>>,
variable: String,
},
Count { input: Box<AstNode> },
Exists { input: Box<AstNode> },
Id { input: Box<AstNode> },
Label { input: Box<AstNode> },
Values {
input: Box<AstNode>,
properties: Vec<String>,
},
ValueMap {
input: Box<AstNode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
properties: Option<Vec<String>>,
},
Project {
input: Box<AstNode>,
projections: Vec<Projection>,
},
ProjectBindings {
input: Box<AstNode>,
projections: Vec<BindingProjection>,
distinct: bool,
},
EdgeProperties { input: Box<AstNode> },
CreateIndex {
spec: IndexSpec,
if_not_exists: bool,
},
DropIndex {
spec: IndexSpec,
},
GetIndexOperation {
operation_id: String,
},
RetryIndexOperation {
operation_id: String,
},
AbortIndexOperation {
operation_id: String,
},
AddN {
#[serde(default, skip_serializing_if = "Option::is_none")]
input: Option<Box<AstNode>>,
label: String,
properties: Vec<(String, PropertyInput)>,
},
AddE {
input: Box<AstNode>,
label: String,
to: NodeRef,
properties: Vec<(String, PropertyInput)>,
},
SetProperty {
input: Box<AstNode>,
name: String,
value: PropertyInput,
},
RemoveProperty {
input: Box<AstNode>,
name: String,
},
Drop { input: Box<AstNode> },
DropEdge {
input: Box<AstNode>,
to: NodeRef,
},
DropEdgeLabeled {
input: Box<AstNode>,
to: NodeRef,
label: String,
},
DropEdgeById {
#[serde(default, skip_serializing_if = "Option::is_none")]
input: Option<Box<AstNode>>,
edges: EdgeRef,
},
OrderBy {
input: Box<AstNode>,
property: String,
order: Order,
},
OrderByMultiple {
input: Box<AstNode>,
orderings: Vec<(String, Order)>,
},
Repeat {
input: Box<AstNode>,
config: RepeatConfig,
},
Union {
input: Box<AstNode>,
traversals: Vec<SubTraversal>,
},
Choose {
input: Box<AstNode>,
condition: Predicate,
then_traversal: SubTraversal,
#[serde(default, skip_serializing_if = "Option::is_none")]
else_traversal: Option<SubTraversal>,
},
Coalesce {
input: Box<AstNode>,
traversals: Vec<SubTraversal>,
},
Optional {
input: Box<AstNode>,
traversal: SubTraversal,
},
Group {
input: Box<AstNode>,
property: String,
},
GroupCount {
input: Box<AstNode>,
property: String,
},
AggregateBy {
input: Box<AstNode>,
function: AggregateFunction,
property: String,
},
Fold { input: Box<AstNode> },
Unfold { input: Box<AstNode> },
Path { input: Box<AstNode> },
SimplePath { input: Box<AstNode> },
WithSack {
input: Box<AstNode>,
initial: PropertyValue,
},
SackSet {
input: Box<AstNode>,
property: String,
},
SackAdd {
input: Box<AstNode>,
property: String,
},
SackGet { input: Box<AstNode> },
ShortestPath {
source: NodeRef,
target: NodeRef,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
direction: ShortestPathDirection,
max_depth: usize,
},
}
impl AstNode {
pub fn is_read_only(&self) -> bool {
match self {
Self::Context
| Self::Nodes { .. }
| Self::NodesWhere { .. }
| Self::Edges { .. }
| Self::EdgesWhere { .. }
| Self::VectorSearchNodes { .. }
| Self::TextSearchNodes { .. }
| Self::VectorSearchEdges { .. }
| Self::TextSearchEdges { .. }
| Self::GetIndexOperation { .. }
| Self::ShortestPath { .. } => true,
Self::CreateIndex { .. }
| Self::DropIndex { .. }
| Self::RetryIndexOperation { .. }
| Self::AbortIndexOperation { .. }
| Self::AddN { .. }
| Self::AddE { .. }
| Self::SetProperty { .. }
| Self::RemoveProperty { .. }
| Self::Drop { .. }
| Self::DropEdge { .. }
| Self::DropEdgeLabeled { .. }
| Self::DropEdgeById { .. } => false,
Self::VectorSearchNodesWithin { input, .. }
| Self::VectorSearchEdgesWithin { input, .. }
| Self::Out { input, .. }
| Self::In { input, .. }
| Self::Both { input, .. }
| Self::OutE { input, .. }
| Self::InE { input, .. }
| Self::BothE { input, .. }
| Self::OutN { input }
| Self::InN { input }
| Self::OtherN { input }
| Self::Has { input, .. }
| Self::HasLabel { input, .. }
| Self::HasKey { input, .. }
| Self::Where { input, .. }
| Self::Dedup { input }
| Self::Within { input, .. }
| Self::Without { input, .. }
| Self::EdgeHas { input, .. }
| Self::EdgeHasLabel { input, .. }
| Self::Limit { input, .. }
| Self::Skip { input, .. }
| Self::Range { input, .. }
| Self::As { input, .. }
| Self::Store { input, .. }
| Self::Select { input, .. }
| Self::Bind { input, .. }
| Self::Count { input }
| Self::Exists { input }
| Self::Id { input }
| Self::Label { input }
| Self::Values { input, .. }
| Self::ValueMap { input, .. }
| Self::Project { input, .. }
| Self::ProjectBindings { input, .. }
| Self::EdgeProperties { input }
| Self::OrderBy { input, .. }
| Self::OrderByMultiple { input, .. }
| Self::Group { input, .. }
| Self::GroupCount { input, .. }
| Self::AggregateBy { input, .. }
| Self::Fold { input }
| Self::Unfold { input }
| Self::Path { input }
| Self::SimplePath { input }
| Self::WithSack { input, .. }
| Self::SackSet { input, .. }
| Self::SackAdd { input, .. }
| Self::SackGet { input } => input.is_read_only(),
Self::Inject { input, .. } => input.as_deref().map(Self::is_read_only).unwrap_or(true),
Self::Repeat { input, config } => {
input.is_read_only() && config.traversal.root.is_read_only()
}
Self::Union { input, traversals } | Self::Coalesce { input, traversals } => {
input.is_read_only()
&& traversals
.iter()
.all(|traversal| traversal.root.is_read_only())
}
Self::Choose {
input,
then_traversal,
else_traversal,
..
} => {
input.is_read_only()
&& then_traversal.root.is_read_only()
&& else_traversal
.as_ref()
.map(|traversal| traversal.root.is_read_only())
.unwrap_or(true)
}
Self::Optional { input, traversal } => {
input.is_read_only() && traversal.root.is_read_only()
}
}
}
pub fn is_terminal(&self) -> bool {
matches!(
self,
Self::Count { .. }
| Self::Exists { .. }
| Self::Id { .. }
| Self::Label { .. }
| Self::Values { .. }
| Self::ValueMap { .. }
| Self::Project { .. }
| Self::ProjectBindings { .. }
| Self::EdgeProperties { .. }
| Self::CreateIndex { .. }
| Self::DropIndex { .. }
| Self::GetIndexOperation { .. }
| Self::RetryIndexOperation { .. }
| Self::AbortIndexOperation { .. }
| Self::Group { .. }
| Self::GroupCount { .. }
| Self::AggregateBy { .. }
| Self::ShortestPath { .. }
)
}
}
#[derive(Debug, Clone)]
enum Operation {
Out(Option<String>),
In(Option<String>),
Both(Option<String>),
OutE(Option<String>),
InE(Option<String>),
BothE(Option<String>),
OutN,
InN,
OtherN,
Has(String, PropertyValue),
HasLabel(String),
HasKey(String),
Where(Predicate),
Dedup,
Within(String),
Without(String),
EdgeHas(String, PropertyInput),
EdgeHasLabel(String),
VectorSearchNodesWithin {
label: String,
property: String,
tenant_value: Option<PropertyInput>,
query_vector: PropertyInput,
k: StreamBound,
},
VectorSearchEdgesWithin {
label: String,
property: String,
tenant_value: Option<PropertyInput>,
query_vector: PropertyInput,
k: StreamBound,
},
Limit(StreamBound),
Skip(StreamBound),
Range(StreamBound, StreamBound),
As(String),
Store(String),
Select(String),
Bind(String),
Inject(String),
Count,
Exists,
Id,
Label,
Values(Vec<String>),
ValueMap(Option<Vec<String>>),
Project(Vec<Projection>),
ProjectBindings {
projections: Vec<BindingProjection>,
distinct: bool,
},
EdgeProperties,
AddN {
label: String,
properties: Vec<(String, PropertyInput)>,
},
AddE {
label: String,
to: NodeRef,
properties: Vec<(String, PropertyInput)>,
},
SetProperty(String, PropertyInput),
RemoveProperty(String),
Drop,
DropEdge(NodeRef),
DropEdgeLabeled {
to: NodeRef,
label: String,
},
DropEdgeById(EdgeRef),
OrderBy(String, Order),
OrderByMultiple(Vec<(String, Order)>),
Repeat(RepeatConfig),
Union(Vec<SubTraversal>),
Choose {
condition: Predicate,
then_traversal: SubTraversal,
else_traversal: Option<SubTraversal>,
},
Coalesce(Vec<SubTraversal>),
Optional(SubTraversal),
Group(String),
GroupCount(String),
AggregateBy(AggregateFunction, String),
Fold,
Unfold,
Path,
SimplePath,
WithSack(PropertyValue),
SackSet(String),
SackAdd(String),
SackGet,
}
impl Operation {
fn apply(self, input: AstNode) -> AstNode {
let input = Box::new(input);
match self {
Self::Out(label) => AstNode::Out { input, label },
Self::In(label) => AstNode::In { input, label },
Self::Both(label) => AstNode::Both { input, label },
Self::OutE(label) => AstNode::OutE { input, label },
Self::InE(label) => AstNode::InE { input, label },
Self::BothE(label) => AstNode::BothE { input, label },
Self::OutN => AstNode::OutN { input },
Self::InN => AstNode::InN { input },
Self::OtherN => AstNode::OtherN { input },
Self::Has(property, value) => AstNode::Has {
input,
property,
value,
},
Self::HasLabel(label) => AstNode::HasLabel { input, label },
Self::HasKey(property) => AstNode::HasKey { input, property },
Self::Where(predicate) => AstNode::Where { input, predicate },
Self::Dedup => AstNode::Dedup { input },
Self::Within(variable) => AstNode::Within { input, variable },
Self::Without(variable) => AstNode::Without { input, variable },
Self::EdgeHas(property, value) => AstNode::EdgeHas {
input,
property,
value,
},
Self::EdgeHasLabel(label) => AstNode::EdgeHasLabel { input, label },
Self::VectorSearchNodesWithin {
label,
property,
tenant_value,
query_vector,
k,
} => AstNode::VectorSearchNodesWithin {
input,
label,
property,
tenant_value,
query_vector,
k,
},
Self::VectorSearchEdgesWithin {
label,
property,
tenant_value,
query_vector,
k,
} => AstNode::VectorSearchEdgesWithin {
input,
label,
property,
tenant_value,
query_vector,
k,
},
Self::Limit(count) => AstNode::Limit { input, count },
Self::Skip(count) => AstNode::Skip { input, count },
Self::Range(start, end) => AstNode::Range { input, start, end },
Self::As(name) => AstNode::As { input, name },
Self::Store(name) => AstNode::Store { input, name },
Self::Select(name) => AstNode::Select { input, name },
Self::Bind(name) => AstNode::Bind { input, name },
Self::Inject(variable) => AstNode::Inject {
input: Some(input),
variable,
},
Self::Count => AstNode::Count { input },
Self::Exists => AstNode::Exists { input },
Self::Id => AstNode::Id { input },
Self::Label => AstNode::Label { input },
Self::Values(properties) => AstNode::Values { input, properties },
Self::ValueMap(properties) => AstNode::ValueMap { input, properties },
Self::Project(projections) => AstNode::Project { input, projections },
Self::ProjectBindings {
projections,
distinct,
} => AstNode::ProjectBindings {
input,
projections,
distinct,
},
Self::EdgeProperties => AstNode::EdgeProperties { input },
Self::AddN { label, properties } => AstNode::AddN {
input: Some(input),
label,
properties,
},
Self::AddE {
label,
to,
properties,
} => AstNode::AddE {
input,
label,
to,
properties,
},
Self::SetProperty(name, value) => AstNode::SetProperty { input, name, value },
Self::RemoveProperty(name) => AstNode::RemoveProperty { input, name },
Self::Drop => AstNode::Drop { input },
Self::DropEdge(to) => AstNode::DropEdge { input, to },
Self::DropEdgeLabeled { to, label } => AstNode::DropEdgeLabeled { input, to, label },
Self::DropEdgeById(edges) => AstNode::DropEdgeById {
input: Some(input),
edges,
},
Self::OrderBy(property, order) => AstNode::OrderBy {
input,
property,
order,
},
Self::OrderByMultiple(orderings) => AstNode::OrderByMultiple { input, orderings },
Self::Repeat(config) => AstNode::Repeat { input, config },
Self::Union(traversals) => AstNode::Union { input, traversals },
Self::Choose {
condition,
then_traversal,
else_traversal,
} => AstNode::Choose {
input,
condition,
then_traversal,
else_traversal,
},
Self::Coalesce(traversals) => AstNode::Coalesce { input, traversals },
Self::Optional(traversal) => AstNode::Optional { input, traversal },
Self::Group(property) => AstNode::Group { input, property },
Self::GroupCount(property) => AstNode::GroupCount { input, property },
Self::AggregateBy(function, property) => AstNode::AggregateBy {
input,
function,
property,
},
Self::Fold => AstNode::Fold { input },
Self::Unfold => AstNode::Unfold { input },
Self::Path => AstNode::Path { input },
Self::SimplePath => AstNode::SimplePath { input },
Self::WithSack(initial) => AstNode::WithSack { input, initial },
Self::SackSet(property) => AstNode::SackSet { input, property },
Self::SackAdd(property) => AstNode::SackAdd { input, property },
Self::SackGet => AstNode::SackGet { input },
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SubTraversal {
pub root: Box<AstNode>,
}
impl Default for SubTraversal {
fn default() -> Self {
Self::new()
}
}
impl SubTraversal {
pub fn new() -> Self {
Self {
root: Box::new(AstNode::Context),
}
}
fn push(mut self, operation: Operation) -> Self {
self.root = Box::new(operation.apply(*self.root));
self
}
pub fn out(self, label: Option<impl Into<String>>) -> Self {
self.push(Operation::Out(label.map(Into::into)))
}
pub fn in_(self, label: Option<impl Into<String>>) -> Self {
self.push(Operation::In(label.map(Into::into)))
}
pub fn both(self, label: Option<impl Into<String>>) -> Self {
self.push(Operation::Both(label.map(Into::into)))
}
pub fn out_e(self, label: Option<impl Into<String>>) -> Self {
self.push(Operation::OutE(label.map(Into::into)))
}
pub fn in_e(self, label: Option<impl Into<String>>) -> Self {
self.push(Operation::InE(label.map(Into::into)))
}
pub fn both_e(self, label: Option<impl Into<String>>) -> Self {
self.push(Operation::BothE(label.map(Into::into)))
}
pub fn out_n(self) -> Self {
self.push(Operation::OutN)
}
pub fn in_n(self) -> Self {
self.push(Operation::InN)
}
pub fn other_n(self) -> Self {
self.push(Operation::OtherN)
}
pub fn has(self, property: impl Into<String>, value: impl Into<PropertyValue>) -> Self {
self.push(Operation::Has(property.into(), value.into()))
}
pub fn has_label(self, label: impl Into<String>) -> Self {
self.push(Operation::HasLabel(label.into()))
}
pub fn has_key(self, property: impl Into<String>) -> Self {
self.push(Operation::HasKey(property.into()))
}
pub fn where_(self, predicate: Predicate) -> Self {
self.push(Operation::Where(predicate))
}
pub fn dedup(self) -> Self {
self.push(Operation::Dedup)
}
pub fn within(self, var_name: impl Into<String>) -> Self {
self.push(Operation::Within(var_name.into()))
}
pub fn without(self, var_name: impl Into<String>) -> Self {
self.push(Operation::Without(var_name.into()))
}
pub fn edge_has(self, property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
self.push(Operation::EdgeHas(property.into(), value.into()))
}
pub fn edge_has_label(self, label: impl Into<String>) -> Self {
self.push(Operation::EdgeHasLabel(label.into()))
}
pub fn limit(self, n: impl Into<StreamBound>) -> Self {
self.push(Operation::Limit(n.into()))
}
pub fn skip(self, n: impl Into<StreamBound>) -> Self {
self.push(Operation::Skip(n.into()))
}
pub fn range(self, start: impl Into<StreamBound>, end: impl Into<StreamBound>) -> Self {
self.push(Operation::Range(start.into(), end.into()))
}
pub fn as_(self, name: impl Into<String>) -> Self {
self.push(Operation::As(name.into()))
}
pub fn store(self, name: impl Into<String>) -> Self {
self.push(Operation::Store(name.into()))
}
pub fn select(self, name: impl Into<String>) -> Self {
self.push(Operation::Select(name.into()))
}
pub fn bind(self, name: impl Into<String>) -> Self {
self.push(Operation::Bind(validate_binding_name(name)))
}
pub fn order_by(self, property: impl Into<String>, order: Order) -> Self {
self.push(Operation::OrderBy(property.into(), order))
}
pub fn order_by_multiple(self, orderings: Vec<(impl Into<String>, Order)>) -> Self {
self.push(Operation::OrderByMultiple(
orderings
.into_iter()
.map(|(property, order)| (property.into(), order))
.collect(),
))
}
pub fn path(self) -> Self {
self.push(Operation::Path)
}
pub fn simple_path(self) -> Self {
self.push(Operation::SimplePath)
}
}
pub fn sub() -> SubTraversal {
SubTraversal::new()
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RepeatConfig {
pub traversal: SubTraversal,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub times: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub until: Option<Predicate>,
pub emit: EmitBehavior,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub emit_predicate: Option<Predicate>,
pub max_depth: usize,
}
impl RepeatConfig {
pub fn new(traversal: SubTraversal) -> Self {
Self {
traversal,
times: None,
until: None,
emit: EmitBehavior::None,
emit_predicate: None,
max_depth: 100,
}
}
pub fn times(mut self, n: usize) -> Self {
self.times = Some(n);
self
}
pub fn until(mut self, predicate: Predicate) -> Self {
self.until = Some(predicate);
self
}
pub fn emit_all(mut self) -> Self {
self.emit = EmitBehavior::All;
self
}
pub fn emit_before(mut self) -> Self {
self.emit = EmitBehavior::Before;
self
}
pub fn emit_after(mut self) -> Self {
self.emit = EmitBehavior::After;
self
}
pub fn emit_if(mut self, predicate: Predicate) -> Self {
self.emit = EmitBehavior::After;
self.emit_predicate = Some(predicate);
self
}
pub fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Traversal<S: TraversalState = OnNodes, M: MutationMode = ReadOnly> {
root: Option<AstNode>,
_state: PhantomData<S>,
_mode: PhantomData<M>,
}
impl<S: TraversalState, M: MutationMode> Default for Traversal<S, M> {
fn default() -> Self {
Self {
root: None,
_state: PhantomData,
_mode: PhantomData,
}
}
}
impl<S: TraversalState, M: MutationMode> Traversal<S, M> {
pub fn into_ast(self) -> AstNode {
self.root
.expect("traversal must contain at least one AST node before execution")
}
pub fn root(&self) -> Option<&AstNode> {
self.root.as_ref()
}
pub fn has_terminal(&self) -> bool {
self.root.as_ref().is_some_and(AstNode::is_terminal)
}
fn from_root<T: TraversalState>(root: AstNode) -> Traversal<T, M> {
Traversal {
root: Some(root),
_state: PhantomData,
_mode: PhantomData,
}
}
fn push<T: TraversalState>(self, operation: Operation) -> Traversal<T, M> {
let root = self
.root
.expect("cannot append traversal operation before a source node");
Traversal::<T, M>::from_root(operation.apply(root))
}
fn push_mutation<T: TraversalState>(self, operation: Operation) -> Traversal<T, WriteEnabled> {
let root = self
.root
.expect("cannot append mutation operation before a source node");
Traversal {
root: Some(operation.apply(root)),
_state: PhantomData,
_mode: PhantomData,
}
}
}
impl Traversal<Empty, ReadOnly> {
pub fn new() -> Self {
Self::default()
}
fn source<T: TraversalState>(self, root: AstNode) -> Traversal<T, ReadOnly> {
assert!(
self.root.is_none(),
"source operation cannot be appended to an existing traversal"
);
Traversal {
root: Some(root),
_state: PhantomData,
_mode: PhantomData,
}
}
fn mutation_source<T: TraversalState>(self, root: AstNode) -> Traversal<T, WriteEnabled> {
assert!(
self.root.is_none(),
"source mutation cannot be appended to an existing traversal"
);
Traversal {
root: Some(root),
_state: PhantomData,
_mode: PhantomData,
}
}
pub fn n(self, nodes: impl Into<NodeRef>) -> Traversal<OnNodes> {
self.source(AstNode::Nodes {
reference: nodes.into(),
})
}
pub fn n_where(self, predicate: SourcePredicate) -> Traversal<OnNodes> {
self.source(AstNode::NodesWhere { predicate })
}
pub fn n_with_label(self, label: impl Into<String>) -> Traversal<OnNodes> {
self.n_where(Predicate::eq("$label", label.into()))
}
pub fn n_with_label_where(
self,
label: impl Into<String>,
predicate: SourcePredicate,
) -> Traversal<OnNodes> {
self.n_where(Predicate::and(vec![
Predicate::eq("$label", label.into()),
predicate,
]))
}
pub fn vector_search_nodes(
self,
label: impl Into<String>,
property: impl Into<String>,
query_vector: Vec<f32>,
k: usize,
tenant_value: Option<PropertyValue>,
) -> Traversal<OnNodes> {
self.vector_search_nodes_with(
label,
property,
query_vector,
k,
tenant_value.map(PropertyInput::from),
)
}
pub fn vector_search_nodes_with(
self,
label: impl Into<String>,
property: impl Into<String>,
query_vector: impl Into<PropertyInput>,
k: impl Into<StreamBound>,
tenant_value: Option<PropertyInput>,
) -> Traversal<OnNodes> {
self.source(AstNode::VectorSearchNodes {
label: label.into(),
property: property.into(),
tenant_value,
query_vector: query_vector.into(),
k: k.into(),
})
}
pub fn text_search_nodes(
self,
label: impl Into<String>,
property: impl Into<String>,
query_text: impl Into<String>,
k: usize,
tenant_value: Option<PropertyValue>,
) -> Traversal<OnNodes> {
self.text_search_nodes_with(
label,
property,
PropertyInput::from(query_text.into()),
k,
tenant_value.map(PropertyInput::from),
)
}
pub fn text_search_nodes_with(
self,
label: impl Into<String>,
property: impl Into<String>,
query_text: impl Into<PropertyInput>,
k: impl Into<StreamBound>,
tenant_value: Option<PropertyInput>,
) -> Traversal<OnNodes> {
self.source(AstNode::TextSearchNodes {
label: label.into(),
property: property.into(),
tenant_value,
query_text: query_text.into(),
k: k.into(),
})
}
pub fn e(self, edges: impl Into<EdgeRef>) -> Traversal<OnEdges> {
self.source(AstNode::Edges {
reference: edges.into(),
})
}
pub fn e_where(self, predicate: SourcePredicate) -> Traversal<OnEdges> {
self.source(AstNode::EdgesWhere { predicate })
}
pub fn e_with_label(self, label: impl Into<String>) -> Traversal<OnEdges> {
self.e_where(Predicate::eq("$label", label.into()))
}
pub fn e_with_label_where(
self,
label: impl Into<String>,
predicate: SourcePredicate,
) -> Traversal<OnEdges> {
self.e_where(Predicate::and(vec![
Predicate::eq("$label", label.into()),
predicate,
]))
}
pub fn vector_search_edges(
self,
label: impl Into<String>,
property: impl Into<String>,
query_vector: Vec<f32>,
k: usize,
tenant_value: Option<PropertyValue>,
) -> Traversal<OnEdges> {
self.vector_search_edges_with(
label,
property,
query_vector,
k,
tenant_value.map(PropertyInput::from),
)
}
pub fn vector_search_edges_with(
self,
label: impl Into<String>,
property: impl Into<String>,
query_vector: impl Into<PropertyInput>,
k: impl Into<StreamBound>,
tenant_value: Option<PropertyInput>,
) -> Traversal<OnEdges> {
self.source(AstNode::VectorSearchEdges {
label: label.into(),
property: property.into(),
tenant_value,
query_vector: query_vector.into(),
k: k.into(),
})
}
pub fn text_search_edges(
self,
label: impl Into<String>,
property: impl Into<String>,
query_text: impl Into<String>,
k: usize,
tenant_value: Option<PropertyValue>,
) -> Traversal<OnEdges> {
self.text_search_edges_with(
label,
property,
PropertyInput::from(query_text.into()),
k,
tenant_value.map(PropertyInput::from),
)
}
pub fn text_search_edges_with(
self,
label: impl Into<String>,
property: impl Into<String>,
query_text: impl Into<PropertyInput>,
k: impl Into<StreamBound>,
tenant_value: Option<PropertyInput>,
) -> Traversal<OnEdges> {
self.source(AstNode::TextSearchEdges {
label: label.into(),
property: property.into(),
tenant_value,
query_text: query_text.into(),
k: k.into(),
})
}
pub fn shortest_path(
self,
source: impl Into<NodeRef>,
target: impl Into<NodeRef>,
max_depth: usize,
) -> Traversal<Terminal> {
self.shortest_path_with(
source,
target,
None::<String>,
ShortestPathDirection::Out,
max_depth,
)
}
pub fn shortest_path_with(
self,
source: impl Into<NodeRef>,
target: impl Into<NodeRef>,
label: Option<impl Into<String>>,
direction: ShortestPathDirection,
max_depth: usize,
) -> Traversal<Terminal> {
self.source(AstNode::ShortestPath {
source: source.into(),
target: target.into(),
label: label.map(Into::into),
direction,
max_depth,
})
}
pub fn create_index_if_not_exists(self, spec: IndexSpec) -> Traversal<Terminal, WriteEnabled> {
self.mutation_source(AstNode::CreateIndex {
spec,
if_not_exists: true,
})
}
pub fn drop_index(self, spec: IndexSpec) -> Traversal<Terminal, WriteEnabled> {
self.mutation_source(AstNode::DropIndex { spec })
}
pub fn get_index_operation(self, operation_id: impl Into<String>) -> Traversal<Terminal> {
self.source(AstNode::GetIndexOperation {
operation_id: operation_id.into(),
})
}
pub fn retry_index_operation(
self,
operation_id: impl Into<String>,
) -> Traversal<Terminal, WriteEnabled> {
self.mutation_source(AstNode::RetryIndexOperation {
operation_id: operation_id.into(),
})
}
pub fn abort_index_operation(
self,
operation_id: impl Into<String>,
) -> Traversal<Terminal, WriteEnabled> {
self.mutation_source(AstNode::AbortIndexOperation {
operation_id: operation_id.into(),
})
}
pub fn create_vector_index_nodes(
self,
label: impl Into<String>,
property: impl Into<String>,
dimension: std::num::NonZeroUsize,
metric: crate::index::VectorDistanceMetric,
tenant_property: Option<impl Into<String>>,
) -> Traversal<Terminal, WriteEnabled> {
self.create_index_if_not_exists(IndexSpec::node_vector(
label,
property,
dimension,
metric,
tenant_property,
))
}
pub fn create_vector_index_edges(
self,
label: impl Into<String>,
property: impl Into<String>,
dimension: std::num::NonZeroUsize,
metric: crate::index::VectorDistanceMetric,
tenant_property: Option<impl Into<String>>,
) -> Traversal<Terminal, WriteEnabled> {
self.create_index_if_not_exists(IndexSpec::edge_vector(
label,
property,
dimension,
metric,
tenant_property,
))
}
pub fn create_text_index_nodes(
self,
label: impl Into<String>,
property: impl Into<String>,
tenant_property: Option<impl Into<String>>,
) -> Traversal<Terminal, WriteEnabled> {
self.create_index_if_not_exists(IndexSpec::node_text(label, property, tenant_property))
}
pub fn create_text_index_edges(
self,
label: impl Into<String>,
property: impl Into<String>,
tenant_property: Option<impl Into<String>>,
) -> Traversal<Terminal, WriteEnabled> {
self.create_index_if_not_exists(IndexSpec::edge_text(label, property, tenant_property))
}
pub fn add_n<K, V>(
self,
label: impl Into<String>,
properties: Vec<(K, V)>,
) -> Traversal<OnNodes, WriteEnabled>
where
K: Into<String>,
V: Into<PropertyInput>,
{
self.mutation_source(AstNode::AddN {
input: None,
label: label.into(),
properties: collect_properties(properties),
})
}
pub fn inject(self, var_name: impl Into<String>) -> Traversal<OnNodes, ReadOnly> {
self.source(AstNode::Inject {
input: None,
variable: var_name.into(),
})
}
pub fn drop_edge_by_id(self, edges: impl Into<EdgeRef>) -> Traversal<OnNodes, WriteEnabled> {
self.mutation_source(AstNode::DropEdgeById {
input: None,
edges: edges.into(),
})
}
}
fn collect_properties<K, V>(properties: Vec<(K, V)>) -> Vec<(String, PropertyInput)>
where
K: Into<String>,
V: Into<PropertyInput>,
{
properties
.into_iter()
.map(|(key, value)| (key.into(), value.into()))
.collect()
}
impl<M: MutationMode> Traversal<OnNodes, M> {
pub fn out(self, label: Option<impl Into<String>>) -> Traversal<OnNodes, M> {
self.push(Operation::Out(label.map(Into::into)))
}
pub fn in_(self, label: Option<impl Into<String>>) -> Traversal<OnNodes, M> {
self.push(Operation::In(label.map(Into::into)))
}
pub fn both(self, label: Option<impl Into<String>>) -> Traversal<OnNodes, M> {
self.push(Operation::Both(label.map(Into::into)))
}
pub fn out_e(self, label: Option<impl Into<String>>) -> Traversal<OnEdges, M> {
self.push(Operation::OutE(label.map(Into::into)))
}
pub fn in_e(self, label: Option<impl Into<String>>) -> Traversal<OnEdges, M> {
self.push(Operation::InE(label.map(Into::into)))
}
pub fn both_e(self, label: Option<impl Into<String>>) -> Traversal<OnEdges, M> {
self.push(Operation::BothE(label.map(Into::into)))
}
pub fn has(self, property: impl Into<String>, value: impl Into<PropertyValue>) -> Self {
self.push(Operation::Has(property.into(), value.into()))
}
pub fn has_label(self, label: impl Into<String>) -> Self {
self.push(Operation::HasLabel(label.into()))
}
pub fn has_key(self, property: impl Into<String>) -> Self {
self.push(Operation::HasKey(property.into()))
}
pub fn where_(self, predicate: Predicate) -> Self {
self.push(Operation::Where(predicate))
}
pub fn vector_search(
self,
label: impl Into<String>,
property: impl Into<String>,
query_vector: Vec<f32>,
k: usize,
tenant_value: Option<PropertyValue>,
) -> Self {
self.vector_search_with(
label,
property,
query_vector,
k,
tenant_value.map(PropertyInput::from),
)
}
pub fn vector_search_with(
self,
label: impl Into<String>,
property: impl Into<String>,
query_vector: impl Into<PropertyInput>,
k: impl Into<StreamBound>,
tenant_value: Option<PropertyInput>,
) -> Self {
self.push(Operation::VectorSearchNodesWithin {
label: label.into(),
property: property.into(),
tenant_value,
query_vector: query_vector.into(),
k: k.into(),
})
}
pub fn dedup(self) -> Self {
self.push(Operation::Dedup)
}
pub fn within(self, var_name: impl Into<String>) -> Self {
self.push(Operation::Within(var_name.into()))
}
pub fn without(self, var_name: impl Into<String>) -> Self {
self.push(Operation::Without(var_name.into()))
}
pub fn limit(self, n: impl Into<StreamBound>) -> Self {
self.push(Operation::Limit(n.into()))
}
pub fn skip(self, n: impl Into<StreamBound>) -> Self {
self.push(Operation::Skip(n.into()))
}
pub fn range(self, start: impl Into<StreamBound>, end: impl Into<StreamBound>) -> Self {
self.push(Operation::Range(start.into(), end.into()))
}
pub fn as_(self, name: impl Into<String>) -> Self {
self.push(Operation::As(name.into()))
}
pub fn store(self, name: impl Into<String>) -> Self {
self.push(Operation::Store(name.into()))
}
pub fn select(self, name: impl Into<String>) -> Self {
self.push(Operation::Select(name.into()))
}
pub fn bind(self, name: impl Into<String>) -> Self {
self.push(Operation::Bind(validate_binding_name(name)))
}
pub fn inject(self, var_name: impl Into<String>) -> Self {
self.push(Operation::Inject(var_name.into()))
}
pub fn count(self) -> Traversal<Terminal, M> {
self.push(Operation::Count)
}
pub fn exists(self) -> Traversal<Terminal, M> {
self.push(Operation::Exists)
}
pub fn id(self) -> Traversal<Terminal, M> {
self.push(Operation::Id)
}
pub fn label(self) -> Traversal<Terminal, M> {
self.push(Operation::Label)
}
pub fn values(self, properties: Vec<impl Into<String>>) -> Traversal<Terminal, M> {
self.push(Operation::Values(
properties.into_iter().map(Into::into).collect(),
))
}
pub fn value_map(self, properties: Option<Vec<impl Into<String>>>) -> Traversal<Terminal, M> {
self.push(Operation::ValueMap(
properties.map(|items| items.into_iter().map(Into::into).collect()),
))
}
pub fn project<P>(self, projections: Vec<P>) -> Traversal<Terminal, M>
where
P: Into<Projection>,
{
self.push(Operation::Project(
projections.into_iter().map(Into::into).collect(),
))
}
pub fn project_bindings(self, projections: Vec<BindingProjection>) -> Traversal<Terminal, M> {
self.push(Operation::ProjectBindings {
projections: validate_binding_projections(projections),
distinct: false,
})
}
pub fn project_distinct_bindings(
self,
projections: Vec<BindingProjection>,
) -> Traversal<Terminal, M> {
self.push(Operation::ProjectBindings {
projections: validate_binding_projections(projections),
distinct: true,
})
}
pub fn order_by(self, property: impl Into<String>, order: Order) -> Self {
self.push(Operation::OrderBy(property.into(), order))
}
pub fn order_by_multiple(self, orderings: Vec<(impl Into<String>, Order)>) -> Self {
self.push(Operation::OrderByMultiple(
orderings
.into_iter()
.map(|(property, order)| (property.into(), order))
.collect(),
))
}
pub fn repeat(self, config: RepeatConfig) -> Self {
self.push(Operation::Repeat(config))
}
pub fn union(self, traversals: Vec<SubTraversal>) -> Self {
self.push(Operation::Union(traversals))
}
pub fn choose(
self,
condition: Predicate,
then_traversal: SubTraversal,
else_traversal: Option<SubTraversal>,
) -> Self {
self.push(Operation::Choose {
condition,
then_traversal,
else_traversal,
})
}
pub fn coalesce(self, traversals: Vec<SubTraversal>) -> Self {
self.push(Operation::Coalesce(traversals))
}
pub fn optional(self, traversal: SubTraversal) -> Self {
self.push(Operation::Optional(traversal))
}
pub fn group(self, property: impl Into<String>) -> Traversal<Terminal, M> {
self.push(Operation::Group(property.into()))
}
pub fn group_count(self, property: impl Into<String>) -> Traversal<Terminal, M> {
self.push(Operation::GroupCount(property.into()))
}
pub fn aggregate_by(
self,
function: AggregateFunction,
property: impl Into<String>,
) -> Traversal<Terminal, M> {
self.push(Operation::AggregateBy(function, property.into()))
}
pub fn fold(self) -> Self {
self.push(Operation::Fold)
}
pub fn unfold(self) -> Self {
self.push(Operation::Unfold)
}
pub fn path(self) -> Self {
self.push(Operation::Path)
}
pub fn simple_path(self) -> Self {
self.push(Operation::SimplePath)
}
pub fn with_sack(self, initial: PropertyValue) -> Self {
self.push(Operation::WithSack(initial))
}
pub fn sack_set(self, property: impl Into<String>) -> Self {
self.push(Operation::SackSet(property.into()))
}
pub fn sack_add(self, property: impl Into<String>) -> Self {
self.push(Operation::SackAdd(property.into()))
}
pub fn sack_get(self) -> Self {
self.push(Operation::SackGet)
}
pub fn add_n<K, V>(
self,
label: impl Into<String>,
properties: Vec<(K, V)>,
) -> Traversal<OnNodes, WriteEnabled>
where
K: Into<String>,
V: Into<PropertyInput>,
{
self.push_mutation(Operation::AddN {
label: label.into(),
properties: collect_properties(properties),
})
}
pub fn add_e<K, V>(
self,
label: impl Into<String>,
to: impl Into<NodeRef>,
properties: Vec<(K, V)>,
) -> Traversal<OnNodes, WriteEnabled>
where
K: Into<String>,
V: Into<PropertyInput>,
{
self.push_mutation(Operation::AddE {
label: label.into(),
to: to.into(),
properties: collect_properties(properties),
})
}
pub fn set_property(
self,
name: impl Into<String>,
value: impl Into<PropertyInput>,
) -> Traversal<OnNodes, WriteEnabled> {
self.push_mutation(Operation::SetProperty(name.into(), value.into()))
}
pub fn remove_property(self, name: impl Into<String>) -> Traversal<OnNodes, WriteEnabled> {
self.push_mutation(Operation::RemoveProperty(name.into()))
}
pub fn drop(self) -> Traversal<OnNodes, WriteEnabled> {
self.push_mutation(Operation::Drop)
}
pub fn drop_edge(self, to: impl Into<NodeRef>) -> Traversal<OnNodes, WriteEnabled> {
self.push_mutation(Operation::DropEdge(to.into()))
}
pub fn drop_edge_labeled(
self,
to: impl Into<NodeRef>,
label: impl Into<String>,
) -> Traversal<OnNodes, WriteEnabled> {
self.push_mutation(Operation::DropEdgeLabeled {
to: to.into(),
label: label.into(),
})
}
pub fn drop_edge_by_id(self, edges: impl Into<EdgeRef>) -> Traversal<OnNodes, WriteEnabled> {
self.push_mutation(Operation::DropEdgeById(edges.into()))
}
}
impl<M: MutationMode> Traversal<OnEdges, M> {
pub fn out_n(self) -> Traversal<OnNodes, M> {
self.push(Operation::OutN)
}
pub fn in_n(self) -> Traversal<OnNodes, M> {
self.push(Operation::InN)
}
pub fn other_n(self) -> Traversal<OnNodes, M> {
self.push(Operation::OtherN)
}
pub fn has(self, property: impl Into<String>, value: impl Into<PropertyValue>) -> Self {
self.push(Operation::Has(property.into(), value.into()))
}
pub fn has_label(self, label: impl Into<String>) -> Self {
self.push(Operation::HasLabel(label.into()))
}
pub fn has_key(self, property: impl Into<String>) -> Self {
self.push(Operation::HasKey(property.into()))
}
pub fn where_(self, predicate: Predicate) -> Self {
self.push(Operation::Where(predicate))
}
pub fn vector_search(
self,
label: impl Into<String>,
property: impl Into<String>,
query_vector: Vec<f32>,
k: usize,
tenant_value: Option<PropertyValue>,
) -> Self {
self.vector_search_with(
label,
property,
query_vector,
k,
tenant_value.map(PropertyInput::from),
)
}
pub fn vector_search_with(
self,
label: impl Into<String>,
property: impl Into<String>,
query_vector: impl Into<PropertyInput>,
k: impl Into<StreamBound>,
tenant_value: Option<PropertyInput>,
) -> Self {
self.push(Operation::VectorSearchEdgesWithin {
label: label.into(),
property: property.into(),
tenant_value,
query_vector: query_vector.into(),
k: k.into(),
})
}
pub fn edge_has(self, property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
self.push(Operation::EdgeHas(property.into(), value.into()))
}
pub fn edge_has_label(self, label: impl Into<String>) -> Self {
self.push(Operation::EdgeHasLabel(label.into()))
}
pub fn set_property(
self,
name: impl Into<String>,
value: impl Into<PropertyInput>,
) -> Traversal<OnEdges, WriteEnabled> {
self.push_mutation(Operation::SetProperty(name.into(), value.into()))
}
pub fn remove_property(self, name: impl Into<String>) -> Traversal<OnEdges, WriteEnabled> {
self.push_mutation(Operation::RemoveProperty(name.into()))
}
pub fn dedup(self) -> Self {
self.push(Operation::Dedup)
}
pub fn limit(self, n: impl Into<StreamBound>) -> Self {
self.push(Operation::Limit(n.into()))
}
pub fn skip(self, n: impl Into<StreamBound>) -> Self {
self.push(Operation::Skip(n.into()))
}
pub fn range(self, start: impl Into<StreamBound>, end: impl Into<StreamBound>) -> Self {
self.push(Operation::Range(start.into(), end.into()))
}
pub fn as_(self, name: impl Into<String>) -> Self {
self.push(Operation::As(name.into()))
}
pub fn store(self, name: impl Into<String>) -> Self {
self.push(Operation::Store(name.into()))
}
pub fn bind(self, name: impl Into<String>) -> Self {
self.push(Operation::Bind(validate_binding_name(name)))
}
pub fn count(self) -> Traversal<Terminal, M> {
self.push(Operation::Count)
}
pub fn exists(self) -> Traversal<Terminal, M> {
self.push(Operation::Exists)
}
pub fn id(self) -> Traversal<Terminal, M> {
self.push(Operation::Id)
}
pub fn label(self) -> Traversal<Terminal, M> {
self.push(Operation::Label)
}
pub fn values(self, properties: Vec<impl Into<String>>) -> Traversal<Terminal, M> {
self.push(Operation::Values(
properties.into_iter().map(Into::into).collect(),
))
}
pub fn value_map(self, properties: Option<Vec<impl Into<String>>>) -> Traversal<Terminal, M> {
self.push(Operation::ValueMap(
properties.map(|items| items.into_iter().map(Into::into).collect()),
))
}
pub fn project<P>(self, projections: Vec<P>) -> Traversal<Terminal, M>
where
P: Into<Projection>,
{
self.push(Operation::Project(
projections.into_iter().map(Into::into).collect(),
))
}
pub fn project_bindings(self, projections: Vec<BindingProjection>) -> Traversal<Terminal, M> {
self.push(Operation::ProjectBindings {
projections: validate_binding_projections(projections),
distinct: false,
})
}
pub fn project_distinct_bindings(
self,
projections: Vec<BindingProjection>,
) -> Traversal<Terminal, M> {
self.push(Operation::ProjectBindings {
projections: validate_binding_projections(projections),
distinct: true,
})
}
pub fn edge_properties(self) -> Traversal<Terminal, M> {
self.push(Operation::EdgeProperties)
}
pub fn order_by(self, property: impl Into<String>, order: Order) -> Self {
self.push(Operation::OrderBy(property.into(), order))
}
}
pub fn g() -> Traversal<Empty> {
Traversal::new()
}