use std::collections::BTreeSet;
use crate::analysis::expr::{self, ast::BinaryOp};
use crate::model::{parse_logical_type, LogicalType, COMPOSITE_TYPES, PRIMITIVE_TYPES};
use crate::plan::{PlanNodeKind, TransformationPlan};
use super::model::CapabilityGap;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PlanRequirements {
pub logical_types: BTreeSet<String>,
pub semantic_actions: BTreeSet<String>,
pub functions: BTreeSet<String>,
pub rules: BTreeSet<String>,
pub operators: BTreeSet<String>,
}
impl PlanRequirements {
#[must_use]
pub fn from_plan(plan: &TransformationPlan) -> Self {
let mut req = Self::default();
for input in &plan.inputs {
collect_schema_types(input.schema.as_ref(), &mut req.logical_types);
}
for output in &plan.outputs {
collect_schema_types(output.schema.as_ref(), &mut req.logical_types);
}
for function in &plan.functions {
req.functions.insert(function.function.clone());
if let Some(type_name) = &function.type_name {
collect_type_name(type_name, &mut req.logical_types);
}
}
for node in &plan.nodes {
match &node.kind {
PlanNodeKind::SemanticAction(action) => {
req.semantic_actions.insert(action.action.clone());
}
PlanNodeKind::Rule(rule) => {
req.rules.insert(rule.rule.clone());
}
PlanNodeKind::Expression(expression) => {
if let Some(type_name) = &expression.type_name {
collect_type_name(type_name, &mut req.logical_types);
}
if let Some(body) = expression.expr.as_deref() {
if let Ok(ast) = crate::analysis::expr::parse::parse_expression(body) {
collect_expr_requirements(&ast, &mut req);
}
}
}
}
}
req
}
#[must_use]
pub fn gaps_against(
&self,
declaration: &super::model::EngineCapabilityDeclaration,
) -> Vec<CapabilityGap> {
let mut gaps = Vec::new();
let categories = &declaration.categories;
for required in &self.logical_types {
if !supports_type(required, &categories.logical_types) {
gaps.push(CapabilityGap {
category: "logicalTypes".into(),
required: required.clone(),
});
}
}
for required in &self.semantic_actions {
if !categories.semantic_actions.iter().any(|s| s == required) {
gaps.push(CapabilityGap {
category: "semanticActions".into(),
required: required.clone(),
});
}
}
for required in &self.functions {
if !categories.functions.iter().any(|s| s == required) {
gaps.push(CapabilityGap {
category: "functions".into(),
required: required.clone(),
});
}
}
for required in &self.rules {
if !categories.rules.iter().any(|s| s == required) {
gaps.push(CapabilityGap {
category: "rules".into(),
required: required.clone(),
});
}
}
for required in &self.operators {
if !categories.operators.iter().any(|s| s == required) {
gaps.push(CapabilityGap {
category: "operators".into(),
required: required.clone(),
});
}
}
gaps
}
}
fn collect_schema_types(schema: Option<&crate::model::Schema>, out: &mut BTreeSet<String>) {
let Some(schema) = schema else {
return;
};
for field in &schema.fields {
collect_type_name(&field.type_name, out);
}
}
fn collect_type_name(type_name: &str, out: &mut BTreeSet<String>) {
out.insert(type_name.to_string());
if let Ok(parsed) = parse_logical_type(type_name) {
match parsed {
LogicalType::Primitive(name) => {
out.insert(name);
}
LogicalType::Composite { kind, params } => {
out.insert(kind);
for param in params {
collect_type_name(¶m, out);
}
}
LogicalType::Extension(name) => {
out.insert(name);
}
}
}
}
fn supports_type(required: &str, supported: &[String]) -> bool {
if supported.iter().any(|s| s == required) {
return true;
}
if PRIMITIVE_TYPES.contains(&required) || COMPOSITE_TYPES.contains(&required) {
return supported.iter().any(|s| s == required);
}
if let Ok(parsed) = parse_logical_type(required) {
return match parsed {
LogicalType::Primitive(name) => supported.iter().any(|s| s == &name),
LogicalType::Composite { kind, params } => {
supported.iter().any(|s| s == &kind)
&& params.iter().all(|p| supports_type(p, supported))
}
LogicalType::Extension(name) => supported.iter().any(|s| s == &name),
};
}
false
}
fn collect_expr_requirements(ast: &expr::ast::Expr, req: &mut PlanRequirements) {
match ast {
expr::ast::Expr::Literal { .. } => {}
expr::ast::Expr::FieldRef { .. } => {}
expr::ast::Expr::Unary { op, expr, .. } => {
req.operators.insert(unary_op_name(*op).into());
collect_expr_requirements(expr, req);
}
expr::ast::Expr::Binary {
op, left, right, ..
} => {
req.operators.insert(binary_op_name(*op).into());
collect_expr_requirements(left, req);
collect_expr_requirements(right, req);
}
expr::ast::Expr::Call { callee, args, .. } => {
if callee.contains(':') {
req.functions.insert(callee.clone());
}
for arg in args {
collect_expr_requirements(arg, req);
}
}
}
}
fn unary_op_name(op: expr::ast::UnaryOp) -> &'static str {
match op {
expr::ast::UnaryOp::Negate => "negate",
expr::ast::UnaryOp::Not => "not",
}
}
fn binary_op_name(op: BinaryOp) -> &'static str {
match op {
BinaryOp::Add => "add",
BinaryOp::Sub => "sub",
BinaryOp::Mul => "mul",
BinaryOp::Div => "div",
BinaryOp::Eq => "eq",
BinaryOp::Neq => "neq",
BinaryOp::Lt => "lt",
BinaryOp::Lte => "lte",
BinaryOp::Gt => "gt",
BinaryOp::Gte => "gte",
BinaryOp::And => "and",
BinaryOp::Or => "or",
}
}