drizzle-core 0.4.0

A type-safe SQL query builder for Rust
Documentation
//! Traits that describe foreign-key relations between tables, generated by
//! the table and schema macros.

/// The table `Self` has a foreign key to `Target`.
///
/// Generated by the table macros for each declared foreign key, on the
/// table that declares it.
pub trait Relation<Target: ?Sized> {}

/// `Self` has exactly one foreign key to `Target`.
///
/// Generated by the table macros on the table that declares the key. It
/// lets `.join(table)` derive the ON condition from the key, in either
/// direction (see [`JoinKey`]). With several foreign keys to the same table,
/// or keys in both directions, pass an explicit condition:
/// `.join((table, condition))`.
pub trait Joinable<Target: ?Sized> {
    /// Returns the `(self_column, target_column)` pairs the ON condition
    /// matches.
    fn fk_columns() -> &'static [(&'static str, &'static str)];
}

/// The foreign key `.join(table)` derives its ON condition from, joining
/// `Self` to `From`.
///
/// `Via` says which table declares the key, and is inferred:
/// [`JoinForward`] when `Self` has the key to `From`, [`JoinReverse`] when
/// `From` has the key to `Self`. When both do, the join is ambiguous and
/// does not compile; pass the condition instead.
#[diagnostic::on_unimplemented(
    message = "no single foreign key joins `{Self}` to `{From}`",
    label = "a bare table joins on the one foreign key between the two tables",
    note = "with no key, or several, name the condition: `.join((table, eq(left, right)))`"
)]
pub trait JoinKey<From: ?Sized, Via> {
    /// `(joined_column, from_column)` pairs the ON condition matches, in
    /// key order, read through [`Self::pair`].
    fn pairs() -> &'static [(&'static str, &'static str)];

    /// The `(joined_column, from_column)` names of `pair`.
    fn pair(pair: &(&'static str, &'static str)) -> (&'static str, &'static str);
}

/// [`JoinKey`] direction: the joined table declares the key.
#[derive(Debug, Clone, Copy)]
pub struct JoinForward;

/// [`JoinKey`] direction: the table joined from declares the key.
#[derive(Debug, Clone, Copy)]
pub struct JoinReverse;

/// `.join((table, condition))`: an explicit ON condition, no key involved.
#[derive(Debug, Clone, Copy)]
pub struct JoinExplicit;

impl<Joined: Joinable<From>, From: ?Sized> JoinKey<From, JoinForward> for Joined {
    fn pairs() -> &'static [(&'static str, &'static str)] {
        Joined::fk_columns()
    }

    fn pair(pair: &(&'static str, &'static str)) -> (&'static str, &'static str) {
        *pair
    }
}

impl<Joined, From: Joinable<Joined> + ?Sized> JoinKey<From, JoinReverse> for Joined {
    fn pairs() -> &'static [(&'static str, &'static str)] {
        From::fk_columns()
    }

    fn pair(pair: &(&'static str, &'static str)) -> (&'static str, &'static str) {
        (pair.1, pair.0)
    }
}

/// The table `T` is part of the schema `Self`.
///
/// Generated by the schema macros. Query builders require it, so using a
/// table that is missing from the schema struct is a compile error.
#[diagnostic::on_unimplemented(
    message = "table `{T}` is not part of schema `{Self}`",
    label = "add this table as a field in your schema struct",
    note = "all tables used in queries must be included in the schema"
)]
pub trait SchemaHasTable<T: ?Sized> {}

// =============================================================================
// Query API: RelationDef and Cardinality
// =============================================================================

/// A relation between two tables for the relational query API (`db.query`).
///
/// Generated by the schema macros for each relation; not implemented by
/// hand.
#[cfg(feature = "query")]
pub trait RelationDef: private::Sealed + 'static {
    /// The table the relation starts from.
    type Source;
    /// The table the relation loads.
    type Target: crate::query::QueryTable;
    /// How many target rows each source row has: [`Many`], [`One`] or
    /// [`OptionalOne`].
    type Card: CardWrap;
    /// The relation's name, used as its JSON key.
    const NAME: &'static str;
    /// Column pairs for the join condition.
    ///
    /// Each pair `(a, b)` renders `target_alias."a" = parent_alias."b"`.
    fn fk_columns() -> &'static [(&'static str, &'static str)];

    /// Junction table metadata for many-to-many relations.
    ///
    /// `None` for a direct foreign key. When `Some`, the generated SQL joins
    /// through the junction table instead.
    #[must_use]
    fn junction() -> Option<JunctionMeta> {
        None
    }
}

