use llkv_plan::{
AlterTablePlan, CreateIndexPlan, CreateTablePlan, CreateViewPlan, DropIndexPlan, DropTablePlan,
DropViewPlan, RenameTablePlan,
};
use llkv_result::Result;
use sqlparser::ast::{CreateTable, ObjectName, ObjectNamePart, TableConstraint};
use std::collections::HashSet;
pub trait CatalogDdl {
type CreateTableOutput;
type DropTableOutput;
type RenameTableOutput;
type AlterTableOutput;
type CreateIndexOutput;
type DropIndexOutput;
fn create_table(&self, plan: CreateTablePlan) -> Result<Self::CreateTableOutput>;
fn drop_table(&self, plan: DropTablePlan) -> Result<Self::DropTableOutput>;
fn create_view(&self, plan: CreateViewPlan) -> Result<()>;
fn drop_view(&self, plan: DropViewPlan) -> Result<()>;
fn rename_table(&self, plan: RenameTablePlan) -> Result<Self::RenameTableOutput>;
fn alter_table(&self, plan: AlterTablePlan) -> Result<Self::AlterTableOutput>;
fn create_index(&self, plan: CreateIndexPlan) -> Result<Self::CreateIndexOutput>;
fn drop_index(&self, plan: DropIndexPlan) -> Result<Self::DropIndexOutput>;
}
pub trait ObjectNameExt {
fn canonical_ident(&self) -> Option<String>;
}
impl ObjectNameExt for ObjectName {
fn canonical_ident(&self) -> Option<String> {
self.0.last().and_then(|part| match part {
ObjectNamePart::Identifier(ident) => Some(ident.value.to_ascii_uppercase()),
ObjectNamePart::Function(_) => None,
})
}
}
pub trait TableConstraintExt {
fn normalize(self) -> TableConstraint;
}
impl TableConstraintExt for TableConstraint {
fn normalize(self) -> TableConstraint {
match self {
TableConstraint::ForeignKey {
mut name,
mut index_name,
columns,
foreign_table,
referred_columns,
on_delete,
on_update,
characteristics,
} => {
if name.is_none() {
name = index_name.clone();
}
index_name = None;
TableConstraint::ForeignKey {
name,
index_name,
columns,
foreign_table,
referred_columns,
on_delete,
on_update,
characteristics,
}
}
TableConstraint::PrimaryKey {
name,
index_name: _,
index_type,
columns,
index_options,
characteristics,
} => TableConstraint::PrimaryKey {
name,
index_name: None,
index_type,
columns,
index_options,
characteristics,
},
TableConstraint::Unique {
name,
index_name: _,
index_type,
columns,
index_options,
nulls_distinct,
characteristics,
index_type_display,
} => TableConstraint::Unique {
name,
index_name: None,
index_type,
columns,
index_options,
nulls_distinct,
characteristics,
index_type_display,
},
other => other,
}
}
}
pub trait OrderCreateTablesExt {
fn order_by_foreign_keys(self) -> Vec<CreateTable>;
}
impl OrderCreateTablesExt for Vec<CreateTable> {
fn order_by_foreign_keys(mut self) -> Vec<CreateTable> {
let mut ordered = Vec::with_capacity(self.len());
if self.is_empty() {
return ordered;
}
let table_names: HashSet<String> = self
.iter()
.filter_map(|table| table.name.canonical_ident())
.collect();
let mut resolved: HashSet<String> = HashSet::new();
while !self.is_empty() {
let mut progress = false;
let current_round = std::mem::take(&mut self);
let mut next_round = Vec::new();
for table in current_round.into_iter() {
let table_name = table.name.canonical_ident().unwrap_or_default();
let deps = collect_foreign_key_dependencies(&table);
let deps_satisfied = deps.into_iter().all(|dep| {
dep.is_empty()
|| dep == table_name
|| !table_names.contains(&dep)
|| resolved.contains(&dep)
});
if deps_satisfied {
resolved.insert(table_name);
ordered.push(table);
progress = true;
} else {
next_round.push(table);
}
}
if !progress {
ordered.append(&mut next_round);
break;
}
self = next_round;
}
ordered
}
}
fn collect_foreign_key_dependencies(table: &CreateTable) -> Vec<String> {
let mut deps = Vec::new();
for constraint in &table.constraints {
if let TableConstraint::ForeignKey { foreign_table, .. } = constraint
&& let Some(name) = foreign_table.canonical_ident()
{
deps.push(name);
}
}
deps
}