osirisdb 0.7.0

A SQL database engine built from scratch in Rust featuring a custom parser, binder, query planner, optimizer, catalog, and storage engine.
Documentation
use crate::common::symbol::Symbol;

/// Errors produced during the binding phase.
///
/// Binding validates names against the catalog and resolves references.
/// All errors carry [`Symbol`]s — resolve via the interner for display.
#[derive(Debug, Clone, PartialEq)]
pub enum BindError {
    /// A `CREATE DATABASE` was attempted but the database already exists
    /// and `IF NOT EXISTS` was not specified.
    DatabaseAlreadyExists(Symbol),

    /// The role specified in `OWNER` does not exist in the catalog.
    RoleNotFound(Symbol),

    /// The tablespace specified in `TABLESPACE` does not exist in the catalog.
    TablespaceNotFound(Symbol),

    /// `CONNECTION LIMIT` was given a value below -1.
    InvalidConnectionLimit(i64),

    /// A `CREATE SCHEMA` was attempted but the schema already exists
    /// and `IF NOT EXISTS` was not specified.
    SchemaAlreadyExists(Symbol),

    /// A `CREATE SCHEMA` referenced a database that does not exist.
    DatabaseNotFound(Symbol),

    /// A `CREATE TABLE` referenced a schema that does not exist.
    SchemaNotFound(Symbol),

    /// A `CREATE TABLE` was attempted but the table already exists
    /// and `IF NOT EXISTS` was not specified.
    TableAlreadyExists(Symbol),

    /// A `CREATE TABLE` defined the same column name more than once.
    DuplicateColumnName(Symbol),

    /// A table-level constraint (e.g. `PRIMARY KEY (col)`, `UNIQUE (col)`,
    /// `FOREIGN KEY (col) ...`) referenced a column name that is not
    /// defined on this table.
    ColumnNotFound(Symbol),

    /// More than one `PRIMARY KEY` was specified for a table — either
    /// via multiple column-level `PRIMARY KEY` constraints, multiple
    /// table-level `PRIMARY KEY` constraints, or a combination of both.
    MultiplePrimaryKeys,

    /// A table referenced by a query does not exist in the catalog.
    TableNotFound(Symbol),

    /// The query specifies an INSERT source that is not supported.
    UnsupportedInsertSource,

    /// The query contains an expression that is not supported.
    UnsupportedExpression,

    /// The query contains an ON CONFLICT clause that is not supported.
    UnsupportedOnConflict,

    /// The query contains a RETURNING clause that is not supported.
    UnsupportedReturning,

    /// The number of target columns does not match the number of source columns.
    ColumnCountMismatch { expected: usize, found: usize },

    /// An `INSERT` left a `NOT NULL` column with no supplied value.
    ///
    /// Distinct from [`BindError::ColumnCountMismatch`] — the row had the
    /// right number of values, but a required column simply wasn't among
    /// the columns targeted by the statement.
    MissingNotNullColumn(Symbol),

    /// A `SELECT` statement feature which is not yet implemented.
    UnsupportedSelect,

    /// A supplied value is incompatible with the column's declared data type.
    ///
    /// `col`      — the column's interned name symbol
    /// `row`      — 0-based row index in the VALUES list (for diagnostics)
    /// `expected` — human-readable name of the declared type
    /// `got`      — human-readable description of the actual value kind
    TypeMismatch {
        col: Symbol,
        row: usize,
        expected: &'static str,
        got: &'static str,
    },

    /// Division by zero attempted in an expression evaluation.
    DivisionByZero,
}

