drizzle-core 0.1.6

A type-safe SQL query builder for Rust
Documentation
//! Compile-time relation marker traits.

/// Type-level relation marker: `Self` has an outgoing FK to `Target`.
///
/// Generated by table macros for each declared foreign key.
pub trait Relation<Target: ?Sized> {}

/// Compile-time FK join: `Self` has exactly one FK to `Target`.
///
/// Generated by table macros when a single FK exists from `Self` to `Target`.
/// Tables with multiple FKs to the same target must use explicit join conditions.
pub trait Joinable<Target: ?Sized> {
    /// Returns `(self_column, target_column)` pairs for the FK.
    fn fk_columns() -> &'static [(&'static str, &'static str)];
}

/// Marker trait proving that table `T` is part of schema `Self`.
///
/// Generated by schema derive macros and used by compile-time checked builders.
#[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
// =============================================================================

#[cfg(feature = "query")]
pub trait RelationDef: private::Sealed + 'static {
    /// Source table ZST.
    type Source;
    /// Target table ZST.
    type Target: crate::query::QueryTable;
    /// Cardinality: `Many`, `One`, or `OptionalOne`.
    type Card: CardWrap;
    /// Relation name (used in JSON keys).
    const NAME: &'static str;
    /// FK column pairs for the join condition.
    ///
    /// Each pair `(a, b)` generates `target_alias."a" = parent_alias."b"`.
    fn fk_columns() -> &'static [(&'static str, &'static str)];

    /// Junction table metadata for many-to-many relations.
    ///
    /// Returns `None` for direct FK relations. When `Some`, the SQL generator
    /// emits an `INNER JOIN` through the junction table instead of a direct
    /// FK correlation.
    #[must_use]
    fn junction() -> Option<JunctionMeta> {
        None
    }
}

/// Maps a cardinality marker to a wrapper type.
#[cfg(feature = "query")]
pub trait CardWrap: private::Sealed {
    /// The cardinality for runtime SQL generation decisions.
    const CARDINALITY: crate::query::RelCardinality;
    type Wrap<T>;
}

/// Many-cardinality: wraps data as `Vec<T>`.
#[cfg(feature = "query")]
pub struct Many;

/// One-cardinality: wraps data as `T` (exactly one row expected).
#[cfg(feature = "query")]
pub struct One;

/// Optional one-cardinality: wraps data 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 {
    /// Junction table name (e.g., "`post_categories`").
    pub table_name: &'static str,
    /// (`junction_col`, `source_col`) — WHERE correlation with parent row.
    pub source_fk: &'static [(&'static str, &'static str)],
    /// (`junction_col`, `target_col`) — INNER JOIN with 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 CardWrap for Many {
    const CARDINALITY: crate::query::RelCardinality = crate::query::RelCardinality::Many;
    type Wrap<T> = crate::prelude::Vec<T>;
}

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

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

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