use crate::query_plan::pipeline::ASTTransformer;
use crate::sql::parser::ast::{Condition, LogicalOp, SelectStatement, WhereClause};
use anyhow::Result;
use tracing::debug;
pub struct QualifyToWhereTransformer;
impl QualifyToWhereTransformer {
pub fn new() -> Self {
Self
}
}
impl Default for QualifyToWhereTransformer {
fn default() -> Self {
Self::new()
}
}
impl ASTTransformer for QualifyToWhereTransformer {
fn name(&self) -> &str {
"QualifyToWhereTransformer"
}
fn description(&self) -> &str {
"Converts QUALIFY clauses to WHERE clauses after window function lifting"
}
fn transform(&mut self, mut stmt: SelectStatement) -> Result<SelectStatement> {
if stmt.qualify.is_none() {
return Ok(stmt);
}
let qualify_expr = stmt.qualify.take().unwrap();
debug!("Converting QUALIFY clause to WHERE");
if let Some(mut where_clause) = stmt.where_clause.take() {
debug!("Combining QUALIFY with existing WHERE using AND");
if let Some(last_condition) = where_clause.conditions.last_mut() {
last_condition.connector = Some(LogicalOp::And);
}
where_clause.conditions.push(Condition {
expr: qualify_expr,
connector: None,
});
stmt.where_clause = Some(where_clause);
} else {
debug!("Creating new WHERE clause from QUALIFY");
stmt.where_clause = Some(WhereClause {
conditions: vec![Condition {
expr: qualify_expr,
connector: None,
}],
});
}
Ok(stmt)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sql::parser::ast::{ColumnRef, QuoteStyle, SqlExpression};
#[test]
fn test_qualify_to_where_simple() {
let mut stmt = SelectStatement::default();
stmt.qualify = Some(SqlExpression::BinaryOp {
left: Box::new(SqlExpression::Column(ColumnRef::unquoted("rn".to_string()))),
op: "<=".to_string(),
right: Box::new(SqlExpression::NumberLiteral("3".to_string())),
});
let mut transformer = QualifyToWhereTransformer::new();
let result = transformer.transform(stmt).unwrap();
assert!(result.qualify.is_none());
assert!(result.where_clause.is_some());
let where_clause = result.where_clause.unwrap();
assert_eq!(where_clause.conditions.len(), 1);
}
#[test]
fn test_qualify_combined_with_existing_where() {
let mut stmt = SelectStatement::default();
stmt.where_clause = Some(WhereClause {
conditions: vec![Condition {
expr: SqlExpression::BinaryOp {
left: Box::new(SqlExpression::Column(ColumnRef::unquoted(
"region".to_string(),
))),
op: "=".to_string(),
right: Box::new(SqlExpression::StringLiteral("North".to_string())),
},
connector: None,
}],
});
stmt.qualify = Some(SqlExpression::BinaryOp {
left: Box::new(SqlExpression::Column(ColumnRef::unquoted("rn".to_string()))),
op: "<=".to_string(),
right: Box::new(SqlExpression::NumberLiteral("3".to_string())),
});
let mut transformer = QualifyToWhereTransformer::new();
let result = transformer.transform(stmt).unwrap();
assert!(result.qualify.is_none());
let where_clause = result.where_clause.unwrap();
assert_eq!(where_clause.conditions.len(), 2);
assert!(matches!(
where_clause.conditions[0].connector,
Some(LogicalOp::And)
));
}
}