use std::marker::PhantomData;
use std::sync::Arc;
use crate::graph_elements::{GraphEdge, GraphVertex, InMemoryEdge, InMemoryVertex};
use crate::storage::cow::Graph;
use crate::storage::interner::StringInterner;
use crate::storage::GraphStorage;
use crate::traversal::context::SnapshotLike;
use crate::traversal::step::{execute_traversal, DynStep, Step};
use crate::traversal::streaming::StreamingExecutor;
use crate::traversal::{ExecutionContext, Traversal, TraversalSource, Traverser};
use crate::value::{EdgeId, Value, VertexId};
pub struct GraphTraversalSource<'g> {
snapshot: &'g dyn SnapshotLike,
#[cfg_attr(not(feature = "full-text"), allow(dead_code))]
graph: Option<Arc<Graph>>,
}
impl<'g> GraphTraversalSource<'g> {
pub fn from_snapshot<S: SnapshotLike + ?Sized>(snapshot: &'g S) -> Self {
Self {
snapshot: snapshot.as_dyn(),
graph: None,
}
}
pub fn from_snapshot_with_graph<S: SnapshotLike + ?Sized>(
snapshot: &'g S,
graph: Arc<Graph>,
) -> Self {
Self {
snapshot: snapshot.as_dyn(),
graph: Some(graph),
}
}
fn bound(&self, traversal: Traversal<(), Value>) -> BoundTraversal<'g, (), Value> {
BoundTraversal::new_with_graph(self.snapshot, traversal, self.graph.clone())
}
pub fn v(&self) -> BoundTraversal<'g, (), Value> {
self.bound(Traversal::with_source(TraversalSource::AllVertices))
}
pub fn v_ids<I>(&self, ids: I) -> BoundTraversal<'g, (), Value>
where
I: IntoIterator<Item = VertexId>,
{
self.bound(Traversal::with_source(TraversalSource::Vertices(ids.into_iter().collect())))
}
pub fn e(&self) -> BoundTraversal<'g, (), Value> {
self.bound(Traversal::with_source(TraversalSource::AllEdges))
}
pub fn e_ids<I>(&self, ids: I) -> BoundTraversal<'g, (), Value>
where
I: IntoIterator<Item = EdgeId>,
{
self.bound(Traversal::with_source(TraversalSource::Edges(ids.into_iter().collect())))
}
pub fn inject<T, I>(&self, values: I) -> BoundTraversal<'g, (), Value>
where
I: IntoIterator<Item = T>,
T: Into<Value>,
{
let values: Vec<Value> = values.into_iter().map(Into::into).collect();
self.bound(Traversal::with_source(TraversalSource::Inject(values)))
}
#[inline]
pub fn storage(&self) -> &'g dyn GraphStorage {
self.snapshot.storage()
}
#[inline]
pub fn interner(&self) -> &'g StringInterner {
self.snapshot.interner()
}
#[cfg(feature = "full-text")]
pub fn search_text(
&self,
property: &str,
query: &str,
k: usize,
) -> Result<BoundTraversal<'g, (), Value>, crate::storage::text::TextIndexError> {
use crate::storage::text::TextQuery;
self.search_text_query(property, &TextQuery::Match(query.to_string()), k)
}
#[cfg(feature = "full-text")]
pub fn search_text_query(
&self,
property: &str,
query: &crate::storage::text::TextQuery,
k: usize,
) -> Result<BoundTraversal<'g, (), Value>, crate::storage::text::TextIndexError> {
let graph = self.graph.as_ref().ok_or_else(|| {
crate::storage::text::TextIndexError::Storage(crate::error::StorageError::IndexError(
"full-text search requires a live Graph handle; \
construct GraphTraversalSource via from_snapshot_with_graph \
(e.g. via Graph::execute_script, Graph::query, Graph::gql, \
or Graph::gql_with_params)"
.to_string(),
))
})?;
let index = graph.text_index_v(property).ok_or_else(|| {
crate::storage::text::TextIndexError::Storage(crate::error::StorageError::IndexError(
format!("no vertex text index registered for property {property:?}"),
))
})?;
let hits = index.search(query, k)?;
let scored: Vec<(VertexId, f32)> = hits
.into_iter()
.filter_map(|h| h.element.as_vertex().map(|v| (v, h.score)))
.collect();
Ok(self.bound(
Traversal::with_source(TraversalSource::VerticesWithTextScore(scored)),
))
}
#[cfg(feature = "full-text")]
pub fn search_text_e(
&self,
property: &str,
query: &str,
k: usize,
) -> Result<BoundTraversal<'g, (), Value>, crate::storage::text::TextIndexError> {
use crate::storage::text::TextQuery;
self.search_text_query_e(property, &TextQuery::Match(query.to_string()), k)
}
#[cfg(feature = "full-text")]
pub fn search_text_query_e(
&self,
property: &str,
query: &crate::storage::text::TextQuery,
k: usize,
) -> Result<BoundTraversal<'g, (), Value>, crate::storage::text::TextIndexError> {
let graph = self.graph.as_ref().ok_or_else(|| {
crate::storage::text::TextIndexError::Storage(crate::error::StorageError::IndexError(
"full-text search requires a live Graph handle; \
construct GraphTraversalSource via from_snapshot_with_graph \
(e.g. via Graph::execute_script, Graph::query, Graph::gql, \
or Graph::gql_with_params)"
.to_string(),
))
})?;
let index = graph.text_index_e(property).ok_or_else(|| {
crate::storage::text::TextIndexError::Storage(crate::error::StorageError::IndexError(
format!("no edge text index registered for property {property:?}"),
))
})?;
let hits = index.search(query, k)?;
let scored: Vec<(EdgeId, f32)> = hits
.into_iter()
.filter_map(|h| h.element.as_edge().map(|e| (e, h.score)))
.collect();
Ok(self.bound(
Traversal::with_source(TraversalSource::EdgesWithTextScore(scored)),
))
}
pub fn v_by_property(
&self,
label: Option<&str>,
property: &str,
value: impl Into<Value>,
) -> BoundTraversal<'g, (), Value> {
let value = value.into();
let vertex_ids: Vec<VertexId> = self
.snapshot
.storage()
.vertices_by_property(label, property, &value)
.map(|v| v.id)
.collect();
self.bound(Traversal::with_source(TraversalSource::Vertices(vertex_ids)))
}
pub fn v_by_property_range(
&self,
label: Option<&str>,
property: &str,
start: std::ops::Bound<&Value>,
end: std::ops::Bound<&Value>,
) -> BoundTraversal<'g, (), Value> {
let vertex_ids: Vec<VertexId> = self
.snapshot
.storage()
.vertices_by_property_range(label, property, start, end)
.map(|v| v.id)
.collect();
self.bound(Traversal::with_source(TraversalSource::Vertices(vertex_ids)))
}
pub fn e_by_property(
&self,
label: Option<&str>,
property: &str,
value: impl Into<Value>,
) -> BoundTraversal<'g, (), Value> {
let value = value.into();
let edge_ids: Vec<EdgeId> = self
.snapshot
.storage()
.edges_by_property(label, property, &value)
.map(|e| e.id)
.collect();
self.bound(Traversal::with_source(TraversalSource::Edges(edge_ids)))
}
pub fn add_v(&self, label: impl Into<String>) -> BoundTraversal<'g, (), Value> {
use crate::traversal::mutation::AddVStep;
let mut traversal = Traversal::<(), Value>::with_source(TraversalSource::Inject(vec![]));
traversal = traversal.add_step(AddVStep::new(label));
self.bound(traversal)
}
pub fn add_e(&self, label: impl Into<String>) -> AddEdgeBuilder<'g> {
AddEdgeBuilder::new(self.snapshot, label.into())
}
}
pub struct BoundTraversal<'g, In, Out> {
snapshot: &'g dyn SnapshotLike,
traversal: Traversal<In, Out>,
track_paths: bool,
graph: Option<Arc<Graph>>,
}
impl<'g, In, Out> BoundTraversal<'g, In, Out> {
pub(crate) fn new(snapshot: &'g dyn SnapshotLike, traversal: Traversal<In, Out>) -> Self {
Self {
snapshot,
traversal,
track_paths: false,
graph: None,
}
}
pub(crate) fn new_with_graph(
snapshot: &'g dyn SnapshotLike,
traversal: Traversal<In, Out>,
graph: Option<Arc<Graph>>,
) -> Self {
Self {
snapshot,
traversal,
track_paths: false,
graph,
}
}
pub fn with_path(mut self) -> Self {
self.track_paths = true;
self
}
#[inline]
pub fn is_tracking_paths(&self) -> bool {
self.track_paths
}
pub fn add_step<NewOut>(self, step: impl Step + 'static) -> BoundTraversal<'g, In, NewOut> {
BoundTraversal {
snapshot: self.snapshot,
traversal: self.traversal.add_step(step),
track_paths: self.track_paths,
graph: self.graph,
}
}
pub fn append<Mid>(self, anon: Traversal<Out, Mid>) -> BoundTraversal<'g, In, Mid> {
BoundTraversal {
snapshot: self.snapshot,
traversal: self.traversal.append(anon),
track_paths: self.track_paths,
graph: self.graph,
}
}
#[allow(dead_code)] fn create_context(&self) -> ExecutionContext<'g> {
ExecutionContext::new(self.snapshot.storage(), self.snapshot.interner())
}
pub fn execute(self) -> TraversalExecutor<'g> {
TraversalExecutor::new(
self.snapshot.storage(),
self.snapshot.interner(),
self.traversal,
self.track_paths,
)
}
pub fn streaming_execute(self) -> StreamingExecutor {
let (source, steps) = self.traversal.into_steps();
StreamingExecutor::new(
self.snapshot.arc_streamable(),
self.snapshot.arc_interner(),
steps,
source,
self.track_paths,
)
}
#[deprecated(
since = "0.2.0",
note = "Use iter() instead - it now uses streaming by default"
)]
pub fn streaming_iter(self) -> impl Iterator<Item = Value> + Send {
self.streaming_execute().map(|t| t.value)
}
#[deprecated(
since = "0.2.0",
note = "Use traversers() instead - it now uses streaming by default"
)]
pub fn streaming_traversers(self) -> impl Iterator<Item = Traverser> + Send {
self.streaming_execute()
}
#[inline]
pub fn storage(&self) -> &'g dyn GraphStorage {
self.snapshot.storage()
}
#[inline]
pub fn interner(&self) -> &'g StringInterner {
self.snapshot.interner()
}
#[inline]
pub fn step_count(&self) -> usize {
self.traversal.step_count()
}
pub fn step_names(&self) -> Vec<&'static str> {
self.traversal.step_names()
}
}
impl<'g, In> BoundTraversal<'g, In, Value> {
pub fn has_label(self, label: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::HasLabelStep;
self.add_step(HasLabelStep::single(label))
}
pub fn has_label_any<I, S>(self, labels: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
use crate::traversal::filter::HasLabelStep;
self.add_step(HasLabelStep::any(labels))
}
pub fn has(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::HasStep;
self.add_step(HasStep::new(key))
}
pub fn has_not(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::HasNotStep;
self.add_step(HasNotStep::new(key))
}
pub fn has_value(
self,
key: impl Into<String>,
value: impl Into<Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::HasValueStep;
self.add_step(HasValueStep::new(key, value))
}
pub fn has_where(
self,
key: impl Into<String>,
predicate: impl crate::traversal::predicate::Predicate + 'static,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::HasWhereStep;
self.add_step(HasWhereStep::new(key, predicate))
}
pub fn filter<F>(self, predicate: F) -> BoundTraversal<'g, In, Value>
where
F: Fn(&crate::traversal::ExecutionContext, &Value) -> bool + Clone + Send + Sync + 'static,
{
use crate::traversal::filter::FilterStep;
self.add_step(FilterStep::new(predicate))
}
pub fn dedup(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::DedupStep;
self.add_step(DedupStep::new())
}
pub fn dedup_by_key(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::DedupByKeyStep;
self.add_step(DedupByKeyStep::new(key))
}
pub fn dedup_by_label(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::DedupByLabelStep;
self.add_step(DedupByLabelStep::new())
}
pub fn dedup_by(
self,
sub: crate::traversal::Traversal<crate::value::Value, crate::value::Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::DedupByTraversalStep;
self.add_step(DedupByTraversalStep::new(sub))
}
pub fn limit(self, count: usize) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::LimitStep;
self.add_step(LimitStep::new(count))
}
pub fn skip(self, count: usize) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::SkipStep;
self.add_step(SkipStep::new(count))
}
pub fn range(self, start: usize, end: usize) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::RangeStep;
self.add_step(RangeStep::new(start, end))
}
pub fn has_id(self, id: impl Into<Value>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::HasIdStep;
self.add_step(HasIdStep::from_value(id))
}
pub fn has_ids<I, T>(self, ids: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = T>,
T: Into<Value>,
{
use crate::traversal::filter::HasIdStep;
self.add_step(HasIdStep::from_values(
ids.into_iter().map(Into::into).collect(),
))
}
pub fn is_(
self,
predicate: impl crate::traversal::predicate::Predicate + 'static,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::IsStep;
self.add_step(IsStep::new(predicate))
}
pub fn is_eq(self, value: impl Into<Value>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::IsStep;
self.add_step(IsStep::eq(value))
}
pub fn simple_path(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::SimplePathStep;
self.add_step(SimplePathStep::new())
}
pub fn cyclic_path(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::CyclicPathStep;
self.add_step(CyclicPathStep::new())
}
pub fn tail(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::TailStep;
self.add_step(TailStep::last())
}
pub fn tail_n(self, count: usize) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::TailStep;
self.add_step(TailStep::new(count))
}
pub fn coin(self, probability: f64) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::CoinStep;
self.add_step(CoinStep::new(probability))
}
pub fn sample(self, count: usize) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::SampleStep;
self.add_step(SampleStep::new(count))
}
pub fn has_key(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::HasKeyStep;
self.add_step(HasKeyStep::new(key))
}
pub fn has_key_any<I, S>(self, keys: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
use crate::traversal::filter::HasKeyStep;
self.add_step(HasKeyStep::any(keys))
}
pub fn has_prop_value(self, value: impl Into<Value>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::HasPropValueStep;
self.add_step(HasPropValueStep::new(value))
}
pub fn has_prop_value_any<I, V>(self, values: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = V>,
V: Into<Value>,
{
use crate::traversal::filter::HasPropValueStep;
self.add_step(HasPropValueStep::any(values))
}
pub fn where_p(
self,
predicate: impl crate::traversal::predicate::Predicate + 'static,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::filter::WherePStep;
self.add_step(WherePStep::new(predicate))
}
pub fn out(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::OutStep;
self.add_step(OutStep::new())
}
pub fn out_labels(self, labels: &[&str]) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::OutStep;
let labels: Vec<String> = labels.iter().map(|s| s.to_string()).collect();
self.add_step(OutStep::with_labels(labels))
}
pub fn in_(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::InStep;
self.add_step(InStep::new())
}
pub fn in_labels(self, labels: &[&str]) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::InStep;
let labels: Vec<String> = labels.iter().map(|s| s.to_string()).collect();
self.add_step(InStep::with_labels(labels))
}
pub fn both(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::BothStep;
self.add_step(BothStep::new())
}
pub fn both_labels(self, labels: &[&str]) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::BothStep;
let labels: Vec<String> = labels.iter().map(|s| s.to_string()).collect();
self.add_step(BothStep::with_labels(labels))
}
pub fn shortest_path_to(self, target: VertexId) -> BoundTraversal<'g, In, Value> {
use crate::traversal::algorithm_steps::ShortestPathStep;
self.add_step(ShortestPathStep::new(target))
}
pub fn dijkstra_to(
self,
target: VertexId,
weight_property: &str,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::algorithm_steps::DijkstraStep;
self.add_step(DijkstraStep::new(target, weight_property.to_string()))
}
pub fn astar_to(
self,
target: VertexId,
weight_property: &str,
heuristic_property: &str,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::algorithm_steps::AstarStep;
self.add_step(AstarStep::new(
target,
weight_property.to_string(),
heuristic_property.to_string(),
))
}
pub fn k_shortest_paths_to(
self,
target: VertexId,
k: usize,
weight_property: &str,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::algorithm_steps::KShortestPathsStep;
self.add_step(KShortestPathsStep::new(
target,
k,
weight_property.to_string(),
))
}
pub fn bfs_traversal(
self,
max_depth: Option<u32>,
edge_labels: Option<Vec<String>>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::algorithm_steps::BfsTraversalStep;
self.add_step(BfsTraversalStep::new(max_depth, edge_labels))
}
pub fn dfs_traversal(
self,
max_depth: Option<u32>,
edge_labels: Option<Vec<String>>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::algorithm_steps::DfsTraversalStep;
self.add_step(DfsTraversalStep::new(max_depth, edge_labels))
}
pub fn bidirectional_bfs_to(
self,
target: VertexId,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::algorithm_steps::BidirectionalBfsStep;
self.add_step(BidirectionalBfsStep::new(target))
}
pub fn iddfs_to(
self,
target: VertexId,
max_depth: u32,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::algorithm_steps::IddfsStep;
self.add_step(IddfsStep::new(target, max_depth))
}
pub fn out_e(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::OutEStep;
self.add_step(OutEStep::new())
}
pub fn out_e_labels(self, labels: &[&str]) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::OutEStep;
let labels: Vec<String> = labels.iter().map(|s| s.to_string()).collect();
self.add_step(OutEStep::with_labels(labels))
}
pub fn in_e(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::InEStep;
self.add_step(InEStep::new())
}
pub fn in_e_labels(self, labels: &[&str]) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::InEStep;
let labels: Vec<String> = labels.iter().map(|s| s.to_string()).collect();
self.add_step(InEStep::with_labels(labels))
}
pub fn both_e(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::BothEStep;
self.add_step(BothEStep::new())
}
pub fn both_e_labels(self, labels: &[&str]) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::BothEStep;
let labels: Vec<String> = labels.iter().map(|s| s.to_string()).collect();
self.add_step(BothEStep::with_labels(labels))
}
pub fn out_v(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::OutVStep;
self.add_step(OutVStep::new())
}
pub fn in_v(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::InVStep;
self.add_step(InVStep::new())
}
pub fn both_v(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::BothVStep;
self.add_step(BothVStep::new())
}
pub fn other_v(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::navigation::OtherVStep;
self.add_step(OtherVStep::new())
}
pub fn values(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::ValuesStep;
self.add_step(ValuesStep::new(key))
}
pub fn values_multi<I, S>(self, keys: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
use crate::traversal::transform::ValuesStep;
self.add_step(ValuesStep::from_keys(keys))
}
pub fn id(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::IdStep;
self.add_step(IdStep::new())
}
pub fn label(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::LabelStep;
self.add_step(LabelStep::new())
}
pub fn key(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::KeyStep;
self.add_step(KeyStep::new())
}
pub fn value(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::ValueStep;
self.add_step(ValueStep::new())
}
pub fn loops(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::LoopsStep;
self.add_step(LoopsStep::new())
}
#[cfg(feature = "full-text")]
pub fn text_score(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::TextScoreStep;
self.add_step(TextScoreStep::new())
}
pub fn index(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::IndexStep;
self.add_step(IndexStep::new())
}
pub fn properties(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::PropertiesStep;
self.add_step(PropertiesStep::new())
}
pub fn properties_keys<I, S>(self, keys: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
use crate::traversal::transform::PropertiesStep;
self.add_step(PropertiesStep::from_keys(keys))
}
pub fn value_map(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::ValueMapStep;
self.add_step(ValueMapStep::new())
}
pub fn value_map_keys<I, S>(self, keys: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
use crate::traversal::transform::ValueMapStep;
self.add_step(ValueMapStep::from_keys(keys))
}
pub fn value_map_with_tokens(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::ValueMapStep;
self.add_step(ValueMapStep::new().with_tokens())
}
pub fn element_map(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::ElementMapStep;
self.add_step(ElementMapStep::new())
}
pub fn element_map_keys<I, S>(self, keys: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
use crate::traversal::transform::ElementMapStep;
self.add_step(ElementMapStep::from_keys(keys))
}
pub fn property_map(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::PropertyMapStep;
self.add_step(PropertyMapStep::new())
}
pub fn property_map_keys<I, S>(self, keys: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
use crate::traversal::transform::PropertyMapStep;
self.add_step(PropertyMapStep::from_keys(keys))
}
pub fn map<F>(self, f: F) -> BoundTraversal<'g, In, Value>
where
F: Fn(&crate::traversal::ExecutionContext, &Value) -> Value + Clone + Send + Sync + 'static,
{
use crate::traversal::transform::MapStep;
self.add_step(MapStep::new(f))
}
pub fn flat_map<F>(self, f: F) -> BoundTraversal<'g, In, Value>
where
F: Fn(&crate::traversal::ExecutionContext, &Value) -> Vec<Value>
+ Clone
+ Send
+ Sync
+ 'static,
{
use crate::traversal::transform::FlatMapStep;
self.add_step(FlatMapStep::new(f))
}
pub fn constant(self, value: impl Into<Value>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::ConstantStep;
self.add_step(ConstantStep::new(value))
}
pub fn path(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::PathStep;
self.add_step(PathStep::new())
}
pub fn as_(self, label: &str) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::AsStep;
self.add_step(AsStep::new(label))
}
pub fn select(self, labels: &[&str]) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::SelectStep;
let labels: Vec<String> = labels.iter().map(|s| s.to_string()).collect();
self.add_step(SelectStep::new(labels))
}
pub fn select_one(self, label: &str) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::SelectStep;
self.add_step(SelectStep::single(label))
}
pub fn select_keys(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::SelectKeysStep;
self.add_step(SelectKeysStep::new())
}
pub fn select_values(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::SelectValuesStep;
self.add_step(SelectValuesStep::new())
}
pub fn unfold(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::UnfoldStep;
self.add_step(UnfoldStep::new())
}
pub fn mean(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::MeanStep;
self.add_step(MeanStep::new())
}
pub fn count_step(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::aggregate::CountStep;
self.add_step(CountStep::new())
}
pub fn sum_step(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::SumStep;
self.add_step(SumStep::new())
}
pub fn min_step(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::aggregate::MinStep;
self.add_step(MinStep::new())
}
pub fn max_step(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::aggregate::MaxStep;
self.add_step(MaxStep::new())
}
pub fn fold_step(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::transform::FoldStep;
self.add_step(FoldStep::new())
}
pub fn order(self) -> crate::traversal::transform::BoundOrderBuilder<'g, In> {
use crate::traversal::transform::BoundOrderBuilder;
let track_paths = self.track_paths;
let (source, steps) = self.traversal.into_steps();
BoundOrderBuilder::new(self.snapshot, source, steps, track_paths)
}
#[cfg(feature = "gql")]
pub fn math(self, expression: &str) -> crate::traversal::transform::BoundMathBuilder<'g, In> {
use crate::traversal::transform::BoundMathBuilder;
let track_paths = self.track_paths;
let (source, steps) = self.traversal.into_steps();
BoundMathBuilder::new(self.snapshot, source, steps, expression, track_paths)
}
pub fn project(
self,
keys: &[&str],
) -> crate::traversal::transform::BoundProjectBuilder<'g, In> {
use crate::traversal::transform::BoundProjectBuilder;
let track_paths = self.track_paths;
let (source, steps) = self.traversal.into_steps();
let key_strings: Vec<String> = keys.iter().map(|k| k.to_string()).collect();
BoundProjectBuilder::new(self.snapshot, source, steps, key_strings, track_paths)
}
pub fn group(self) -> crate::traversal::aggregate::BoundGroupBuilder<'g, In> {
use crate::traversal::aggregate::BoundGroupBuilder;
let track_paths = self.track_paths;
let (source, steps) = self.traversal.into_steps();
BoundGroupBuilder::new(self.snapshot, source, steps, track_paths)
}
pub fn group_count(self) -> crate::traversal::aggregate::BoundGroupCountBuilder<'g, In> {
use crate::traversal::aggregate::BoundGroupCountBuilder;
let track_paths = self.track_paths;
let (source, steps) = self.traversal.into_steps();
BoundGroupCountBuilder::new(self.snapshot, source, steps, track_paths)
}
pub fn where_(
self,
sub: crate::traversal::Traversal<Value, Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::WhereStep;
self.add_step(WhereStep::new(sub))
}
pub fn not(
self,
sub: crate::traversal::Traversal<Value, Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::NotStep;
self.add_step(NotStep::new(sub))
}
pub fn where_neq(self, label: &str) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::WhereNeqStep;
self.add_step(WhereNeqStep::new(label))
}
pub fn where_eq(self, label: &str) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::WhereEqStep;
self.add_step(WhereEqStep::new(label))
}
pub fn and_(
self,
subs: Vec<crate::traversal::Traversal<Value, Value>>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::AndStep;
self.add_step(AndStep::new(subs))
}
pub fn or_(
self,
subs: Vec<crate::traversal::Traversal<Value, Value>>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::OrStep;
self.add_step(OrStep::new(subs))
}
pub fn union(
self,
branches: Vec<crate::traversal::Traversal<Value, Value>>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::UnionStep;
self.add_step(UnionStep::new(branches))
}
pub fn coalesce(
self,
branches: Vec<crate::traversal::Traversal<Value, Value>>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::CoalesceStep;
self.add_step(CoalesceStep::new(branches))
}
pub fn choose(
self,
condition: crate::traversal::Traversal<Value, Value>,
if_true: crate::traversal::Traversal<Value, Value>,
if_false: crate::traversal::Traversal<Value, Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::ChooseStep;
self.add_step(ChooseStep::new(condition, if_true, if_false))
}
pub fn optional(
self,
sub: crate::traversal::Traversal<Value, Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::OptionalStep;
self.add_step(OptionalStep::new(sub))
}
pub fn local(
self,
sub: crate::traversal::Traversal<Value, Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::LocalStep;
self.add_step(LocalStep::new(sub))
}
pub fn property(
self,
key: impl Into<String>,
value: impl Into<Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::mutation::PropertyStep;
self.add_step(PropertyStep::new(key, value))
}
pub fn drop(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::mutation::DropStep;
self.add_step(DropStep::new())
}
pub fn add_e(self, label: impl Into<String>) -> BoundAddEdgeBuilder<'g, In> {
BoundAddEdgeBuilder::from_traversal(self, label.into())
}
pub fn store(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::sideeffect::StoreStep;
self.add_step(StoreStep::new(key))
}
pub fn aggregate(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::sideeffect::AggregateStep;
self.add_step(AggregateStep::new(key))
}
pub fn cap(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::sideeffect::CapStep;
self.add_step(CapStep::new(key))
}
pub fn cap_multi<I, S>(self, keys: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
use crate::traversal::sideeffect::CapStep;
self.add_step(CapStep::multi(keys))
}
pub fn side_effect(
self,
traversal: crate::traversal::Traversal<Value, Value>,
) -> BoundTraversal<'g, In, Value> {
use crate::traversal::sideeffect::SideEffectStep;
self.add_step(SideEffectStep::new(traversal))
}
pub fn profile(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::sideeffect::ProfileStep;
self.add_step(ProfileStep::new())
}
pub fn profile_as(self, key: impl Into<String>) -> BoundTraversal<'g, In, Value> {
use crate::traversal::sideeffect::ProfileStep;
self.add_step(ProfileStep::with_key(key))
}
pub fn repeat(
self,
sub: crate::traversal::Traversal<Value, Value>,
) -> crate::traversal::repeat::RepeatTraversal<'g, In> {
crate::traversal::repeat::RepeatTraversal::new(
self.snapshot,
self.traversal,
sub,
self.track_paths,
)
}
}
impl<'g, In, Out> Clone for BoundTraversal<'g, In, Out> {
fn clone(&self) -> Self {
Self {
snapshot: self.snapshot,
traversal: self.traversal.clone(),
track_paths: self.track_paths,
graph: self.graph.clone(),
}
}
}
impl<'g, In, Out> std::fmt::Debug for BoundTraversal<'g, In, Out> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BoundTraversal")
.field("step_count", &self.traversal.step_count())
.field("step_names", &self.traversal.step_names())
.field("track_paths", &self.track_paths)
.finish()
}
}
#[cfg(all(feature = "reactive", not(target_arch = "wasm32")))]
impl<'g, In, Out> BoundTraversal<'g, In, Out> {
pub fn subscribe(&self) -> crate::traversal::reactive::Subscription {
self.subscribe_with(crate::traversal::reactive::SubscribeOptions::default())
}
pub fn subscribe_with(
&self,
opts: crate::traversal::reactive::SubscribeOptions,
) -> crate::traversal::reactive::Subscription {
let matcher = crate::traversal::reactive::QueryMatcher::compile(
self.traversal.steps(),
self.traversal.source(),
);
let manager = self
.snapshot
.subscription_manager()
.expect("snapshot does not support reactive subscriptions");
let snapshot_fn = self
.snapshot
.reactive_snapshot_fn()
.expect("snapshot does not support reactive snapshot factory");
manager.subscribe(matcher, opts, snapshot_fn)
}
}
pub struct TraversalExecutor<'g> {
results: std::vec::IntoIter<Traverser>,
_phantom: PhantomData<&'g ()>,
}
impl<'g> TraversalExecutor<'g> {
fn new<In, Out>(
storage: &'g dyn GraphStorage,
interner: &'g StringInterner,
traversal: Traversal<In, Out>,
track_paths: bool,
) -> Self {
let ctx = if track_paths {
ExecutionContext::with_path_tracking(storage, interner)
} else {
ExecutionContext::new(storage, interner)
};
let (source, steps) = traversal.into_steps();
let start: Box<dyn Iterator<Item = Traverser> + '_> = match source {
Some(TraversalSource::AllVertices) => Box::new(storage.all_vertices().map(move |v| {
let mut t = Traverser::from_vertex(v.id);
if track_paths {
t.extend_path_unlabeled();
}
t
})),
Some(TraversalSource::Vertices(ids)) => {
Box::new(ids.into_iter().filter_map(move |id| {
storage.get_vertex(id).map(|_| {
let mut t = Traverser::from_vertex(id);
if track_paths {
t.extend_path_unlabeled();
}
t
})
}))
}
Some(TraversalSource::AllEdges) => Box::new(storage.all_edges().map(move |e| {
let mut t = Traverser::from_edge(e.id);
if track_paths {
t.extend_path_unlabeled();
}
t
})),
Some(TraversalSource::Edges(ids)) => Box::new(ids.into_iter().filter_map(move |id| {
storage.get_edge(id).map(|_| {
let mut t = Traverser::from_edge(id);
if track_paths {
t.extend_path_unlabeled();
}
t
})
})),
Some(TraversalSource::Inject(values)) => Box::new(values.into_iter().map(move |v| {
let mut t = Traverser::new(v);
if track_paths {
t.extend_path_unlabeled();
}
t
})),
#[cfg(feature = "full-text")]
Some(TraversalSource::VerticesWithTextScore(hits)) => {
Box::new(hits.into_iter().filter_map(move |(id, score)| {
storage.get_vertex(id).map(|_| {
let mut t = Traverser::from_vertex(id);
t.set_sack(score);
if track_paths {
t.extend_path_unlabeled();
}
t
})
}))
}
#[cfg(feature = "full-text")]
Some(TraversalSource::EdgesWithTextScore(hits)) => {
Box::new(hits.into_iter().filter_map(move |(id, score)| {
storage.get_edge(id).map(|_| {
let mut t = Traverser::from_edge(id);
t.set_sack(score);
if track_paths {
t.extend_path_unlabeled();
}
t
})
}))
}
None => Box::new(std::iter::empty()),
};
let lazy_iter = execute_traversal(&ctx, &steps, start);
let results: Vec<Traverser> = lazy_iter.collect();
Self {
results: results.into_iter(),
_phantom: PhantomData,
}
}
pub fn len(&self) -> usize {
self.results.len()
}
pub fn is_empty(&self) -> bool {
self.results.len() == 0
}
}
impl Iterator for TraversalExecutor<'_> {
type Item = Traverser;
fn next(&mut self) -> Option<Self::Item> {
self.results.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.results.size_hint()
}
}
impl ExactSizeIterator for TraversalExecutor<'_> {
fn len(&self) -> usize {
self.results.len()
}
}
impl<'g, In, Out> BoundTraversal<'g, In, Out> {
pub fn explain(self) -> crate::traversal::explain::TraversalExplanation {
let indexes = self
.graph
.as_ref()
.map(|g| g.list_indexes())
.unwrap_or_default();
#[cfg(feature = "full-text")]
let text_indexes = self
.graph
.as_ref()
.map(|g| {
let mut all = g.list_text_indexes_v();
all.extend(g.list_text_indexes_e());
all
})
.unwrap_or_default();
#[cfg(not(feature = "full-text"))]
let text_indexes: Vec<String> = Vec::new();
crate::traversal::explain::TraversalExplanation::from_steps(
self.traversal.source(),
self.traversal.steps(),
&indexes,
&text_indexes,
)
}
pub fn to_list(self) -> Vec<Value> {
self.execute().map(|t| t.value).collect()
}
pub fn to_set(self) -> std::collections::HashSet<Value> {
self.execute().map(|t| t.value).collect()
}
pub fn next(self) -> Option<Value> {
if self.traversal.has_barrier() {
self.execute().next().map(|t| t.value)
} else {
self.streaming_execute().next().map(|t| t.value)
}
}
pub fn has_next(self) -> bool {
if self.traversal.has_barrier() {
self.execute().next().is_some()
} else {
self.streaming_execute().next().is_some()
}
}
pub fn one(self) -> Result<Value, crate::error::TraversalError> {
if self.traversal.has_barrier() {
let results: Vec<_> = self.execute().take(2).collect();
match results.len() {
1 => Ok(results.into_iter().next().unwrap().value),
n => Err(crate::error::TraversalError::NotOne(n)),
}
} else {
let mut iter = self.streaming_execute();
let first = iter.next();
let second = iter.next();
match (first, second) {
(Some(t), None) => Ok(t.value),
(None, _) => Err(crate::error::TraversalError::NotOne(0)),
(Some(_), Some(_)) => Err(crate::error::TraversalError::NotOne(2)),
}
}
}
pub fn iterate(self) {
if self.traversal.has_barrier() {
for _ in self.execute() {}
} else {
for _ in self.streaming_execute() {}
}
}
pub fn count(self) -> u64 {
if self.traversal.has_barrier() {
self.execute().map(|t| t.bulk).sum()
} else {
self.streaming_execute().map(|t| t.bulk).sum()
}
}
pub fn take(self, n: usize) -> Vec<Value> {
if self.traversal.has_barrier() {
self.execute().take(n).map(|t| t.value).collect()
} else {
self.streaming_execute().take(n).map(|t| t.value).collect()
}
}
pub fn iter(self) -> impl Iterator<Item = Value> + Send {
self.streaming_execute().map(|t| t.value)
}
pub fn eager_iter(self) -> impl Iterator<Item = Value> + 'g {
self.execute().map(|t| t.value)
}
pub fn traversers(self) -> impl Iterator<Item = Traverser> + Send {
self.streaming_execute()
}
pub fn eager_traversers(self) -> impl Iterator<Item = Traverser> + 'g {
self.execute()
}
pub fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Value) -> B,
{
self.execute().map(|t| t.value).fold(init, f)
}
pub fn sum(self) -> Value {
let mut int_sum: i64 = 0;
let mut float_sum: f64 = 0.0;
let mut has_float = false;
for traverser in self.execute() {
match traverser.value {
Value::Int(n) => int_sum += n,
Value::Float(f) => {
has_float = true;
float_sum += f;
}
_ => {}
}
}
if has_float {
Value::Float(int_sum as f64 + float_sum)
} else {
Value::Int(int_sum)
}
}
pub fn min(self) -> Option<Value> {
self.execute()
.map(|t| t.value)
.min_by(|a, b| a.to_comparable().cmp(&b.to_comparable()))
}
pub fn max(self) -> Option<Value> {
self.execute()
.map(|t| t.value)
.max_by(|a, b| a.to_comparable().cmp(&b.to_comparable()))
}
pub fn branch(self, branch_traversal: Traversal<Value, Value>) -> BranchBuilder<'g, In> {
let track_paths = self.track_paths;
let (source, steps) = self.traversal.into_steps();
BranchBuilder::new(self.snapshot, source, steps, branch_traversal, track_paths)
}
pub fn choose_by(self, branch_traversal: Traversal<Value, Value>) -> BranchBuilder<'g, In> {
self.branch(branch_traversal)
}
pub fn to_vertex_list(self, graph: Arc<Graph>) -> Vec<InMemoryVertex> {
self.execute()
.filter_map(|t| match t.value {
Value::Vertex(id) => Some(GraphVertex::new(id, Arc::clone(&graph))),
_ => None,
})
.collect()
}
pub fn next_vertex(self, graph: Arc<Graph>) -> Option<InMemoryVertex> {
self.execute().find_map(|t| match t.value {
Value::Vertex(id) => Some(GraphVertex::new(id, Arc::clone(&graph))),
_ => None,
})
}
pub fn one_vertex(
self,
graph: Arc<Graph>,
) -> Result<InMemoryVertex, crate::error::TraversalError> {
let ids: Vec<_> = self
.execute()
.filter_map(|t| match t.value {
Value::Vertex(id) => Some(id),
_ => None,
})
.take(2)
.collect();
match ids.len() {
1 => Ok(GraphVertex::new(ids[0], graph)),
n => Err(crate::error::TraversalError::NotOne(n)),
}
}
pub fn to_edge_list(self, graph: Arc<Graph>) -> Vec<InMemoryEdge> {
self.execute()
.filter_map(|t| match t.value {
Value::Edge(id) => Some(GraphEdge::new(id, Arc::clone(&graph))),
_ => None,
})
.collect()
}
pub fn next_edge(self, graph: Arc<Graph>) -> Option<InMemoryEdge> {
self.execute().find_map(|t| match t.value {
Value::Edge(id) => Some(GraphEdge::new(id, Arc::clone(&graph))),
_ => None,
})
}
pub fn one_edge(self, graph: Arc<Graph>) -> Result<InMemoryEdge, crate::error::TraversalError> {
let ids: Vec<_> = self
.execute()
.filter_map(|t| match t.value {
Value::Edge(id) => Some(id),
_ => None,
})
.take(2)
.collect();
match ids.len() {
1 => Ok(GraphEdge::new(ids[0], graph)),
n => Err(crate::error::TraversalError::NotOne(n)),
}
}
pub fn vertex_iter(self, graph: Arc<Graph>) -> impl Iterator<Item = InMemoryVertex> + 'g {
self.execute().filter_map(move |t| match t.value {
Value::Vertex(id) => Some(GraphVertex::new(id, Arc::clone(&graph))),
_ => None,
})
}
pub fn edge_iter(self, graph: Arc<Graph>) -> impl Iterator<Item = InMemoryEdge> + 'g {
self.execute().filter_map(move |t| match t.value {
Value::Edge(id) => Some(GraphEdge::new(id, Arc::clone(&graph))),
_ => None,
})
}
}
pub struct BranchBuilder<'g, In> {
snapshot: &'g dyn SnapshotLike,
source: Option<TraversalSource>,
steps: Vec<Box<dyn DynStep>>,
branch_traversal: Traversal<Value, Value>,
options: std::collections::HashMap<
crate::traversal::branch::OptionKeyWrapper,
Traversal<Value, Value>,
>,
none_branch: Option<Traversal<Value, Value>>,
track_paths: bool,
_phantom: PhantomData<In>,
}
impl<'g, In> BranchBuilder<'g, In> {
pub(crate) fn new(
snapshot: &'g dyn SnapshotLike,
source: Option<TraversalSource>,
steps: Vec<Box<dyn DynStep>>,
branch_traversal: Traversal<Value, Value>,
track_paths: bool,
) -> Self {
Self {
snapshot,
source,
steps,
branch_traversal,
options: std::collections::HashMap::new(),
none_branch: None,
track_paths,
_phantom: PhantomData,
}
}
pub fn option<K: Into<crate::traversal::branch::OptionKey>>(
mut self,
key: K,
branch: Traversal<Value, Value>,
) -> Self {
use crate::traversal::branch::{OptionKey, OptionKeyWrapper};
let key = key.into();
match key {
OptionKey::None => {
self.none_branch = Some(branch);
}
OptionKey::Value(_) => {
self.options.insert(OptionKeyWrapper(key), branch);
}
}
self
}
pub fn option_none(mut self, branch: Traversal<Value, Value>) -> Self {
self.none_branch = Some(branch);
self
}
fn finalize(mut self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::branch::BranchStep;
let mut step = BranchStep::new(self.branch_traversal);
step.options = self.options;
step.none_branch = self.none_branch;
self.steps.push(Box::new(step));
let traversal = Traversal {
steps: self.steps,
source: self.source,
_phantom: PhantomData,
};
let mut bound = BoundTraversal::new(self.snapshot, traversal);
if self.track_paths {
bound = bound.with_path();
}
bound
}
pub fn to_list(self) -> Vec<Value> {
self.finalize().to_list()
}
pub fn count(self) -> u64 {
self.finalize().count()
}
pub fn next(self) -> Option<Value> {
self.finalize().next()
}
pub fn one(self) -> Result<Value, crate::error::TraversalError> {
self.finalize().one()
}
pub fn iterate(self) {
self.finalize().iterate()
}
pub fn has_next(self) -> bool {
self.finalize().has_next()
}
pub fn out(self) -> BoundTraversal<'g, In, Value> {
self.finalize().out()
}
pub fn in_(self) -> BoundTraversal<'g, In, Value> {
self.finalize().in_()
}
pub fn both(self) -> BoundTraversal<'g, In, Value> {
self.finalize().both()
}
pub fn out_e(self) -> BoundTraversal<'g, In, Value> {
self.finalize().out_e()
}
pub fn in_e(self) -> BoundTraversal<'g, In, Value> {
self.finalize().in_e()
}
pub fn both_e(self) -> BoundTraversal<'g, In, Value> {
self.finalize().both_e()
}
pub fn has_label(self, label: &str) -> BoundTraversal<'g, In, Value> {
self.finalize().has_label(label)
}
pub fn has_label_any<I, S>(self, labels: I) -> BoundTraversal<'g, In, Value>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.finalize().has_label_any(labels)
}
pub fn has(self, key: &str) -> BoundTraversal<'g, In, Value> {
self.finalize().has(key)
}
pub fn has_value(self, key: &str, value: impl Into<Value>) -> BoundTraversal<'g, In, Value> {
self.finalize().has_value(key, value)
}
pub fn has_where(
self,
key: impl Into<String>,
predicate: impl crate::traversal::predicate::Predicate + 'static,
) -> BoundTraversal<'g, In, Value> {
self.finalize().has_where(key, predicate)
}
pub fn dedup(self) -> BoundTraversal<'g, In, Value> {
self.finalize().dedup()
}
pub fn limit(self, n: usize) -> BoundTraversal<'g, In, Value> {
self.finalize().limit(n)
}
pub fn values(self, key: &str) -> BoundTraversal<'g, In, Value> {
self.finalize().values(key)
}
pub fn id(self) -> BoundTraversal<'g, In, Value> {
self.finalize().id()
}
pub fn label(self) -> BoundTraversal<'g, In, Value> {
self.finalize().label()
}
pub fn as_(self, label: &str) -> BoundTraversal<'g, In, Value> {
self.finalize().as_(label)
}
pub fn select_one(self, label: &str) -> BoundTraversal<'g, In, Value> {
self.finalize().select_one(label)
}
pub fn path(self) -> BoundTraversal<'g, In, Value> {
self.finalize().path()
}
pub fn constant(self, value: impl Into<Value>) -> BoundTraversal<'g, In, Value> {
self.finalize().constant(value)
}
}
pub struct AddEdgeBuilder<'g> {
snapshot: &'g dyn SnapshotLike,
label: String,
from: Option<crate::traversal::EdgeEndpoint>,
to: Option<crate::traversal::EdgeEndpoint>,
properties: std::collections::HashMap<String, Value>,
}
impl<'g> AddEdgeBuilder<'g> {
fn new(snapshot: &'g dyn SnapshotLike, label: String) -> Self {
Self {
snapshot,
label,
from: None,
to: None,
properties: std::collections::HashMap::new(),
}
}
pub fn from_vertex(mut self, id: VertexId) -> Self {
self.from = Some(crate::traversal::EdgeEndpoint::VertexId(id));
self
}
pub fn from_label(mut self, label: impl Into<String>) -> Self {
self.from = Some(crate::traversal::EdgeEndpoint::StepLabel(label.into()));
self
}
pub fn to_vertex(mut self, id: VertexId) -> Self {
self.to = Some(crate::traversal::EdgeEndpoint::VertexId(id));
self
}
pub fn to_label(mut self, label: impl Into<String>) -> Self {
self.to = Some(crate::traversal::EdgeEndpoint::StepLabel(label.into()));
self
}
pub fn property(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
self.properties.insert(key.into(), value.into());
self
}
pub fn build(self) -> BoundTraversal<'g, (), Value> {
use crate::traversal::mutation::AddEStep;
let mut step = AddEStep::new(&self.label);
if let Some(from) = self.from {
step = match from {
crate::traversal::EdgeEndpoint::VertexId(id) => step.from_vertex(id),
crate::traversal::EdgeEndpoint::StepLabel(label) => step.from_label(label),
crate::traversal::EdgeEndpoint::Traverser => step.from_traverser(),
};
}
if let Some(to) = self.to {
step = match to {
crate::traversal::EdgeEndpoint::VertexId(id) => step.to_vertex(id),
crate::traversal::EdgeEndpoint::StepLabel(label) => step.to_label(label),
crate::traversal::EdgeEndpoint::Traverser => step.to_traverser(),
};
}
for (key, value) in self.properties {
step = step.property(key, value);
}
let mut traversal = Traversal::<(), Value>::with_source(TraversalSource::Inject(vec![]));
traversal = traversal.add_step(step);
BoundTraversal::new(self.snapshot, traversal)
}
pub fn next(self) -> Option<Value> {
self.build().next()
}
pub fn to_list(self) -> Vec<Value> {
self.build().to_list()
}
pub fn iterate(self) {
self.build().iterate()
}
}
pub struct BoundAddEdgeBuilder<'g, In> {
snapshot: &'g dyn SnapshotLike,
traversal: Traversal<In, Value>,
track_paths: bool,
label: String,
from: Option<crate::traversal::EdgeEndpoint>,
to: Option<crate::traversal::EdgeEndpoint>,
properties: std::collections::HashMap<String, Value>,
}
impl<'g, In> BoundAddEdgeBuilder<'g, In> {
fn from_traversal(bound: BoundTraversal<'g, In, Value>, label: String) -> Self {
Self {
snapshot: bound.snapshot,
traversal: bound.traversal,
track_paths: bound.track_paths,
label,
from: Some(crate::traversal::EdgeEndpoint::Traverser), to: None,
properties: std::collections::HashMap::new(),
}
}
pub fn from_vertex(mut self, id: VertexId) -> Self {
self.from = Some(crate::traversal::EdgeEndpoint::VertexId(id));
self
}
pub fn from_label(mut self, label: impl Into<String>) -> Self {
self.from = Some(crate::traversal::EdgeEndpoint::StepLabel(label.into()));
self
}
pub fn to_vertex(mut self, id: VertexId) -> Self {
self.to = Some(crate::traversal::EdgeEndpoint::VertexId(id));
self
}
pub fn to_label(mut self, label: impl Into<String>) -> Self {
self.to = Some(crate::traversal::EdgeEndpoint::StepLabel(label.into()));
self
}
pub fn property(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
self.properties.insert(key.into(), value.into());
self
}
pub fn build(self) -> BoundTraversal<'g, In, Value> {
use crate::traversal::mutation::AddEStep;
let mut step = AddEStep::new(&self.label);
if let Some(from) = self.from {
step = match from {
crate::traversal::EdgeEndpoint::VertexId(id) => step.from_vertex(id),
crate::traversal::EdgeEndpoint::StepLabel(label) => step.from_label(label),
crate::traversal::EdgeEndpoint::Traverser => step.from_traverser(),
};
}
if let Some(to) = self.to {
step = match to {
crate::traversal::EdgeEndpoint::VertexId(id) => step.to_vertex(id),
crate::traversal::EdgeEndpoint::StepLabel(label) => step.to_label(label),
crate::traversal::EdgeEndpoint::Traverser => step.to_traverser(),
};
}
for (key, value) in self.properties {
step = step.property(key, value);
}
BoundTraversal {
snapshot: self.snapshot,
traversal: self.traversal.add_step(step),
track_paths: self.track_paths,
graph: None,
}
}
pub fn next(self) -> Option<Value> {
self.build().next()
}
pub fn to_list(self) -> Vec<Value> {
self.build().to_list()
}
pub fn iterate(self) {
self.build().iterate()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::Graph;
use std::collections::HashMap;
fn create_empty_graph() -> Graph {
Graph::new()
}
fn create_test_graph() -> Graph {
let graph = Graph::new();
let v1 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Alice".to_string()));
props.insert("age".to_string(), Value::Int(30));
props
});
let v2 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(25));
props
});
let v3 = graph.add_vertex("software", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Graph DB".to_string()));
props.insert("version".to_string(), Value::Float(1.0));
props
});
let v4 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Charlie".to_string()));
props.insert("age".to_string(), Value::Int(35));
props
});
graph.add_edge(v1, v2, "knows", HashMap::new()).unwrap();
graph.add_edge(v2, v3, "uses", HashMap::new()).unwrap();
graph.add_edge(v1, v3, "uses", HashMap::new()).unwrap();
graph.add_edge(v2, v4, "knows", HashMap::new()).unwrap();
graph
.add_edge(v4, v1, "knows", {
let mut props = HashMap::new();
props.insert("since".to_string(), Value::Int(2020));
props
})
.unwrap();
graph
}
mod graph_traversal_source_tests {
use super::*;
#[test]
fn from_snapshot_creates_source() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let _ = g.storage();
let _ = g.interner();
}
#[test]
fn v_creates_all_vertices_traversal() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v().to_list();
assert_eq!(results.len(), 4);
for value in &results {
assert!(value.is_vertex());
}
}
#[test]
fn v_ids_creates_specific_vertices_traversal() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v_ids([VertexId(0), VertexId(2)]).to_list();
assert_eq!(results.len(), 2);
let ids: Vec<VertexId> = results.iter().filter_map(|v| v.as_vertex_id()).collect();
assert!(ids.contains(&VertexId(0)));
assert!(ids.contains(&VertexId(2)));
}
#[test]
fn v_ids_filters_nonexistent() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v_ids([VertexId(0), VertexId(999)]).to_list();
assert_eq!(results.len(), 1);
assert_eq!(results[0].as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn e_creates_all_edges_traversal() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.e().to_list();
assert_eq!(results.len(), 5);
for value in &results {
assert!(value.is_edge());
}
}
#[test]
fn e_ids_creates_specific_edges_traversal() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.e_ids([EdgeId(0), EdgeId(1)]).to_list();
assert_eq!(results.len(), 2);
let ids: Vec<EdgeId> = results.iter().filter_map(|v| v.as_edge_id()).collect();
assert!(ids.contains(&EdgeId(0)));
assert!(ids.contains(&EdgeId(1)));
}
#[test]
fn e_ids_filters_nonexistent() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.e_ids([EdgeId(0), EdgeId(999)]).to_list();
assert_eq!(results.len(), 1);
assert_eq!(results[0].as_edge_id(), Some(EdgeId(0)));
}
#[test]
fn inject_creates_value_traversal() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.inject([1i64, 2i64, 3i64]).to_list();
assert_eq!(results.len(), 3);
assert_eq!(results[0], Value::Int(1));
assert_eq!(results[1], Value::Int(2));
assert_eq!(results[2], Value::Int(3));
}
#[test]
fn inject_with_mixed_types() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let values: Vec<Value> = vec![
Value::Int(1),
Value::String("hello".to_string()),
Value::Bool(true),
];
let results = g.inject(values).to_list();
assert_eq!(results.len(), 3);
assert_eq!(results[0], Value::Int(1));
assert_eq!(results[1], Value::String("hello".to_string()));
assert_eq!(results[2], Value::Bool(true));
}
#[test]
fn empty_graph_returns_empty() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
assert_eq!(g.v().count(), 0);
assert_eq!(g.e().count(), 0);
}
}
mod bound_traversal_tests {
use super::*;
use crate::traversal::step::IdentityStep;
#[test]
fn add_step_chains_correctly() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversal: BoundTraversal<'_, (), Value> = g.v().add_step(IdentityStep::new());
assert_eq!(traversal.step_count(), 1);
assert_eq!(traversal.step_names(), vec!["identity"]);
let results = traversal.to_list();
assert_eq!(results.len(), 4);
}
#[test]
fn append_merges_traversals() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let anon: Traversal<Value, Value> =
Traversal::<Value, Value>::new().add_step(IdentityStep::new());
let traversal = g.v().append(anon);
assert_eq!(traversal.step_count(), 1);
let results = traversal.to_list();
assert_eq!(results.len(), 4);
}
#[test]
fn clone_creates_independent_copy() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let t1 = g.v();
let t2 = t1.clone();
let results1 = t1.to_list();
let results2 = t2.to_list();
assert_eq!(results1.len(), results2.len());
}
#[test]
fn debug_format() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversal: BoundTraversal<'_, (), Value> = g.v().add_step(IdentityStep::new());
let debug_str = format!("{:?}", traversal);
assert!(debug_str.contains("BoundTraversal"));
assert!(debug_str.contains("step_count"));
}
}
mod terminal_step_tests {
use super::*;
#[test]
fn to_list_collects_all() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v().to_list();
assert_eq!(results.len(), 4);
}
#[test]
fn to_set_deduplicates() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v().to_set();
assert_eq!(results.len(), 4);
}
#[test]
fn next_returns_first() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.v().next();
assert!(result.is_some());
assert!(result.unwrap().is_vertex());
}
#[test]
fn next_returns_none_for_empty() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.v().next();
assert!(result.is_none());
}
#[test]
fn has_next_returns_true_for_nonempty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
assert!(g.v().has_next());
}
#[test]
fn has_next_returns_false_for_empty() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
assert!(!g.v().has_next());
}
#[test]
fn one_returns_single_value() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.v_ids([VertexId(0)]).one();
assert!(result.is_ok());
assert_eq!(result.unwrap().as_vertex_id(), Some(VertexId(0)));
}
#[test]
fn one_errors_on_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.v_ids([VertexId(999)]).one();
assert!(result.is_err());
match result.unwrap_err() {
crate::error::TraversalError::NotOne(n) => assert_eq!(n, 0),
_ => panic!("Expected NotOne error"),
}
}
#[test]
fn one_errors_on_multiple() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.v().one();
assert!(result.is_err());
match result.unwrap_err() {
crate::error::TraversalError::NotOne(n) => assert_eq!(n, 2),
_ => panic!("Expected NotOne error"),
}
}
#[test]
fn iterate_consumes_without_collecting() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
g.v().iterate();
}
#[test]
fn count_returns_correct_value() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
assert_eq!(g.v().count(), 4);
assert_eq!(g.e().count(), 5);
}
#[test]
fn count_returns_zero_for_empty() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
assert_eq!(g.v().count(), 0);
}
#[test]
fn take_returns_first_n() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v().take(2);
assert_eq!(results.len(), 2);
}
#[test]
fn take_returns_all_if_less_than_n() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v().take(100);
assert_eq!(results.len(), 4);
}
#[test]
fn iter_produces_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let values: Vec<Value> = g.v().iter().collect();
assert_eq!(values.len(), 4);
}
#[test]
fn traversers_produces_traversers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversers: Vec<Traverser> = g.v().traversers().collect();
assert_eq!(traversers.len(), 4);
for t in &traversers {
assert!(t.is_vertex());
}
}
#[test]
fn fold_accumulates() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let count = g
.inject([1i64, 2i64, 3i64])
.fold(0i64, |acc, v| acc + v.as_i64().unwrap_or(0));
assert_eq!(count, 6);
}
#[test]
fn sum_adds_integers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.inject([1i64, 2i64, 3i64]).sum();
assert_eq!(result, Value::Int(6));
}
#[test]
fn sum_handles_floats() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let values: Vec<Value> = vec![Value::Int(1), Value::Float(2.5), Value::Int(3)];
let result = g.inject(values).sum();
assert!(matches!(result, Value::Float(f) if (f - 6.5).abs() < 1e-10));
}
#[test]
fn sum_empty_returns_zero() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let values: Vec<Value> = vec![];
let result = g.inject(values).sum();
assert_eq!(result, Value::Int(0));
}
#[test]
fn min_finds_minimum() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.inject([3i64, 1i64, 2i64]).min();
assert_eq!(result, Some(Value::Int(1)));
}
#[test]
fn min_empty_returns_none() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let values: Vec<Value> = vec![];
let result = g.inject(values).min();
assert!(result.is_none());
}
#[test]
fn max_finds_maximum() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.inject([3i64, 1i64, 2i64]).max();
assert_eq!(result, Some(Value::Int(3)));
}
#[test]
fn max_empty_returns_none() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let values: Vec<Value> = vec![];
let result = g.inject(values).max();
assert!(result.is_none());
}
}
mod traversal_executor_tests {
use super::*;
#[test]
fn executor_iterates_correctly() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let executor = g.v().execute();
assert_eq!(executor.len(), 4);
assert!(!executor.is_empty());
let values: Vec<_> = executor.collect();
assert_eq!(values.len(), 4);
}
#[test]
fn executor_empty_for_empty_graph() {
let graph = create_empty_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let executor = g.v().execute();
assert_eq!(executor.len(), 0);
assert!(executor.is_empty());
}
#[test]
fn executor_size_hint_is_exact() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let executor = g.v().execute();
let (lower, upper) = executor.size_hint();
assert_eq!(lower, 4);
assert_eq!(upper, Some(4));
}
}
mod has_label_step_integration_tests {
use super::*;
#[test]
fn has_label_filters_vertices() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let people = g.v().has_label("person").to_list();
assert_eq!(people.len(), 3);
for v in &people {
assert!(v.is_vertex());
}
}
#[test]
fn has_label_filters_to_software() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let software = g.v().has_label("software").to_list();
assert_eq!(software.len(), 1);
}
#[test]
fn has_label_returns_empty_for_nonexistent_label() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let unknown = g.v().has_label("unknown").to_list();
assert!(unknown.is_empty());
}
#[test]
fn has_label_filters_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let knows_edges = g.e().has_label("knows").to_list();
assert_eq!(knows_edges.len(), 3);
for e in &knows_edges {
assert!(e.is_edge());
}
}
#[test]
fn has_label_any_filters_multiple_labels() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let entities = g.v().has_label_any(["person", "software"]).to_list();
assert_eq!(entities.len(), 4);
}
#[test]
fn has_label_any_works_with_edges() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let edges = g.e().has_label_any(["knows", "uses"]).to_list();
assert_eq!(edges.len(), 5);
}
#[test]
fn has_label_can_be_chained() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g.v().has_label("person").has_label("software").to_list();
assert!(result.is_empty());
}
#[test]
fn has_label_count_works() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let count = g.v().has_label("person").count();
assert_eq!(count, 3);
}
#[test]
fn has_label_with_specific_vertex_ids() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let result = g
.v_ids([VertexId(0), VertexId(2)])
.has_label("person")
.to_list();
assert_eq!(result.len(), 1);
assert_eq!(result[0].as_vertex_id(), Some(VertexId(0)));
}
}
mod path_tracking_tests {
use super::*;
#[test]
fn with_path_enables_path_tracking() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversers = g.v_ids([VertexId(0)]).with_path().out().traversers();
for t in traversers {
assert_eq!(t.path.len(), 2);
}
}
#[test]
fn path_not_tracked_by_default() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversers = g.v_ids([VertexId(0)]).out().traversers();
for t in traversers {
assert!(t.path.is_empty());
}
}
#[test]
fn path_step_returns_path_list() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let paths = g.v_ids([VertexId(0)]).with_path().out().path().to_list();
assert_eq!(paths.len(), 2);
for path in &paths {
assert!(matches!(path, Value::List(_)));
if let Value::List(elements) = path {
assert_eq!(elements.len(), 2);
}
}
}
#[test]
fn path_tracks_multi_hop_traversal() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversers = g
.v_ids([VertexId(0)])
.with_path()
.out_labels(&["knows"])
.out()
.traversers();
for t in traversers {
assert_eq!(t.path.len(), 3);
}
}
#[test]
fn as_step_labels_current_position() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversers = g
.v_ids([VertexId(0)])
.as_("a")
.out_labels(&["knows"])
.traversers();
for t in traversers {
assert!(t.path.has_label("a"));
}
}
#[test]
fn select_single_label_returns_value() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_ids([VertexId(0)])
.as_("a")
.out_labels(&["knows"])
.select_one("a")
.to_list();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Vertex(VertexId(0)));
}
#[test]
fn select_multiple_labels_returns_map() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_ids([VertexId(0)])
.as_("a")
.out_labels(&["knows"])
.as_("b")
.select(&["a", "b"])
.to_list();
assert_eq!(results.len(), 1);
match &results[0] {
Value::Map(map) => {
assert!(map.contains_key("a"));
assert!(map.contains_key("b"));
assert_eq!(map.get("a"), Some(&Value::Vertex(VertexId(0))));
assert_eq!(map.get("b"), Some(&Value::Vertex(VertexId(1))));
}
_ => panic!("Expected Value::Map"),
}
}
#[test]
fn select_missing_label_filters_out() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_ids([VertexId(0)])
.out()
.select_one("nonexistent")
.to_list();
assert!(results.is_empty());
}
#[test]
fn as_step_works_without_path_tracking() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_ids([VertexId(0)])
.as_("start")
.out()
.select_one("start")
.to_list();
assert_eq!(results.len(), 2); for result in &results {
assert_eq!(result, &Value::Vertex(VertexId(0)));
}
}
#[test]
fn path_with_edge_steps() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversers = g
.v_ids([VertexId(0)])
.with_path()
.out_e()
.in_v()
.traversers();
for t in traversers {
assert_eq!(t.path.len(), 3);
}
}
#[test]
fn with_path_is_opt_in() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let traversal = g.v();
assert!(!traversal.is_tracking_paths());
let traversal = g.v().with_path();
assert!(traversal.is_tracking_paths());
}
}
mod index_aware_source_tests {
use super::*;
use crate::index::IndexBuilder;
use std::ops::Bound;
fn create_indexed_graph() -> Graph {
let graph = Graph::new();
let _v1 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Alice".to_string()));
props.insert("age".to_string(), Value::Int(25));
props
});
let _v2 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(30));
props
});
let _v3 = graph.add_vertex("person", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Charlie".to_string()));
props.insert("age".to_string(), Value::Int(35));
props
});
let _v4 = graph.add_vertex("company", {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("TechCorp".to_string()));
props.insert("size".to_string(), Value::Int(100));
props
});
let index_spec = IndexBuilder::vertex()
.label("person")
.property("age")
.build()
.unwrap();
graph.create_index(index_spec).unwrap();
graph
}
fn create_graph_with_edge_index() -> Graph {
let graph = Graph::new();
let v1 = graph.add_vertex("person", HashMap::new());
let v2 = graph.add_vertex("person", HashMap::new());
let v3 = graph.add_vertex("person", HashMap::new());
graph
.add_edge(v1, v2, "knows", {
let mut props = HashMap::new();
props.insert("weight".to_string(), Value::Float(1.0));
props
})
.unwrap();
graph
.add_edge(v2, v3, "knows", {
let mut props = HashMap::new();
props.insert("weight".to_string(), Value::Float(2.0));
props
})
.unwrap();
graph
.add_edge(v1, v3, "knows", {
let mut props = HashMap::new();
props.insert("weight".to_string(), Value::Float(1.0));
props
})
.unwrap();
let index_spec = IndexBuilder::edge()
.label("knows")
.property("weight")
.build()
.unwrap();
graph.create_index(index_spec).unwrap();
graph
}
#[test]
fn v_by_property_finds_exact_match() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v_by_property(Some("person"), "age", 30i64).to_list();
assert_eq!(results.len(), 1);
assert!(results[0].is_vertex());
}
#[test]
fn v_by_property_returns_empty_when_no_match() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v_by_property(Some("person"), "age", 100i64).to_list();
assert!(results.is_empty());
}
#[test]
fn v_by_property_with_no_label_searches_all() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v_by_property(None, "age", 30i64).to_list();
assert_eq!(results.len(), 1);
}
#[test]
fn v_by_property_works_without_index() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.v_by_property(Some("company"), "size", 100i64).to_list();
assert_eq!(results.len(), 1);
}
#[test]
fn v_by_property_range_finds_inclusive_range() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_by_property_range(
Some("person"),
"age",
Bound::Included(&Value::Int(25)),
Bound::Included(&Value::Int(30)),
)
.to_list();
assert_eq!(results.len(), 2);
}
#[test]
fn v_by_property_range_with_exclusive_bounds() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_by_property_range(
Some("person"),
"age",
Bound::Excluded(&Value::Int(25)),
Bound::Excluded(&Value::Int(35)),
)
.to_list();
assert_eq!(results.len(), 1);
}
#[test]
fn v_by_property_range_with_unbounded_end() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_by_property_range(
Some("person"),
"age",
Bound::Included(&Value::Int(30)),
Bound::Unbounded,
)
.to_list();
assert_eq!(results.len(), 2);
}
#[test]
fn v_by_property_range_with_unbounded_start() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_by_property_range(
Some("person"),
"age",
Bound::Unbounded,
Bound::Included(&Value::Int(30)),
)
.to_list();
assert_eq!(results.len(), 2);
}
#[test]
fn v_by_property_range_returns_empty_for_no_match() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g
.v_by_property_range(
Some("person"),
"age",
Bound::Included(&Value::Int(100)),
Bound::Included(&Value::Int(200)),
)
.to_list();
assert!(results.is_empty());
}
#[test]
fn e_by_property_finds_exact_match() {
let graph = create_graph_with_edge_index();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.e_by_property(Some("knows"), "weight", 1.0f64).to_list();
assert_eq!(results.len(), 2);
for result in &results {
assert!(result.is_edge());
}
}
#[test]
fn e_by_property_returns_empty_when_no_match() {
let graph = create_graph_with_edge_index();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.e_by_property(Some("knows"), "weight", 99.0f64).to_list();
assert!(results.is_empty());
}
#[test]
fn e_by_property_with_no_label_searches_all() {
let graph = create_graph_with_edge_index();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let results = g.e_by_property(None, "weight", 2.0f64).to_list();
assert_eq!(results.len(), 1);
}
#[test]
fn v_by_property_can_chain_steps() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let names = g
.v_by_property(Some("person"), "age", 30i64)
.values("name")
.to_list();
assert_eq!(names.len(), 1);
assert_eq!(names[0], Value::String("Bob".to_string()));
}
#[test]
fn v_by_property_range_can_chain_steps() {
let graph = create_indexed_graph();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let count = g
.v_by_property_range(
Some("person"),
"age",
Bound::Included(&Value::Int(25)),
Bound::Included(&Value::Int(35)),
)
.count();
assert_eq!(count, 3); }
#[test]
fn e_by_property_can_chain_navigation() {
let graph = create_graph_with_edge_index();
let snapshot = graph.snapshot();
let g = GraphTraversalSource::from_snapshot(&snapshot);
let targets = g
.e_by_property(Some("knows"), "weight", 1.0f64)
.in_v()
.to_list();
assert_eq!(targets.len(), 2);
for target in &targets {
assert!(target.is_vertex());
}
}
}
mod streaming_tests {
use super::*;
#[test]
fn iter_matches_eager_to_list() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<_> = g.v().to_list();
let g = snapshot.gremlin();
let streaming: Vec<_> = g.v().iter().collect();
assert_eq!(eager.len(), streaming.len());
for value in &eager {
assert!(streaming.contains(value));
}
}
#[test]
fn iter_with_filter_matches_eager() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<_> = g.v().has_label("person").to_list();
let g = snapshot.gremlin();
let streaming: Vec<_> = g.v().has_label("person").iter().collect();
assert_eq!(eager.len(), streaming.len());
}
#[test]
fn iter_with_navigation_matches_eager() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<_> = g.v().out().to_list();
let g = snapshot.gremlin();
let streaming: Vec<_> = g.v().out().iter().collect();
assert_eq!(eager.len(), streaming.len());
}
#[test]
fn iter_early_termination() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let results: Vec<_> = g.v().iter().take(2).collect();
assert_eq!(results.len(), 2);
}
#[test]
fn iter_next_returns_single() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let first = g.v().iter().next();
assert!(first.is_some());
assert!(first.unwrap().is_vertex());
}
#[test]
fn streaming_execute_provides_traversers() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let mut executor = g.v().streaming_execute();
let first = executor.next();
assert!(first.is_some());
let traverser = first.unwrap();
assert!(traverser.value.is_vertex());
}
#[test]
fn traversers_includes_path() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let results: Vec<_> = g.v().with_path().traversers().collect();
assert_eq!(results.len(), 4);
for t in &results {
assert_eq!(t.path.len(), 1);
}
}
#[test]
fn iter_multi_hop_navigation() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<_> = g.v().out().out().to_list();
let g = snapshot.gremlin();
let streaming: Vec<_> = g.v().out().out().iter().collect();
assert_eq!(eager.len(), streaming.len());
}
#[test]
fn iter_with_values_step() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<_> = g.v().has_label("person").values("name").to_list();
let g = snapshot.gremlin();
let streaming: Vec<_> = g.v().has_label("person").values("name").iter().collect();
assert_eq!(eager.len(), streaming.len());
for value in &streaming {
assert!(matches!(value, Value::String(_)));
}
}
#[test]
fn iter_inject_source() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let eager: Vec<_> = g
.inject([Value::Int(1), Value::Int(2), Value::Int(3)])
.to_list();
let g = snapshot.gremlin();
let streaming: Vec<_> = g
.inject([Value::Int(1), Value::Int(2), Value::Int(3)])
.iter()
.collect();
assert_eq!(eager, streaming);
}
#[test]
fn iter_empty_result() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let results: Vec<_> = g.v().has_label("nonexistent").iter().collect();
assert!(results.is_empty());
}
#[test]
fn iter_count_via_iterator() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let g = snapshot.gremlin();
let count = g.v().iter().count();
assert_eq!(count, 4);
}
}
}