impl std::fmt::Display for BindError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BindError::DatabaseAlreadyExists(s) => {
                write!(f, "database {:?} already exists", s)
            }
            BindError::RoleNotFound(s) => {
                write!(f, "role {:?} does not exist", s)
            }
            BindError::TablespaceNotFound(s) => {
                write!(f, "tablespace {:?} does not exist", s)
            }
            BindError::InvalidConnectionLimit(n) => {
                write!(f, "invalid connection limit {}, must be >= -1", n)
            }
            BindError::SchemaAlreadyExists(s) => {
                write!(f, "schema {:?} already exist", s)
            }
            BindError::DatabaseNotFound(s) => {
                write!(f, "database {:?} does not exist", s)
            }
            BindError::SchemaNotFound(s) => {
                write!(f, "schema {:?} does not exist", s)
            }
            BindError::TableAlreadyExists(s) => {
                write!(f, "table {:?} already exist", s)
            }
            BindError::DuplicateColumnName(s) => {
                write!(f, "column {:?} specified more than once", s)
            }
            BindError::ColumnNotFound(s) => {
                write!(f, "column {:?} does not exist", s)
            }
            BindError::MultiplePrimaryKeys => {
                write!(f, "multiple primary keys for table specified")
            }
            BindError::TableNotFound(s) => {
                write!(f, "table {:?} does not exist", s)
            }
            BindError::UnsupportedInsertSource => {
                write!(f, "unsupported insert source")
            }
            BindError::UnsupportedExpression => {
                write!(f, "unsupported expression")
            }
            BindError::UnsupportedOnConflict => {
                write!(f, "unsupported ON CONFLICT clause")
            }
            BindError::UnsupportedReturning => {
                write!(f, "unsupported RETURNING clause")
            }
            BindError::ColumnCountMismatch { expected, found } => {
                write!(
                    f,
                    "column count mismatch: expected {}, found {}",
                    expected, found
                )
            }
            BindError::MissingNotNullColumn(s) => {
                write!(f, "column {:?} is NOT NULL but was not given a value", s)
            }
            BindError::UnsupportedSelect => {
                write!(f, "unsupported feature request for select")
            }
            BindError::TypeMismatch {
                col,
                row,
                expected,
                got,
            } => {
                write!(
                    f,
                    "type mismatch at row {}: column {:?} expects {} but got {}",
                    row, col, expected, got
                )
            }
            BindError::DivisionByZero => {
                write!(f, "division by zero")
            }
        }
    }
}

impl BindError {
    pub fn format(&self, interner: &crate::common::Interner) -> String {
        match self {
            BindError::DatabaseAlreadyExists(s) => {
                format!("database \"{}\" already exists", interner.resolve(*s))
            }
            BindError::RoleNotFound(s) => {
                format!("role \"{}\" does not exist", interner.resolve(*s))
            }
            BindError::TablespaceNotFound(s) => {
                format!("tablespace \"{}\" does not exist", interner.resolve(*s))
            }
            BindError::InvalidConnectionLimit(n) => {
                format!("invalid connection limit {}, must be >= -1", n)
            }
            BindError::SchemaAlreadyExists(s) => {
                format!("schema \"{}\" already exists", interner.resolve(*s))
            }
            BindError::DatabaseNotFound(s) => {
                format!("database \"{}\" does not exist", interner.resolve(*s))
            }
            BindError::SchemaNotFound(s) => {
                format!("schema \"{}\" does not exist", interner.resolve(*s))
            }
            BindError::TableAlreadyExists(s) => {
                format!("table \"{}\" already exists", interner.resolve(*s))
            }
            BindError::DuplicateColumnName(s) => {
                format!(
                    "column \"{}\" specified more than once",
                    interner.resolve(*s)
                )
            }
            BindError::ColumnNotFound(s) => {
                format!("column \"{}\" does not exist", interner.resolve(*s))
            }
            BindError::MultiplePrimaryKeys => {
                "multiple primary keys for table specified".to_string()
            }
            BindError::TableNotFound(s) => {
                format!("table \"{}\" does not exist", interner.resolve(*s))
            }
            BindError::UnsupportedInsertSource => "unsupported insert source".to_string(),
            BindError::UnsupportedExpression => "unsupported expression".to_string(),
            BindError::UnsupportedOnConflict => "unsupported ON CONFLICT clause".to_string(),
            BindError::UnsupportedReturning => "unsupported RETURNING clause".to_string(),
            BindError::ColumnCountMismatch { expected, found } => {
                format!(
                    "column count mismatch: expected {}, found {}",
                    expected, found
                )
            }
            BindError::MissingNotNullColumn(s) => {
                format!(
                    "column \"{}\" is NOT NULL but was not given a value",
                    interner.resolve(*s)
                )
            }
            BindError::UnsupportedSelect => "unsupported feature request for select".to_string(),
            BindError::TypeMismatch {
                col,
                row,
                expected,
                got,
            } => {
                format!(
                    "type mismatch at row {}: column \"{}\" expects {} but got {}",
                    row,
                    interner.resolve(*col),
                    expected,
                    got
                )
            }
            BindError::DivisionByZero => "division by zero".to_string(),
        }
    }
}

impl std::error::Error for BindError {}