use thiserror::Error;
use crate::{
ast::{Column, Expr},
catalog::{ColumnSchema, ForeignKey, Index, TableSchema},
parser::rename_default_table,
value::Type,
};
use super::{Parser, ParserError};
enum TableProp {
Column {
name: String,
ty: Type,
nullable: bool,
primary_key: bool,
},
PrimaryKey(Vec<Expr>),
ForeignKey(Vec<Expr>, String, Vec<Expr>),
UniqueKey(Vec<Expr>),
Key(Vec<Expr>),
Check(Expr),
ReferencedBy(Vec<String>),
}
#[derive(Debug, Error)]
pub enum CreateTableError {
#[error("duplicate primary key declarations")]
DuplicatePrimaryKeys,
#[error("empty primary key")]
EmptyPrimaryKey,
#[error("empty foreign key constraint with empty left hand side expression")]
ForeignKeyConstraintWithEmptyLhs,
#[error("empty foreign key constraint with empty right hand side expression")]
ForeignKeyConstraintWithEmptyRhs,
#[error("duplicate index")]
DuplicateIndex,
#[error("duplicate check condition")]
DuplicateCheck,
}
impl<'a> Parser<'a> {
fn parse_table_prop(&mut self) -> Result<TableProp, ParserError> {
match self.try_consume_symbol()?.as_str() {
"PRIMARY" => {
self.try_consume_keyword("KEY")?;
self.try_consume_open_paren()?;
let exprs = self.parse_expr_list()?;
self.try_consume_close_paren()?;
Ok(TableProp::PrimaryKey(exprs))
}
"FOREIGN" => {
self.try_consume_keyword("KEY")?;
self.try_consume_open_paren()?;
let a_exprs = self.parse_expr_list()?;
self.try_consume_close_paren()?;
self.try_consume_keyword("REFERENCES")?;
let b_table = self.try_consume_symbol()?;
self.try_consume_open_paren()?;
let b_exprs = self.parse_expr_list()?;
self.try_consume_close_paren()?;
Ok(TableProp::ForeignKey(a_exprs, b_table, b_exprs))
}
"UNIQUE" => {
self.try_consume_keyword("KEY")?;
self.try_consume_open_paren()?;
let exprs = self.parse_expr_list()?;
self.try_consume_close_paren()?;
Ok(TableProp::UniqueKey(exprs))
}
"KEY" => {
self.try_consume_open_paren()?;
let exprs = self.parse_expr_list()?;
self.try_consume_close_paren()?;
Ok(TableProp::Key(exprs))
}
"CHECK" => {
self.try_consume_open_paren()?;
let expr = self.parse_expr()?;
self.try_consume_close_paren()?;
Ok(TableProp::Check(expr))
}
"REFERENCED" => {
self.try_consume_keyword("BY")?;
self.try_consume_open_paren()?;
let tables = self.make_parse_list(Self::try_consume_symbol)?;
self.try_consume_close_paren()?;
Ok(TableProp::ReferencedBy(tables))
}
column_name => {
let ty = match self.try_consume_symbol()?.as_str() {
"ANY" => Type::Any,
"BOOL" => Type::Bool,
"INT" => Type::Int,
"TEXT" => Type::String,
hey => return Err(ParserError::UnexpectedKeyword(hey.to_string())),
};
let nullable = if self.try_consume_not().is_ok() {
self.try_consume_null()?;
false
} else if self.try_consume_null().is_ok() {
true
} else {
true
};
let primary_key = if self.try_consume_keyword("PRIMARY").is_ok() {
self.try_consume_keyword("KEY")?;
true
} else {
false
};
Ok(TableProp::Column {
name: column_name.to_string(),
ty,
nullable,
primary_key,
})
}
}
}
pub(crate) fn parse_create_table_statement(&mut self) -> Result<TableSchema, ParserError> {
self.try_consume_keyword("CREATE")?;
let readonly = self.try_consume_keyword("READONLY").is_ok();
self.try_consume_keyword("TABLE")?;
let name = self.try_consume_symbol()?;
let mut table = TableSchema {
readonly,
name: name.to_string(),
columns: vec![],
checks: vec![],
indexes: vec![],
primary_key: vec![],
foreign_keys: vec![],
referenced_by: vec![],
};
self.try_consume_open_paren()?;
let props = self.make_parse_list(Self::parse_table_prop)?;
for prop in props {
match prop {
TableProp::Column {
name,
ty,
nullable,
primary_key,
} => {
table.columns.push(ColumnSchema {
name: name.clone(),
ty,
nullable,
});
if primary_key {
if !table.primary_key.is_empty() {
return Err(ParserError::CreateTable(
CreateTableError::DuplicatePrimaryKeys,
));
}
table.primary_key = vec![Expr::Column(Column(table.name.clone(), name))];
}
}
TableProp::PrimaryKey(vec) => {
if !table.primary_key.is_empty() {
return Err(ParserError::CreateTable(
CreateTableError::DuplicatePrimaryKeys,
));
}
if vec.is_empty() {
return Err(ParserError::CreateTable(CreateTableError::EmptyPrimaryKey));
}
table.primary_key = vec
.into_iter()
.map(|e| rename_default_table(&e, &table.name))
.collect();
}
TableProp::ForeignKey(lhs, t, rhs) => {
if lhs.is_empty() {
return Err(ParserError::CreateTable(
CreateTableError::ForeignKeyConstraintWithEmptyLhs,
));
}
if rhs.is_empty() {
return Err(ParserError::CreateTable(
CreateTableError::ForeignKeyConstraintWithEmptyRhs,
));
}
table.foreign_keys.push(ForeignKey {
lhs_exprs: lhs
.into_iter()
.map(|e| rename_default_table(&e, &table.name))
.collect(),
rhs_exprs: rhs
.into_iter()
.map(|e| rename_default_table(&e, &t))
.collect(),
rhs_table: t,
});
}
TableProp::UniqueKey(exprs) => {
if table.indexes.iter().find(|i| i.exprs == exprs).is_some() {
return Err(ParserError::CreateTable(CreateTableError::DuplicateIndex));
}
table.indexes.push(Index {
exprs: exprs
.into_iter()
.map(|e| rename_default_table(&e, &table.name))
.collect(),
unique: true,
});
}
TableProp::Key(exprs) => {
if table.indexes.iter().find(|i| i.exprs == exprs).is_some() {
return Err(ParserError::CreateTable(CreateTableError::DuplicateIndex));
}
table.indexes.push(Index {
exprs: exprs
.into_iter()
.map(|e| rename_default_table(&e, &table.name))
.collect(),
unique: false,
});
}
TableProp::Check(expr) => {
if table.checks.iter().find(|c| **c == expr).is_some() {
return Err(ParserError::CreateTable(CreateTableError::DuplicateCheck));
}
table.checks.push(rename_default_table(&expr, &table.name));
}
TableProp::ReferencedBy(rhs_tables) => {
table.referenced_by = rhs_tables;
}
}
}
self.try_consume_close_paren()?;
self.try_consume_semi()?;
if table.primary_key.is_empty() {
return Err(ParserError::CreateTable(CreateTableError::EmptyPrimaryKey));
}
Ok(table)
}
}