use std::borrow::Cow;
use std::fmt;
use std::sync::Arc;
use crate::error::TraverserResult;
use crate::graph::GraphStorage;
use crate::graph::schema::GraphSchema;
use crate::{By, Traverser, TraverserContext};
pub mod barrier;
pub mod cardinality;
pub mod cast;
pub mod column;
pub mod compressed;
pub(crate) mod depth;
pub mod direction;
pub(crate) mod glob_path;
pub mod gtype;
pub mod json_path;
pub(crate) mod literal;
pub(crate) mod math_expr;
pub mod merge;
pub mod number;
pub mod operator;
pub mod path;
pub(crate) mod path_arena;
pub(crate) mod physical;
pub mod pop;
pub mod predicate;
mod prop_key;
pub mod property_map;
pub(crate) mod sack;
pub(crate) mod scalar;
pub mod scope;
pub mod stream_map;
pub(crate) mod uuid;
pub mod value_map;
pub mod with_options;
pub use barrier::Barrier;
pub use cardinality::Cardinality;
pub use cast::CastPolicy;
pub use column::Column;
pub use compact_str::CompactString;
pub use compressed::CompressedText;
pub use direction::Direction;
pub use gtype::{GType, StreamKind};
pub use json_path::{JsonPath, JsonPathSegment};
pub use merge::Merge;
pub(crate) use number::N;
pub use number::Number;
pub use operator::Operator;
pub(crate) use path::TraverserPath;
pub(crate) use path_arena::{PathArena, PathRef};
pub use pop::Pop;
#[cfg(feature = "script")]
pub(crate) use predicate::TextRegex;
pub use predicate::{P, PValue};
pub use prop_key::{PropKey, PropKeys};
pub use property_map::PropertyMap;
pub(crate) use sack::{SackArena, SackRef, SackSpec};
pub use scope::Scope;
pub use stream_map::StreamMap;
pub use value_map::ValueMap;
pub use with_options::WithOptions;
#[non_exhaustive]
pub enum ElementProperty {
Null,
String(CompactString),
Number(Number),
Boolean(bool),
Uuid(u128),
Object(Box<PropertyMap>),
Array(Vec<ElementProperty>),
Vertex(Box<PropertyMap>),
Edge(Box<PropertyMap>),
Map(Box<ValueMap>),
Compressed(Box<CompressedText>),
}
impl Clone for ElementProperty {
fn clone(&self) -> Self {
match self {
ElementProperty::Null => ElementProperty::Null,
ElementProperty::String(text) => ElementProperty::String(text.clone()),
ElementProperty::Number(number) => ElementProperty::Number(number.clone()),
ElementProperty::Boolean(flag) => ElementProperty::Boolean(*flag),
ElementProperty::Uuid(bits) => ElementProperty::Uuid(*bits),
ElementProperty::Object(map) => ElementProperty::Object(map.clone()),
ElementProperty::Array(items) => ElementProperty::Array(items.clone()),
ElementProperty::Vertex(map) => ElementProperty::Vertex(map.clone()),
ElementProperty::Edge(map) => ElementProperty::Edge(map.clone()),
ElementProperty::Map(map) => ElementProperty::Map(map.clone()),
ElementProperty::Compressed(text) => clone_compressed(text),
}
}
}
#[inline(never)]
#[cold]
fn clone_compressed(text: &CompressedText) -> ElementProperty {
ElementProperty::Compressed(Box::new(text.clone()))
}
impl PartialEq for ElementProperty {
fn eq(&self, other: &Self) -> bool {
use ElementProperty as E;
match (self, other) {
(E::Null, E::Null) => true,
(E::String(a), E::String(b)) => a == b,
(E::String(_), E::Compressed(_))
| (E::Compressed(_), E::String(_))
| (E::Compressed(_), E::Compressed(_)) => compressed_eq(self, other),
(E::Number(a), E::Number(b)) => a == b,
(E::Boolean(a), E::Boolean(b)) => a == b,
(E::Uuid(a), E::Uuid(b)) => a == b,
(E::Object(a), E::Object(b)) => a == b,
(E::Array(a), E::Array(b)) => a == b,
(E::Vertex(a), E::Vertex(b)) => a == b,
(E::Edge(a), E::Edge(b)) => a == b,
(E::Map(a), E::Map(b)) => a == b,
(
E::Null
| E::String(_)
| E::Number(_)
| E::Boolean(_)
| E::Uuid(_)
| E::Object(_)
| E::Array(_)
| E::Vertex(_)
| E::Edge(_)
| E::Map(_)
| E::Compressed(_),
_,
) => false,
}
}
}
impl Eq for ElementProperty {}
#[inline(never)]
#[cold]
fn hash_compressed<H: std::hash::Hasher>(text: &CompressedText, state: &mut H) {
text.with_text(|text| ElementProperty::hash_string_key(text, state))
}
#[inline(never)]
#[cold]
fn compare_text(a: &ElementProperty, b: &ElementProperty) -> std::cmp::Ordering {
a.with_text(|a| b.with_text(|b| a.cmp(b)))
.flatten()
.unwrap_or(std::cmp::Ordering::Equal)
}
#[inline(never)]
#[cold]
fn compressed_eq(a: &ElementProperty, b: &ElementProperty) -> bool {
match (a, b) {
(ElementProperty::Compressed(a), ElementProperty::Compressed(b)) => a.logical_eq(b),
(ElementProperty::String(a), ElementProperty::Compressed(b))
| (ElementProperty::Compressed(b), ElementProperty::String(a)) => b.eq_str(a),
_ => false,
}
}
impl ElementProperty {
pub fn logical(&self) -> Cow<'_, ElementProperty> {
if self.holds_compressed() {
Cow::Owned(self.clone().into_logical())
} else {
Cow::Borrowed(self)
}
}
#[inline(never)]
pub fn to_logical(&self) -> ElementProperty {
if self.holds_compressed() {
self.clone().into_logical()
} else {
self.clone()
}
}
#[inline(never)]
pub fn into_logical(self) -> ElementProperty {
match self {
ElementProperty::Compressed(text) => ElementProperty::String(text.text()),
ElementProperty::Array(mut items) => {
for item in items.iter_mut().filter(|item| item.holds_compressed()) {
*item = std::mem::replace(item, ElementProperty::Null).into_logical();
}
ElementProperty::Array(items)
}
ElementProperty::Object(mut map) => {
map.values_mut().for_each(ElementProperty::make_logical);
ElementProperty::Object(map)
}
ElementProperty::Vertex(mut map) => {
map.values_mut().for_each(ElementProperty::make_logical);
ElementProperty::Vertex(map)
}
ElementProperty::Edge(mut map) => {
map.values_mut().for_each(ElementProperty::make_logical);
ElementProperty::Edge(map)
}
ElementProperty::Map(map) => {
if map
.iter()
.any(|(k, v)| k.holds_compressed() || v.holds_compressed())
{
ElementProperty::from_entries(
map.into_iter()
.map(|(k, v)| (k.into_logical(), v.into_logical())),
)
} else {
ElementProperty::Map(map)
}
}
plain @ (ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_)) => plain,
}
}
pub(crate) fn make_logical(&mut self) {
if self.holds_compressed() {
*self = std::mem::replace(self, ElementProperty::Null).into_logical();
}
}
pub fn holds_compressed(&self) -> bool {
self.holds_compressed_within(0)
}
fn holds_compressed_within(&self, depth: usize) -> bool {
const RECURSION_DEPTH: usize = 32;
if depth >= RECURSION_DEPTH {
return self.holds_compressed_deep();
}
match self {
ElementProperty::Compressed(_) => true,
ElementProperty::Array(items) => items
.iter()
.any(|item| item.holds_compressed_within(depth + 1)),
ElementProperty::Object(map)
| ElementProperty::Vertex(map)
| ElementProperty::Edge(map) => map
.values()
.any(|value| value.holds_compressed_within(depth + 1)),
ElementProperty::Map(map) => map.iter().any(|(k, v)| {
k.holds_compressed_within(depth + 1) || v.holds_compressed_within(depth + 1)
}),
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_) => false,
}
}
#[inline(never)]
fn holds_compressed_deep(&self) -> bool {
let mut stack: Vec<&ElementProperty> = Vec::new();
let mut current = self;
loop {
match current {
ElementProperty::Compressed(_) => return true,
ElementProperty::Array(items) => stack.extend(items.iter()),
ElementProperty::Object(map)
| ElementProperty::Vertex(map)
| ElementProperty::Edge(map) => stack.extend(map.values()),
ElementProperty::Map(map) => {
for (key, value) in map.iter() {
stack.push(key);
stack.push(value);
}
}
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_) => {}
}
match stack.pop() {
Some(next) => current = next,
None => return false,
}
}
}
pub fn with_text<R>(&self, f: impl FnOnce(&str) -> R) -> Option<R> {
match self {
ElementProperty::String(text) => Some(f(text.as_str())),
ElementProperty::Compressed(text) => Some(text.with_text(f)),
_ => None,
}
}
pub fn is_text(&self) -> bool {
matches!(
self,
ElementProperty::String(_) | ElementProperty::Compressed(_)
)
}
pub fn text(&self) -> Option<Cow<'_, str>> {
match self {
ElementProperty::String(text) => Some(Cow::Borrowed(text.as_str())),
ElementProperty::Compressed(text) => Some(Cow::Owned(text.text().into_string())),
_ => None,
}
}
pub(crate) fn element_ref_kind(&self) -> Option<&'static str> {
match self {
ElementProperty::Vertex(_) => Some("vertex"),
ElementProperty::Edge(_) => Some("edge"),
ElementProperty::Map(_) => Some("map with a non-string key"),
ElementProperty::Array(items) => items.iter().find_map(Self::element_ref_kind),
ElementProperty::Object(map) => map.values().find_map(Self::element_ref_kind),
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Compressed(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_) => None,
}
}
pub fn from_entries(
entries: impl IntoIterator<Item = (ElementProperty, ElementProperty)>,
) -> ElementProperty {
entries.into_iter().collect::<ValueMap>().into_property()
}
pub(crate) fn hash_string_key<H: std::hash::Hasher>(key: &str, state: &mut H) {
use std::hash::Hash;
state.write_u8(1);
key.hash(state);
}
pub fn as_array(&self) -> Option<&Vec<ElementProperty>> {
match self {
ElementProperty::Array(val) => Some(val),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
ElementProperty::String(value) => Some(value.as_str()),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match self {
ElementProperty::String(value) => Some(value.as_str()),
_ => None,
}
}
pub fn as_number(&self) -> Option<&Self> {
match self {
ElementProperty::Number(_) => Some(self),
_ => None,
}
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
ElementProperty::Boolean(value) => Some(*value),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
ElementProperty::Number(value) => value.as_i64(),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
ElementProperty::Number(value) => value.as_f64(),
_ => None,
}
}
pub fn as_object(&self) -> Option<&PropertyMap> {
match self {
ElementProperty::Object(value) => Some(value),
_ => None,
}
}
pub fn as_vertex(&self) -> Option<&PropertyMap> {
match self {
ElementProperty::Vertex(value) => Some(value),
_ => None,
}
}
pub fn as_edge(&self) -> Option<&PropertyMap> {
match self {
ElementProperty::Edge(value) => Some(value),
_ => None,
}
}
pub fn as_uuid(&self) -> Option<u128> {
match self {
ElementProperty::Uuid(value) => Some(*value),
_ => None,
}
}
pub fn estimated_heap_bytes(&self) -> u64 {
match self {
ElementProperty::Null
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_) => 0,
ElementProperty::String(value) => {
if value.is_heap_allocated() {
value.capacity() as u64
} else {
0
}
}
ElementProperty::Compressed(text) => text.heap_bytes(),
ElementProperty::Array(values) => {
let container =
values.capacity() as u64 * std::mem::size_of::<ElementProperty>() as u64;
container
+ values
.iter()
.map(ElementProperty::estimated_heap_bytes)
.sum::<u64>()
}
ElementProperty::Object(element)
| ElementProperty::Vertex(element)
| ElementProperty::Edge(element) => estimated_element_heap_bytes(element),
ElementProperty::Map(map) => {
let entry_size = std::mem::size_of::<(ElementProperty, ElementProperty)>() as u64;
map.len() as u64 * entry_size
+ map
.iter()
.map(|(k, v)| k.estimated_heap_bytes() + v.estimated_heap_bytes())
.sum::<u64>()
}
}
}
}
fn type_rank(value: &ElementProperty) -> u8 {
match value {
ElementProperty::Null => 0,
ElementProperty::Boolean(_) => 1,
ElementProperty::Number(_) => 2,
ElementProperty::String(_) | ElementProperty::Compressed(_) => 3,
ElementProperty::Uuid(_) => 4,
ElementProperty::Array(_) => 5,
ElementProperty::Object(_) => 6,
ElementProperty::Map(_) => 7,
ElementProperty::Vertex(_) => 8,
ElementProperty::Edge(_) => 9,
}
}
fn compare_elements(a: &PropertyMap, b: &PropertyMap) -> std::cmp::Ordering {
let mut left = a.iter();
let mut right = b.iter();
loop {
return match (left.next(), right.next()) {
(Some((a_key, a_value)), Some((b_key, b_value))) => {
match a_key.as_str().cmp(b_key.as_str()) {
std::cmp::Ordering::Equal => match compare_element_property(a_value, b_value) {
std::cmp::Ordering::Equal => continue,
other => other,
},
other => other,
}
}
(Some(_), None) => std::cmp::Ordering::Greater,
(None, Some(_)) => std::cmp::Ordering::Less,
(None, None) => std::cmp::Ordering::Equal,
};
}
}
fn compare_elements_by_id(a: &PropertyMap, b: &PropertyMap) -> std::cmp::Ordering {
if let (Some(a_id), Some(b_id)) = (a.get("id"), b.get("id")) {
match compare_element_property(a_id, b_id) {
std::cmp::Ordering::Equal => {}
other => return other,
}
}
compare_elements(a, b)
}
pub(crate) fn compare_element_property(
a: &ElementProperty,
b: &ElementProperty,
) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a, b) {
(ElementProperty::Null, ElementProperty::Null) => Ordering::Equal,
(ElementProperty::Boolean(a), ElementProperty::Boolean(b)) => a.cmp(b),
(ElementProperty::Number(a), ElementProperty::Number(b)) => a.cmp(b),
(ElementProperty::String(a), ElementProperty::String(b)) => a.as_str().cmp(b.as_str()),
(ElementProperty::Compressed(_), b) if b.is_text() => compare_text(a, b),
(a, ElementProperty::Compressed(_)) if a.is_text() => compare_text(a, b),
(ElementProperty::Uuid(a), ElementProperty::Uuid(b)) => a.cmp(b),
(ElementProperty::Array(a), ElementProperty::Array(b)) => {
let mut left = a.iter();
let mut right = b.iter();
loop {
return match (left.next(), right.next()) {
(Some(a_value), Some(b_value)) => {
match compare_element_property(a_value, b_value) {
Ordering::Equal => continue,
other => other,
}
}
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,
};
}
}
(ElementProperty::Object(a), ElementProperty::Object(b)) => compare_elements(a, b),
(ElementProperty::Map(a), ElementProperty::Map(b)) => {
let mut left = a.iter();
let mut right = b.iter();
loop {
return match (left.next(), right.next()) {
(Some((a_key, a_value)), Some((b_key, b_value))) => {
match compare_element_property(a_key, b_key) {
Ordering::Equal => match compare_element_property(a_value, b_value) {
Ordering::Equal => continue,
other => other,
},
other => other,
}
}
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,
};
}
}
(ElementProperty::Vertex(a), ElementProperty::Vertex(b)) => compare_elements_by_id(a, b),
(ElementProperty::Edge(a), ElementProperty::Edge(b)) => compare_elements_by_id(a, b),
(a, b) => type_rank(a).cmp(&type_rank(b)),
}
}
pub(crate) fn logical_element(element: &PropertyMap) -> PropertyMap {
element
.iter()
.map(|(key, value)| (key.clone(), value.to_logical()))
.collect()
}
pub(crate) fn estimated_element_heap_bytes(element: &PropertyMap) -> u64 {
let entry_size = std::mem::size_of::<(CompactString, ElementProperty)>() as u64;
let mut total = element.0.capacity() as u64 * entry_size;
for (key, value) in element.iter() {
if key.is_heap_allocated() {
total += key.capacity() as u64;
}
total += value.estimated_heap_bytes();
}
total
}
impl fmt::Debug for ElementProperty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ElementProperty::Null => write!(f, "null"),
ElementProperty::String(value) => write!(f, "\"{}\"", value),
ElementProperty::Object(obj) => write!(f, "{:?}", obj),
ElementProperty::Map(map) => write!(f, "{:?}", map),
ElementProperty::Vertex(vertex) => write!(
f,
"v[{:?}]",
vertex.get("id").unwrap_or(&ElementProperty::Null)
),
ElementProperty::Edge(edge) => {
write!(
f,
"e[{:?}][{:?}-{:?}->{:?}]",
edge.get("id").unwrap_or(&ElementProperty::Null),
edge.get("_from").unwrap_or(&ElementProperty::Null),
edge.get("label").unwrap_or(&ElementProperty::Null),
edge.get("_to").unwrap_or(&ElementProperty::Null)
)
}
ElementProperty::Array(array) => write!(f, "{:?}", array),
ElementProperty::Number(value) => write!(f, "{}", value),
ElementProperty::Boolean(value) => write!(f, "{}", value),
ElementProperty::Uuid(value) => write!(f, "{}", uuid::format_canonical(*value)),
ElementProperty::Compressed(text) => write!(f, "{text:?}"),
}
}
}
impl From<&str> for ElementProperty {
fn from(value: &str) -> Self {
ElementProperty::String(CompactString::new(value))
}
}
impl From<&&str> for ElementProperty {
fn from(value: &&str) -> Self {
ElementProperty::String(CompactString::new(*value))
}
}
impl From<String> for ElementProperty {
fn from(value: String) -> Self {
ElementProperty::String(CompactString::new(value))
}
}
impl From<&String> for ElementProperty {
fn from(value: &String) -> Self {
ElementProperty::String(CompactString::new(value))
}
}
impl From<CompactString> for ElementProperty {
fn from(value: CompactString) -> Self {
ElementProperty::String(value)
}
}
impl From<&CompactString> for ElementProperty {
fn from(value: &CompactString) -> Self {
ElementProperty::String(value.clone())
}
}
impl<'a> From<Cow<'a, str>> for ElementProperty {
fn from(value: Cow<'a, str>) -> Self {
ElementProperty::String(CompactString::new(value))
}
}
impl From<usize> for ElementProperty {
fn from(value: usize) -> Self {
ElementProperty::Number(value.into())
}
}
impl From<&usize> for ElementProperty {
fn from(value: &usize) -> Self {
ElementProperty::Number((*value).into())
}
}
impl From<Vec<usize>> for ElementProperty {
fn from(value: Vec<usize>) -> Self {
ElementProperty::Array(value.iter().map(|r| r.into()).collect())
}
}
impl From<i32> for ElementProperty {
fn from(value: i32) -> Self {
ElementProperty::Number(value.into())
}
}
impl From<&i32> for ElementProperty {
fn from(value: &i32) -> Self {
ElementProperty::Number((*value).into())
}
}
impl From<Vec<i32>> for ElementProperty {
fn from(value: Vec<i32>) -> Self {
ElementProperty::Array(value.iter().map(|r| r.into()).collect())
}
}
impl From<i64> for ElementProperty {
fn from(value: i64) -> Self {
ElementProperty::Number(value.into())
}
}
impl From<&i64> for ElementProperty {
fn from(value: &i64) -> Self {
ElementProperty::Number((*value).into())
}
}
impl From<Vec<i64>> for ElementProperty {
fn from(value: Vec<i64>) -> Self {
ElementProperty::Array(value.iter().map(|r| r.into()).collect())
}
}
impl From<f64> for ElementProperty {
fn from(value: f64) -> Self {
ElementProperty::Number(value.into())
}
}
impl From<&f64> for ElementProperty {
fn from(value: &f64) -> Self {
ElementProperty::Number((*value).into())
}
}
impl From<Vec<f64>> for ElementProperty {
fn from(value: Vec<f64>) -> Self {
ElementProperty::Array(value.iter().map(|r| r.into()).collect())
}
}
impl From<Vec<&str>> for ElementProperty {
fn from(value: Vec<&str>) -> Self {
ElementProperty::Array(value.iter().map(|r| r.into()).collect())
}
}
impl From<bool> for ElementProperty {
fn from(value: bool) -> Self {
ElementProperty::Boolean(value)
}
}
impl From<&GraphSchema> for ElementProperty {
fn from(schema: &GraphSchema) -> Self {
schema.to_value()
}
}
impl From<GraphSchema> for ElementProperty {
fn from(schema: GraphSchema) -> Self {
schema.to_value()
}
}
impl std::hash::Hash for ElementProperty {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
ElementProperty::Null => state.write_u8(0),
ElementProperty::String(val) => Self::hash_string_key(val, state),
ElementProperty::Compressed(val) => hash_compressed(val, state),
ElementProperty::Number(val) => {
state.write_u8(2);
val.hash(state);
}
ElementProperty::Boolean(val) => {
state.write_u8(3);
val.hash(state);
}
ElementProperty::Uuid(val) => {
state.write_u8(4);
val.hash(state);
}
ElementProperty::Object(val) => {
state.write_u8(5);
value_map::hash_entries_unordered(val.iter(), state);
}
ElementProperty::Array(val) => {
state.write_u8(6);
val.hash(state);
}
ElementProperty::Vertex(val) => {
state.write_u8(7);
value_map::hash_entries_unordered(val.iter(), state);
}
ElementProperty::Edge(val) => {
state.write_u8(8);
value_map::hash_entries_unordered(val.iter(), state);
}
ElementProperty::Map(val) => {
state.write_u8(9);
val.hash(state);
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PropertyHandleKind {
Element,
Value,
}
#[non_exhaustive]
pub enum TraverserValue<S: GraphStorage> {
Computed(ElementProperty, Option<S::VertexId>),
VertexProperty(CompactString, S::VertexId, PropertyHandleKind),
Vertex(S::VertexId),
Edge(S::EdgeId),
EdgeProperty(CompactString, S::EdgeId, PropertyHandleKind),
PathProperty(Box<(CompactString, ElementProperty)>),
Path(TraverserPath<S>),
Record {
keys: Arc<Vec<CompactString>>,
values: Vec<TraverserValue<S>>,
},
Object(StreamMap<S>),
Entry(Box<(TraverserValue<S>, TraverserValue<S>)>),
Array(Vec<TraverserValue<S>>),
Schema(Box<GraphSchema>),
Graph(Arc<crate::graph::Graph>),
}
impl<S: GraphStorage> std::fmt::Debug for TraverserValue<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TraverserValue::Computed(value, id) => {
f.debug_tuple("Computed").field(value).field(id).finish()
}
TraverserValue::VertexProperty(prop, id, _) => f
.debug_tuple("VertexProperty")
.field(prop)
.field(id)
.finish(),
TraverserValue::Vertex(id) => f.debug_tuple("Vertex").field(id).finish(),
TraverserValue::Edge(id) => f.debug_tuple("Edge").field(id).finish(),
TraverserValue::EdgeProperty(prop, id, _) => {
f.debug_tuple("EdgeProperty").field(prop).field(id).finish()
}
TraverserValue::Path(path) => f.debug_tuple("Path").field(path).finish(),
TraverserValue::Record { keys, values } => f
.debug_struct("Record")
.field("keys", keys)
.field("values", values)
.finish(),
TraverserValue::Object(obj) => f.debug_tuple("Object").field(obj).finish(),
TraverserValue::PathProperty(prop) => f
.debug_tuple("PathProperty")
.field(&prop.0)
.field(&prop.1)
.finish(),
TraverserValue::Entry(entry) => f
.debug_tuple("Entry")
.field(&entry.0)
.field(&entry.1)
.finish(),
TraverserValue::Array(arr) => f.debug_tuple("Array").field(arr).finish(),
TraverserValue::Schema(schema) => f.debug_tuple("Schema").field(schema).finish(),
TraverserValue::Graph(graph) => {
let graph = graph.read();
f.debug_struct("Graph")
.field("vertices", &graph.vertex_count())
.field("edges", &graph.edge_count())
.finish()
}
}
}
}
impl<S: GraphStorage> Clone for TraverserValue<S> {
fn clone(&self) -> Self {
match self {
TraverserValue::Computed(value, id) => TraverserValue::Computed(value.clone(), *id),
TraverserValue::VertexProperty(prop, id, kind) => {
TraverserValue::VertexProperty(prop.clone(), *id, *kind)
}
TraverserValue::Vertex(id) => TraverserValue::Vertex(*id),
TraverserValue::Edge(id) => TraverserValue::Edge(*id),
TraverserValue::EdgeProperty(prop, id, kind) => {
TraverserValue::EdgeProperty(prop.clone(), *id, *kind)
}
TraverserValue::Path(path) => TraverserValue::Path(path.clone()),
TraverserValue::Record { keys, values } => TraverserValue::Record {
keys: keys.clone(),
values: values.clone(),
},
TraverserValue::Object(obj) => TraverserValue::Object(obj.clone()),
TraverserValue::PathProperty(prop) => TraverserValue::PathProperty(prop.clone()),
TraverserValue::Entry(entry) => TraverserValue::Entry(entry.clone()),
TraverserValue::Array(arr) => TraverserValue::Array(arr.clone()),
TraverserValue::Schema(schema) => TraverserValue::Schema(schema.clone()),
TraverserValue::Graph(graph) => TraverserValue::Graph(graph.clone()),
}
}
}
impl<S: GraphStorage> std::cmp::Eq for TraverserValue<S> {}
impl<S: GraphStorage> PartialEq<TraverserValue<S>> for TraverserValue<S> {
fn eq(&self, other: &TraverserValue<S>) -> bool {
match (self, other) {
(Self::Computed(l0, _), Self::Computed(r0, _)) => l0 == r0,
(Self::VertexProperty(l0, l1, _), Self::VertexProperty(r0, r1, _)) => {
l0 == r0 && l1 == r1
}
(Self::Vertex(l0), Self::Vertex(r0)) => l0 == r0,
(Self::Edge(l0), Self::Edge(r0)) => l0 == r0,
(Self::EdgeProperty(l0, l1, _), Self::EdgeProperty(r0, r1, _)) => l0 == r0 && l1 == r1,
(Self::Path(l0), Self::Path(r0)) => l0 == r0,
(Self::Object(l0), Self::Object(r0)) => l0 == r0,
(Self::PathProperty(l0), Self::PathProperty(r0)) => l0 == r0,
(Self::Entry(l0), Self::Entry(r0)) => l0 == r0,
(Self::Array(l0), Self::Array(r0)) => l0 == r0,
(Self::Schema(l0), Self::Schema(r0)) => l0 == r0,
(Self::Graph(l0), Self::Graph(r0)) => Arc::ptr_eq(l0, r0),
(
Self::Record {
keys: l_keys,
values: l_vals,
},
Self::Record {
keys: r_keys,
values: r_vals,
},
) => {
if l_vals.len() != r_vals.len() {
return false;
}
if Arc::ptr_eq(l_keys, r_keys) || l_keys == r_keys {
l_vals == r_vals
} else {
for (lk, lv) in l_keys.iter().zip(l_vals.iter()) {
if let Some(r_idx) = r_keys.iter().position(|rk| rk == lk) {
if &r_vals[r_idx] != lv {
return false;
}
} else {
return false;
}
}
true
}
}
(Self::Record { keys, values }, Self::Object(obj))
| (Self::Object(obj), Self::Record { keys, values }) => {
if keys.len() != obj.len() {
return false;
}
for (k, v) in keys.iter().zip(values.iter()) {
let key_tv = TraverserValue::Computed(ElementProperty::String(k.clone()), None);
match obj.get(&key_tv) {
Some(obj_val) => {
if obj_val != v {
return false;
}
}
None => return false,
}
}
true
}
_ => false,
}
}
}
impl<S: GraphStorage> std::hash::Hash for TraverserValue<S> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
TraverserValue::Computed(value, _) => value.hash(state),
TraverserValue::VertexProperty(property, node_index, _) => {
property.hash(state);
node_index.hash(state);
}
TraverserValue::Vertex(vertex) => vertex.hash(state),
TraverserValue::Edge(edge) => edge.hash(state),
TraverserValue::EdgeProperty(property, edge_index, _) => {
property.hash(state);
edge_index.hash(state);
}
TraverserValue::Path(path) => path.hash(state),
TraverserValue::Object(obj) => value_map::hash_entries_unordered(obj.iter(), state),
TraverserValue::Record { keys, values } => {
for (k, v) in keys.iter().zip(values.iter()) {
k.hash(state);
v.hash(state);
}
}
TraverserValue::PathProperty(prop) => {
prop.0.hash(state);
prop.1.hash(state);
}
TraverserValue::Entry(entry) => {
entry.0.hash(state);
entry.1.hash(state);
}
TraverserValue::Array(arr) => arr.hash(state),
TraverserValue::Schema(schema) => {
schema.meta.is_some().hash(state);
schema.mode.hash(state);
schema.vertices.len().hash(state);
schema.edges.len().hash(state);
}
TraverserValue::Graph(graph) => Arc::as_ptr(graph).hash(state),
}
}
}
impl<S: GraphStorage> From<&ElementProperty> for TraverserValue<S> {
fn from(value: &ElementProperty) -> Self {
match value {
ElementProperty::Null => TraverserValue::Computed(value.clone(), None),
ElementProperty::String(_) => TraverserValue::Computed(value.clone(), None),
ElementProperty::Number(_) => TraverserValue::Computed(value.clone(), None),
ElementProperty::Boolean(_) => TraverserValue::Computed(value.clone(), None),
ElementProperty::Uuid(_) => TraverserValue::Computed(value.clone(), None),
ElementProperty::Compressed(text) => {
TraverserValue::Computed(ElementProperty::String(text.text()), None)
}
ElementProperty::Array(value) => {
TraverserValue::Array(value.iter().map(|r| r.into()).collect())
}
ElementProperty::Object(obj)
| ElementProperty::Vertex(obj)
| ElementProperty::Edge(obj) => TraverserValue::Object(
obj.iter()
.map(|(k, v)| (TraverserValue::from(k.clone()), TraverserValue::from(v)))
.collect(),
),
ElementProperty::Map(map) => TraverserValue::Object(
map.iter()
.map(|(k, v)| (TraverserValue::from(k), TraverserValue::from(v)))
.collect(),
),
}
}
}
impl<S: GraphStorage> From<ElementProperty> for TraverserValue<S> {
fn from(value: ElementProperty) -> Self {
match value {
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_) => TraverserValue::Computed(value, None),
ElementProperty::Compressed(text) => {
TraverserValue::Computed(ElementProperty::String(text.text()), None)
}
ElementProperty::Array(array) => {
TraverserValue::Array(array.into_iter().map(|r| r.into()).collect())
}
ElementProperty::Object(obj)
| ElementProperty::Vertex(obj)
| ElementProperty::Edge(obj) => TraverserValue::Object(
obj.into_iter()
.map(|(k, v)| (TraverserValue::from(k), TraverserValue::from(v)))
.collect(),
),
ElementProperty::Map(map) => TraverserValue::Object(
map.into_iter()
.map(|(k, v)| (TraverserValue::from(k), TraverserValue::from(v)))
.collect(),
),
}
}
}
impl<S: GraphStorage> From<&str> for TraverserValue<S> {
fn from(value: &str) -> Self {
TraverserValue::Computed(ElementProperty::String(CompactString::new(value)), None)
}
}
impl<S: GraphStorage> From<String> for TraverserValue<S> {
fn from(value: String) -> Self {
TraverserValue::Computed(ElementProperty::String(CompactString::new(value)), None)
}
}
impl<S: GraphStorage> From<CompactString> for TraverserValue<S> {
fn from(value: CompactString) -> Self {
TraverserValue::Computed(ElementProperty::String(value), None)
}
}
impl<'a, S: GraphStorage> From<Cow<'a, str>> for TraverserValue<S> {
fn from(value: Cow<'a, str>) -> Self {
TraverserValue::Computed(ElementProperty::String(CompactString::new(value)), None)
}
}
impl<S: GraphStorage> TraverserValue<S> {
pub fn type_name(&self) -> &'static str {
match self {
TraverserValue::Computed(_, _) => "computed",
TraverserValue::VertexProperty(_, _, _) => "vertexProperty",
TraverserValue::Vertex(_) => "vertex",
TraverserValue::Path(_) => "path",
TraverserValue::Edge(_) => "edge",
TraverserValue::EdgeProperty(_, _, _) => "edgeProperty",
TraverserValue::Record { .. } => "record",
TraverserValue::Object(_) => "object",
TraverserValue::PathProperty(_) => "pathProperty",
TraverserValue::Entry(_) => "entry",
TraverserValue::Array(_) => "array",
TraverserValue::Schema(_) => "schema",
TraverserValue::Graph(_) => "graph",
}
}
pub(crate) fn kind_name(&self) -> String {
match self {
TraverserValue::Computed(value, _) => crate::graph::schema::value_type_name(value),
TraverserValue::Record { .. } => "object".to_string(),
other => other.type_name().to_string(),
}
}
pub fn into_node_property(&self, storage: &S) -> TraverserResult<ElementProperty> {
match self {
TraverserValue::Computed(value, _) => Ok(value.to_logical()),
TraverserValue::Vertex(node_index) => Self::materialize_vertex(storage, node_index),
TraverserValue::Record { keys, values } => {
let mut res: PropertyMap = PropertyMap::with_capacity(keys.len());
for (key, val) in keys.iter().zip(values.iter()) {
match val {
TraverserValue::Array(array) => {
let mut array_vals = Vec::with_capacity(array.len());
for v in array {
array_vals.push(v.into_node_property(storage)?);
}
res.insert(key.clone(), ElementProperty::Array(array_vals));
}
v => {
res.insert(key.clone(), v.into_node_property(storage)?);
}
}
}
Ok(ElementProperty::Object(Box::new(res)))
}
TraverserValue::Object(obj) => {
let mut res: PropertyMap = PropertyMap::with_capacity(obj.len());
let mut typed: Option<ValueMap> = None;
for (key_tv, val_tv) in obj.iter() {
let key = key_tv.into_node_property(storage)?;
let val = val_tv.into_node_property(storage)?;
if let Some(map) = typed.as_mut() {
map.insert(key, val);
continue;
}
match key {
ElementProperty::String(text) => {
res.insert(text, val);
}
other => {
let mut map = ValueMap::with_capacity(res.len() + 1);
for (k, v) in res.0.drain(..) {
map.insert(ElementProperty::String(k), v);
}
map.insert(other, val);
typed = Some(map);
}
}
}
Ok(match typed {
Some(map) => map.into_property(),
None => ElementProperty::Object(Box::new(res)),
})
}
TraverserValue::PathProperty(prop) => Ok(prop.1.to_logical()),
TraverserValue::Entry(entry) => {
let (key_tv, val_tv) = entry.as_ref();
let key = key_tv.into_node_property(storage)?;
let val = val_tv.into_node_property(storage)?;
Ok(ElementProperty::from_entries([(key, val)]))
}
TraverserValue::Edge(edge_index) => Self::materialize_edge(storage, edge_index),
TraverserValue::VertexProperty(property, node_index, _) => {
Self::materialize_vertex_property(storage, property, node_index)
}
TraverserValue::EdgeProperty(property, edge_index, _) => {
Self::materialize_edge_property(storage, property, edge_index)
}
TraverserValue::Path(path) => {
let mut objects = Vec::with_capacity(path.len());
for object in path.objects() {
objects.push(object.into_node_property(storage)?);
}
Ok(ElementProperty::Array(objects))
}
TraverserValue::Array(array) => {
let mut values = Vec::with_capacity(array.len());
for v in array {
values.push(v.into_node_property(storage)?);
}
Ok(ElementProperty::Array(values))
}
TraverserValue::Schema(schema) => Ok(schema.as_ref().into()),
TraverserValue::Graph(graph) => crate::graph::subgraph::graph_to_property(graph),
}
}
#[inline(never)]
fn materialize_vertex(
storage: &S,
node_index: &S::VertexId,
) -> TraverserResult<ElementProperty> {
let props = storage.vertex_properties(*node_index);
let props_len = props.map_or(0, |p| p.len());
let mut map = PropertyMap::with_capacity(props_len + 2);
if let Some(props) = props {
for (k, v) in props {
map.insert(k.clone(), v.to_logical());
}
}
if let Some(lbl) = storage.vertex_label(*node_index) {
map.insert(
CompactString::new("label"),
ElementProperty::String(CompactString::new(lbl)),
);
}
if !map.contains_key("id") {
let id = crate::graph::storage::stored_vertex_id(storage, *node_index)?.to_string();
map.insert(
CompactString::new("id"),
ElementProperty::String(CompactString::new(id)),
);
}
Ok(ElementProperty::Vertex(Box::new(map)))
}
#[inline(never)]
fn materialize_edge(storage: &S, edge_index: &S::EdgeId) -> TraverserResult<ElementProperty> {
let props = storage.edge_properties(*edge_index);
let props_len = props.map_or(0, |p| p.len());
let mut map = PropertyMap::with_capacity(props_len + 4);
if let Some(props) = props {
for (k, v) in props {
map.insert(k.clone(), v.to_logical());
}
}
let edge_id = crate::graph::storage::stored_edge_id(storage, *edge_index)?.to_string();
map.insert(
CompactString::new("id"),
ElementProperty::String(CompactString::new(edge_id)),
);
if let Some((v_from, v_to)) = storage.edge_endpoints(*edge_index) {
let from_id = crate::graph::storage::stored_vertex_id(storage, v_from)?.to_string();
let to_id = crate::graph::storage::stored_vertex_id(storage, v_to)?.to_string();
map.insert(
CompactString::new("_from"),
ElementProperty::String(CompactString::new(from_id)),
);
map.insert(
CompactString::new("_to"),
ElementProperty::String(CompactString::new(to_id)),
);
}
if let Some(label) = storage.edge_label(*edge_index) {
map.insert(
CompactString::new("label"),
ElementProperty::String(CompactString::new(label)),
);
} else {
map.insert(CompactString::new("label"), ElementProperty::Null);
}
Ok(ElementProperty::Edge(Box::new(map)))
}
#[inline(never)]
fn materialize_vertex_property(
storage: &S,
property: &CompactString,
node_index: &S::VertexId,
) -> TraverserResult<ElementProperty> {
if property.eq("id") {
let id = crate::graph::storage::stored_vertex_id(storage, *node_index)?.to_string();
Ok(ElementProperty::String(CompactString::new(id)))
} else if property.eq("label") {
Ok(
crate::graph::storage::stored_vertex_label(storage, *node_index)?
.map(|l| ElementProperty::String(CompactString::new(l)))
.unwrap_or(ElementProperty::Null),
)
} else if property.eq("labels") {
Ok(ElementProperty::Array(
crate::graph::storage::stored_vertex_labels(storage, *node_index)?
.iter()
.map(|l| ElementProperty::String(l.clone()))
.collect(),
))
} else {
let Some(props) = storage.vertex_properties(*node_index) else {
return Err(Box::new(crate::error::GraphError::vertex_removed().into()));
};
let prop_value = props.get(property.as_str());
Ok(prop_value
.map(|v| v.to_logical())
.unwrap_or(ElementProperty::Null))
}
}
#[inline(never)]
fn materialize_edge_property(
storage: &S,
property: &CompactString,
edge_index: &S::EdgeId,
) -> TraverserResult<ElementProperty> {
if property.eq("id") {
let id = crate::graph::storage::stored_edge_id(storage, *edge_index)?.to_string();
Ok(ElementProperty::String(CompactString::new(id)))
} else if property.eq("label") {
Ok(
crate::graph::storage::stored_edge_label(storage, *edge_index)?
.map(|l| ElementProperty::String(CompactString::new(l)))
.unwrap_or(ElementProperty::Null),
)
} else if property.eq("labels") {
Ok(ElementProperty::Array(
crate::graph::storage::stored_edge_label(storage, *edge_index)?
.map(|l| vec![ElementProperty::String(CompactString::new(l))])
.unwrap_or_default(),
))
} else {
let props = storage.edge_properties(*edge_index);
if props.is_none() && !storage.contains_edge(*edge_index) {
return Err(Box::new(crate::error::GraphError::edge_removed().into()));
}
let prop_value = props.and_then(|map| map.get(property.as_str()));
Ok(prop_value
.map(|v| v.to_logical())
.unwrap_or(ElementProperty::Null))
}
}
pub fn as_schema(&self) -> Option<&GraphSchema> {
match self {
TraverserValue::Schema(schema) => Some(schema.as_ref()),
_ => None,
}
}
pub fn into_schema(self) -> Option<GraphSchema> {
match self {
TraverserValue::Schema(schema) => Some(*schema),
_ => None,
}
}
fn t_token_error(
context: &TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> Box<crate::error::TraverserError> {
let actual = match value {
TraverserValue::VertexProperty(_, _, PropertyHandleKind::Value)
| TraverserValue::EdgeProperty(_, _, PropertyHandleKind::Value) => {
crate::traversal::step::local_scope::stored_value(context, value).map_or_else(
|| "null".to_string(),
|stored| TraverserValue::<S>::Computed(stored.clone(), None).kind_name(),
)
}
other => other.kind_name(),
};
Box::new(crate::error::TraverserError::cast_exception_str(
crate::error::T_TOKEN_EXPECTED,
actual,
))
}
pub(crate) fn fetch_traverser_value_by<'g>(
context: &mut TraverserContext<'_, S>,
traverser: &Traverser<S>,
traverser_value: &TraverserValue<S>,
by_val: &By<'g, S>,
) -> TraverserResult<Option<TraverserValue<S>>> {
Ok(match by_val {
By::Identity => Some(traverser_value.clone()),
By::Value => match traverser_value {
TraverserValue::VertexProperty(_, _, PropertyHandleKind::Element)
| TraverserValue::EdgeProperty(_, _, PropertyHandleKind::Element) => {
crate::traversal::step::local_scope::stored_value(context, traverser_value)
.map(|stored| TraverserValue::Computed(stored.clone(), None))
}
TraverserValue::PathProperty(property) => {
Some(TraverserValue::Computed(property.1.clone(), None))
}
TraverserValue::Entry(entry) => Some(entry.1.clone()),
other => return Err(Self::t_token_error(context, other)),
},
By::Key => match traverser_value {
TraverserValue::VertexProperty(key, _, PropertyHandleKind::Element)
| TraverserValue::EdgeProperty(key, _, PropertyHandleKind::Element) => Some(
TraverserValue::Computed(ElementProperty::String(key.clone()), None),
),
TraverserValue::PathProperty(property) => Some(TraverserValue::Computed(
ElementProperty::String(property.0.clone()),
None,
)),
TraverserValue::Entry(entry) => Some(entry.0.clone()),
other => return Err(Self::t_token_error(context, other)),
},
By::Label => {
Self::reject_token_target(traverser_value, "T.label")?;
let v = TraverserValue::fetch_label(context, traverser_value)?;
v.map(|v| {
TraverserValue::Computed(
ElementProperty::String(CompactString::new(v.as_ref())),
None,
)
})
}
By::Id => {
Self::reject_token_target(traverser_value, "T.id")?;
let v = TraverserValue::fetch_id(context, traverser_value)?;
v.map(|v| {
TraverserValue::Computed(
ElementProperty::String(CompactString::new(v.as_ref())),
None,
)
})
}
By::Property(prop) => {
let v = TraverserValue::fetch_property(context, traverser_value, prop)?;
v.map(|v| v.into())
}
By::Traversal(child) => child
.first(&mut *context, traverser)?
.map(|first| first.value),
By::Order(_) => None,
By::Count => None,
By::JsonPath(path) => {
TraverserValue::resolve_json_path(context, traverser_value, path.segments())
}
By::Labels(labels) => {
return Err(Box::new(crate::error::TraverserError::QueryExecution {
query: format!("by({})", crate::value::literal::rhai_string_array(labels)),
reason: "a vertex-label list is not a projection".to_string(),
}));
}
By::Column(column) => TraverserValue::column_of(context, traverser_value, *column),
})
}
fn reject_token_target(value: &TraverserValue<S>, token: &'static str) -> TraverserResult<()> {
let supported = match value {
TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::Object(_)
| TraverserValue::Record { .. } => true,
TraverserValue::VertexProperty(_, _, PropertyHandleKind::Element)
| TraverserValue::PathProperty(_) => {
if token == "T.id" {
return Err(Box::new(crate::error::TraverserError::QueryExecution {
query: "id".to_string(),
reason:
crate::traversal::step::map_steps::step_id::PROPERTY_ELEMENT_ID_REASON
.to_string(),
}));
}
true
}
TraverserValue::VertexProperty(key, _, PropertyHandleKind::Value)
| TraverserValue::EdgeProperty(key, _, _) => key == "label" || key == "id",
TraverserValue::Computed(..)
| TraverserValue::Path(_)
| TraverserValue::Entry(_)
| TraverserValue::Array(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => false,
};
if supported {
return Ok(());
}
Err(Box::new(crate::error::TraverserError::QueryExecution {
query: format!("by({token})"),
reason: format!(
"{token} can only be read from a vertex, an edge or a vertex property, not from a {}",
match value {
TraverserValue::VertexProperty(_, _, PropertyHandleKind::Value)
| TraverserValue::EdgeProperty(..) => "property value",
other => other.type_name(),
}
),
}))
}
pub(crate) fn path_labels(path: &crate::value::path::TraverserPath<S>) -> TraverserValue<S> {
TraverserValue::Array(
path.steps
.iter()
.map(|step| {
TraverserValue::Array(
step.label
.iter()
.flatten()
.map(|label| {
TraverserValue::Computed(
ElementProperty::String(label.clone()),
None,
)
})
.collect(),
)
})
.collect(),
)
}
fn column_of(
context: &TraverserContext<'_, S>,
value: &TraverserValue<S>,
column: crate::value::Column,
) -> Option<TraverserValue<S>> {
use crate::value::Column;
fn property_column<S: GraphStorage>(
property: &ElementProperty,
column: Column,
) -> Option<TraverserValue<S>> {
match property {
ElementProperty::Object(entries) => Some(TraverserValue::Array(match column {
Column::Keys => entries
.keys()
.map(|key| {
TraverserValue::Computed(ElementProperty::String(key.clone()), None)
})
.collect(),
Column::Values => entries
.values()
.cloned()
.map(TraverserValue::from)
.collect(),
})),
ElementProperty::Map(entries) => Some(TraverserValue::Array(match column {
Column::Keys => entries.keys().cloned().map(TraverserValue::from).collect(),
Column::Values => entries
.values()
.cloned()
.map(TraverserValue::from)
.collect(),
})),
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Compressed(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_)
| ElementProperty::Vertex(_)
| ElementProperty::Edge(_)
| ElementProperty::Array(_) => None,
}
}
match value {
TraverserValue::Entry(entry) => Some(match column {
Column::Keys => entry.0.clone(),
Column::Values => entry.1.clone(),
}),
TraverserValue::Object(map) => Some(TraverserValue::Array(match column {
Column::Keys => map.keys().cloned().collect(),
Column::Values => map.values().cloned().collect(),
})),
TraverserValue::Record { keys, values } => Some(TraverserValue::Array(match column {
Column::Keys => keys
.iter()
.map(|key| TraverserValue::Computed(ElementProperty::String(key.clone()), None))
.collect(),
Column::Values => values.clone(),
})),
TraverserValue::Computed(property, _) => property_column(property, column),
TraverserValue::PathProperty(prop) => property_column(&prop.1, column),
TraverserValue::VertexProperty(_, _, _) | TraverserValue::EdgeProperty(_, _, _) => {
crate::traversal::step::local_scope::stored_value(context, value)
.and_then(|stored| property_column(stored, column))
}
TraverserValue::Path(path) => match column {
Column::Keys => Some(Self::path_labels(path)),
Column::Values => Some(TraverserValue::Array(path.objects().cloned().collect())),
},
TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::Array(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => None,
}
}
pub(crate) fn resolve_json_path(
context: &TraverserContext<'_, S>,
traverser_value: &TraverserValue<S>,
segments: &[crate::value::json_path::JsonPathSegment],
) -> Option<TraverserValue<S>> {
use crate::value::json_path::{
JsonPathSegment, descend_properties, descend_property, walk_json_path,
};
match traverser_value {
TraverserValue::Vertex(node_index) => {
let props = context.storage.vertex_properties(*node_index)?;
descend_properties(props, segments).map(|leaf| leaf.clone().into())
}
TraverserValue::Edge(edge_index) => {
let props = context.storage.edge_properties(*edge_index)?;
descend_properties(props, segments).map(|leaf| leaf.clone().into())
}
TraverserValue::VertexProperty(prop_name, node_index, _) => {
let root = context
.storage
.vertex_properties(*node_index)?
.get(prop_name.as_str())?;
descend_property(root, segments).map(|leaf| leaf.clone().into())
}
TraverserValue::EdgeProperty(prop_name, edge_index, _) => {
let root = context
.storage
.edge_properties(*edge_index)?
.get(prop_name.as_str())?;
descend_property(root, segments).map(|leaf| leaf.clone().into())
}
TraverserValue::Computed(root, _) => {
descend_property(root, segments).map(|leaf| leaf.clone().into())
}
TraverserValue::Object(obj) => {
let (JsonPathSegment::Key(first), rest) = segments.split_first()? else {
return None;
};
let lookup = TraverserValue::Computed(ElementProperty::String(first.clone()), None);
walk_json_path(obj.get(&lookup)?.clone(), rest)
}
TraverserValue::Record { keys, values } => {
let (JsonPathSegment::Key(first), rest) = segments.split_first()? else {
return None;
};
let idx = keys.iter().position(|k| k == first.as_str())?;
walk_json_path(values[idx].clone(), rest)
}
TraverserValue::Array(arr) => {
let (JsonPathSegment::Index(index), rest) = segments.split_first()? else {
return None;
};
let idx = crate::value::json_path::resolve_index(*index, arr.len())?;
walk_json_path(arr.get(idx)?.clone(), rest)
}
_ => None,
}
}
pub(crate) fn fetch_property(
context: &TraverserContext<'_, S>,
traverser_value: &TraverserValue<S>,
prop: &str,
) -> TraverserResult<Option<ElementProperty>> {
match traverser_value {
TraverserValue::Computed(node_property, _) => Ok(match node_property {
ElementProperty::Object(obj) => obj.get(prop).map(ElementProperty::to_logical),
ElementProperty::Vertex(vertex) => {
vertex.get(prop).map(ElementProperty::to_logical)
}
ElementProperty::Edge(edge) => edge.get(prop).map(ElementProperty::to_logical),
ElementProperty::Map(map) => map.get_str(prop).map(ElementProperty::to_logical),
ElementProperty::Null => None,
ElementProperty::String(_) | ElementProperty::Compressed(_) => None,
ElementProperty::Array(_) => None,
ElementProperty::Number(_) => None,
ElementProperty::Boolean(_) => None,
ElementProperty::Uuid(_) => None,
}),
TraverserValue::VertexProperty(vertex_property, vertex_index, _) => {
let v = context.storage.vertex_properties(*vertex_index);
let v = v.and_then(|v| v.get(vertex_property));
match v {
Some(v) => Ok(match v {
ElementProperty::Object(obj) => {
obj.get(prop).map(ElementProperty::to_logical)
}
ElementProperty::Vertex(vertex) => {
vertex.get(prop).map(ElementProperty::to_logical)
}
ElementProperty::Edge(edge) => {
edge.get(prop).map(ElementProperty::to_logical)
}
ElementProperty::Map(map) => {
map.get_str(prop).map(ElementProperty::to_logical)
}
ElementProperty::Null => None,
ElementProperty::String(_) | ElementProperty::Compressed(_) => None,
ElementProperty::Array(_) => None,
ElementProperty::Number(_) => None,
ElementProperty::Boolean(_) => None,
ElementProperty::Uuid(_) => None,
}),
None => Ok(None),
}
}
TraverserValue::Vertex(vertex_index) => {
let v = context.storage.vertex_properties(*vertex_index);
Ok(v.and_then(|v| v.get(prop)).map(ElementProperty::to_logical))
}
TraverserValue::Edge(edge_index) => {
let e = context.storage.edge_properties(*edge_index);
Ok(e.and_then(|e| e.get(prop)).map(ElementProperty::to_logical))
}
TraverserValue::Record { keys, values } => {
if let Some(idx) = keys.iter().position(|k| k == prop) {
values[idx].into_node_property(context.storage).map(Some)
} else {
Ok(None)
}
}
TraverserValue::EdgeProperty(edge_property, edge_index, _) => {
let e = context.storage.edge_properties(*edge_index);
let e = e.and_then(|e| e.get(edge_property));
match e {
Some(v) => Ok(match v {
ElementProperty::Object(obj) => {
obj.get(prop).map(ElementProperty::to_logical)
}
ElementProperty::Vertex(vertex) => {
vertex.get(prop).map(ElementProperty::to_logical)
}
ElementProperty::Edge(edge) => {
edge.get(prop).map(ElementProperty::to_logical)
}
ElementProperty::Map(map) => {
map.get_str(prop).map(ElementProperty::to_logical)
}
ElementProperty::Null => None,
ElementProperty::String(_) | ElementProperty::Compressed(_) => None,
ElementProperty::Array(_) => None,
ElementProperty::Number(_) => None,
ElementProperty::Boolean(_) => None,
ElementProperty::Uuid(_) => None,
}),
None => Ok(None),
}
}
TraverserValue::Object(obj) => {
let key = TraverserValue::Computed(
ElementProperty::String(CompactString::new(prop)),
None,
);
match obj.get(&key) {
Some(v) => v.into_node_property(context.storage).map(Some),
None => Ok(None),
}
}
TraverserValue::Path(_) => Ok(None),
TraverserValue::PathProperty(_) | TraverserValue::Entry(_) => Ok(None),
TraverserValue::Array(_) => Ok(None),
TraverserValue::Schema(_) | TraverserValue::Graph(_) => Ok(None),
}
}
pub(crate) fn fetch_id<'a>(
context: &'a TraverserContext<'_, S>,
traverser_value: &'a TraverserValue<S>,
) -> TraverserResult<Option<Cow<'a, str>>> {
Ok(match traverser_value {
TraverserValue::Vertex(vertex_index) => {
let id = Cow::Borrowed(crate::graph::storage::stored_vertex_id(
&*context.storage,
*vertex_index,
)?);
Some(id)
}
TraverserValue::Edge(edge_index) => {
let id = Cow::Borrowed(crate::graph::storage::stored_edge_id(
&*context.storage,
*edge_index,
)?);
Some(id)
}
TraverserValue::VertexProperty(prop, vertex_index, _) => {
if prop == "id" {
let id = Cow::Borrowed(crate::graph::storage::stored_vertex_id(
&*context.storage,
*vertex_index,
)?);
Some(id)
} else {
None
}
}
TraverserValue::EdgeProperty(prop, edge_index, _) => {
if prop == "id" {
let id = Cow::Borrowed(crate::graph::storage::stored_edge_id(
&*context.storage,
*edge_index,
)?);
Some(id)
} else {
None
}
}
TraverserValue::Object(obj) => {
let id_key = TraverserValue::Computed(
ElementProperty::String(CompactString::new("id")),
None,
);
match obj.get(&id_key) {
Some(TraverserValue::Computed(ElementProperty::String(id), _)) => {
Some(Cow::Borrowed(id.as_str()))
}
_ => None,
}
}
TraverserValue::Record { keys, values } => {
if let Some(idx) = keys.iter().position(|k| k == "id") {
match &values[idx] {
TraverserValue::Computed(ElementProperty::String(id), _) => {
Some(Cow::Borrowed(id.as_str()))
}
v => v
.into_node_property(context.storage)
.ok()
.and_then(|ep| match ep {
ElementProperty::String(s) => Some(Cow::Owned(s.into_string())),
_ => None,
}),
}
} else {
None
}
}
TraverserValue::Computed(_, _) => None,
TraverserValue::Path(_) => None,
TraverserValue::PathProperty(_) | TraverserValue::Entry(_) => None,
TraverserValue::Array(_) => None,
TraverserValue::Schema(_) | TraverserValue::Graph(_) => None,
})
}
pub(crate) fn fetch_label<'a>(
context: &'a TraverserContext<'_, S>,
traverser_value: &'a TraverserValue<S>,
) -> TraverserResult<Option<Cow<'a, str>>> {
Ok(match traverser_value {
TraverserValue::Vertex(vertex_index) => {
crate::graph::storage::stored_vertex_label(&*context.storage, *vertex_index)?
.map(Cow::Borrowed)
}
TraverserValue::Edge(edge_index) => {
crate::graph::storage::stored_edge_label(&*context.storage, *edge_index)?
.map(Cow::Borrowed)
}
TraverserValue::VertexProperty(key, _, PropertyHandleKind::Element) => {
Some(Cow::Borrowed(key.as_str()))
}
TraverserValue::PathProperty(prop) => Some(Cow::Borrowed(prop.0.as_str())),
TraverserValue::VertexProperty(prop, vertex_index, PropertyHandleKind::Value) => {
if prop == "label" {
crate::graph::storage::stored_vertex_label(&*context.storage, *vertex_index)?
.map(Cow::Borrowed)
} else {
None
}
}
TraverserValue::EdgeProperty(prop, edge_index, _) => {
if prop == "label" {
crate::graph::storage::stored_edge_label(&*context.storage, *edge_index)?
.map(Cow::Borrowed)
} else {
None
}
}
TraverserValue::Object(obj) => {
let key = TraverserValue::Computed(
ElementProperty::String(CompactString::new("label")),
None,
);
match obj.get(&key) {
Some(TraverserValue::Computed(ElementProperty::String(label), _)) => {
Some(Cow::Borrowed(label.as_str()))
}
Some(v) => v
.into_node_property(context.storage)
.ok()
.and_then(|ep| match ep {
ElementProperty::String(s) => Some(Cow::Owned(s.into_string())),
_ => None,
}),
None => None,
}
}
TraverserValue::Record { keys, values } => {
if let Some(idx) = keys.iter().position(|k| k == "label") {
match &values[idx] {
TraverserValue::Computed(ElementProperty::String(label), _) => {
Some(Cow::Borrowed(label.as_str()))
}
v => v
.into_node_property(context.storage)
.ok()
.and_then(|ep| match ep {
ElementProperty::String(s) => Some(Cow::Owned(s.into_string())),
_ => None,
}),
}
} else {
None
}
}
TraverserValue::Computed(_, _) => None,
TraverserValue::Path(_) => None,
TraverserValue::Entry(_) => None,
TraverserValue::Array(_) => None,
TraverserValue::Schema(_) | TraverserValue::Graph(_) => None,
})
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ArgStrings {
None,
String(String),
List(Vec<String>),
}
impl From<String> for ArgStrings {
fn from(value: String) -> Self {
ArgStrings::String(value)
}
}
impl From<&str> for ArgStrings {
fn from(value: &str) -> Self {
ArgStrings::String(value.to_string())
}
}
impl From<Vec<&str>> for ArgStrings {
fn from(value: Vec<&str>) -> Self {
ArgStrings::List(value.iter().map(|x| x.to_string()).collect())
}
}
impl From<Vec<String>> for ArgStrings {
fn from(value: Vec<String>) -> Self {
ArgStrings::List(value)
}
}
impl From<&Vec<String>> for ArgStrings {
fn from(value: &Vec<String>) -> Self {
ArgStrings::List(value.clone())
}
}
impl From<CompactString> for ArgStrings {
fn from(value: CompactString) -> Self {
ArgStrings::String(value.into_string())
}
}
impl From<&CompactString> for ArgStrings {
fn from(value: &CompactString) -> Self {
ArgStrings::String(value.to_string())
}
}
impl From<Vec<CompactString>> for ArgStrings {
fn from(value: Vec<CompactString>) -> Self {
ArgStrings::List(value.into_iter().map(|x| x.into_string()).collect())
}
}
impl From<&Vec<CompactString>> for ArgStrings {
fn from(value: &Vec<CompactString>) -> Self {
ArgStrings::List(value.iter().map(|x| x.to_string()).collect())
}
}
impl From<Option<()>> for ArgStrings {
fn from(_value: Option<()>) -> Self {
ArgStrings::None
}
}
fn arg_text(value: &ElementProperty) -> String {
match value {
ElementProperty::String(text) => text.to_string(),
ElementProperty::Compressed(text) => text.text().into_string(),
ElementProperty::Uuid(_) => physical::to_display_text(value).into_string(),
other => physical::to_json_text(other).unwrap_or_else(|err| err.to_string()),
}
}
impl From<ElementProperty> for ArgStrings {
fn from(value: ElementProperty) -> Self {
match value {
ElementProperty::String(value) => ArgStrings::String(value.into_string()),
ElementProperty::Compressed(text) => ArgStrings::String(text.text().into_string()),
ElementProperty::Object(obj)
| ElementProperty::Vertex(obj)
| ElementProperty::Edge(obj) => {
let id = obj.get("id");
match id {
Some(ElementProperty::String(value)) => ArgStrings::String(value.to_string()),
_ => ArgStrings::List(Vec::new()),
}
}
ElementProperty::Null | ElementProperty::Map(_) => ArgStrings::List(Vec::new()),
ElementProperty::Array(array) => ArgStrings::List(array.iter().map(arg_text).collect()),
ElementProperty::Number(n) => ArgStrings::String(n.n.to_string()),
ElementProperty::Boolean(b) => ArgStrings::String(b.to_string()),
ElementProperty::Uuid(u) => ArgStrings::String(uuid::format_canonical(u).into_string()),
}
}
}
impl From<&ElementProperty> for ArgStrings {
fn from(value: &ElementProperty) -> Self {
match value {
ElementProperty::String(value) => ArgStrings::String(value.to_string()),
ElementProperty::Compressed(text) => ArgStrings::String(text.text().into_string()),
ElementProperty::Object(obj)
| ElementProperty::Vertex(obj)
| ElementProperty::Edge(obj) => {
let id = obj.get("id");
match id {
Some(ElementProperty::String(value)) => ArgStrings::String(value.to_string()),
_ => ArgStrings::List(Vec::new()),
}
}
ElementProperty::Null | ElementProperty::Map(_) => ArgStrings::List(Vec::new()),
ElementProperty::Array(array) => ArgStrings::List(array.iter().map(arg_text).collect()),
ElementProperty::Number(n) => ArgStrings::String(n.n.to_string()),
ElementProperty::Boolean(b) => ArgStrings::String(b.to_string()),
ElementProperty::Uuid(u) => {
ArgStrings::String(uuid::format_canonical(*u).into_string())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::TraversalGraph;
use fnv::FnvHasher;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut s = FnvHasher::default();
t.hash(&mut s);
s.finish()
}
#[test]
fn test_traverser_value_hash_and_eq_without_panics() {
let path: TraverserPath<TraversalGraph> = TraverserPath { steps: vec![] };
let val_path = TraverserValue::Path(path.clone());
assert_eq!(val_path, val_path);
let _ = calculate_hash(&val_path);
let mut obj: StreamMap<TraversalGraph> = StreamMap::default();
let k = TraverserValue::Computed(ElementProperty::String("key".into()), None);
let v = TraverserValue::Computed(ElementProperty::Number(Number::from_i64(42)), None);
obj.insert(k, v);
let val_obj = TraverserValue::Object(obj);
assert_eq!(val_obj, val_obj);
let _ = calculate_hash(&val_obj);
let arr: Vec<TraverserValue<TraversalGraph>> = vec![
TraverserValue::Computed(ElementProperty::Boolean(true), None),
TraverserValue::Computed(ElementProperty::Number(Number::from_f64(1.25)), None),
];
let val_arr = TraverserValue::Array(arr);
assert_eq!(val_arr, val_arr);
let _ = calculate_hash(&val_arr);
}
#[test]
fn test_compact_string_inline_and_conversions() {
let short_str = "hello_world"; let compact = CompactString::new(short_str);
assert!(!compact.is_heap_allocated());
let ep_from_str: ElementProperty = short_str.into();
assert_eq!(ep_from_str.as_str(), Some("hello_world"));
assert_eq!(ep_from_str.as_string(), Some("hello_world"));
let string_val = String::from("a_slightly_longer_string_for_testing");
let ep_from_string: ElementProperty = string_val.clone().into();
assert_eq!(
ep_from_string.as_str(),
Some("a_slightly_longer_string_for_testing")
);
assert_eq!(
ep_from_string.as_string(),
Some("a_slightly_longer_string_for_testing")
);
let cow_val: Cow<'_, str> = Cow::Borrowed("cow_string");
let ep_from_cow: ElementProperty = cow_val.into();
assert_eq!(ep_from_cow.as_str(), Some("cow_string"));
let tv_from_str: TraverserValue<TraversalGraph> = short_str.into();
match tv_from_str {
TraverserValue::Computed(ElementProperty::String(cs), None) => {
assert!(!cs.is_heap_allocated());
assert_eq!(cs.as_str(), "hello_world");
}
_ => panic!("Expected Computed(ElementProperty::String)"),
}
}
#[test]
fn element_property_is_boxed_down_to_32_bytes() {
assert_eq!(std::mem::size_of::<ElementProperty>(), 32);
assert_eq!(std::mem::align_of::<ElementProperty>(), 16);
}
fn sample_object() -> PropertyMap {
let mut inner: PropertyMap = PropertyMap::default();
inner.insert(
CompactString::new("k"),
ElementProperty::Number(Number::from_i64(1)),
);
let mut outer: PropertyMap = PropertyMap::default();
outer.insert(
CompactString::new("name"),
ElementProperty::String("alice".into()),
);
outer.insert(
CompactString::new("tags"),
ElementProperty::Array(vec!["x".into(), "y".into()]),
);
outer.insert(
CompactString::new("nested"),
ElementProperty::Object(Box::new(inner)),
);
outer
}
#[test]
fn object_converts_to_traverser_value_recursively_without_panic() {
let ep = ElementProperty::Object(Box::new(sample_object()));
let tv: TraverserValue<TraversalGraph> = (&ep).into();
match &tv {
TraverserValue::Object(obj) => assert_eq!(obj.len(), 3),
_ => panic!("expected TraverserValue::Object"),
}
let tv_owned: TraverserValue<TraversalGraph> = ep.into();
assert_eq!(tv, tv_owned);
}
#[test]
fn vertex_and_edge_element_properties_convert_to_traverser_object() {
let vertex = ElementProperty::Vertex(Box::new(sample_object()));
let tv: TraverserValue<TraversalGraph> = vertex.into();
assert!(matches!(tv, TraverserValue::Object(_)));
let edge = ElementProperty::Edge(Box::new(sample_object()));
let tv: TraverserValue<TraversalGraph> = edge.into();
assert!(matches!(tv, TraverserValue::Object(_)));
}
#[test]
fn argument_lists_render_items_as_physical_text_never_debug() {
let list: ArgStrings = ElementProperty::Array(vec![
ElementProperty::String("person".into()),
ElementProperty::Number(Number::from_i64(42)),
ElementProperty::Null,
ElementProperty::Object(Box::new(sample_object())),
ElementProperty::Vertex(Box::new(sample_object())),
])
.into();
let ArgStrings::List(items) = list else {
panic!("an array converts to an argument list");
};
assert_eq!(items[0], "person");
assert_eq!(items[1], "42");
assert_eq!(items[2], "null");
let json = r#"{"name":"alice","tags":["x","y"],"nested":{"k":1}}"#;
assert_eq!(items[3], json);
assert_eq!(items[4], json);
}
#[test]
fn schema_mode_is_spelled_as_in_the_schema_file() {
let schema = GraphSchema::new().with_mode(crate::graph::schema::SchemaMode::Open);
let ElementProperty::Object(map) = schema.to_value() else {
panic!("a schema renders as an object");
};
assert_eq!(
map.get("mode"),
Some(&ElementProperty::String("open".into()))
);
}
#[test]
fn scalar_arguments_convert_without_panic() {
let number_arg: ArgStrings = ElementProperty::Number(Number::from_i64(42)).into();
assert!(matches!(number_arg, ArgStrings::String(s) if s == "42"));
let bool_arg: ArgStrings = (&ElementProperty::Boolean(false)).into();
assert!(matches!(bool_arg, ArgStrings::String(s) if s == "false"));
}
#[test]
fn values_naming_nothing_are_an_empty_list_never_no_argument() {
let empty = |arg: ArgStrings| matches!(arg, ArgStrings::List(items) if items.is_empty());
let map = ElementProperty::Map(Default::default());
let no_id = ElementProperty::Object(Box::new(
[("id", ElementProperty::Number(Number::from_i64(1)))]
.into_iter()
.collect(),
));
for value in [
ElementProperty::Null,
map,
no_id,
ElementProperty::Object(Default::default()),
] {
assert!(empty((&value).into()), "{value:?} by reference");
assert!(empty(value.clone().into()), "{value:?} owned");
}
let with_id = ElementProperty::Object(Box::new(
[("id", ElementProperty::String("v1".into()))]
.into_iter()
.collect(),
));
assert!(matches!(ArgStrings::from(with_id), ArgStrings::String(s) if s == "v1"));
assert!(matches!(ArgStrings::from(None::<()>), ArgStrings::None));
}
#[test]
fn fetch_property_and_label_handle_edge_property_and_object_arms_without_panic() {
use crate::traversal::context::{TraversalStrategy, TraverserContext};
let mut storage = TraversalGraph::tinkerpop_modern();
let edge_index = storage.get_edge_by_id("0").unwrap().0;
let context =
TraverserContext::new(&mut storage, TraversalStrategy::none(), Default::default());
let edge_prop = TraverserValue::EdgeProperty(
CompactString::new("weight"),
edge_index,
PropertyHandleKind::Value,
);
assert_eq!(
TraverserValue::fetch_property(&context, &edge_prop, "anything").unwrap(),
None
);
let mut obj: StreamMap<TraversalGraph> = StreamMap::default();
obj.insert(
TraverserValue::Computed(ElementProperty::String("name".into()), None),
TraverserValue::Computed(ElementProperty::String("alice".into()), None),
);
let object_value = TraverserValue::Object(obj);
assert_eq!(
TraverserValue::fetch_property(&context, &object_value, "name").unwrap(),
Some(ElementProperty::String("alice".into()))
);
assert_eq!(
TraverserValue::fetch_property(&context, &object_value, "missing").unwrap(),
None
);
assert_eq!(
TraverserValue::fetch_label(&context, &object_value).unwrap(),
None
);
let mut labeled_obj: StreamMap<TraversalGraph> = StreamMap::default();
labeled_obj.insert(
TraverserValue::Computed(ElementProperty::String("label".into()), None),
TraverserValue::Computed(ElementProperty::String("person".into()), None),
);
let labeled_value = TraverserValue::Object(labeled_obj);
let label = TraverserValue::fetch_label(&context, &labeled_value).unwrap();
assert_eq!(label.as_deref(), Some("person"));
}
#[test]
fn traverser_stays_small_after_boxing_schema() {
assert!(
std::mem::size_of::<crate::traversal::traverser::Traverser<TraversalGraph>>() <= 96,
"Traverser<TraversalGraph> grew past its expected small-handle size; check for a new \
unboxed large variant in TraverserValue"
);
}
#[test]
fn compare_element_property_numeric_cross_sub_type_equality_and_ordering() {
let int_one = ElementProperty::Number(Number::from_i64(1));
let float_one = ElementProperty::Number(Number::from_f64(1.0));
let int_two = ElementProperty::Number(Number::from_i64(2));
let float_half = ElementProperty::Number(Number::from_f64(0.5));
assert_eq!(
compare_element_property(&int_one, &float_one),
Ordering::Equal
);
assert_eq!(
compare_element_property(&float_one, &int_one),
Ordering::Equal
);
assert_eq!(compare_element_property(&int_one, &int_two), Ordering::Less);
assert_eq!(
compare_element_property(&int_two, &int_one),
Ordering::Greater
);
assert_eq!(
compare_element_property(&float_half, &int_one),
Ordering::Less
);
assert_eq!(
compare_element_property(&int_one, &float_half),
Ordering::Greater
);
}
#[test]
fn compare_element_property_string_lexicographic_ordering() {
let apple = ElementProperty::String(CompactString::new("apple"));
let banana = ElementProperty::String(CompactString::new("banana"));
let apple2 = ElementProperty::String(CompactString::new("apple"));
assert_eq!(compare_element_property(&apple, &banana), Ordering::Less);
assert_eq!(compare_element_property(&banana, &apple), Ordering::Greater);
assert_eq!(compare_element_property(&apple, &apple2), Ordering::Equal);
}
#[test]
fn compare_element_property_boolean_false_before_true() {
let f = ElementProperty::Boolean(false);
let t = ElementProperty::Boolean(true);
assert_eq!(compare_element_property(&f, &t), Ordering::Less);
assert_eq!(compare_element_property(&t, &f), Ordering::Greater);
assert_eq!(compare_element_property(&f, &f), Ordering::Equal);
}
#[test]
fn compare_element_property_mixed_types_do_not_panic_and_use_defined_type_rank() {
let number = ElementProperty::Number(Number::from_i64(42));
let string = ElementProperty::String(CompactString::new("42"));
let boolean = ElementProperty::Boolean(true);
let null = ElementProperty::Null;
assert_eq!(compare_element_property(&null, &boolean), Ordering::Less);
assert_eq!(compare_element_property(&boolean, &number), Ordering::Less);
assert_eq!(compare_element_property(&number, &string), Ordering::Less);
assert_eq!(
compare_element_property(&string, &number),
Ordering::Greater
);
for _ in 0..3 {
assert_eq!(compare_element_property(&number, &string), Ordering::Less);
}
let mut values = vec![
ElementProperty::String(CompactString::new("z")),
ElementProperty::Number(Number::from_f64(3.5)),
ElementProperty::Boolean(false),
ElementProperty::Null,
ElementProperty::Uuid(7),
];
values.sort_by(compare_element_property);
assert_eq!(
values,
vec![
ElementProperty::Null,
ElementProperty::Boolean(false),
ElementProperty::Number(Number::from_f64(3.5)),
ElementProperty::String(CompactString::new("z")),
ElementProperty::Uuid(7),
]
);
}
#[test]
fn compare_element_property_array_element_wise_then_by_length() {
let short = ElementProperty::Array(vec![ElementProperty::Number(Number::from_i64(1))]);
let long = ElementProperty::Array(vec![
ElementProperty::Number(Number::from_i64(1)),
ElementProperty::Number(Number::from_i64(2)),
]);
let bigger_first =
ElementProperty::Array(vec![ElementProperty::Number(Number::from_i64(9))]);
assert_eq!(compare_element_property(&short, &long), Ordering::Less);
assert_eq!(
compare_element_property(&short, &bigger_first),
Ordering::Less
);
assert_eq!(
compare_element_property(&short, &short.clone()),
Ordering::Equal
);
}
#[test]
fn compare_element_property_orders_elements_by_id_and_vertices_before_edges() {
fn element(entries: &[(&str, &str)]) -> Box<PropertyMap> {
let mut element = PropertyMap::default();
for (key, value) in entries {
element.insert(
CompactString::new(key),
ElementProperty::String(CompactString::new(value)),
);
}
Box::new(element)
}
let edge_10 = ElementProperty::Edge(element(&[("_from", "4"), ("_to", "5"), ("id", "10")]));
let edge_11 = ElementProperty::Edge(element(&[("_from", "4"), ("_to", "3"), ("id", "11")]));
assert_eq!(compare_element_property(&edge_10, &edge_11), Ordering::Less);
assert_eq!(
compare_element_property(&edge_11, &edge_10),
Ordering::Greater
);
assert_eq!(
compare_element_property(&edge_10, &edge_10.clone()),
Ordering::Equal
);
let vertex_9 = ElementProperty::Vertex(element(&[("id", "9"), ("name", "z")]));
assert_eq!(
compare_element_property(&vertex_9, &edge_10),
Ordering::Less
);
assert_eq!(
compare_element_property(&edge_10, &vertex_9),
Ordering::Greater
);
let no_id_a = ElementProperty::Vertex(element(&[("name", "a")]));
let no_id_b = ElementProperty::Vertex(element(&[("name", "b")]));
assert_eq!(compare_element_property(&no_id_a, &no_id_b), Ordering::Less);
}
}