use std::collections::HashMap;
use crate::planning::plan::core::nodes::base::plan_node_enum::PlanNodeEnum;
use crate::planning::plan::core::nodes::base::plan_node_traits::PlanNode;
use crate::planning::plan::core::nodes::traversal::traversal_node::ExpandAllNode;
use linkrs_core::types::expr::Expression;
use linkrs_core::Value;
fn flat_bypass_names(
col_names: &[String],
edge_props: &Option<Vec<String>>,
dst_props: &Option<Vec<String>>,
) -> Vec<String> {
let (Some(edge_var), Some(dst_var)) = (col_names.get(1), col_names.get(2)) else {
return Vec::new();
};
let mut names = Vec::new();
if let Some(props) = edge_props {
for prop in props {
names.push(format!("{edge_var}.{prop}"));
}
}
if let Some(props) = dst_props {
for prop in props {
names.push(format!("{dst_var}.{prop}"));
}
}
names
}
pub(super) fn has_input_bypass_conflict(
expand: &ExpandAllNode,
edge_props: &Option<Vec<String>>,
dst_props: &Option<Vec<String>>,
candidates: &[(ExpandAllNode, Vec<&PlanNodeEnum>)],
needs_map: &HashMap<i64, (Option<Vec<String>>, Option<Vec<String>>)>,
) -> bool {
let current: std::collections::HashSet<String> =
flat_bypass_names(expand.col_names(), edge_props, dst_props)
.into_iter()
.collect();
if current.is_empty() {
return false;
}
for (other, other_ancestors) in candidates {
if other.id() == expand.id() {
continue;
}
let below = other_ancestors.iter().any(|anc| match anc {
PlanNodeEnum::ExpandAll(parent) => parent.id() == expand.id(),
_ => false,
});
if !below {
continue;
}
let Some((other_edge, other_dst)) = needs_map.get(&other.id()) else {
continue;
};
for name in flat_bypass_names(other.col_names(), other_edge, other_dst) {
if current.contains(&name) {
return true;
}
}
}
false
}
pub(super) fn expand_closed_loop(expand: &ExpandAllNode) -> bool {
if expand.any_edge_type() || expand.edge_types().is_empty() {
return false;
}
true
}
pub(super) fn expand_skip_rows(
expand: &ExpandAllNode,
ancestors: &[&PlanNodeEnum],
edge_props: Option<&Vec<String>>,
dst_props: Option<&Vec<String>>,
) -> bool {
let Some(edge_needs) = edge_props else {
return false;
};
let Some(dst_needs) = dst_props else {
return false;
};
let has_props = !edge_needs.is_empty() || !dst_needs.is_empty();
if !expand_closed_loop(expand) {
return false;
}
let mut rest = ancestors.iter().rev();
let terminator = loop {
match rest.next() {
None => return false,
Some(PlanNodeEnum::Filter(filter)) => {
if !filter.subqueries().is_empty() {
return false;
}
let is_true = filter
.condition()
.get_expression()
.is_some_and(|expr| matches!(expr, Expression::Literal(Value::Bool(true))));
if !is_true {
return false;
}
}
Some(node) => break node,
}
};
match terminator {
PlanNodeEnum::Project(project) => {
if !project.subqueries().is_empty() {
return false;
}
project.columns().iter().all(|col| {
col.expression
.expression()
.map(|meta| is_passthrough_or_const(meta.inner()))
.unwrap_or(true)
})
}
PlanNodeEnum::Aggregate(agg) => {
if has_props {
return false;
}
if !agg.group_keys().is_empty() || !agg.grouping_sets().is_empty() {
return false;
}
if agg.aggregation_distinct().iter().any(|d| *d) {
return false;
}
if agg.aggregation_filters().iter().any(|f| f.is_some()) {
return false;
}
let funcs = agg.aggregation_functions();
let args = agg.aggregation_args();
if funcs.is_empty() || funcs.len() != args.len() {
return false;
}
funcs.iter().zip(args.iter()).all(|(func, arg)| {
matches!(
func,
linkrs_core::types::operators::AggregateFunction::Count
) && (arg.is_empty()
|| (arg.len() == 1 && matches!(arg[0], Expression::Variable(_))))
})
}
PlanNodeEnum::ExpandAll(next) => {
let Some(dst_var) = expand.col_names().get(2) else {
return false;
};
if next.col_names().first().map(String::as_str) != Some(dst_var.as_str()) {
return false;
}
if next.path_semantic().is_some() {
return false;
}
if !expand_closed_loop(next) {
return false;
}
next.step_limit().unwrap_or(1) == 1
&& next.step_limits().is_none()
&& next.filter().is_none()
&& next.src_vids().is_empty()
}
_ => false,
}
}
fn is_passthrough_or_const(expression: &Expression) -> bool {
match expression {
Expression::Variable(_) | Expression::Literal(_) => true,
Expression::Property { object, .. } => {
matches!(object.as_ref(), Expression::Variable(_))
}
_ => false,
}
}