use construe::StrConstrue;
use crate::{
Column,
Bind,
Fetch
};
use crate::column::ColumnDef;
use crate::table::{
Table,
HasKey
};
pub trait Value: Bind + Fetch {
const DEFINITION: ValueDef;
const COLUMN_COUNT: usize = Self::DEFINITION.inner.count_columns();
type References;
}
#[derive(Debug, PartialEq, Eq)]
pub struct ValueDef {
pub unique: bool,
pub nullable: bool,
pub inner: NestedValueDef,
pub reference: Option<ForeignKey>,
pub checks: &'static [Check]
}
#[derive(Debug, PartialEq, Eq)]
pub enum NestedValueDef {
Column (ColumnDef),
Value (&'static ValueDef),
Values (&'static [(&'static str, ValueDef)]),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Check {
Sql(&'static str)
}
#[derive(Debug, PartialEq, Eq)]
pub struct ForeignKey {
pub table_name: &'static str,
pub deferrable: bool,
pub on_delete: FkConflictAction,
pub on_update: FkConflictAction
}
impl ForeignKey {
pub const fn define_for<T: Table + HasKey>() -> Self {
Self {
table_name: T::NAME,
deferrable: true,
on_delete: FkConflictAction::Restrict,
on_update: FkConflictAction::Restrict
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FkConflictAction {
Cascade,
Restrict,
SetNull
}
pub(crate) struct StrChain<'l> {
name: &'l str,
link: Option<&'l StrChain<'l>>
}
impl<'l> StrChain<'l> {
pub const fn start(name: &'l str) -> Self {
Self { name, link: None }
}
pub const fn with(&'l self, name: &'l str) -> Self {
Self { name, link: Some(self) }
}
pub const fn join<const L: usize>(
&self,
mut construe: StrConstrue<L>,
separator: &str)
-> StrConstrue<L>
{
if let Some(prev) = self.link.as_ref() {
construe = prev.join(construe, separator);
construe = construe.push_str(separator);
}
construe.push_str(self.name)
}
}
impl ForeignKey {
pub(crate) const fn push_sql<const N: usize>(&self, mut sc: StrConstrue<N>)
-> StrConstrue<N>
{
sc = sc.push_str(" REFERENCES ");
sc = sc.push_str(self.table_name);
sc = sc.push_str("\n\t\tON UPDATE ");
sc = sc.push_str(self.on_update.as_sql());
sc = sc.push_str("\n\t\tON DELETE ");
sc = sc.push_str(self.on_delete.as_sql());
if self.deferrable {
sc = sc.push_str("\n\t\tDEFERRABLE INITIALLY DEFERRED");
}
sc
}
}
impl FkConflictAction {
const fn as_sql(self) -> &'static str {
match self {
Self::Cascade => "CASCADE",
Self::Restrict => "RESTRICT",
Self::SetNull => "SET NULL",
}
}
}
impl<T: Column> Value for T {
type References = ();
const DEFINITION: ValueDef = ValueDef {
unique: false,
nullable: false,
inner: NestedValueDef::Column(<Self as Column>::DEFINITION),
reference: None,
checks: &[],
};
}
impl<T: Value> Value for Option<T> {
type References = T::References;
const DEFINITION: ValueDef = ValueDef {
nullable: true,
..T::DEFINITION
};
}
impl ValueDef {
pub const fn unique(self) -> Self {
Self {unique: true, ..self}
}
pub(crate) const fn push_sql<const N: usize>(
&self,
name: &str,
mut sc: StrConstrue<N>)
-> StrConstrue<N>
{
sc = self.inner.push_sql(self.nullable, &StrChain::start(name), sc);
if self.unique && self.inner.count_columns() == 1 {
sc = sc.push_str(" UNIQUE");
}
sc
}
pub(crate) const fn push_constraint_sql<const N: usize>(
&self,
chain: &StrChain<'_>,
mut sc: StrConstrue<N>)
-> StrConstrue<N>
{
if self.unique && self.inner.count_columns() != 1 {
sc = sc.push_str(",\n\tUNIQUE (");
sc = self.inner.push_column_names(chain, sc);
sc = sc.push_str(")");
}
if let Some(ref fk_ref) = self.reference {
sc = sc.push_str(",\n\tFOREIGN KEY (");
sc = self.inner.push_column_names(chain, sc);
sc = sc.push_str(")");
sc = fk_ref.push_sql(sc)
}
match self.inner {
NestedValueDef::Column(_) => {},
NestedValueDef::Value(v) => sc = v.push_constraint_sql(chain, sc),
NestedValueDef::Values(mut values) => {
while let [(name, def), rest @ ..] = values {
values = rest;
sc = def.push_constraint_sql(&chain.with(name), sc);
}
}
}
sc
}
}
impl NestedValueDef {
pub(crate) const fn push_column_names<const N: usize>(
&self,
chain: &StrChain<'_>,
mut sc: StrConstrue<N>)
-> StrConstrue<N>
{
match self {
Self::Column(_def) => chain.join(sc, "_"),
Self::Value(def) => def.inner.push_column_names(chain, sc),
Self::Values([(name, def)]) =>
def.inner.push_column_names(&chain.with(name), sc),
Self::Values([first, rest @ ..]) => {
let (name, def) = first;
sc = def.inner.push_column_names(&chain.with(name), sc);
sc = sc.push_str(", ");
Self::Values(rest).push_column_names(chain, sc)
},
Self::Values([]) => panic!("empty Values([])")
}
}
pub(crate) const fn push_sql<const N: usize>(
&self,
nullable: bool,
chain: &StrChain<'_>,
mut sc: StrConstrue<N>)
-> StrConstrue<N>
{
match self {
Self::Column(def) if nullable => def.nullable().push_sql(chain, sc),
Self::Column(def) => def.push_sql(chain, sc),
Self::Value(def) => def.inner
.push_sql(nullable | def.nullable, chain, sc),
Self::Values([(name, def)]) => def.inner.
push_sql(nullable | def.nullable, &chain.with(name), sc),
Self::Values([(first_name, first_def), rest @ ..]) => {
sc = first_def.inner.push_sql(
nullable | first_def.nullable,
&chain.with(first_name),
sc
);
sc = sc.push_str(",\n\t");
Self::Values(rest).push_sql(nullable, chain, sc)
},
Self::Values([]) => panic!("empty Values([])")
}
}
const fn count_columns(&self) -> usize {
match self {
Self::Column(_def) => 1,
Self::Value(def) => def.inner.count_columns(),
Self::Values([(_, first), rest @ ..]) =>
first.inner.count_columns() + Self::Values(rest).count_columns(),
Self::Values([]) => 0
}
}
}