matdb 0.1.0

An experimental embedded SQL-like DBMS
Documentation
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)
    }
}