use crate::error::{QuillSQLError, QuillSQLResult};
use crate::catalog::Catalog;
use crate::plan::logical_plan::{
Analyze, LogicalPlan, OrderByExpr, TransactionModes, TransactionScope,
};
use crate::sql::ast;
use crate::sql::ast::Value;
use crate::utils::table_ref::TableReference;
use sqlparser::ast::{Ident, ObjectType};
pub struct PlannerContext<'a> {
pub catalog: &'a Catalog,
}
pub struct LogicalPlanner<'a> {
pub context: PlannerContext<'a>,
}
impl<'a> LogicalPlanner<'a> {
pub fn plan(&mut self, stmt: &ast::Statement) -> QuillSQLResult<LogicalPlan> {
match stmt {
ast::Statement::CreateTable {
name,
columns,
if_not_exists,
engine,
..
} => self.plan_create_table(name, columns, *if_not_exists, engine.as_deref()),
ast::Statement::CreateIndex {
name,
table_name,
using,
columns,
..
} => self.plan_create_index(name, table_name, using.as_ref(), columns),
ast::Statement::Drop {
object_type,
if_exists,
names,
cascade,
restrict: _,
purge,
} => match object_type {
ObjectType::Table => self.plan_drop_table(names, *if_exists, *cascade, *purge),
ObjectType::Index => self.plan_drop_index(names, *if_exists, *cascade, *purge),
other => Err(QuillSQLError::NotSupport(format!(
"DROP {} is not supported",
other
))),
},
ast::Statement::Query(query) => self.plan_query(query),
ast::Statement::Insert {
table_name,
columns,
source,
..
} => self.plan_insert(table_name, columns, source),
ast::Statement::Update {
table,
assignments,
selection,
..
} => self.plan_update(table, assignments, selection),
ast::Statement::Delete {
tables,
from,
using,
selection,
returning,
} => {
if !tables.is_empty() {
return Err(QuillSQLError::Plan(
"DELETE with table aliases is not supported".to_string(),
));
}
if using.is_some() {
return Err(QuillSQLError::Plan(
"DELETE USING clause is not supported".to_string(),
));
}
if returning.is_some() {
return Err(QuillSQLError::Plan(
"DELETE RETURNING is not supported".to_string(),
));
}
if from.len() != 1 {
return Err(QuillSQLError::Plan(
"DELETE must target exactly one table".to_string(),
));
}
self.plan_delete(&from[0], selection)
}
ast::Statement::Explain { statement, .. } => self.plan_explain(statement),
ast::Statement::StartTransaction { modes, .. } => self.plan_begin_transaction(modes),
ast::Statement::Commit { .. } => Ok(LogicalPlan::CommitTransaction),
ast::Statement::Rollback { .. } => Ok(LogicalPlan::RollbackTransaction),
ast::Statement::SetTransaction {
modes,
snapshot,
session,
} => self.plan_set_transaction(*session, snapshot, modes),
ast::Statement::Analyze {
table_name,
partitions,
for_columns,
columns,
cache_metadata,
noscan,
compute_statistics: _,
} => self.plan_analyze(
table_name,
partitions,
*for_columns,
columns,
*cache_metadata,
*noscan,
),
_ => unimplemented!(),
}
}
fn plan_begin_transaction(
&self,
modes: &[ast::TransactionMode],
) -> QuillSQLResult<LogicalPlan> {
Ok(LogicalPlan::BeginTransaction(TransactionModes::from_modes(
modes,
)))
}
fn plan_set_transaction(
&self,
session_scope: bool,
snapshot: &Option<Value>,
modes: &[ast::TransactionMode],
) -> QuillSQLResult<LogicalPlan> {
if snapshot.is_some() {
return Err(QuillSQLError::Plan(
"SET TRANSACTION SNAPSHOT is not supported".to_string(),
));
};
let logical_scope = if session_scope {
TransactionScope::Session
} else {
TransactionScope::Transaction
};
Ok(LogicalPlan::SetTransaction {
scope: logical_scope,
modes: TransactionModes::from_modes(modes),
})
}
pub fn bind_order_by_expr(&self, order_by: &ast::OrderByExpr) -> QuillSQLResult<OrderByExpr> {
let expr = self.bind_expr(&order_by.expr)?;
Ok(OrderByExpr {
expr: Box::new(expr),
asc: order_by.asc.unwrap_or(true),
nulls_first: order_by.nulls_first.unwrap_or(false),
})
}
pub fn bind_table_name(&self, table_name: &ast::ObjectName) -> QuillSQLResult<TableReference> {
match table_name.0.as_slice() {
[table] => Ok(TableReference::Bare {
table: table.value.clone(),
}),
[schema, table] => Ok(TableReference::Partial {
schema: schema.value.clone(),
table: table.value.clone(),
}),
[catalog, schema, table] => Ok(TableReference::Full {
catalog: catalog.value.clone(),
schema: schema.value.clone(),
table: table.value.clone(),
}),
_ => Err(QuillSQLError::Plan(format!(
"Fail to plan table name: {}",
table_name
))),
}
}
fn plan_analyze(
&self,
table_name: &ast::ObjectName,
partitions: &Option<Vec<ast::Expr>>,
for_columns: bool,
columns: &[Ident],
cache_metadata: bool,
noscan: bool,
) -> QuillSQLResult<LogicalPlan> {
if partitions.as_ref().is_some_and(|p| !p.is_empty()) {
return Err(QuillSQLError::Plan(
"ANALYZE PARTITION is not supported".to_string(),
));
}
if for_columns || !columns.is_empty() {
return Err(QuillSQLError::Plan(
"ANALYZE FOR COLUMNS is not supported".to_string(),
));
}
if cache_metadata || noscan {
return Err(QuillSQLError::Plan(
"ANALYZE options NOSCAN/CACHE METADATA are not supported".to_string(),
));
}
let table_ref = self.bind_table_name(table_name)?;
Ok(LogicalPlan::Analyze(Analyze { table: table_ref }))
}
}