use super::eligibility::{analyze_typed, Boundary, Eligibility, ExecutorContext};
use super::inference::{
derive_vertex_types_with_inference, infer_scc_predicate_schemas, InferenceError,
};
use super::ir::{HypergraphRule, VertexId};
use super::reference::{RefEvalError, RefRelationStore};
use super::typed::derive_vertex_types;
use super::var_order::{AppearanceOrder, VariableOrder};
use crate::ast::Rule;
use std::collections::BTreeMap;
use std::fmt::Write;
use xlog_core::ScalarType;
#[derive(Debug, Clone, PartialEq)]
pub enum RulePlan {
MultiwayCandidate {
head_predicate: String,
hypergraph: HypergraphRule,
variable_order: Vec<VertexId>,
},
BinaryFallback {
head_predicate: String,
boundaries: Vec<Boundary>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum PlanError {
ConflictingVariableType {
var: String,
first_predicate: String,
first_position: usize,
first_type: ScalarType,
second_predicate: String,
second_position: usize,
second_type: ScalarType,
},
InferenceConflict {
predicate: String,
column: usize,
first_rule_index: usize,
first_type: ScalarType,
second_rule_index: usize,
second_type: ScalarType,
},
RuleHeadPredicateMismatch {
group_key: String,
rule_index: usize,
observed: String,
},
}
pub fn plan_rule(rule: &Rule, base_relations: &RefRelationStore) -> Result<RulePlan, PlanError> {
let vertex_types = match derive_vertex_types(rule, base_relations) {
Ok(map) => map,
Err(RefEvalError::ConflictingVariableType {
var,
first_predicate,
first_position,
first_type,
second_predicate,
second_position,
second_type,
}) => {
return Err(PlanError::ConflictingVariableType {
var,
first_predicate,
first_position,
first_type,
second_predicate,
second_position,
second_type,
});
}
Err(other) => {
unreachable!(
"derive_vertex_types contract returns only ConflictingVariableType: got {other:?}"
);
}
};
let hypergraph = HypergraphRule::from_rule(rule);
match analyze_typed(&hypergraph, &vertex_types, ExecutorContext::HashFallback) {
Eligibility::Eligible => {
let variable_order = AppearanceOrder.order(&hypergraph);
Ok(RulePlan::MultiwayCandidate {
head_predicate: rule.head.predicate.clone(),
hypergraph,
variable_order,
})
}
Eligibility::Ineligible(boundaries) => Ok(RulePlan::BinaryFallback {
head_predicate: rule.head.predicate.clone(),
boundaries,
}),
}
}
pub fn plan_rules(
rules: &[Rule],
base_relations: &RefRelationStore,
) -> Result<Vec<RulePlan>, PlanError> {
rules.iter().map(|r| plan_rule(r, base_relations)).collect()
}
pub fn plan_scc_rules(
rules: &BTreeMap<String, Vec<Rule>>,
base_relations: &RefRelationStore,
) -> Result<BTreeMap<String, Vec<RulePlan>>, PlanError> {
for (predicate, group) in rules.iter() {
for (rule_index, rule) in group.iter().enumerate() {
if &rule.head.predicate != predicate {
return Err(PlanError::RuleHeadPredicateMismatch {
group_key: predicate.clone(),
rule_index,
observed: rule.head.predicate.clone(),
});
}
}
}
let inferred = match infer_scc_predicate_schemas(rules, base_relations) {
Ok(s) => s,
Err(InferenceError::ConflictingPredicateColumnType {
predicate,
column,
first_rule_index,
first_type,
second_rule_index,
second_type,
}) => {
return Err(PlanError::InferenceConflict {
predicate,
column,
first_rule_index,
first_type,
second_rule_index,
second_type,
});
}
};
let mut out: BTreeMap<String, Vec<RulePlan>> = BTreeMap::new();
for (predicate, group) in rules.iter() {
let mut plans: Vec<RulePlan> = Vec::with_capacity(group.len());
for rule in group {
let vertex_types =
match derive_vertex_types_with_inference(rule, base_relations, &inferred) {
Ok(map) => map,
Err(RefEvalError::ConflictingVariableType {
var,
first_predicate,
first_position,
first_type,
second_predicate,
second_position,
second_type,
}) => {
return Err(PlanError::ConflictingVariableType {
var,
first_predicate,
first_position,
first_type,
second_predicate,
second_position,
second_type,
});
}
Err(other) => unreachable!(
"derive_vertex_types_with_inference contract returns only \
ConflictingVariableType: got {other:?}"
),
};
let hypergraph = HypergraphRule::from_rule(rule);
let plan =
match analyze_typed(&hypergraph, &vertex_types, ExecutorContext::HashFallback) {
Eligibility::Eligible => {
let variable_order = AppearanceOrder.order(&hypergraph);
RulePlan::MultiwayCandidate {
head_predicate: rule.head.predicate.clone(),
hypergraph,
variable_order,
}
}
Eligibility::Ineligible(boundaries) => RulePlan::BinaryFallback {
head_predicate: rule.head.predicate.clone(),
boundaries,
},
};
plans.push(plan);
}
out.insert(predicate.clone(), plans);
}
Ok(out)
}
pub fn explain_plans(plans: &[RulePlan]) -> String {
let mut bodies: Vec<(&str, String)> =
plans.iter().map(|p| (head_of(p), render_body(p))).collect();
bodies.sort_by(|(ha, ba), (hb, bb)| ha.cmp(hb).then_with(|| ba.cmp(bb)));
let mut out = String::new();
let mut last_head: Option<&str> = None;
let mut rank: usize = 0;
for (head, body) in &bodies {
match last_head {
Some(prev) if prev == *head => rank += 1,
_ => rank = 0,
}
last_head = Some(*head);
let _ = writeln!(out, "{head}/{rank}: {body}");
}
out
}
fn render_body(plan: &RulePlan) -> String {
match plan {
RulePlan::MultiwayCandidate {
hypergraph,
variable_order,
..
} => {
let names: Vec<&str> = variable_order
.iter()
.map(|vid| hypergraph.vertex(*vid).name.as_str())
.collect();
format!("multiway vars=[{}]", names.join(", "))
}
RulePlan::BinaryFallback { boundaries, .. } => {
format!("binary-fallback boundaries={boundaries:?}")
}
}
}
fn head_of(plan: &RulePlan) -> &str {
match plan {
RulePlan::MultiwayCandidate { head_predicate, .. } => head_predicate,
RulePlan::BinaryFallback { head_predicate, .. } => head_predicate,
}
}