use hawdb_core::Value;
use hawdb_cypher::RelationshipDirection;
use hawdb_plan_cypher::{
AggregateFunction, AggregateTarget, Aggregation, CompositeRangeSeek, ExactPropertySeekBranch,
NodeProjectionAccess, PhysicalPlan, Predicate, Projection, ProjectionExpression,
};
use std::collections::BTreeMap;
pub(super) const VARIABLES: [&str; 7] = ["n", "m", "r", "s", "left", "right", "missing"];
pub(super) const PROPERTIES: [&str; 5] = ["key", "score", "x", "y", "missing"];
pub(super) const ACCESS_KINDS: usize = 18;
pub(super) const UNARY_KINDS: usize = 11;
pub(super) fn access(kind: usize, variable: &str, label: &str) -> PhysicalPlan {
let variable = variable.to_string();
let label = label.to_string();
let property = "key".to_string();
let values = vec![Value::Int(7), Value::Null];
let lower = Some((Value::Int(3), false));
let upper = Some((Value::Int(9), true));
let predicates = vec![("key".into(), Value::Int(7)), ("x".into(), Value::Null)];
let branches = vec![
ExactPropertySeekBranch {
property: "key".into(),
values: values.clone(),
},
ExactPropertySeekBranch {
property: "y".into(),
values: Vec::new(),
},
];
let seek = CompositeRangeSeek {
index_properties: vec!["key".into(), "x".into(), "score".into()],
equality_prefix: predicates.clone(),
range_property: "score".into(),
lower: lower.clone(),
upper: upper.clone(),
};
match kind {
0 => PhysicalPlan::SeqNodeScan { variable, label },
1 => PhysicalPlan::IndexNodeSeek {
variable,
label,
property,
value: Value::Int(7),
},
2 => PhysicalPlan::IndexNodeMultiSeek {
variable,
label,
property,
values,
},
3 => PhysicalPlan::IndexNodeUnionSeek {
variable,
label,
branches,
},
4 => PhysicalPlan::IndexNodeCompositeSeek {
variable,
label,
predicates,
},
5 => PhysicalPlan::IndexNodeCompositeRangeSeek {
variable,
label,
seek,
},
6 => PhysicalPlan::IndexNodeRangeSeek {
variable,
label,
property,
lower,
upper,
},
7 => PhysicalPlan::IndexNodeTextSeek {
variable,
label,
property,
query: "term".into(),
},
8..=14 => {
let access = match kind {
8 => NodeProjectionAccess::LabelScan,
9 => NodeProjectionAccess::PropertyValues { property, values },
10 => NodeProjectionAccess::PropertyUnion { branches },
11 => NodeProjectionAccess::CompositeEquality { predicates },
12 => NodeProjectionAccess::CompositeRange { seek },
13 => NodeProjectionAccess::PropertyRange {
property,
lower,
upper,
},
14 => NodeProjectionAccess::FullText {
property,
query: "term".into(),
},
_ => unreachable!(),
};
PhysicalPlan::NodeProjectionScanExec {
variable: variable.clone(),
label,
access,
required_properties: vec!["key".into()],
predicate: Some(Predicate::PropertyEq {
variable,
property: "key".into(),
value: Value::Int(7),
}),
items: Vec::new(),
}
}
15 => PhysicalPlan::EmptyExec,
16 => PhysicalPlan::SourceSegmentScan {
variable,
predicate: Predicate::ConstantBool(true),
},
17 => PhysicalPlan::NodeCountExec {
label,
output: variable,
},
_ => unreachable!("unknown access fixture"),
}
}
pub(super) fn expand(input: PhysicalPlan, named: bool, same_endpoint: bool) -> PhysicalPlan {
PhysicalPlan::AdjacencyExpandExec {
source_variable: "n".into(),
source_label: "Left".into(),
rel_variable: named.then(|| "r".into()),
rel_type: "LINK".into(),
rel_properties: BTreeMap::new(),
direction: RelationshipDirection::Outgoing,
target_variable: if same_endpoint { "n" } else { "m" }.into(),
target_label: "Right".into(),
min_hops: 1,
max_hops: 2,
optional: false,
graph_budget: None,
input: Box::new(input),
}
}
pub(super) fn wrap(kind: usize, input: PhysicalPlan) -> PhysicalPlan {
if kind == 10 {
return expand(input, true, false);
}
let input = Box::new(input);
match kind {
0 => PhysicalPlan::FilterExec {
predicate: Predicate::PropertyEq {
variable: "n".into(),
property: "key".into(),
value: Value::Int(7),
},
input,
},
1 => PhysicalPlan::ProjectExec {
items: Vec::new(),
input,
},
2 => PhysicalPlan::DistinctExec { input },
3 => PhysicalPlan::SortExec {
items: Vec::new(),
input,
},
4 => PhysicalPlan::TopNExec {
items: Vec::new(),
offset: 2,
limit: 7,
input,
},
5 => PhysicalPlan::LimitExec {
offset: 3,
limit: Some(9),
input,
},
6 => PhysicalPlan::AggregateExec {
group_keys: vec![Projection {
expression: ProjectionExpression::Property {
variable: "n".into(),
property: "key".into(),
},
name: "key".into(),
}],
items: ["n", "m", "r"]
.into_iter()
.map(|variable| Aggregation {
function: AggregateFunction::Count,
target: AggregateTarget::Variable(variable.into()),
distinct: true,
name: format!("count_{variable}"),
})
.collect(),
input,
},
7 => PhysicalPlan::NodeColumnLookupExec {
variable: "left".into(),
label: "Lookup".into(),
property: "score".into(),
column: "score".into(),
optional: true,
node_visibility_predicate: None,
input,
},
8 => PhysicalPlan::AdjacencyExistsExec {
source_variable: "n".into(),
rel_type: "LINK".into(),
direction: RelationshipDirection::Incoming,
target_variable: "m".into(),
input,
},
9 => PhysicalPlan::OptionalDegreeExec {
source_variable: "n".into(),
rel_type: "LINK".into(),
rel_properties: BTreeMap::new(),
direction: RelationshipDirection::Undirected,
target_label: "Right".into(),
target_properties: BTreeMap::new(),
alias: "degree".into(),
input,
},
_ => unreachable!("unknown unary fixture"),
}
}
pub(super) fn product(left: PhysicalPlan, right: PhysicalPlan) -> PhysicalPlan {
PhysicalPlan::NodeCartesianProductExec {
left: Box::new(left),
right: Box::new(right),
}
}
pub(super) struct Random(pub(super) u64);
impl Random {
pub(super) fn next(&mut self) -> usize {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0 as usize
}
pub(super) fn plan(&mut self, depth: usize, remaining: &mut usize) -> PhysicalPlan {
*remaining -= 1;
let kind = self.next() % 15;
if depth == 0 || *remaining == 0 || kind == 14 {
let kind = self.next() % ACCESS_KINDS;
let variable = VARIABLES[self.next() % (VARIABLES.len() - 1)];
let label = ["Left", "Right", "Lookup"][self.next() % 3];
return access(kind, variable, label);
}
if kind == 11 && *remaining >= 2 {
*remaining -= 1;
let left = self.plan(depth - 1, remaining);
*remaining += 1;
let right = self.plan(depth - 1, remaining);
product(left, right)
} else {
let input = self.plan(depth - 1, remaining);
match kind {
12 => expand(input, false, false),
13 => expand(input, true, true),
_ => wrap(kind % UNARY_KINDS, input),
}
}
}
}