use crate::error::{TraverserError, TraverserResult};
use crate::graph::GraphStorage;
use crate::traversal::{Traverser, TraverserContext};
use crate::value::{ElementProperty, TraverserPath, TraverserValue};
pub(crate) enum LocalShape<S: GraphStorage> {
List(Vec<TraverserValue<S>>),
Map(Vec<(TraverserValue<S>, TraverserValue<S>)>),
Path(TraverserPath<S>),
Scalar(TraverserValue<S>),
}
pub(crate) fn stored_value<'c, S: GraphStorage>(
context: &'c TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> Option<&'c ElementProperty> {
match value {
TraverserValue::VertexProperty(property, node, _) => context
.storage
.vertex_properties(*node)
.and_then(|properties| properties.get(property)),
TraverserValue::EdgeProperty(property, edge, _) => context
.storage
.edge_properties(*edge)
.and_then(|properties| properties.get(property)),
TraverserValue::Computed(_, _)
| TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::Path(_)
| TraverserValue::Record { .. }
| TraverserValue::Object(_)
| TraverserValue::PathProperty(_)
| TraverserValue::Entry(_)
| TraverserValue::Array(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => None,
}
}
fn classify_property<S: GraphStorage>(
property: ElementProperty,
fallback: impl FnOnce(ElementProperty) -> TraverserValue<S>,
) -> LocalShape<S> {
match property {
ElementProperty::Array(items) => {
LocalShape::List(items.into_iter().map(TraverserValue::from).collect())
}
ElementProperty::Object(entries) => LocalShape::Map(
entries
.into_iter()
.map(|(key, value)| {
(
TraverserValue::Computed(ElementProperty::String(key), None),
TraverserValue::from(value),
)
})
.collect(),
),
ElementProperty::Map(entries) => LocalShape::Map(
entries
.into_iter()
.map(|(key, value)| (TraverserValue::from(key), TraverserValue::from(value)))
.collect(),
),
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Compressed(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_)
| ElementProperty::Vertex(_)
| ElementProperty::Edge(_) => LocalShape::Scalar(fallback(property.into_logical())),
}
}
pub(crate) fn classify<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: TraverserValue<S>,
) -> TraverserResult<LocalShape<S>> {
Ok(match value {
TraverserValue::Array(items) => LocalShape::List(items),
TraverserValue::Object(map) => LocalShape::Map(map.into_iter().collect()),
TraverserValue::Record { keys, values } => LocalShape::Map(
keys.iter()
.cloned()
.map(|key| TraverserValue::Computed(ElementProperty::String(key), None))
.zip(values)
.collect(),
),
TraverserValue::Path(path) => LocalShape::Path(path),
TraverserValue::Computed(property, node) => classify_property(property, |property| {
TraverserValue::Computed(property, node)
}),
TraverserValue::VertexProperty(_, _, _) | TraverserValue::EdgeProperty(_, _, _) => {
match stored_value(context, &value) {
Some(
stored @ (ElementProperty::Array(_)
| ElementProperty::Object(_)
| ElementProperty::Map(_)),
) => {
classify_property(stored.clone(), |property| {
TraverserValue::Computed(property, None)
})
}
Some(_) | None => LocalShape::Scalar(value),
}
}
TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::PathProperty(_)
| TraverserValue::Entry(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => LocalShape::Scalar(value),
})
}
pub(crate) fn local_len<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> TraverserResult<usize> {
fn property_len(property: &ElementProperty) -> usize {
match property {
ElementProperty::Array(items) => items.len(),
ElementProperty::Object(entries) => entries.len(),
ElementProperty::Map(entries) => entries.len(),
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Compressed(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_)
| ElementProperty::Vertex(_)
| ElementProperty::Edge(_) => 1,
}
}
Ok(match value {
TraverserValue::Array(items) => items.len(),
TraverserValue::Object(map) => map.len(),
TraverserValue::Record { values, .. } => values.len(),
TraverserValue::Path(path) => path.len(),
TraverserValue::Computed(property, _) => property_len(property),
TraverserValue::VertexProperty(_, _, _) | TraverserValue::EdgeProperty(_, _, _) => {
stored_value(context, value).map_or(1, property_len)
}
TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::PathProperty(_)
| TraverserValue::Entry(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => 1,
})
}
fn keep_vec<T>(items: &mut Vec<T>, start: usize, end: usize) {
items.truncate(end);
items.drain(..start.min(items.len()));
}
fn keep_map<K, V, H>(entries: &mut indexmap::IndexMap<K, V, H>, start: usize, end: usize) {
entries.truncate(end);
entries.drain(..start.min(entries.len()));
}
fn slice_stored<S: GraphStorage>(
stored: &ElementProperty,
bounds: impl FnOnce(usize) -> (usize, usize),
) -> Option<TraverserValue<S>> {
match stored {
ElementProperty::Array(items) => {
let (start, end) = bounds(items.len());
Some(TraverserValue::Computed(
ElementProperty::Array(items[start..end].to_vec()),
None,
))
}
ElementProperty::Object(entries) => {
let (start, end) = bounds(entries.len());
let sliced: crate::value::PropertyMap = entries
.iter()
.skip(start)
.take(end - start)
.map(|(key, value)| (key.clone(), value.to_logical()))
.collect();
Some(TraverserValue::Computed(
ElementProperty::Object(Box::new(sliced)),
None,
))
}
ElementProperty::Map(entries) => {
let (start, end) = bounds(entries.len());
let sliced = ElementProperty::from_entries(
entries
.iter()
.skip(start)
.take(end - start)
.map(|(key, value)| (key.clone(), value.clone())),
);
Some(TraverserValue::Computed(sliced, None))
}
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Compressed(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_)
| ElementProperty::Vertex(_)
| ElementProperty::Edge(_) => None,
}
}
pub(crate) enum LocalSlice<S: GraphStorage> {
Value(TraverserValue<S>),
Path,
}
pub(crate) fn slice_local<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: TraverserValue<S>,
bounds: impl FnOnce(usize) -> (usize, usize),
) -> LocalSlice<S> {
LocalSlice::Value(match value {
TraverserValue::Array(mut items) => {
let (start, end) = bounds(items.len());
keep_vec(&mut items, start, end);
TraverserValue::Array(items)
}
TraverserValue::Object(mut map) => {
let (start, end) = bounds(map.len());
keep_map(&mut map.0, start, end);
TraverserValue::Object(map)
}
TraverserValue::Record { keys, mut values } => {
let len = values.len();
let (start, end) = bounds(len);
if start == 0 && end == len {
TraverserValue::Record { keys, values }
} else {
keep_vec(&mut values, start, end);
let keys = std::sync::Arc::new(
keys.iter()
.skip(start)
.take(end - start)
.cloned()
.collect::<Vec<_>>(),
);
TraverserValue::Record { keys, values }
}
}
TraverserValue::Computed(ElementProperty::Array(mut items), node) => {
let (start, end) = bounds(items.len());
keep_vec(&mut items, start, end);
TraverserValue::Computed(ElementProperty::Array(items), node)
}
TraverserValue::Computed(ElementProperty::Object(mut entries), node) => {
let (start, end) = bounds(entries.len());
keep_map(&mut entries.0, start, end);
TraverserValue::Computed(ElementProperty::Object(entries), node)
}
TraverserValue::Path(_) => return LocalSlice::Path,
TraverserValue::VertexProperty(_, _, _) | TraverserValue::EdgeProperty(_, _, _) => {
match stored_value(context, &value).and_then(|stored| slice_stored(stored, bounds)) {
Some(sliced) => sliced,
None => value,
}
}
TraverserValue::Computed(_, _)
| TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::PathProperty(_)
| TraverserValue::Entry(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => value,
})
}
pub(crate) fn is_null<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> bool {
match value {
TraverserValue::Computed(property, _) => matches!(property, ElementProperty::Null),
TraverserValue::VertexProperty(key, _, _) | TraverserValue::EdgeProperty(key, _, _) => {
key != "id"
&& key != "label"
&& matches!(
stored_value(context, value),
None | Some(ElementProperty::Null)
)
}
TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::Path(_)
| TraverserValue::Record { .. }
| TraverserValue::Object(_)
| TraverserValue::PathProperty(_)
| TraverserValue::Entry(_)
| TraverserValue::Array(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => false,
}
}
pub(crate) enum LocalItem<'a, S: GraphStorage> {
Value(&'a TraverserValue<S>),
Stored(&'a ElementProperty),
}
pub(crate) enum LocalMapped<S: GraphStorage> {
Done(TraverserValue<S>),
Single(TraverserValue<S>),
}
pub(crate) fn map_local_elements<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: TraverserValue<S>,
mut map: impl FnMut(usize, LocalItem<'_, S>) -> TraverserResult<ElementProperty>,
) -> TraverserResult<LocalMapped<S>> {
let mapped = match &value {
TraverserValue::Array(items) => items
.iter()
.enumerate()
.map(|(index, item)| map(index, LocalItem::Value(item)))
.collect::<TraverserResult<Vec<_>>>()?,
TraverserValue::Computed(ElementProperty::Array(items), _) => items
.iter()
.enumerate()
.map(|(index, item)| map(index, LocalItem::Stored(item)))
.collect::<TraverserResult<Vec<_>>>()?,
TraverserValue::VertexProperty(_, _, _) | TraverserValue::EdgeProperty(_, _, _) => {
match stored_value(context, &value) {
Some(ElementProperty::Array(items)) => items
.iter()
.enumerate()
.map(|(index, item)| map(index, LocalItem::Stored(item)))
.collect::<TraverserResult<Vec<_>>>()?,
Some(_) | None => return Ok(LocalMapped::Single(value)),
}
}
TraverserValue::Computed(_, _)
| TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::Path(_)
| TraverserValue::Record { .. }
| TraverserValue::Object(_)
| TraverserValue::PathProperty(_)
| TraverserValue::Entry(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => return Ok(LocalMapped::Single(value)),
};
Ok(LocalMapped::Done(TraverserValue::Computed(
ElementProperty::Array(mapped),
None,
)))
}
const STRING_ACCEPTS: &str = "string or list of strings";
fn local_string_item<S: GraphStorage>(
context: &TraverserContext<'_, S>,
step: &'static str,
index: usize,
item: LocalItem<'_, S>,
f: &impl Fn(&str) -> TraverserResult<ElementProperty>,
) -> TraverserResult<ElementProperty> {
match item {
LocalItem::Stored(ElementProperty::Null) => Ok(ElementProperty::Null),
LocalItem::Stored(ElementProperty::String(text)) => {
bounded_string(context, f(text.as_str())?)
}
LocalItem::Stored(ElementProperty::Compressed(text)) => {
let value = text.with_text(|text| f(text))?;
bounded_string(context, value)
}
LocalItem::Stored(
other @ (ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_)
| ElementProperty::Array(_)
| ElementProperty::Object(_)
| ElementProperty::Map(_)
| ElementProperty::Vertex(_)
| ElementProperty::Edge(_)),
) => Err(Box::new(TraverserError::local_element_type(
step,
STRING_ACCEPTS,
index,
crate::graph::schema::value_type_name(other),
))),
LocalItem::Value(value) => {
if is_null(context, value) {
return Ok(ElementProperty::Null);
}
let text = match value {
TraverserValue::Computed(_, _)
| TraverserValue::VertexProperty(_, _, _)
| TraverserValue::EdgeProperty(_, _, _) => {
Traverser::as_string_ref(context, value)?
}
TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::Path(_)
| TraverserValue::Record { .. }
| TraverserValue::Object(_)
| TraverserValue::PathProperty(_)
| TraverserValue::Entry(_)
| TraverserValue::Array(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => None,
};
match text {
Some(text) => bounded_string(context, f(&text)?),
None => Err(Box::new(TraverserError::local_element_type(
step,
STRING_ACCEPTS,
index,
Traverser::observed_type_name(context, value),
))),
}
}
}
}
#[inline]
pub(crate) fn bounded_string<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: ElementProperty,
) -> TraverserResult<ElementProperty> {
if let ElementProperty::String(text) = &value {
context.check_string_bytes(text.len())?;
}
Ok(value)
}
pub(crate) fn map_local_strings<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: TraverserValue<S>,
step: &'static str,
f: impl Fn(&str) -> TraverserResult<ElementProperty>,
) -> TraverserResult<LocalMapped<S>> {
if is_null(context, &value) {
return Ok(LocalMapped::Done(TraverserValue::Computed(
ElementProperty::Null,
None,
)));
}
map_local_elements(context, value, |index, item| {
local_string_item(context, step, index, item, &f)
})
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::value::PropertyHandleKind;
use compact_str::CompactString;
use super::*;
use crate::TraversalGraph;
use crate::traversal::context::{TraversalStrategy, TraverserContext};
use crate::value::StreamMap;
fn shape_name<S: GraphStorage>(shape: &LocalShape<S>) -> (&'static str, usize) {
match shape {
LocalShape::List(items) => ("list", items.len()),
LocalShape::Map(entries) => ("map", entries.len()),
LocalShape::Path(path) => ("path", path.len()),
LocalShape::Scalar(_) => ("scalar", 1),
}
}
fn modern_with_collections() -> TraversalGraph {
let mut storage = TraversalGraph::tinkerpop_modern();
let list = ElementProperty::Array(vec![1i64.into(), 2i64.into(), 3i64.into()]);
let mut map = crate::value::PropertyMap::default();
map.insert(CompactString::new("a"), 1i64.into());
map.insert(CompactString::new("b"), 2i64.into());
let _ = storage.traversal_mut().v("1").property("list", list).next();
let _ = storage
.traversal_mut()
.v("1")
.property("map", ElementProperty::Object(Box::new(map)))
.next();
storage
}
#[test]
fn classify_every_kind() {
let mut storage = modern_with_collections();
let node = GraphStorage::get_vertex_by_id(&storage, "1").expect("vertex 1");
let edge = GraphStorage::get_edge_by_id(&storage, "0").expect("edge 0");
let context =
TraverserContext::new(&mut storage, TraversalStrategy::none(), Default::default());
let number = || TraverserValue::<TraversalGraph>::Computed(7i64.into(), None);
let mut object: StreamMap<TraversalGraph> = StreamMap::default();
object.insert(number(), number());
let cases: Vec<(TraverserValue<TraversalGraph>, (&str, usize))> = vec![
(TraverserValue::Array(vec![number(), number()]), ("list", 2)),
(
TraverserValue::Computed(
ElementProperty::Array(vec![1i64.into(), ElementProperty::Null]),
None,
),
("list", 2),
),
(
TraverserValue::VertexProperty("list".into(), node, PropertyHandleKind::Value),
("list", 3),
),
(
TraverserValue::VertexProperty("map".into(), node, PropertyHandleKind::Value),
("map", 2),
),
(
TraverserValue::VertexProperty("age".into(), node, PropertyHandleKind::Value),
("scalar", 1),
),
(
TraverserValue::VertexProperty("missing".into(), node, PropertyHandleKind::Value),
("scalar", 1),
),
(
TraverserValue::EdgeProperty("weight".into(), edge, PropertyHandleKind::Value),
("scalar", 1),
),
(TraverserValue::Object(object), ("map", 1)),
(
TraverserValue::Record {
keys: Arc::new(vec!["a".into(), "b".into(), "c".into()]),
values: vec![number(), number(), number()],
},
("map", 3),
),
(TraverserValue::Path(TraverserPath::default()), ("path", 0)),
(number(), ("scalar", 1)),
(
TraverserValue::Computed(ElementProperty::Null, None),
("scalar", 1),
),
(TraverserValue::Vertex(node), ("scalar", 1)),
(TraverserValue::Edge(edge), ("scalar", 1)),
(
TraverserValue::Entry(Box::new((number(), number()))),
("scalar", 1),
),
];
for (value, expected) in cases {
let rendered = format!("{value:?}");
let len = local_len(&context, &value).expect("local_len");
let shape = classify(&context, value).expect("classify");
assert_eq!(shape_name(&shape), expected, "classify({rendered})");
let expected_len = match expected.0 {
"scalar" => 1,
_ => expected.1,
};
assert_eq!(len, expected_len, "local_len({rendered})");
}
}
#[test]
fn scalar_property_stays_lazy() {
let mut storage = TraversalGraph::tinkerpop_modern();
let node = GraphStorage::get_vertex_by_id(&storage, "1").expect("vertex 1");
let context =
TraverserContext::new(&mut storage, TraversalStrategy::none(), Default::default());
let shape = classify(
&context,
TraverserValue::VertexProperty("age".into(), node, PropertyHandleKind::Value),
)
.expect("classify");
match shape {
LocalShape::Scalar(TraverserValue::VertexProperty(key, _, _)) => assert_eq!(key, "age"),
LocalShape::Scalar(other) => panic!("expected a lazy handle, got {other:?}"),
LocalShape::List(_) | LocalShape::Map(_) | LocalShape::Path(_) => {
panic!("expected a scalar")
}
}
}
}