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);
}
}