use-db-key 0.1.0

Primitive database key metadata for RustUse
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

//! Key metadata primitives for `RustUse`.

use use_db_name::{ColumnName, TableName};

/// Broad database key kind.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum KeyKind {
    /// Primary key metadata.
    #[default]
    Primary,
    /// Foreign key metadata.
    Foreign,
    /// Unique key metadata.
    Unique,
    /// Candidate key metadata.
    Candidate,
    /// Composite key metadata.
    Composite,
}

/// A key column reference.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct KeyColumn {
    table: Option<TableName>,
    column: ColumnName,
}

impl KeyColumn {
    /// Creates an unqualified key column.
    #[must_use]
    pub const fn new(column: ColumnName) -> Self {
        Self {
            table: None,
            column,
        }
    }

    /// Adds a table qualifier.
    #[must_use]
    pub fn with_table(mut self, table: TableName) -> Self {
        self.table = Some(table);
        self
    }

    /// Returns the optional table qualifier.
    #[must_use]
    pub const fn table(&self) -> Option<&TableName> {
        self.table.as_ref()
    }

    /// Returns the column name.
    #[must_use]
    pub const fn column(&self) -> &ColumnName {
        &self.column
    }
}

/// Primary key metadata.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PrimaryKey {
    table: TableName,
    column: ColumnName,
}

impl PrimaryKey {
    /// Creates single-column primary key metadata.
    #[must_use]
    pub const fn new(table: TableName, column: ColumnName) -> Self {
        Self { table, column }
    }

    /// Returns the table name.
    #[must_use]
    pub const fn table(&self) -> &TableName {
        &self.table
    }

    /// Returns the column name.
    #[must_use]
    pub const fn column(&self) -> &ColumnName {
        &self.column
    }
}

/// Foreign key metadata.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ForeignKey {
    source: KeyColumn,
    target: KeyColumn,
}

impl ForeignKey {
    /// Creates foreign key metadata.
    #[must_use]
    pub const fn new(source: KeyColumn, target: KeyColumn) -> Self {
        Self { source, target }
    }

    /// Creates foreign key metadata from table and column names.
    #[must_use]
    pub fn from_columns(
        table: TableName,
        column: ColumnName,
        referenced_table: TableName,
        referenced_column: ColumnName,
    ) -> Self {
        Self::new(
            KeyColumn::new(column).with_table(table),
            KeyColumn::new(referenced_column).with_table(referenced_table),
        )
    }

    /// Returns the source column.
    #[must_use]
    pub const fn source(&self) -> &KeyColumn {
        &self.source
    }

    /// Returns the target column.
    #[must_use]
    pub const fn target(&self) -> &KeyColumn {
        &self.target
    }
}

/// Unique key metadata.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct UniqueKey {
    columns: Vec<KeyColumn>,
}

impl UniqueKey {
    /// Creates unique key metadata when at least one column is supplied.
    #[must_use]
    pub fn new(columns: Vec<KeyColumn>) -> Option<Self> {
        (!columns.is_empty()).then_some(Self { columns })
    }

    /// Returns the key columns.
    #[must_use]
    pub fn columns(&self) -> &[KeyColumn] {
        &self.columns
    }
}

/// Composite key metadata.
pub type CompositeKey = UniqueKey;

/// Candidate key metadata.
pub type CandidateKey = UniqueKey;

#[cfg(test)]
mod tests {
    use super::{ForeignKey, KeyColumn, PrimaryKey, UniqueKey};
    use use_db_name::{ColumnName, TableName};

    #[test]
    fn stores_key_metadata() -> Result<(), Box<dyn std::error::Error>> {
        let table = TableName::new("users")?;
        let column = ColumnName::new("id")?;
        let primary_key = PrimaryKey::new(table.clone(), column.clone());
        let foreign_key = ForeignKey::from_columns(
            TableName::new("posts")?,
            ColumnName::new("user_id")?,
            table.clone(),
            column.clone(),
        );
        let unique = UniqueKey::new(vec![
            KeyColumn::new(column.clone()).with_table(table.clone()),
        ])
        .expect("non-empty key");

        assert_eq!(primary_key.table(), &table);
        assert_eq!(primary_key.column(), &column);
        assert_eq!(foreign_key.target().table().expect("target table"), &table);
        assert_eq!(unique.columns()[0].column(), &column);
        assert_eq!(UniqueKey::new(Vec::new()), None);
        Ok(())
    }
}