/// Maps a cardinality marker to the type that holds loaded rows: `Vec<T>`,
/// `T` or `Option<T>`.
#[cfg(feature = "query")]
pub trait CardWrap: private::Sealed {
    /// The same cardinality, as a runtime value.
    const CARDINALITY: crate::query::RelCardinality;
    /// The container for loaded rows of type `T`.
    type Wrap<T>;

    /// Maps the wrapped value from `A` to `B`.
    fn map_wrap<A, B>(data: Self::Wrap<A>, f: impl FnMut(A) -> B) -> Self::Wrap<B>;
}

/// Builds the row type that holds a loaded relation.
///
/// Generated for each relation. `Row<Inner, Child>` is a generated struct
/// such as `UsersWithPosts<Inner, Child>`.
#[cfg(feature = "query")]
pub trait AssembleRel: RelationDef {
    /// Row type produced when this relation is included in a query.
    type Row<Inner, Child>;

    /// Builds a with-row from the composed inner row and this relation's data.
    fn assemble_row<Inner, Child>(
        inner: Inner,
        data: <Self::Card as CardWrap>::Wrap<Child>,
    ) -> Self::Row<Inner, Child>;
}

/// Cardinalities that `.limit(...)`, `.offset(...)` and `.first()` apply
/// to: those that load any number of rows.
#[cfg(feature = "query")]
#[diagnostic::on_unimplemented(
    message = "`.limit`, `.offset` and `.first` apply only to relations that load many rows",
    label = "this relation loads at most one row",
    note = "a LIMIT or OFFSET here could only drop or skip that row"
)]
pub trait Paginated: private::Sealed {}

/// Cardinalities that `.r#where(...)` applies to: every one but [`One`],
/// whose row is required.
#[cfg(feature = "query")]
#[diagnostic::on_unimplemented(
    message = "a required relation cannot be filtered with `.r#where`",
    label = "this relation always loads exactly one row",
    note = "filter the root query instead, or make the foreign key an `Option` so the relation \
            loads an `Option`"
)]
pub trait Filtered: private::Sealed {}

/// Cardinality marker: any number of rows, loaded as `Vec<T>`.
#[cfg(feature = "query")]
pub struct Many;

/// Cardinality marker: exactly one row, loaded as `T`.
#[cfg(feature = "query")]
pub struct One;

/// Cardinality marker: zero or one row, loaded as `Option<T>`.
#[cfg(feature = "query")]
pub struct OptionalOne;

/// Metadata for many-to-many relations through a junction table.
#[cfg(feature = "query")]
#[derive(Debug, Clone, Copy)]
pub struct JunctionMeta {
    /// The junction table.
    pub table: crate::TableSqlRef,
    /// `(junction_col, source_col)` pairs that tie a junction row to the
    /// parent row.
    pub source_fk: &'static [(&'static str, &'static str)],
    /// `(junction_col, target_col)` pairs used to join the target table.
    pub target_fk: &'static [(&'static str, &'static str)],
}

#[cfg(feature = "query")]
impl private::Sealed for Many {}
#[cfg(feature = "query")]
impl private::Sealed for One {}
#[cfg(feature = "query")]
impl private::Sealed for OptionalOne {}

#[cfg(feature = "query")]
impl Paginated for Many {}
#[cfg(feature = "query")]
impl Filtered for Many {}
#[cfg(feature = "query")]
impl Filtered for OptionalOne {}

#[cfg(feature = "query")]
impl CardWrap for Many {
    const CARDINALITY: crate::query::RelCardinality = crate::query::RelCardinality::Many;
    type Wrap<T> = crate::prelude::Vec<T>;

    fn map_wrap<A, B>(data: Self::Wrap<A>, f: impl FnMut(A) -> B) -> Self::Wrap<B> {
        data.into_iter().map(f).collect()
    }
}

#[cfg(feature = "query")]
impl CardWrap for One {
    const CARDINALITY: crate::query::RelCardinality = crate::query::RelCardinality::One;
    type Wrap<T> = T;

    fn map_wrap<A, B>(data: Self::Wrap<A>, mut f: impl FnMut(A) -> B) -> Self::Wrap<B> {
        f(data)
    }
}

#[cfg(feature = "query")]
impl CardWrap for OptionalOne {
    const CARDINALITY: crate::query::RelCardinality = crate::query::RelCardinality::OptionalOne;
    type Wrap<T> = Option<T>;

    fn map_wrap<A, B>(data: Self::Wrap<A>, f: impl FnMut(A) -> B) -> Self::Wrap<B> {
        data.map(f)
    }
}

#[cfg(feature = "query")]
#[doc(hidden)]
pub mod private {
    pub trait Sealed {}
}