use std::collections::HashSet;
use fnv::FnvBuildHasher;
use crate::graph::GraphStorage;
use crate::traversal::optimizer::path_analysis::{PathAnalysis, PathRequirement};
use crate::traversal::step::flatbarrier_steps::step_dedup::DedupKey;
use crate::traversal::step::local_scope::{LocalShape, classify, is_null, stored_value};
use crate::*;
type KeySet<S> = HashSet<DedupKey<S>, FnvBuildHasher>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ListFunction {
Combine,
Difference,
Disjunct,
Intersect,
Merge,
Product,
}
impl ListFunction {
pub(crate) fn name(self) -> &'static str {
match self {
ListFunction::Combine => "combine",
ListFunction::Difference => "difference",
ListFunction::Disjunct => "disjunct",
ListFunction::Intersect => "intersect",
ListFunction::Merge => "merge",
ListFunction::Product => "product",
}
}
fn uses_keys(self) -> bool {
match self {
ListFunction::Combine | ListFunction::Product => false,
ListFunction::Difference
| ListFunction::Disjunct
| ListFunction::Intersect
| ListFunction::Merge => true,
}
}
}
struct ConstList<S: GraphStorage> {
items: Vec<ElementProperty>,
keys: Vec<DedupKey<S>>,
set: KeySet<S>,
}
impl<S: GraphStorage> Clone for ConstList<S> {
fn clone(&self) -> Self {
Self {
items: self.items.clone(),
keys: self.keys.clone(),
set: self.set.clone(),
}
}
}
enum Operand<'g, S: GraphStorage + 'g> {
List(ConstList<S>),
Map(ElementProperty),
Invalid(ElementProperty),
Traversal(AnonymousTraversal<'g, S>),
}
impl<'g, S: GraphStorage + 'g> Clone for Operand<'g, S> {
fn clone(&self) -> Self {
match self {
Operand::List(list) => Operand::List(list.clone()),
Operand::Map(map) => Operand::Map(map.clone()),
Operand::Invalid(value) => Operand::Invalid(value.clone()),
Operand::Traversal(traversal) => Operand::Traversal(traversal.clone()),
}
}
}
pub(super) struct StepListFunction<'g, S: GraphStorage + 'g> {
kind: ListFunction,
operand: Operand<'g, S>,
}
impl<'g, S: GraphStorage + 'g> Clone for StepListFunction<'g, S> {
fn clone(&self) -> Self {
Self {
kind: self.kind,
operand: self.operand.clone(),
}
}
}
impl<'g, S: GraphStorage + 'g> fmt::Display for StepListFunction<'g, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = self.kind.name();
match &self.operand {
Operand::List(list) => write!(
f,
"{name}({})",
constant_text(&ElementProperty::Array(list.items.clone()))
),
Operand::Map(value) | Operand::Invalid(value) => {
write!(f, "{name}({})", constant_text(value))
}
Operand::Traversal(traversal) => write!(f, "{name}({traversal})"),
}
}
}
fn constant_text(value: &ElementProperty) -> String {
crate::value::literal::rhai_literal(value)
}
pub(crate) enum ListArg<'g, S: GraphStorage + 'g> {
Value(ElementProperty),
Traversal(AnonymousTraversal<'g, S>),
}
pub(crate) fn list_function_step<'g, S: GraphStorage + 'g>(
kind: ListFunction,
arg: ListArg<'g, S>,
) -> GraphStep<'g, S> {
let operand = match arg {
ListArg::Traversal(traversal) => Operand::Traversal(traversal),
ListArg::Value(ElementProperty::Array(items)) => {
let keys: Vec<DedupKey<S>> = items.iter().cloned().map(DedupKey::Value).collect();
let set = keys.iter().cloned().collect();
Operand::List(ConstList { items, keys, set })
}
ListArg::Value(map @ (ElementProperty::Object(_) | ElementProperty::Map(_))) => {
Operand::Map(map)
}
ListArg::Value(other) => Operand::Invalid(other),
};
GraphStep::Map(kind.name(), Box::new(StepListFunction { kind, operand }))
}
#[cfg(feature = "script")]
impl<'g, S: GraphStorage + 'g> AnonymousTraversal<'g, S> {
pub(crate) fn list_function(self, kind: ListFunction, arg: ListArg<'g, S>) -> Self {
self.push_step(list_function_step(kind, arg))
}
}
macro_rules! list_function_builders {
($kind:expr, $name:ident, $name_t:ident, $doc:literal, $example:literal) => {
impl<'g, S: GraphStorage + 'g> GraphTraversalSource<'g, S> {
#[doc = $doc]
#[doc = $example]
pub fn $name(self, list: impl Into<ElementProperty>) -> Self {
self.push_step(list_function_step($kind, ListArg::Value(list.into())))
}
#[doc = concat!("`", stringify!($name), "(traversal)`: like [`", stringify!($name), "`](Self::", stringify!($name), "), but the second operand is the first result of `traversal`, run on the incoming traverser (path included); it must be a list (end the child with `fold()`). A traversal without a result is an error.")]
pub fn $name_t(self, traversal: AnonymousTraversal<'g, S>) -> Self {
self.push_step(list_function_step($kind, ListArg::Traversal(traversal)))
}
}
impl __ {
#[doc = concat!("Anonymous `", stringify!($name), "()`; see [`GraphTraversalSource::", stringify!($name), "`].")]
pub fn $name<'g, S: GraphStorage + 'g>(
list: impl Into<ElementProperty>,
) -> AnonymousTraversal<'g, S> {
AnonymousTraversal::new().$name(list)
}
#[doc = concat!("Anonymous `", stringify!($name), "(traversal)`; see [`GraphTraversalSource::", stringify!($name_t), "`].")]
pub fn $name_t<'g, S: GraphStorage + 'g>(
traversal: AnonymousTraversal<'g, S>,
) -> AnonymousTraversal<'g, S> {
AnonymousTraversal::new().$name_t(traversal)
}
}
impl<'g, S: GraphStorage + 'g> AnonymousTraversal<'g, S> {
#[doc = concat!("`", stringify!($name), "()`; see [`GraphTraversalSource::", stringify!($name), "`].")]
pub fn $name(self, list: impl Into<ElementProperty>) -> Self {
self.push_step(list_function_step($kind, ListArg::Value(list.into())))
}
#[doc = concat!("`", stringify!($name), "(traversal)`; see [`GraphTraversalSource::", stringify!($name_t), "`].")]
pub fn $name_t(self, traversal: AnonymousTraversal<'g, S>) -> Self {
self.push_step(list_function_step($kind, ListArg::Traversal(traversal)))
}
}
};
}
pub(super) use list_function_builders;
pub(crate) fn input_error(
step: &'static str,
operand: &'static str,
expected: &'static str,
actual: String,
) -> Box<TraverserError> {
Box::new(TraverserError::Value(ValueError::ListFunctionInput {
step,
operand,
expected,
actual,
}))
}
pub(crate) fn kind_name<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> String {
if is_null(context, value) {
return "null".to_string();
}
match value {
TraverserValue::Computed(property, _) => crate::graph::schema::value_type_name(property),
TraverserValue::VertexProperty(..) | TraverserValue::EdgeProperty(..) => {
match stored_value(context, value) {
Some(stored) => crate::graph::schema::value_type_name(stored),
None => value.type_name().to_string(),
}
}
TraverserValue::Array(_) => "array".to_string(),
TraverserValue::Object(_) | TraverserValue::Record { .. } => "map".to_string(),
_ => value.type_name().to_string(),
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Kind {
List,
Map,
Other,
}
pub(crate) fn kind_of<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> Kind {
let of_property = |property: &ElementProperty| match property {
ElementProperty::Array(_) => Kind::List,
ElementProperty::Object(_) | ElementProperty::Map(_) => Kind::Map,
ElementProperty::Null
| ElementProperty::String(_)
| ElementProperty::Compressed(_)
| ElementProperty::Number(_)
| ElementProperty::Boolean(_)
| ElementProperty::Uuid(_)
| ElementProperty::Vertex(_)
| ElementProperty::Edge(_) => Kind::Other,
};
match value {
TraverserValue::Array(_) | TraverserValue::Path(_) => Kind::List,
TraverserValue::Object(_) | TraverserValue::Record { .. } => Kind::Map,
TraverserValue::Computed(property, _) => of_property(property),
TraverserValue::VertexProperty(..) | TraverserValue::EdgeProperty(..) => {
stored_value(context, value).map_or(Kind::Other, of_property)
}
TraverserValue::Vertex(_)
| TraverserValue::Edge(_)
| TraverserValue::PathProperty(_)
| TraverserValue::Entry(_)
| TraverserValue::Schema(_)
| TraverserValue::Graph(_) => Kind::Other,
}
}
pub(crate) fn list_items<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: TraverserValue<S>,
) -> TraverserResult<Vec<TraverserValue<S>>> {
Ok(match classify(context, value)? {
LocalShape::List(items) => items,
LocalShape::Path(path) => path.steps.into_iter().map(|step| step.value).collect(),
LocalShape::Map(_) | LocalShape::Scalar(_) => Vec::new(),
})
}
fn map_entries<S: GraphStorage>(
context: &TraverserContext<'_, S>,
value: TraverserValue<S>,
) -> TraverserResult<Vec<(TraverserValue<S>, TraverserValue<S>)>> {
Ok(match classify(context, value)? {
LocalShape::Map(entries) => entries,
LocalShape::List(_) | LocalShape::Path(_) | LocalShape::Scalar(_) => Vec::new(),
})
}
enum Right<'a, S: GraphStorage> {
Const(&'a ConstList<S>),
Owned {
items: Vec<TraverserValue<S>>,
keys: Vec<DedupKey<S>>,
},
}
impl<S: GraphStorage> Right<'_, S> {
fn len(&self) -> usize {
match self {
Right::Const(list) => list.items.len(),
Right::Owned { items, .. } => items.len(),
}
}
fn item(&self, i: usize) -> TraverserValue<S> {
match self {
Right::Const(list) => TraverserValue::from(list.items[i].clone()),
Right::Owned { items, .. } => items[i].clone(),
}
}
fn key(&self, i: usize) -> &DedupKey<S> {
match self {
Right::Const(list) => &list.keys[i],
Right::Owned { keys, .. } => &keys[i],
}
}
fn into_items(self) -> Vec<TraverserValue<S>> {
match self {
Right::Const(list) => list
.items
.iter()
.cloned()
.map(TraverserValue::from)
.collect(),
Right::Owned { items, .. } => items,
}
}
fn contains(
&self,
key: &DedupKey<S>,
owned_set: &HashSet<&DedupKey<S>, FnvBuildHasher>,
) -> bool {
match self {
Right::Const(list) => list.set.contains(key),
Right::Owned { .. } => owned_set.contains(key),
}
}
}
enum Resolved<'a, S: GraphStorage> {
List(Right<'a, S>),
Map(Vec<(TraverserValue<S>, TraverserValue<S>)>),
}
impl<'g, S: GraphStorage + 'g> StepListFunction<'g, S> {
fn resolve(
&self,
context: &mut TraverserContext<'_, S>,
source: &Traverser<S>,
incoming: Kind,
) -> TraverserResult<Resolved<'_, S>> {
let step = self.kind.name();
let expected = if incoming == Kind::Map { "map" } else { "list" };
match &self.operand {
Operand::List(list) if incoming == Kind::List => Ok(Resolved::List(Right::Const(list))),
Operand::Map(map) if incoming == Kind::Map => Ok(Resolved::Map(map_entries(
context,
TraverserValue::Computed(map.clone(), None),
)?)),
Operand::List(_) => Err(input_error(step, "argument", expected, "array".to_string())),
Operand::Map(_) => Err(input_error(step, "argument", expected, "map".to_string())),
Operand::Invalid(value) => Err(input_error(
step,
"argument",
expected,
kind_name(context, &TraverserValue::Computed(value.clone(), None)),
)),
Operand::Traversal(traversal) => {
let Some(first) = traversal.first(context, source)? else {
return Err(input_error(
step,
"traversal argument",
expected,
"no result (the traversal yielded nothing)".to_string(),
));
};
let kind = kind_of(context, &first.value);
if kind != incoming {
return Err(input_error(
step,
"traversal argument",
expected,
kind_name(context, &first.value),
));
}
if incoming == Kind::Map {
return Ok(Resolved::Map(map_entries(context, first.value)?));
}
let items = list_items(context, first.value)?;
let keys = if self.kind.uses_keys() {
items
.iter()
.map(|item| DedupKey::of(context, item))
.collect::<TraverserResult<Vec<_>>>()?
} else {
Vec::new()
};
Ok(Resolved::List(Right::Owned { items, keys }))
}
}
}
fn combine_lists(
&self,
context: &TraverserContext<'_, S>,
left: Vec<TraverserValue<S>>,
right: Right<'_, S>,
) -> TraverserResult<Vec<TraverserValue<S>>> {
match self.kind {
ListFunction::Combine => {
let mut out = left;
out.extend(right.into_items());
return Ok(out);
}
ListFunction::Product => {
let mut out = Vec::with_capacity(left.len() * right.len());
for a in &left {
for i in 0..right.len() {
out.push(TraverserValue::Array(vec![a.clone(), right.item(i)]));
}
}
return Ok(out);
}
ListFunction::Difference
| ListFunction::Disjunct
| ListFunction::Intersect
| ListFunction::Merge => {}
}
let owned_set: HashSet<&DedupKey<S>, FnvBuildHasher> = match &right {
Right::Const(_) => HashSet::default(),
Right::Owned { keys, .. } => keys.iter().collect(),
};
let mut seen: KeySet<S> = HashSet::with_capacity_and_hasher(left.len(), Default::default());
let mut out = Vec::with_capacity(left.len());
for item in left {
let key = DedupKey::of(context, &item)?;
let in_right = right.contains(&key, &owned_set);
let keep = match self.kind {
ListFunction::Difference | ListFunction::Disjunct => !in_right,
ListFunction::Intersect => in_right,
ListFunction::Merge => true,
ListFunction::Combine | ListFunction::Product => true,
};
if seen.insert(key) && keep {
out.push(item);
}
}
if matches!(self.kind, ListFunction::Merge | ListFunction::Disjunct) {
for i in 0..right.len() {
let key = right.key(i);
if !seen.contains(key) {
seen.insert(key.clone());
out.push(right.item(i));
}
}
}
Ok(out)
}
}
impl<'g, S: GraphStorage + 'g> GraphStepMap<'g, S> for StepListFunction<'g, S> {
fn process(
&self,
context: &mut TraverserContext<'_, S>,
source: Traverser<S>,
) -> TraverserResult<Traverser<S>> {
self.process_filter(context, source)?.ok_or_else(|| {
Box::new(TraverserError::QueryExecution {
query: self.to_string(),
reason: format!("{}() never drops a traverser", self.kind.name()),
})
})
}
fn process_filter(
&self,
context: &mut TraverserContext<'_, S>,
source: Traverser<S>,
) -> TraverserResult<Option<Traverser<S>>> {
let step = self.kind.name();
let incoming = kind_of(context, &source.value);
let accepted =
incoming == Kind::List || (incoming == Kind::Map && self.kind == ListFunction::Merge);
if !accepted {
let expected = if self.kind == ListFunction::Merge {
"list or map"
} else {
"list"
};
return Err(input_error(
step,
"incoming value",
expected,
kind_name(context, &source.value),
));
}
let right = self.resolve(context, &source, incoming)?;
let Traverser {
value,
path,
bulk,
sack,
} = source;
let value = match right {
Resolved::List(right) => {
let left = list_items(context, value)?;
TraverserValue::Array(self.combine_lists(context, left, right)?)
}
Resolved::Map(entries) => {
let left = map_entries(context, value)?;
let mut merged: StreamMap<S> = StreamMap::with_capacity(left.len() + entries.len());
merged.extend(left);
merged.extend(entries);
TraverserValue::Object(merged)
}
};
Ok(Some(Traverser::with_parts(value, path, bulk, sack)))
}
fn may_filter(&self) -> bool {
true
}
fn materializes(&self) -> bool {
true
}
fn plan_check(&self) -> TraverserResult<()> {
if let Operand::Invalid(value) = &self.operand {
let actual = match value {
ElementProperty::Null => "null".to_string(),
other => crate::graph::schema::value_type_name(other),
};
let expected = if self.kind == ListFunction::Merge {
"list or map"
} else {
"list"
};
return Err(input_error(self.kind.name(), "argument", expected, actual));
}
Ok(())
}
fn analyze_nested_paths(
&mut self,
analysis: &PathAnalysis,
_downstream: &PathRequirement,
) -> PathRequirement {
match &mut self.operand {
Operand::Traversal(traversal) => {
traversal.analyze_paths(analysis, PathRequirement::None)
}
Operand::List(_) | Operand::Map(_) | Operand::Invalid(_) => PathRequirement::None,
}
}
fn child_traversals_mut(&mut self) -> Vec<&mut AnonymousTraversal<'g, S>> {
match &mut self.operand {
Operand::Traversal(traversal) => vec![traversal],
Operand::List(_) | Operand::Map(_) | Operand::Invalid(_) => Vec::new(),
}
}
fn clone_box(&self) -> Box<dyn GraphStepMap<'g, S> + 'g> {
Box::new(self.clone())
}
}