drizzle-core 0.3.0

A type-safe SQL query builder for Rust
Documentation
use crate::relation::SchemaHasTable;
use crate::traits::{SQLForeignKey, type_set::Cons, type_set::Nil};

/// The kind of a table constraint, at runtime.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SQLConstraintKind {
    /// `PRIMARY KEY`.
    PrimaryKey,
    /// `FOREIGN KEY`.
    ForeignKey,
    /// `UNIQUE`.
    Unique,
    /// `CHECK`.
    Check,
}

/// A table constraint described by types, generated by the table macros.
pub trait SQLConstraint {
    /// The table the constraint belongs to.
    type Table;
    /// The kind: [`PrimaryKeyK`], [`ForeignKeyK`], [`UniqueK`] or [`CheckK`].
    type Kind;
    /// The constrained columns, as a tuple.
    type Columns;
}

/// Placeholder [`SQLConstraint`] for a table without one.
pub struct NoConstraint;

impl SQLConstraint for NoConstraint {
    type Table = ();
    type Kind = ();
    type Columns = ();
}

/// Constraint kind: primary key (`#[column(primary)]` or a composite key).
pub struct PrimaryKeyK;
/// Constraint kind: foreign key (`#[column(references = ...)]`).
pub struct ForeignKeyK;
/// Constraint kind: unique (`#[column(unique)]` or a unique constraint).
pub struct UniqueK;
/// Constraint kind: check constraint.
pub struct CheckK;

/// Table `Self` declares a constraint of kind `Kind` ([`PrimaryKeyK`],
/// [`ForeignKeyK`], [`UniqueK`], [`CheckK`]).
///
/// Generated by the table macros. Use it as a bound to require, say, a
/// primary key.
///
/// # Examples
///
/// ```
/// use drizzle_core::{HasConstraint, PrimaryKeyK};
///
/// struct Users;
/// impl HasConstraint<PrimaryKeyK> for Users {}
///
/// fn needs_primary_key<T: HasConstraint<PrimaryKeyK>>() {}
/// needs_primary_key::<Users>();
/// ```
#[diagnostic::on_unimplemented(
    message = "table `{Self}` does not have a `{Kind}` constraint",
    label = "this table is missing the required constraint",
    note = "declare it on the table, e.g. `#[column(primary)]` for `PrimaryKeyK`"
)]
pub trait HasConstraint<Kind> {}

/// `Self` and `T` are the same type. Used to give type-equality checks a
/// clear error message.
#[diagnostic::on_unimplemented(
    message = "foreign key type mismatch: `{Self}` cannot reference column of type `{T}`",
    label = "change this column's type to `{T}` to match the referenced column",
    note = "the foreign key column type must exactly match the referenced column type"
)]
pub trait TypeEq<T> {}
impl<T> TypeEq<T> for T {}

/// The column `Self` belongs to `Table`. Generated by the table macros.
#[diagnostic::on_unimplemented(
    message = "column `{Self}` does not belong to table `{Table}`",
    label = "this column is not defined on the target table",
    note = "constraint columns must be defined on the table they reference"
)]
pub trait ColumnOf<Table> {}

impl<T, Table> ColumnOf<Table> for &T where T: ColumnOf<Table> {}

/// The column `Self` is `NOT NULL`. Generated by the table macros.
#[diagnostic::on_unimplemented(
    message = "column `{Self}` is nullable and cannot be used here",
    label = "this column must be NOT NULL",
    note = "primary key columns cannot be nullable"
)]
pub trait ColumnNotNull {}

/// The Rust value type of a column, as declared on the table struct.
pub trait ColumnValueType {
    /// The field's Rust type.
    type ValueType;
}

/// Every column in the tuple `Cols` belongs to `Table`.
#[diagnostic::on_unimplemented(
    message = "one or more columns do not belong to table `{Table}`",
    label = "these columns are not all defined on the same table",
    note = "all constraint columns must belong to the target table"
)]
pub trait ColumnsBelongTo<Table, Cols> {}
impl<Table> ColumnsBelongTo<Table, ()> for () {}

macro_rules! impl_columns_belong_to_cb {
    ($($C:ident),+) => {
        impl<Table, $($C),+> ColumnsBelongTo<Table, ($($C,)+)> for ()
        where $($C: ColumnOf<Table>,)+ {}
    };
}
with_tuple_sizes!(impl_columns_belong_to_cb);

/// The column tuple `Cols` is not empty.
#[diagnostic::on_unimplemented(
    message = "constraint requires at least one column",
    label = "provide one or more columns for this constraint"
)]
pub trait NonEmptyColSet<Cols> {}

macro_rules! impl_non_empty_col_set_cb {
    ($($C:ident),+) => {
        impl<$($C),+> NonEmptyColSet<($($C,)+)> for () {}
    };
}
with_tuple_sizes!(impl_non_empty_col_set_cb);

/// The column tuple `Cols` has no duplicates.
#[diagnostic::on_unimplemented(
    message = "constraint columns contain duplicates",
    label = "each column must appear only once in a constraint"
)]
pub trait NoDuplicateColSet<Cols> {}
impl NoDuplicateColSet<()> for () {}

/// Every primary key column in `Cols` is `NOT NULL`.
#[diagnostic::on_unimplemented(
    message = "primary key columns must all be NOT NULL",
    label = "one or more primary key columns are nullable — remove `Option<>` from them",
    note = "wrap the column type directly (e.g., `pub id: i64`) instead of `Option<i64>`"
)]
pub trait PkNotNull<Cols> {}
impl PkNotNull<()> for () {}

