use crate::parser::{
ComparisonOperator, Condition, CreateTableStatement, DeleteStatement, DropTableStatement,
InsertStatement, OrderByClause, OrderByItem, SelectStatement, SqlValue, Statement,
UpdateStatement,
};
use crate::query_processor::{ColumnInfo, TableSchema};
use crate::Result;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub enum VectorSimilarityFunction {
CosineSimilarity,
EuclideanDistance,
DotProduct,
}
#[derive(Debug, Clone)]
pub enum ExecutionPlan {
PrimaryKeyLookup {
table: String,
pk_value: SqlValue,
selected_columns: Vec<crate::parser::Expression>,
additional_filter: Option<Condition>,
},
TableRangeScan {
table: String,
selected_columns: Vec<crate::parser::Expression>,
pk_range: PkRange,
additional_filter: Option<Condition>,
limit: Option<u64>,
},
TableScan {
table: String,
selected_columns: Vec<crate::parser::Expression>,
filter: Option<Condition>,
limit: Option<u64>,
},
Insert {
table: String,
rows: Vec<HashMap<String, SqlValue>>,
conflict_resolution: ConflictResolution,
},
Update {
table: String,
assignments: Vec<Assignment>,
scan_plan: Box<ExecutionPlan>,
},
Delete {
table: String,
scan_plan: Box<ExecutionPlan>,
},
CreateTable {
table: String,
schema: TableSchema,
},
DropTable {
table: String,
if_exists: bool,
},
CreateIndex {
index_name: String,
table_name: String,
column_name: String,
unique: bool,
},
DropIndex {
index_name: String,
if_exists: bool,
},
CreateExtension {
extension_name: String,
library_path: Option<String>,
},
DropExtension {
extension_name: String,
},
Begin,
Commit,
Rollback,
IndexScan {
table: String,
index: String,
column_value: crate::parser::SqlValue,
selected_columns: Vec<crate::parser::Expression>,
additional_filter: Option<Condition>,
},
VectorSearch {
table: String,
index: String,
query_vector: Vec<f64>,
similarity_function: VectorSimilarityFunction,
k: usize,
selected_columns: Vec<crate::parser::Expression>,
additional_filter: Option<Condition>,
},
Sort {
input_plan: Box<ExecutionPlan>,
order_by_items: Vec<OrderByItem>,
schema: std::rc::Rc<TableSchema>,
query_schema: crate::query_processor::QuerySchema,
limit: Option<u64>,
},
}
#[derive(Debug, Clone)]
pub struct PkRange {
pub start_bound: Option<PkBound>,
pub end_bound: Option<PkBound>,
}
#[derive(Debug, Clone)]
pub struct PkBound {
pub value: SqlValue,
pub inclusive: bool,
}
#[derive(Debug, Clone)]
pub struct Assignment {
pub column: String,
pub value: crate::parser::Expression,
}
#[derive(Debug, Clone)]
pub enum ConflictResolution {
Error, Ignore, Replace, }
pub struct QueryPlanner {
table_schemas: HashMap<String, Rc<TableSchema>>,
}
impl QueryPlanner {
pub fn new(table_schemas: HashMap<String, Rc<TableSchema>>) -> Self {
Self { table_schemas }
}
fn find_suitable_index(
&self,
table_name: &str,
condition: &crate::parser::Condition,
) -> Option<(String, String, crate::parser::SqlValue)> {
let schema = self.table_schemas.get(table_name)?;
for index in &schema.indexes {
if index.index_type != crate::parser::IndexType::BTree {
continue;
}
if let Some(value) = Self::extract_indexable_value(condition, &index.column_name) {
return Some((index.name.clone(), index.column_name.clone(), value));
}
}
None
}
fn extract_indexable_value(
condition: &crate::parser::Condition,
column_name: &str,
) -> Option<crate::parser::SqlValue> {
match condition {
crate::parser::Condition::Comparison {
left,
operator,
right,
} => {
if let crate::parser::Expression::Column(col_name) = left {
if col_name == column_name
&& *operator == crate::parser::ComparisonOperator::Equal
{
Some(right.clone())
} else {
None
}
} else {
None
}
}
crate::parser::Condition::And(left, right) => {
Self::extract_indexable_value(left, column_name)
.or_else(|| Self::extract_indexable_value(right, column_name))
}
crate::parser::Condition::Or(left, right) => {
let left_val = Self::extract_indexable_value(left, column_name)?;
let right_val = Self::extract_indexable_value(right, column_name)?;
if left_val == right_val {
Some(left_val)
} else {
None
}
}
_ => None,
}
}
pub fn plan(&self, statement: Statement) -> Result<ExecutionPlan> {
match statement {
Statement::Select(select) => self.plan_select(select),
Statement::Insert(insert) => self.plan_insert(insert),
Statement::Update(update) => self.plan_update(update),
Statement::Delete(delete) => self.plan_delete(delete),
Statement::CreateTable(create) => self.plan_create_table(create),
Statement::DropTable(drop) => self.plan_drop_table(drop),
Statement::CreateIndex(create) => self.plan_create_index(create),
Statement::DropIndex(drop) => self.plan_drop_index(drop),
Statement::CreateExtension(create) => self.plan_create_extension(create),
Statement::DropExtension(drop) => self.plan_drop_extension(drop),
Statement::Begin => Ok(ExecutionPlan::Begin),
Statement::Commit => Ok(ExecutionPlan::Commit),
Statement::Rollback => Ok(ExecutionPlan::Rollback),
}
}
fn plan_select(&self, select: SelectStatement) -> Result<ExecutionPlan> {
if let Some(plan) = self.try_primary_key_lookup(&select)? {
return self.wrap_with_sort(plan, &select.order_by);
}
if let Some(plan) = self.try_primary_key_range_scan(&select)? {
return self.wrap_with_sort(plan, &select.order_by);
}
if let Some(plan) = self.try_vector_similarity_search(&select)? {
return self.wrap_with_sort(plan, &select.order_by);
}
if let Some(where_clause) = &select.where_clause {
if let Some((index_name, _column_name, value)) =
self.find_suitable_index(&select.table, &where_clause.condition)
{
let expr_columns =
self.normalize_selected_columns(&select.table, &select.columns)?;
let additional_filter = self.extract_non_index_conditions(
&select.table,
&where_clause.condition,
&index_name,
)?;
let plan = ExecutionPlan::IndexScan {
table: select.table,
index: index_name,
column_value: value,
selected_columns: expr_columns,
additional_filter,
};
return self.wrap_with_sort(plan, &select.order_by);
}
}
let plan = self.plan_table_scan(&select)?;
self.wrap_with_sort(plan, &select.order_by)
}
fn try_primary_key_lookup(&self, select: &SelectStatement) -> Result<Option<ExecutionPlan>> {
if let Some(ref where_clause) = select.where_clause {
if let Some(pk_value) =
self.extract_single_pk_equality(&select.table, &where_clause.condition)?
{
let expr_columns =
self.normalize_selected_columns(&select.table, &select.columns)?;
let additional_filter =
self.extract_non_pk_conditions(&select.table, &where_clause.condition)?;
return Ok(Some(ExecutionPlan::PrimaryKeyLookup {
table: select.table.clone(),
pk_value,
selected_columns: expr_columns,
additional_filter,
}));
}
}
Ok(None)
}
fn extract_single_pk_equality(
&self,
table_name: &str,
condition: &Condition,
) -> Result<Option<SqlValue>> {
let pk_columns = self.get_primary_key_columns(table_name)?;
if pk_columns.len() != 1 {
return Err(crate::Error::SqlError(
"Composite primary keys are not supported for this operation".to_string(),
));
}
let pk_col = &pk_columns[0];
let mut found = None;
Self::find_pk_equality(condition, pk_col, &mut found);
Ok(found)
}
fn find_pk_equality(cond: &Condition, pk_col: &str, found: &mut Option<SqlValue>) {
match cond {
Condition::Comparison {
left: crate::parser::Expression::Column(col_name),
operator,
right,
} if col_name == pk_col && matches!(operator, ComparisonOperator::Equal) => {
*found = Some(right.clone());
}
Condition::Comparison { .. } => {}
Condition::And(left, right) => {
Self::find_pk_equality(left, pk_col, found);
Self::find_pk_equality(right, pk_col, found);
}
_ => {}
}
}
fn try_primary_key_range_scan(
&self,
select: &SelectStatement,
) -> Result<Option<ExecutionPlan>> {
if let Some(ref where_clause) = select.where_clause {
if let Some(pk_range) =
self.extract_pk_range_conditions(&select.table, &where_clause.condition)?
{
let additional_filter =
self.extract_non_pk_conditions(&select.table, &where_clause.condition)?;
let expr_columns =
self.normalize_selected_columns(&select.table, &select.columns)?;
return Ok(Some(ExecutionPlan::TableRangeScan {
table: select.table.clone(),
selected_columns: expr_columns,
pk_range,
additional_filter,
limit: select.limit,
}));
}
}
Ok(None)
}
fn plan_table_scan(&self, select: &SelectStatement) -> Result<ExecutionPlan> {
let expr_columns = self.normalize_selected_columns(&select.table, &select.columns)?;
let filter = select.where_clause.as_ref().map(|w| w.condition.clone());
Ok(ExecutionPlan::TableScan {
table: select.table.clone(),
selected_columns: expr_columns,
filter,
limit: select.limit,
})
}
fn plan_insert(&self, insert: InsertStatement) -> Result<ExecutionPlan> {
let mut rows = Vec::with_capacity(insert.values.len());
for expr_row in insert.values {
let mut row_data = HashMap::with_capacity(insert.columns.len());
for (i, col_name) in insert.columns.iter().enumerate() {
if let Some(expr) = expr_row.get(i) {
let context = HashMap::new(); let value = expr.evaluate(&context).map_err(crate::Error::Other)?;
row_data.insert(col_name.clone(), value);
}
}
rows.push(row_data);
}
Ok(ExecutionPlan::Insert {
table: insert.table,
rows,
conflict_resolution: ConflictResolution::Error,
})
}
fn plan_update(&self, update: UpdateStatement) -> Result<ExecutionPlan> {
let assignments: Vec<Assignment> = update
.assignments
.into_iter()
.map(|a| Assignment {
column: a.column,
value: a.value,
})
.collect();
let expr_columns: Vec<crate::parser::Expression> = self.normalize_selected_columns(
&update.table,
&[crate::parser::Expression::Column("*".to_string())],
)?;
let scan_plan = if let Some(ref where_clause) = update.where_clause {
if let Some(pk_value) =
self.extract_single_pk_equality(&update.table, &where_clause.condition)?
{
let pk_columns = self.get_primary_key_columns(&update.table)?;
if pk_columns.len() == 1 {
ExecutionPlan::PrimaryKeyLookup {
table: update.table.clone(),
pk_value,
selected_columns: expr_columns.clone(),
additional_filter: None,
}
} else {
if let Some(pk_range) =
self.extract_pk_range_conditions(&update.table, &where_clause.condition)?
{
ExecutionPlan::TableRangeScan {
table: update.table.clone(),
selected_columns: expr_columns.clone(),
pk_range,
additional_filter: None,
limit: None,
}
} else {
ExecutionPlan::TableScan {
table: update.table.clone(),
selected_columns: expr_columns.clone(),
filter: Some(where_clause.condition.clone()),
limit: None,
}
}
}
} else {
if let Some(pk_range) =
self.extract_pk_range_conditions(&update.table, &where_clause.condition)?
{
ExecutionPlan::TableRangeScan {
table: update.table.clone(),
selected_columns: expr_columns.clone(),
pk_range,
additional_filter: None,
limit: None,
}
} else {
ExecutionPlan::TableScan {
table: update.table.clone(),
selected_columns: expr_columns.clone(),
filter: Some(where_clause.condition.clone()),
limit: None,
}
}
}
} else {
ExecutionPlan::TableScan {
table: update.table.clone(),
selected_columns: expr_columns.clone(),
filter: None,
limit: None,
}
};
Ok(ExecutionPlan::Update {
table: update.table,
assignments,
scan_plan: Box::new(scan_plan),
})
}
fn plan_delete(&self, delete: DeleteStatement) -> Result<ExecutionPlan> {
let expr_columns: Vec<crate::parser::Expression> = self.normalize_selected_columns(
&delete.table,
&[crate::parser::Expression::Column("*".to_string())],
)?;
let scan_plan = if let Some(ref where_clause) = delete.where_clause {
if let Some(pk_value) =
self.extract_single_pk_equality(&delete.table, &where_clause.condition)?
{
let pk_columns = self.get_primary_key_columns(&delete.table)?;
if pk_columns.len() == 1 {
ExecutionPlan::PrimaryKeyLookup {
table: delete.table.clone(),
pk_value,
selected_columns: expr_columns.clone(),
additional_filter: None,
}
} else {
if let Some(pk_range) =
self.extract_pk_range_conditions(&delete.table, &where_clause.condition)?
{
ExecutionPlan::TableRangeScan {
table: delete.table.clone(),
selected_columns: expr_columns.clone(),
pk_range,
additional_filter: None,
limit: None,
}
} else {
ExecutionPlan::TableScan {
table: delete.table.clone(),
selected_columns: expr_columns.clone(),
filter: Some(where_clause.condition.clone()),
limit: None,
}
}
}
} else {
if let Some(pk_range) =
self.extract_pk_range_conditions(&delete.table, &where_clause.condition)?
{
ExecutionPlan::TableRangeScan {
table: delete.table.clone(),
selected_columns: expr_columns.clone(),
pk_range,
additional_filter: None,
limit: None,
}
} else {
ExecutionPlan::TableScan {
table: delete.table.clone(),
selected_columns: expr_columns.clone(),
filter: Some(where_clause.condition.clone()),
limit: None,
}
}
}
} else {
ExecutionPlan::TableScan {
table: delete.table.clone(),
selected_columns: expr_columns.clone(),
filter: None,
limit: None,
}
};
Ok(ExecutionPlan::Delete {
table: delete.table,
scan_plan: Box::new(scan_plan),
})
}
fn plan_create_table(&self, create: CreateTableStatement) -> Result<ExecutionPlan> {
let schema = TableSchema {
name: create.table.clone(),
columns: create
.columns
.iter()
.map(|col| ColumnInfo {
name: col.name.clone(),
data_type: col.data_type.clone(),
constraints: col.constraints.clone(),
storage_offset: 0,
storage_size: 0,
storage_type_code: 0,
})
.collect(),
indexes: vec![], };
Ok(ExecutionPlan::CreateTable {
table: create.table,
schema,
})
}
fn plan_drop_table(&self, drop: DropTableStatement) -> Result<ExecutionPlan> {
Ok(ExecutionPlan::DropTable {
table: drop.table,
if_exists: drop.if_exists,
})
}
fn plan_create_index(
&self,
create: crate::parser::CreateIndexStatement,
) -> Result<ExecutionPlan> {
let table_name = create.table_name.clone();
let column_name = create.column_name.clone();
let unique = create.unique;
if let Some(_schema) = self.table_schemas.get(&table_name) {
return Ok(ExecutionPlan::CreateIndex {
index_name: create.index_name,
table_name,
column_name,
unique,
});
}
Err(crate::Error::TableNotFound(table_name))
}
fn plan_drop_index(&self, drop: crate::parser::DropIndexStatement) -> Result<ExecutionPlan> {
let index_name = drop.index_name.clone();
let if_exists = drop.if_exists;
Ok(ExecutionPlan::DropIndex {
index_name,
if_exists,
})
}
fn plan_create_extension(
&self,
create: crate::parser::CreateExtensionStatement,
) -> Result<ExecutionPlan> {
Ok(ExecutionPlan::CreateExtension {
extension_name: create.extension_name,
library_path: create.library_path,
})
}
fn plan_drop_extension(
&self,
drop: crate::parser::DropExtensionStatement,
) -> Result<ExecutionPlan> {
Ok(ExecutionPlan::DropExtension {
extension_name: drop.extension_name,
})
}
fn try_vector_similarity_search(
&self,
select: &SelectStatement,
) -> Result<Option<ExecutionPlan>> {
if let Some(order_by) = &select.order_by {
for item in &order_by.items {
if let Some((similarity_func, query_vector, k)) =
self.extract_vector_similarity_from_expr(&item.expression)?
{
if let Some(index_name) = self.find_vector_index(&select.table)? {
let expr_columns =
self.normalize_selected_columns(&select.table, &select.columns)?;
let additional_filter = select
.where_clause
.as_ref()
.map(|where_clause| where_clause.condition.clone());
return Ok(Some(ExecutionPlan::VectorSearch {
table: select.table.clone(),
index: index_name,
query_vector,
similarity_function: similarity_func,
k,
selected_columns: expr_columns,
additional_filter,
}));
}
}
}
}
if let Some(where_clause) = &select.where_clause {
if let Some((similarity_func, query_vector, _)) =
self.extract_vector_similarity_from_condition(&where_clause.condition)?
{
if let Some(index_name) = self.find_vector_index(&select.table)? {
let expr_columns =
self.normalize_selected_columns(&select.table, &select.columns)?;
let additional_filter =
self.extract_non_vector_conditions(&where_clause.condition)?;
return Ok(Some(ExecutionPlan::VectorSearch {
table: select.table.clone(),
index: index_name,
query_vector,
similarity_function: similarity_func,
k: 100, selected_columns: expr_columns,
additional_filter,
}));
}
}
}
Ok(None)
}
fn extract_vector_similarity_from_expr(
&self,
expr: &crate::parser::Expression,
) -> Result<Option<(VectorSimilarityFunction, Vec<f64>, usize)>> {
if let crate::parser::Expression::FunctionCall { name, args } = expr {
let similarity_func = match name.to_uppercase().as_str() {
"COSINE_SIMILARITY" => VectorSimilarityFunction::CosineSimilarity,
"EUCLIDEAN_DISTANCE" => VectorSimilarityFunction::EuclideanDistance,
"DOT_PRODUCT" => VectorSimilarityFunction::DotProduct,
_ => return Ok(None),
};
if args.len() == 2 {
if let (
crate::parser::Expression::Column(_),
crate::parser::Expression::Value(SqlValue::Vector(vector)),
) = (&args[0], &args[1])
{
return Ok(Some((similarity_func, vector.clone(), 10))); }
}
}
Ok(None)
}
fn extract_vector_similarity_from_condition(
&self,
condition: &Condition,
) -> Result<Option<(VectorSimilarityFunction, Vec<f64>, usize)>> {
if let Condition::Comparison {
left,
operator: _,
right,
} = condition
{
if let (
crate::parser::Expression::FunctionCall { name, args },
SqlValue::Real(_threshold),
) = (left, right)
{
let similarity_func = match name.to_uppercase().as_str() {
"COSINE_SIMILARITY" => VectorSimilarityFunction::CosineSimilarity,
"EUCLIDEAN_DISTANCE" => VectorSimilarityFunction::EuclideanDistance,
"DOT_PRODUCT" => VectorSimilarityFunction::DotProduct,
_ => return Ok(None),
};
if args.len() == 2 {
if let (
crate::parser::Expression::Column(_),
crate::parser::Expression::Value(SqlValue::Vector(vector)),
) = (&args[0], &args[1])
{
return Ok(Some((similarity_func, vector.clone(), 100)));
}
}
}
}
Ok(None)
}
fn find_vector_index(&self, table_name: &str) -> Result<Option<String>> {
if let Some(schema) = self.table_schemas.get(table_name) {
for index in &schema.indexes {
if matches!(
index.index_type,
crate::parser::IndexType::HNSW
| crate::parser::IndexType::IVF
| crate::parser::IndexType::LSH
) {
return Ok(Some(index.name.clone()));
}
}
}
Ok(None)
}
fn extract_non_vector_conditions(&self, _condition: &Condition) -> Result<Option<Condition>> {
Ok(None)
}
fn extract_pk_range_conditions(
&self,
table_name: &str,
condition: &Condition,
) -> Result<Option<PkRange>> {
let pk_columns = self.get_primary_key_columns(table_name)?;
let mut pk_conditions = HashMap::new();
Self::collect_pk_range_values(condition, &pk_columns, &mut pk_conditions);
if pk_conditions.is_empty() {
return Ok(None);
}
let mut start_bound = None;
let mut end_bound = None;
for (_column, conditions) in pk_conditions {
for (operator, value) in conditions {
match operator {
ComparisonOperator::GreaterThan => {
if start_bound.is_none() {
start_bound = Some(PkBound {
value: value.clone(),
inclusive: false,
});
}
}
ComparisonOperator::GreaterThanOrEqual => {
if start_bound.is_none() {
start_bound = Some(PkBound {
value: value.clone(),
inclusive: true,
});
}
}
ComparisonOperator::LessThan => {
if end_bound.is_none() {
end_bound = Some(PkBound {
value: value.clone(),
inclusive: false,
});
}
}
ComparisonOperator::LessThanOrEqual => {
if end_bound.is_none() {
end_bound = Some(PkBound {
value: value.clone(),
inclusive: true,
});
}
}
_ => {} }
}
}
if start_bound.is_some() || end_bound.is_some() {
Ok(Some(PkRange {
start_bound,
end_bound,
}))
} else {
Ok(None)
}
}
fn collect_pk_range_values(
condition: &Condition,
pk_columns: &[String],
pk_conditions: &mut HashMap<String, Vec<(ComparisonOperator, SqlValue)>>,
) {
match condition {
Condition::Comparison {
left,
operator,
right,
} => {
if let crate::parser::Expression::Column(col_name) = left {
if pk_columns.contains(col_name) {
pk_conditions
.entry(col_name.clone())
.or_default()
.push((*operator, right.clone()));
}
}
}
Condition::Between {
column: _column,
low: _low,
high: _high,
} => {
if pk_columns.contains(_column) {
pk_conditions
.entry(_column.clone())
.or_default()
.push((ComparisonOperator::GreaterThanOrEqual, _low.clone()));
pk_conditions
.entry(_column.clone())
.or_default()
.push((ComparisonOperator::LessThanOrEqual, _high.clone()));
}
}
Condition::And(left_cond, right_cond) => {
Self::collect_pk_range_values(left_cond, pk_columns, pk_conditions);
Self::collect_pk_range_values(right_cond, pk_columns, pk_conditions);
}
Condition::Or(_, _) => {
}
}
}
fn extract_conditions_excluding_columns(
excluded_columns: &std::collections::HashSet<String>,
condition: &Condition,
) -> Option<Condition> {
match condition {
Condition::Comparison { left, .. } => {
if let crate::parser::Expression::Column(col_name) = left {
if excluded_columns.contains(col_name) {
None
} else {
Some(condition.clone())
}
} else {
Some(condition.clone())
}
}
Condition::Between { column, .. } => {
if excluded_columns.contains(column) {
None
} else {
Some(condition.clone())
}
}
Condition::And(left, right) => {
let l = Self::extract_conditions_excluding_columns(excluded_columns, left);
let r = Self::extract_conditions_excluding_columns(excluded_columns, right);
match (l, r) {
(Some(lc), Some(rc)) => Some(Condition::And(Box::new(lc), Box::new(rc))),
(Some(lc), None) => Some(lc),
(None, Some(rc)) => Some(rc),
(None, None) => None,
}
}
Condition::Or(left, right) => {
let l = Self::extract_conditions_excluding_columns(excluded_columns, left);
let r = Self::extract_conditions_excluding_columns(excluded_columns, right);
match (l, r) {
(Some(lc), Some(rc)) => Some(Condition::Or(Box::new(lc), Box::new(rc))),
(Some(lc), None) => Some(lc),
(None, Some(rc)) => Some(rc),
(None, None) => None,
}
}
}
}
fn extract_non_pk_conditions(
&self,
table_name: &str,
condition: &Condition,
) -> Result<Option<Condition>> {
let pk_columns = self.get_primary_key_columns(table_name)?;
let excluded: std::collections::HashSet<String> = pk_columns.into_iter().collect();
Ok(Self::extract_conditions_excluding_columns(
&excluded, condition,
))
}
fn extract_non_index_conditions(
&self,
table_name: &str,
condition: &Condition,
index_name: &str,
) -> Result<Option<Condition>> {
let schema = self
.table_schemas
.get(table_name)
.ok_or_else(|| crate::Error::Other(format!("Table '{table_name}' not found")))?;
let indexed_column = schema
.indexes
.iter()
.find(|idx| idx.name == index_name)
.map(|idx| idx.column_name.clone())
.ok_or_else(|| crate::Error::Other(format!("Index '{index_name}' not found")))?;
let mut excluded = std::collections::HashSet::new();
excluded.insert(indexed_column);
Ok(Self::extract_conditions_excluding_columns(
&excluded, condition,
))
}
fn normalize_selected_columns(
&self,
table_name: &str,
columns: &[crate::parser::Expression],
) -> Result<Vec<crate::parser::Expression>> {
if columns.len() == 1 {
if let crate::parser::Expression::Column(ref star) = columns[0] {
if star == "*" {
if let Some(schema) = self.table_schemas.get(table_name) {
return Ok(schema
.columns
.iter()
.map(|c| crate::parser::Expression::Column(c.name.clone()))
.collect());
} else {
return Err(crate::Error::Other(format!(
"Table '{table_name}' not found for column expansion"
)));
}
}
}
}
let mut all_columns = Vec::new();
let mut referenced_columns = std::collections::HashSet::new();
for expr in columns {
all_columns.push(expr.clone());
Self::extract_columns_from_expression(expr, &mut referenced_columns);
}
for column_name in referenced_columns {
let column_expr = crate::parser::Expression::Column(column_name);
if !all_columns.iter().any(|expr| expr == &column_expr) {
all_columns.push(column_expr);
}
}
Ok(all_columns)
}
fn extract_columns_from_expression(
expr: &crate::parser::Expression,
columns: &mut std::collections::HashSet<String>,
) {
match expr {
crate::parser::Expression::Column(name) => {
columns.insert(name.clone());
}
crate::parser::Expression::FunctionCall { args, .. } => {
for arg in args {
Self::extract_columns_from_expression(arg, columns);
}
}
crate::parser::Expression::BinaryOp { left, right, .. } => {
Self::extract_columns_from_expression(left, columns);
Self::extract_columns_from_expression(right, columns);
}
crate::parser::Expression::Value(_) => {
}
crate::parser::Expression::AggregateFunction { arg, .. } => {
Self::extract_columns_from_expression(arg, columns);
}
}
}
fn get_primary_key_columns(&self, table_name: &str) -> Result<Vec<String>> {
if let Some(schema) = self.table_schemas.get(table_name) {
let pk_columns: Vec<String> = schema
.columns
.iter()
.filter(|col| {
col.constraints
.contains(&crate::parser::ColumnConstraint::PrimaryKey)
})
.map(|col| col.name.clone())
.collect();
Ok(pk_columns)
} else {
Err(crate::Error::TableNotFound(table_name.to_string()))
}
}
pub fn update_table_schema(&mut self, table_name: String, schema: Rc<TableSchema>) {
self.table_schemas.insert(table_name, schema);
}
pub fn remove_table_schema(&mut self, table_name: &str) {
self.table_schemas.remove(table_name);
}
pub fn table_schemas(&self) -> &HashMap<String, Rc<TableSchema>> {
&self.table_schemas
}
fn wrap_with_sort(
&self,
plan: ExecutionPlan,
order_by: &Option<OrderByClause>,
) -> Result<ExecutionPlan> {
if let Some(order_by_clause) = order_by {
let table_name = plan.primary_table().unwrap_or("");
let schema = self.table_schemas.get(table_name).unwrap().clone();
let selected_columns = match &plan {
ExecutionPlan::PrimaryKeyLookup {
selected_columns, ..
} => selected_columns.clone(),
ExecutionPlan::TableRangeScan {
selected_columns, ..
} => selected_columns.clone(),
ExecutionPlan::TableScan {
selected_columns, ..
} => selected_columns.clone(),
ExecutionPlan::IndexScan {
selected_columns, ..
} => selected_columns.clone(),
ExecutionPlan::VectorSearch {
selected_columns, ..
} => selected_columns.clone(),
_ => schema
.columns
.iter()
.map(|c| crate::parser::Expression::Column(c.name.clone()))
.collect(),
};
let query_schema = crate::query_processor::QuerySchema::new_with_expressions(
&selected_columns,
&schema,
);
let limit = match &plan {
ExecutionPlan::TableScan { limit, .. } => *limit,
ExecutionPlan::TableRangeScan { limit, .. } => *limit,
_ => None,
};
Ok(ExecutionPlan::Sort {
input_plan: Box::new(plan),
order_by_items: order_by_clause.items.clone(),
schema,
query_schema,
limit,
})
} else {
Ok(plan)
}
}
}
impl ExecutionPlan {
pub fn primary_table(&self) -> Option<&str> {
match self {
ExecutionPlan::PrimaryKeyLookup { table, .. } => Some(table),
ExecutionPlan::TableRangeScan { table, .. } => Some(table),
ExecutionPlan::TableScan { table, .. } => Some(table),
ExecutionPlan::Insert { table, .. } => Some(table),
ExecutionPlan::Update { table, .. } => Some(table),
ExecutionPlan::Delete { table, .. } => Some(table),
ExecutionPlan::CreateTable { table, .. } => Some(table),
ExecutionPlan::DropTable { table, .. } => Some(table),
ExecutionPlan::CreateIndex { table_name, .. } => Some(table_name),
ExecutionPlan::CreateExtension { .. } | ExecutionPlan::DropExtension { .. } => None,
ExecutionPlan::IndexScan { table, .. } => Some(table),
ExecutionPlan::VectorSearch { table, .. } => Some(table),
ExecutionPlan::Sort { input_plan, .. } => input_plan.primary_table(),
_ => None,
}
}
pub fn requires_table_scan(&self) -> bool {
match self {
ExecutionPlan::TableScan { .. } => true,
ExecutionPlan::TableRangeScan { .. } => true,
ExecutionPlan::Update { scan_plan, .. } => scan_plan.requires_table_scan(),
ExecutionPlan::Delete { scan_plan, .. } => scan_plan.requires_table_scan(),
ExecutionPlan::IndexScan { .. } => true,
ExecutionPlan::Sort { input_plan, .. } => input_plan.requires_table_scan(),
_ => false,
}
}
pub fn describe(&self) -> String {
match self {
ExecutionPlan::PrimaryKeyLookup {
table, pk_value, ..
} => {
format!("Primary Key Lookup on {table} (key: {pk_value:?})")
}
ExecutionPlan::TableRangeScan {
table, pk_range, ..
} => {
let range_desc = match (&pk_range.start_bound, &pk_range.end_bound) {
(Some(start), Some(end)) => {
let start_value = &start.value;
let end_value = &end.value;
format!("range: {start_value:?} to {end_value:?}")
}
(Some(start), None) => {
let value = &start.value;
format!("range: >= {value:?}")
}
(None, Some(end)) => {
let value = &end.value;
format!("range: <= {value:?}")
}
(None, None) => "range: all".to_string(),
};
format!("Table Range Scan on {table} ({range_desc})")
}
ExecutionPlan::TableScan {
table,
selected_columns,
filter,
limit,
} => {
let columns_str = selected_columns
.iter()
.map(|expr| match expr {
crate::parser::Expression::Column(name) => name.clone(),
_ => format!("{expr:?}"),
})
.collect::<Vec<_>>()
.join(", ");
format!("TableScan({table}, columns=[{columns_str}], filter={filter:?}, limit={limit:?})")
}
ExecutionPlan::Insert { table, rows, .. } => {
let row_count = rows.len();
format!("Insert into {table} ({row_count} rows)")
}
ExecutionPlan::Update {
table, scan_plan, ..
} => {
let plan_desc = scan_plan.describe();
format!("Update {table} via {plan_desc}")
}
ExecutionPlan::Delete {
table, scan_plan, ..
} => {
let plan_desc = scan_plan.describe();
format!("Delete from {table} via {plan_desc}")
}
ExecutionPlan::CreateTable { table, .. } => {
format!("Create Table {table}")
}
ExecutionPlan::DropTable { table, .. } => {
format!("Drop Table {table}")
}
ExecutionPlan::CreateIndex {
index_name,
table_name,
column_name,
unique,
} => {
format!("Create Index {index_name} on {table_name} (column: {column_name}, unique: {unique})")
}
ExecutionPlan::DropIndex {
index_name,
if_exists,
} => {
format!("Drop Index {index_name} (if_exists: {if_exists})")
}
ExecutionPlan::Begin => "Begin Transaction".to_string(),
ExecutionPlan::Commit => "Commit Transaction".to_string(),
ExecutionPlan::Rollback => "Rollback Transaction".to_string(),
ExecutionPlan::CreateExtension { extension_name, .. } => {
format!("Create Extension: {extension_name}")
}
ExecutionPlan::DropExtension { extension_name } => {
format!("Drop Extension: {extension_name}")
}
ExecutionPlan::IndexScan {
table,
index,
column_value,
selected_columns,
additional_filter: _,
} => {
let columns_str = selected_columns
.iter()
.map(|expr| match expr {
crate::parser::Expression::Column(name) => name.clone(),
_ => format!("{expr:?}"),
})
.collect::<Vec<_>>()
.join(", ");
format!(
"Index Scan on {table} (index: {index}, column: {column_value:?}, selected: {columns_str})"
)
}
ExecutionPlan::VectorSearch {
table,
index,
query_vector,
similarity_function,
k,
selected_columns,
additional_filter: _,
} => {
let columns_str = selected_columns
.iter()
.map(|expr| match expr {
crate::parser::Expression::Column(name) => name.clone(),
_ => format!("{expr:?}"),
})
.collect::<Vec<_>>()
.join(", ");
let func_name = match similarity_function {
VectorSimilarityFunction::CosineSimilarity => "COSINE_SIMILARITY",
VectorSimilarityFunction::EuclideanDistance => "EUCLIDEAN_DISTANCE",
VectorSimilarityFunction::DotProduct => "DOT_PRODUCT",
};
format!(
"Vector Search on {table} (index: {index}, function: {func_name}, k: {k}, vector: {query_vector:?}, selected: {columns_str})"
)
}
ExecutionPlan::Sort {
input_plan,
order_by_items,
..
} => {
let order_desc = order_by_items
.iter()
.map(|item| {
let expr_str = match &item.expression {
crate::parser::Expression::Column(name) => name.clone(),
_ => format!("{expr:?}", expr = item.expression),
};
let direction = item.direction;
format!("{expr_str} {direction:?}")
})
.collect::<Vec<_>>()
.join(", ");
let input_desc = input_plan.describe();
format!("Sort on {input_desc} (order: {order_desc})")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::{ColumnConstraint, DataType, SqlValue, Statement, WhereClause};
use crate::query_processor::{ColumnInfo, TableSchema};
use std::collections::HashMap;
use std::rc::Rc;
fn create_test_schema() -> HashMap<String, Rc<TableSchema>> {
let mut schemas = HashMap::new();
let mut users_schema = TableSchema {
name: "users".to_string(),
columns: vec![
ColumnInfo {
name: "id".to_string(),
data_type: DataType::Integer,
constraints: vec![ColumnConstraint::PrimaryKey],
storage_offset: 0,
storage_size: 0,
storage_type_code: 0,
},
ColumnInfo {
name: "name".to_string(),
data_type: DataType::Text(None),
constraints: vec![],
storage_offset: 0,
storage_size: 0,
storage_type_code: 0,
},
ColumnInfo {
name: "email".to_string(),
data_type: DataType::Text(None),
constraints: vec![],
storage_offset: 0,
storage_size: 0,
storage_type_code: 0,
},
],
indexes: vec![], };
let _ = crate::catalog::Catalog::compute_table_metadata(&mut users_schema);
schemas.insert("users".to_string(), Rc::new(users_schema));
schemas
}
#[test]
fn test_primary_key_optimization() {
let schemas = create_test_schema();
let planner = QueryPlanner::new(schemas);
let select = SelectStatement {
columns: vec![
crate::parser::Expression::Column("name".to_string()),
crate::parser::Expression::Column("email".to_string()),
],
table: "users".to_string(),
where_clause: Some(WhereClause {
condition: Condition::Comparison {
left: crate::parser::Expression::Column("id".to_string()),
operator: ComparisonOperator::Equal,
right: SqlValue::Integer(123),
},
}),
order_by: None,
limit: None,
};
let plan = planner.plan(Statement::Select(select)).unwrap();
match plan {
ExecutionPlan::PrimaryKeyLookup {
table, pk_value, ..
} => {
assert_eq!(table, "users");
assert_eq!(pk_value, SqlValue::Integer(123));
}
_ => panic!("Expected PrimaryKeyLookup plan"),
}
}
#[test]
fn test_primary_key_range_scan() {
let schemas = create_test_schema();
let planner = QueryPlanner::new(schemas);
let select = SelectStatement {
columns: vec![crate::parser::Expression::Column("*".to_string())],
table: "users".to_string(),
where_clause: Some(WhereClause {
condition: Condition::And(
Box::new(Condition::Comparison {
left: crate::parser::Expression::Column("id".to_string()),
operator: ComparisonOperator::GreaterThan,
right: SqlValue::Integer(1),
}),
Box::new(Condition::Comparison {
left: crate::parser::Expression::Column("id".to_string()),
operator: ComparisonOperator::LessThan,
right: SqlValue::Integer(10),
}),
),
}),
order_by: None,
limit: Some(10),
};
let plan = planner.plan(Statement::Select(select)).unwrap();
match plan {
ExecutionPlan::TableRangeScan {
table, pk_range, ..
} => {
assert_eq!(table, "users");
assert!(pk_range.start_bound.is_some());
assert!(pk_range.end_bound.is_some());
}
_ => panic!("Expected TableRangeScan plan"),
}
}
#[test]
fn test_table_scan_fallback() {
let schemas = create_test_schema();
let planner = QueryPlanner::new(schemas);
let select = SelectStatement {
columns: vec![crate::parser::Expression::Column("*".to_string())],
table: "users".to_string(),
where_clause: Some(WhereClause {
condition: Condition::Comparison {
left: crate::parser::Expression::Column("name".to_string()),
operator: ComparisonOperator::Equal,
right: SqlValue::Text("John".to_string()),
},
}),
order_by: None,
limit: Some(10),
};
let plan = planner.plan(Statement::Select(select)).unwrap();
match plan {
ExecutionPlan::TableScan { table, limit, .. } => {
assert_eq!(table, "users");
assert_eq!(limit, Some(10));
}
_ => panic!("Expected TableScan plan"),
}
}
}