use crate::optimizer::heuristic::context::RewriteContext;
use crate::optimizer::heuristic::pattern::Pattern;
use crate::optimizer::heuristic::result::{RewriteResult, TransformResult};
use crate::optimizer::heuristic::rule::{EliminationRule, RewriteRule};
use crate::planning::plan::core::nodes::base::plan_node_traits::SingleInputNode;
use crate::planning::plan::core::nodes::{SortItem, SortNode};
use crate::planning::plan::PlanNodeEnum;
#[derive(Debug)]
pub struct EliminateSortRule;
impl EliminateSortRule {
pub fn new() -> Self {
Self
}
fn can_eliminate_sort(&self, sort_node: &SortNode, input: &PlanNodeEnum) -> bool {
let sort_items = sort_node.sort_items();
match input {
PlanNodeEnum::IndexScan(index_scan) => {
let index_columns = vec![index_scan.index_name().to_string()];
self.check_order_match(sort_items, &index_columns)
}
PlanNodeEnum::Sort(inner_sort) => {
self.check_sort_compatibility(sort_items, inner_sort.sort_items())
}
PlanNodeEnum::TopN(topn) => {
self.check_sort_compatibility(sort_items, topn.sort_items())
}
_ => false,
}
}
fn check_order_match(&self, sort_items: &[SortItem], index_columns: &[String]) -> bool {
if sort_items.is_empty() {
return true;
}
for (i, sort_item) in sort_items.iter().enumerate() {
if i >= index_columns.len() {
return false;
}
match sort_item.column_name() {
Some(col_name) if col_name == index_columns[i] => {}
_ => return false,
}
}
true
}
fn check_sort_compatibility(&self, outer_items: &[SortItem], inner_items: &[SortItem]) -> bool {
if outer_items.is_empty() {
return true;
}
if outer_items.len() > inner_items.len() {
return false;
}
for (i, outer_item) in outer_items.iter().enumerate() {
let inner_item = &inner_items[i];
let outer_col = outer_item.column_name();
let inner_col = inner_item.column_name();
if outer_col != inner_col || outer_item.direction != inner_item.direction {
return false;
}
}
true
}
}
impl Default for EliminateSortRule {
fn default() -> Self {
Self::new()
}
}
impl RewriteRule for EliminateSortRule {
fn name(&self) -> &'static str {
"EliminateSortRule"
}
fn pattern(&self) -> Pattern {
Pattern::new_with_name("Sort")
}
fn apply(
&self,
_ctx: &mut RewriteContext,
node: &PlanNodeEnum,
) -> RewriteResult<Option<TransformResult>> {
let sort_node = match node {
PlanNodeEnum::Sort(n) => n,
_ => return Ok(None),
};
let input = sort_node.input();
if self.can_eliminate_sort(sort_node, input) {
let mut result = TransformResult::new();
result.new_nodes.push(input.clone());
return Ok(Some(result));
}
Ok(None)
}
}
impl EliminationRule for EliminateSortRule {
fn can_eliminate(&self, node: &PlanNodeEnum) -> bool {
let sort_node = match node {
PlanNodeEnum::Sort(n) => n,
_ => return false,
};
let input = sort_node.input();
self.can_eliminate_sort(sort_node, input)
}
fn eliminate(
&self,
_ctx: &mut RewriteContext,
node: &PlanNodeEnum,
) -> RewriteResult<Option<TransformResult>> {
let sort_node = match node {
PlanNodeEnum::Sort(n) => n,
_ => return Ok(None),
};
let input = sort_node.input();
let mut result = TransformResult::new();
result.new_nodes.push(input.clone());
Ok(Some(result))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::planning::plan::core::nodes::{SortItem, SortNode, StartNode};
#[test]
fn test_eliminate_sort_rule_name() {
let rule = EliminateSortRule::new();
assert_eq!(rule.name(), "EliminateSortRule");
}
#[test]
fn test_eliminate_sort_rule_pattern() {
let rule = EliminateSortRule::new();
assert!(rule.pattern().matches(&PlanNodeEnum::Sort(
SortNode::new(
PlanNodeEnum::Start(StartNode::new()),
vec![SortItem::column_asc("name".to_string())],
)
.expect("Failed to create SortNode")
)));
}
#[test]
fn test_check_sort_compatibility_exact_match() {
let rule = EliminateSortRule::new();
let outer = vec![
SortItem::column_asc("name".to_string()),
SortItem::column_asc("age".to_string()),
];
let inner = vec![
SortItem::column_asc("name".to_string()),
SortItem::column_asc("age".to_string()),
];
assert!(rule.check_sort_compatibility(&outer, &inner));
}
#[test]
fn test_check_sort_compatibility_prefix_match() {
let rule = EliminateSortRule::new();
let outer = vec![SortItem::column_asc("name".to_string())];
let inner = vec![
SortItem::column_asc("name".to_string()),
SortItem::column_asc("age".to_string()),
];
assert!(rule.check_sort_compatibility(&outer, &inner));
}
#[test]
fn test_check_sort_compatibility_direction_mismatch() {
let rule = EliminateSortRule::new();
let outer = vec![SortItem::column_desc("name".to_string())];
let inner = vec![SortItem::column_asc("name".to_string())];
assert!(!rule.check_sort_compatibility(&outer, &inner));
}
#[test]
fn test_check_sort_compatibility_column_mismatch() {
let rule = EliminateSortRule::new();
let outer = vec![SortItem::column_asc("name".to_string())];
let inner = vec![SortItem::column_asc("age".to_string())];
assert!(!rule.check_sort_compatibility(&outer, &inner));
}
#[test]
fn test_check_sort_compatibility_empty() {
let rule = EliminateSortRule::new();
let outer: Vec<SortItem> = vec![];
let inner = vec![SortItem::column_asc("name".to_string())];
assert!(rule.check_sort_compatibility(&outer, &inner));
}
}