rust-query 0.9.0

A query builder using rust concepts.
Documentation
use std::{
    borrow::Cow,
    collections::{BTreeMap, BTreeSet},
    marker::PhantomData,
};

use crate::{
    IntoExpr,
    schema::{canonical, check_constraint::Parsed, from_db},
    value::{DbTyp, EqTyp, StorableTyp},
};

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Column {
    pub def: canonical::Column,
    pub span: (usize, usize),
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Index {
    pub def: from_db::Index,
    pub span: (usize, usize),
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Table {
    pub columns: BTreeMap<String, Column>,
    pub indices: BTreeSet<Index>,
    pub span: (usize, usize),
}

#[derive(Debug, Default, PartialEq, Eq)]
pub struct Schema {
    pub tables: BTreeMap<&'static str, Table>,
    pub span: (usize, usize),
}

pub struct TypBuilder<S> {
    pub(crate) ast: Table,
    _p: PhantomData<S>,
}

impl<S> Default for TypBuilder<S> {
    fn default() -> Self {
        Self {
            ast: Default::default(),
            _p: Default::default(),
        }
    }
}

impl<S> TypBuilder<S> {
    pub fn col<T: SchemaType<S>>(&mut self, name: &'static str, span: (usize, usize)) {
        let item = Column {
            def: canonical::Column {
                typ: <T::Typ as DbTyp>::TYP,
                nullable: <T::Typ as DbTyp>::NULLABLE,
                fk: <T::Typ as DbTyp>::FK.map(|(table, fk)| (table.to_owned(), fk.to_owned())),
                check: {
                    if let Some(sql) = <T::Typ as StorableTyp>::check(name) {
                        Some(Parsed::parse(&sql))
                    } else {
                        None
                    }
                },
            },
            span,
        };
        let old = self.ast.columns.insert(name.to_owned(), item);
        debug_assert!(old.is_none());
    }

    pub fn index(&mut self, cols: &[&'static str], unique: bool, span: (usize, usize)) {
        let def = from_db::Index {
            columns: cols.iter().copied().map(Cow::Borrowed).collect(),
            unique,
        };
        self.ast.indices.insert(Index { def, span });
    }

    pub fn check_unique_compatible<T: EqTyp>(&mut self) {}
}

#[diagnostic::on_unimplemented(
    message = "Can not use `{Self}` as a column type in schema `{S}`",
    note = "`TableRow<Table>` can be used as a schema column types as long as the table `Table` is not #[no_reference]"
)]
pub trait SchemaType<S>: IntoExpr<'static, S, Typ = Self> + StorableTyp {}

impl<T, S> SchemaType<S> for T where T: IntoExpr<'static, S, Typ = Self> + StorableTyp {}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_bool_check() {
        let out = <bool as StorableTyp>::check("foo").unwrap();
        assert_eq!(out, r#""foo" IN (0, 1)"#);
    }

    #[test]
    #[cfg(feature = "jiff-02")]
    fn test_timestamp_check() {
        let out = <jiff::Timestamp as StorableTyp>::check("foo").unwrap();
        expect_test::expect![[
            r#""foo" IS (ltrim(datetime("foo" || 'Z'), '-') || rtrim(substr("foo", 20, 10), '0 '))"#
        ]]
        .assert_eq(&out);
    }

    #[test]
    #[cfg(feature = "jiff-02")]
    fn test_date_check() {
        let out = <jiff::civil::Date as StorableTyp>::check("foo").unwrap();
        expect_test::expect![[r#""foo" IS ltrim(date("foo"), '-')"#]].assert_eq(&out);
    }
}