drizzle-core 0.1.5

A type-safe SQL query builder for Rust
Documentation
use crate::prelude::*;
use crate::{SQL, SQLColumnInfo, SQLParam, SQLSchema, SQLSchemaType, ToSQL};
use core::any::Any;

pub trait SQLModel<'a, V: SQLParam>: ToSQL<'a, V> {
    /// Columns referenced by this model.
    /// Static models can return a borrowed slice; dynamic models can allocate.
    fn columns(&self) -> Cow<'static, [&'static dyn SQLColumnInfo]>;
    fn values(&self) -> SQL<'a, V>;
}

/// Trait for models that support partial selection of fields
pub trait SQLPartial<'a, Value: SQLParam> {
    /// The type representing a partial model where all fields are optional
    /// for selective querying
    type Partial: SQLModel<'a, Value> + Default + 'a;

    fn partial() -> Self::Partial {
        Default::default()
    }
}

pub trait SQLTable<'a, Type: SQLSchemaType, Value: SQLParam + 'a>:
    SQLSchema<'a, Type, Value> + SQLTableInfo + Default + Clone
{
    type Select: SQLModel<'a, Value> + SQLPartial<'a, Value> + Default + 'a;

    /// The type representing a model for INSERT operations on this table.
    /// Uses PhantomData with tuple markers to track which fields are set
    type Insert<T>: SQLModel<'a, Value> + Default;

    /// The type representing a model for UPDATE operations on this table.
    /// This would be generated by the table macro.
    type Update: SQLModel<'a, Value> + Default + 'a;

    /// The aliased version of this table for self-joins and CTEs.
    /// For a table `Users`, this would be `AliasedUsers`.
    type Aliased: SQLTable<'a, Type, Value>;

    /// Creates an aliased version of this table with the given name.
    /// Used for self-joins and CTEs.
    fn alias(name: &'static str) -> Self::Aliased;
}

pub trait SQLTableInfo: Any + Send + Sync {
    /// Unqualified table name.
    fn name(&self) -> &str;

    /// Optional schema/catalog namespace for this table.
    fn schema(&self) -> Option<&str> {
        None
    }

    /// Fully-qualified table name when schema is present.
    fn qualified_name(&self) -> Cow<'static, str> {
        match self.schema() {
            Some(schema) => Cow::Owned(format!("{schema}.{}", self.name())),
            None => Cow::Owned(self.name().to_string()),
        }
    }

    fn columns(&self) -> &'static [&'static dyn SQLColumnInfo];
    fn dependencies(&self) -> &'static [&'static dyn SQLTableInfo];

    /// Lookup a column by name.
    fn column_named(&self, name: &str) -> Option<&'static dyn SQLColumnInfo> {
        self.columns()
            .iter()
            .copied()
            .find(|col| col.name() == name)
    }
}

// Blanket implementation for static references
impl<T: SQLTableInfo> SQLTableInfo for &'static T {
    fn name(&self) -> &str {
        (*self).name()
    }

    fn schema(&self) -> Option<&str> {
        (*self).schema()
    }

    fn qualified_name(&self) -> Cow<'static, str> {
        (*self).qualified_name()
    }

    fn columns(&self) -> &'static [&'static dyn SQLColumnInfo] {
        (*self).columns()
    }

    fn dependencies(&self) -> &'static [&'static dyn SQLTableInfo] {
        (*self).dependencies()
    }
}

impl core::fmt::Debug for dyn SQLTableInfo {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("SQLTableInfo")
            .field("name", &self.name())
            .field("schema", &self.schema())
            .field("qualified_name", &self.qualified_name())
            .field("columns", &self.columns())
            .finish()
    }
}

pub trait AsTableInfo: Sized + SQLTableInfo {
    fn as_table(&self) -> &dyn SQLTableInfo {
        self
    }
}