macro_rules! impl_pk_not_null_cb {
    ($($C:ident),+) => {
        impl<$($C),+> PkNotNull<($($C,)+)> for ()
        where $($C: ColumnNotNull,)+ {}
    };
}
with_tuple_sizes!(impl_pk_not_null_cb);

/// A foreign key has as many source columns as target columns.
#[diagnostic::on_unimplemented(
    message = "foreign key column count mismatch between source and target",
    label = "the number of FK columns must match the number of referenced columns",
    note = "e.g., a composite FK with 2 source columns must reference exactly 2 target columns"
)]
pub trait FkArityMatch<SrcCols, DstCols> {}

macro_rules! impl_fk_arity_match_cb {
    ($($S:ident),+; $($D:ident),+) => {
        impl<$($S,)+ $($D,)+> FkArityMatch<($($S,)+), ($($D,)+)> for () {}
    };
}
with_dual_tuple_sizes!(impl_fk_arity_match_cb);

/// Each foreign key column has the same Rust type as the column it
/// references.
#[diagnostic::on_unimplemented(
    message = "foreign key column types do not match the referenced columns",
    label = "each FK column's type must match the corresponding referenced column's type"
)]
pub trait FkTypeMatch<SrcCols, DstCols> {}
impl FkTypeMatch<(), ()> for () {}

macro_rules! impl_fk_type_match_cb {
    ($($S:ident),+; $($D:ident),+) => {
        impl<$($S,)+ $($D,)+> FkTypeMatch<($($S,)+), ($($D,)+)> for ()
        where
            $(
                $S: ColumnValueType,
                $D: ColumnValueType,
                <$S as ColumnValueType>::ValueType: TypeEq<<$D as ColumnValueType>::ValueType>,
            )+
        {}
    };
}
with_dual_tuple_sizes!(impl_fk_type_match_cb);

/// Every foreign key in the tuple `Self` targets a table in schema `S`.
#[diagnostic::on_unimplemented(
    message = "foreign key references a table not present in this schema",
    label = "add the referenced table to your schema definition",
    note = "all tables referenced by foreign keys must be included in the schema"
)]
pub trait ForeignKeysInSchema<S> {}
impl<S> ForeignKeysInSchema<S> for () {}

macro_rules! impl_foreign_keys_in_schema_cb {
    ($($Fk:ident),+) => {
        impl<S, $($Fk),+> ForeignKeysInSchema<S> for ($($Fk,)+)
        where
            $(
                $Fk: SQLForeignKey,
                S: SchemaHasTable<<$Fk as SQLForeignKey>::TargetTable>,
            )+
        {}
    };
}
with_tuple_sizes!(impl_foreign_keys_in_schema_cb);

/// Every foreign key of every table in the list `Self` targets a table in
/// schema `S`.
#[diagnostic::on_unimplemented(
    message = "schema contains tables with foreign keys referencing tables not in the schema",
    label = "ensure all FK target tables are included in this schema"
)]
pub trait ValidateTableSetForeignKeys<S> {}
impl<S> ValidateTableSetForeignKeys<S> for Nil {}

/// A table's keys and constraints as types, generated by the table macros.
pub trait SQLTableMeta {
    /// The table's foreign keys, as a tuple.
    type ForeignKeys;
    /// The table's primary key.
    type PrimaryKey;
    /// The table's other constraints, as a tuple.
    type Constraints;
}

impl<S, Head, Tail> ValidateTableSetForeignKeys<S> for Cons<Head, Tail>
where
    Head: SQLTableMeta,
    <Head as SQLTableMeta>::ForeignKeys: ForeignKeysInSchema<S>,
    Tail: ValidateTableSetForeignKeys<S>,
{
}

/// The foreign keys of a schema item's tables all target tables in schema
/// `S`. Checked by the schema macros.
pub trait ValidateSchemaItemForeignKeys<S> {}

impl<S, Item> ValidateSchemaItemForeignKeys<S> for Item
where
    Item: crate::traits::SchemaItemTables,
    <Item as crate::traits::SchemaItemTables>::Tables: ValidateTableSetForeignKeys<S>,
{
}

/// Something that can be the target of `ON CONFLICT (...)` for `Table`.
///
/// Generated for primary-key and unique columns, the primary key itself,
/// unique constraints, and unique indexes. Passing anything else to
/// `.on_conflict(...)` is a compile error.
#[diagnostic::on_unimplemented(
    message = "`{Self}` is not a valid conflict target for table `{Table}`",
    label = "use a primary key, unique column, or unique index/constraint from this table",
    note = "ON CONFLICT targets must be primary key columns, unique columns, or unique indexes"
)]
pub trait ConflictTarget<Table>: Copy {
    /// The column names inside `ON CONFLICT (...)`.
    fn conflict_columns(&self) -> &'static [&'static str];

    /// The `WHERE` predicate of a partial unique index, which the conflict
    /// target must repeat for the database to pick that index.
    fn conflict_where_clause(&self) -> Option<&'static str> {
        None
    }
}

/// A named constraint usable as `ON CONFLICT ON CONSTRAINT "name"`
/// (PostgreSQL only).
///
/// Generated for unique columns and unique constraints that have a name.
#[diagnostic::on_unimplemented(
    message = "`{Self}` is not a named constraint on table `{Table}`",
    label = "ON CONSTRAINT requires a named unique or exclusion constraint"
)]
pub trait NamedConstraint<Table>: Copy {
    /// The constraint's name.
    fn constraint_name(&self) -> &'static str;
}