mod access;
mod assign;
mod control_flow;
mod flat_leaf;
mod join;
mod operation;
mod set_ops;
mod traversal;
mod unwind;
use linkrs_core::types::expr::contextual::ContextualExpression;
use linkrs_core::types::expr::ExpressionId;
use crate::planning::plan::factorization::{
FGroupPos, FactorizationError, FactorizedSchema, FactorizedSchemaCompute,
};
use crate::planning::plan::logical::logical_node_enum::LogicalNodeEnum;
pub(super) fn resolve_id(expr: &ContextualExpression) -> ExpressionId {
expr.id().clone()
}
pub(super) fn register_output_names(
schema: &mut FactorizedSchema,
output_var: Option<&str>,
col_names: &[String],
group: FGroupPos,
) -> Result<(), FactorizationError> {
if let Some(var) = output_var {
schema.insert_name_for_group(var.to_string(), group)?;
}
for name in col_names {
schema.insert_name_for_group(name.clone(), group)?;
}
Ok(())
}
fn pair(child_schemas: &[FactorizedSchema]) -> (&FactorizedSchema, &FactorizedSchema) {
static EMPTY: std::sync::OnceLock<FactorizedSchema> = std::sync::OnceLock::new();
let empty = EMPTY.get_or_init(FactorizedSchema::new);
let left = child_schemas.first().unwrap_or(empty);
let right = child_schemas.get(1).unwrap_or(empty);
(left, right)
}
pub(crate) fn dispatch_module(node: &LogicalNodeEnum) -> &'static str {
match node {
LogicalNodeEnum::ScanVertices(_) => "access",
LogicalNodeEnum::ScanEdges(_) => "access",
LogicalNodeEnum::GetVertices(_) => "access",
LogicalNodeEnum::GetEdges(_) => "access",
LogicalNodeEnum::GetNeighbors(_) => "access",
LogicalNodeEnum::Start(_) => "access",
LogicalNodeEnum::Project(_) => "operation",
LogicalNodeEnum::Filter(_) => "operation",
LogicalNodeEnum::Aggregate(_) => "operation",
LogicalNodeEnum::Flatten(_) => "operation",
LogicalNodeEnum::Sort(_) => "operation",
LogicalNodeEnum::TopN(_) => "operation",
LogicalNodeEnum::Window(_) => "operation",
LogicalNodeEnum::Dedup(_) => "operation",
LogicalNodeEnum::Limit(_) => "operation",
LogicalNodeEnum::Skip(_) => "operation",
LogicalNodeEnum::Sample(_) => "operation",
LogicalNodeEnum::InnerJoin(_) => "join",
LogicalNodeEnum::LeftJoin(_) => "join",
LogicalNodeEnum::RightJoin(_) => "join",
LogicalNodeEnum::CrossJoin(_) => "join",
LogicalNodeEnum::FullOuterJoin(_) => "join",
LogicalNodeEnum::SemiJoin(_) => "join",
LogicalNodeEnum::Traverse(_) => "traversal",
LogicalNodeEnum::Expand(_) => "traversal",
LogicalNodeEnum::ExpandAll(_) => "traversal",
LogicalNodeEnum::AppendVertices(_) => "traversal",
LogicalNodeEnum::BiExpand(_) => "traversal",
LogicalNodeEnum::BiTraverse(_) => "traversal",
LogicalNodeEnum::Union(_) => "set_ops",
LogicalNodeEnum::Minus(_) => "set_ops",
LogicalNodeEnum::Intersect(_) => "set_ops",
LogicalNodeEnum::WcoIntersect(_) => "set_ops",
LogicalNodeEnum::Assign(_) => "assign",
LogicalNodeEnum::Select(_) => "control_flow",
LogicalNodeEnum::Loop(_) => "control_flow",
LogicalNodeEnum::BeginTransaction(_) => "control_flow",
LogicalNodeEnum::Commit(_) => "control_flow",
LogicalNodeEnum::Rollback(_) => "control_flow",
LogicalNodeEnum::PassThrough(_) => "control_flow",
LogicalNodeEnum::Argument(_) => "control_flow",
LogicalNodeEnum::Unwind(_) => "unwind",
LogicalNodeEnum::FulltextSearch(_) => "flat_leaf",
LogicalNodeEnum::FulltextLookup(_) => "flat_leaf",
LogicalNodeEnum::MatchFulltext(_) => "flat_leaf",
#[cfg(feature = "vector")]
LogicalNodeEnum::VectorSearch(_) => "flat_leaf",
#[cfg(feature = "vector")]
LogicalNodeEnum::VectorLookup(_) => "flat_leaf",
#[cfg(feature = "vector")]
LogicalNodeEnum::VectorMatch(_) => "flat_leaf",
LogicalNodeEnum::InsertVertices(_) => "flat_leaf",
LogicalNodeEnum::InsertEdges(_) => "flat_leaf",
LogicalNodeEnum::Update(_) => "flat_leaf",
LogicalNodeEnum::DeleteVertices(_) => "flat_leaf",
LogicalNodeEnum::DeleteEdges(_) => "flat_leaf",
LogicalNodeEnum::DeleteIndex(_) => "flat_leaf",
LogicalNodeEnum::PipeDeleteVertices(_) => "flat_leaf",
LogicalNodeEnum::PipeDeleteEdges(_) => "flat_leaf",
LogicalNodeEnum::CopyFrom(_) => "flat_leaf",
LogicalNodeEnum::CopyTo(_) => "flat_leaf",
LogicalNodeEnum::DataCollect(_) => "flat_leaf",
LogicalNodeEnum::Materialize(_) => "flat_leaf",
LogicalNodeEnum::RollUpApply(_) => "flat_leaf",
LogicalNodeEnum::Apply(_) => "flat_leaf",
LogicalNodeEnum::PatternApply(_) => "flat_leaf",
LogicalNodeEnum::CorrelatedApply(_) => "flat_leaf",
LogicalNodeEnum::MultiShortestPath(_) => "flat_leaf",
LogicalNodeEnum::BFSShortest(_) => "flat_leaf",
LogicalNodeEnum::AllPaths(_) => "flat_leaf",
LogicalNodeEnum::ShortestPath(_) => "flat_leaf",
}
}
pub(super) fn bi_expand_schema(
child_schemas: &[FactorizedSchema],
output_aliases: &[String],
) -> Result<FactorizedSchema, FactorizationError> {
let mut schema = child_schemas.first().cloned().unwrap_or_default();
if schema.has_unflat_group() {
if let Some(pos) = schema.unflat_group_pos() {
schema.flatten_group(pos)?;
}
}
if let Some(build) = child_schemas.get(1) {
let mapping = schema.merge_groups_from(build);
for (expr_id, gpos) in build.expression_to_group_iter() {
let new_pos = mapping.get(gpos).copied().unwrap_or(*gpos);
schema.insert_to_scope_may_repeat(expr_id.clone(), new_pos)?;
}
if schema.has_unflat_group() {
if let Some(pos) = schema.unflat_group_pos() {
schema.flatten_group(pos)?;
}
}
}
let out = schema.create_group();
for alias in output_aliases {
schema.insert_name_for_group(alias.clone(), out)?;
}
schema.validate_at_most_one_unflat()?;
Ok(schema)
}
impl FactorizedSchemaCompute for LogicalNodeEnum {
fn compute_factorized_schema(
&mut self,
child_schemas: &[FactorizedSchema],
) -> Result<FactorizedSchema, FactorizationError> {
debug_assert!(!dispatch_module(self).is_empty());
let schema = match self {
LogicalNodeEnum::ScanVertices(n) => access::scan_vertices(n)?,
LogicalNodeEnum::ScanEdges(n) => access::scan_edges(n)?,
LogicalNodeEnum::GetVertices(n) => access::get_vertices(n, child_schemas)?,
LogicalNodeEnum::GetEdges(n) => access::get_edges(n)?,
LogicalNodeEnum::GetNeighbors(n) => access::get_neighbors(n, child_schemas)?,
LogicalNodeEnum::Start(_) => access::start()?,
LogicalNodeEnum::Project(n) => operation::project(n, child_schemas)?,
LogicalNodeEnum::Filter(n) => operation::filter(n, child_schemas)?,
LogicalNodeEnum::Aggregate(n) => operation::aggregate(n, child_schemas)?,
LogicalNodeEnum::Flatten(n) => operation::flatten(n, child_schemas)?,
LogicalNodeEnum::Sort(n) => operation::sort(n, child_schemas)?,
LogicalNodeEnum::TopN(n) => operation::top_n(n, child_schemas)?,
LogicalNodeEnum::Window(n) => operation::window(n, child_schemas)?,
LogicalNodeEnum::Dedup(n) => operation::dedup(n, child_schemas)?,
LogicalNodeEnum::Limit(n) => operation::limit(n, child_schemas)?,
LogicalNodeEnum::Skip(n) => operation::skip(n, child_schemas)?,
LogicalNodeEnum::Sample(n) => operation::sample(n, child_schemas)?,
LogicalNodeEnum::InnerJoin(n) => {
let (left, right) = pair(child_schemas);
join::binary_join_inner(left, right, &n.hash_keys, &n.probe_keys)?
}
LogicalNodeEnum::LeftJoin(n) => {
let (left, right) = pair(child_schemas);
join::binary_join_inner(left, right, &n.hash_keys, &n.probe_keys)?
}
LogicalNodeEnum::RightJoin(n) => {
let (left, right) = pair(child_schemas);
join::binary_join_right(left, right, &n.hash_keys, &n.probe_keys)?
}
LogicalNodeEnum::CrossJoin(n) => {
let (left, right) = pair(child_schemas);
if n.hash_keys.is_empty() && n.probe_keys.is_empty() {
join::cross_join_no_keys(left, right)?
} else {
join::binary_join_inner(left, right, &n.hash_keys, &n.probe_keys)?
}
}
LogicalNodeEnum::FullOuterJoin(n) => {
let (left, right) = pair(child_schemas);
join::binary_join_full_outer(left, right, &n.hash_keys, &n.probe_keys)?
}
LogicalNodeEnum::SemiJoin(n) => {
let (left, right) = pair(child_schemas);
join::binary_join_inner(left, right, &n.hash_keys, &n.probe_keys)?
}
LogicalNodeEnum::Traverse(n) => traversal::traverse(n, child_schemas)?,
LogicalNodeEnum::Expand(n) => traversal::expand(n, child_schemas)?,
LogicalNodeEnum::ExpandAll(n) => traversal::expand_all(n, child_schemas)?,
LogicalNodeEnum::AppendVertices(n) => traversal::append_vertices(n, child_schemas)?,
LogicalNodeEnum::BiExpand(n) => traversal::bi_expand(n, child_schemas)?,
LogicalNodeEnum::BiTraverse(n) => traversal::bi_traverse(n, child_schemas)?,
LogicalNodeEnum::Union(_) | LogicalNodeEnum::Minus(_) => {
set_ops::union_minus(child_schemas)?
}
LogicalNodeEnum::Intersect(_) => set_ops::intersect(child_schemas)?,
LogicalNodeEnum::WcoIntersect(n) => set_ops::wco_intersect(n, child_schemas)?,
LogicalNodeEnum::Assign(n) => assign::assign(n, child_schemas)?,
LogicalNodeEnum::Select(n) => control_flow::select(n, child_schemas)?,
LogicalNodeEnum::Loop(n) => control_flow::loop_node(n, child_schemas)?,
LogicalNodeEnum::BeginTransaction(_)
| LogicalNodeEnum::Commit(_)
| LogicalNodeEnum::Rollback(_)
| LogicalNodeEnum::PassThrough(_)
| LogicalNodeEnum::Argument(_) => control_flow::passthrough(child_schemas)?,
LogicalNodeEnum::Unwind(n) => unwind::unwind(n, child_schemas)?,
LogicalNodeEnum::FulltextSearch(_)
| LogicalNodeEnum::FulltextLookup(_)
| LogicalNodeEnum::MatchFulltext(_) => flat_leaf::flat_leaf()?,
#[cfg(feature = "vector")]
LogicalNodeEnum::VectorSearch(_)
| LogicalNodeEnum::VectorLookup(_)
| LogicalNodeEnum::VectorMatch(_) => flat_leaf::flat_leaf()?,
LogicalNodeEnum::DataCollect(_)
| LogicalNodeEnum::Materialize(_)
| LogicalNodeEnum::RollUpApply(_) => flat_leaf::flatten_all_from_child(child_schemas)?,
LogicalNodeEnum::InsertVertices(_)
| LogicalNodeEnum::InsertEdges(_)
| LogicalNodeEnum::Update(_) => flat_leaf::flat_leaf()?,
LogicalNodeEnum::Apply(_)
| LogicalNodeEnum::PatternApply(_)
| LogicalNodeEnum::CorrelatedApply(_)
| LogicalNodeEnum::MultiShortestPath(_)
| LogicalNodeEnum::BFSShortest(_)
| LogicalNodeEnum::AllPaths(_)
| LogicalNodeEnum::ShortestPath(_) => flat_leaf::barrier_binary(child_schemas)?,
_ => flat_leaf::flatten_all_from_child(child_schemas)?,
};
debug_assert!(
schema.has_at_most_one_unflat(),
"compute_factorized_schema: at most one unflat group invariant violated"
);
Ok(schema)
}
fn compute_flat_schema(
&mut self,
child_schemas: &[FactorizedSchema],
) -> Result<FactorizedSchema, FactorizationError> {
let flat_children: Vec<FactorizedSchema> = child_schemas
.iter()
.map(|cs| cs.flat_copy())
.collect::<Result<Vec<_>, _>>()?;
let mut result = self.compute_factorized_schema(&flat_children)?;
result.flatten_all()?;
result.validate_at_most_one_unflat()?;
Ok(result)
}
}
#[cfg(test)]
mod tests;