use std::marker::PhantomData;
pub trait Table: 'static {
const NAME: &'static str;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a schema table",
label = "a `with!{{}}` pseudo-table is entered through its `cte::with(..)` binding",
note = "pass the `cte::with(..)` binding itself to `.from(..)`/`.inner_join(..)` — binding a CTE is what puts it in scope"
)]
pub trait BaseTable: Table + private::Sealed {}
mod private {
pub trait Sealed {}
}
#[doc(hidden)]
pub use private::Sealed as BaseTableSealed;
pub trait Nullability: 'static {}
pub struct NotNull;
impl Nullability for NotNull {}
pub struct MaybeNull;
impl Nullability for MaybeNull {}
pub struct Nil;
pub struct Cons<Head, Tail>(PhantomData<(Head, Tail)>);
pub struct TableSlot<T: Table, N: Nullability>(PhantomData<(T, N)>);
pub struct Here;
pub struct There<I>(PhantomData<I>);
pub trait Position {
const POSITION: u32;
}
impl Position for Here {
const POSITION: u32 = 1;
}
impl<I: Position> Position for There<I> {
const POSITION: u32 = I::POSITION + 1;
}
pub(crate) mod proof {
pub trait FoundAt<T, Index> {}
pub trait SupersetOf<Req, Idxs> {}
}
#[diagnostic::on_unimplemented(
message = "`{T}` is not available in this query's scope",
label = "add `.join(<table>, ..)` (or `.from(..)`) for `{T}` before referencing its columns here",
note = "columns can only be referenced once their table has been joined into the current FROM/JOIN scope — and in a generic helper give each table its own `Idx` parameter, since one shared index matches no scope"
)]
pub trait Find<T: Table, Index>: proof::FoundAt<T, Index> {
type Nullability: Nullability;
}
impl<T: Table, N: Nullability, Tail> proof::FoundAt<T, Here> for Cons<TableSlot<T, N>, Tail> {}
impl<T: Table, N: Nullability, Tail> Find<T, Here> for Cons<TableSlot<T, N>, Tail> {
type Nullability = N;
}
impl<T: Table, Head, Tail, I> proof::FoundAt<T, There<I>> for Cons<Head, Tail> where Tail: Find<T, I>
{}
#[diagnostic::do_not_recommend]
impl<T: Table, Head, Tail, I> Find<T, There<I>> for Cons<Head, Tail>
where
Tail: Find<T, I>,
{
type Nullability = <Tail as Find<T, I>>::Nullability;
}
pub trait Concat<Other> {
type Output;
}
impl<Other> Concat<Other> for Nil {
type Output = Other;
}
impl<Head, Tail: Concat<Other>, Other> Concat<Other> for Cons<Head, Tail> {
type Output = Cons<Head, Tail::Output>;
}
#[diagnostic::on_unimplemented(
message = "this expression references a table that isn't in scope here",
label = "requires {Req}, but the current query scope doesn't contain all of it",
note = "in a generic helper, `Idxs` has to be a type parameter of its own — one shared index matches no scope, however right the tables look"
)]
pub trait Superset<Req, Idxs>: proof::SupersetOf<Req, Idxs> {}
impl<S> proof::SupersetOf<Nil, Nil> for S {}
impl<S> Superset<Nil, Nil> for S {}
impl<S, Head: Table, Tail, IdxHead, IdxsTail>
proof::SupersetOf<Cons<Head, Tail>, Cons<IdxHead, IdxsTail>> for S
where
S: Find<Head, IdxHead> + Superset<Tail, IdxsTail>,
{
}
impl<S, Head: Table, Tail, IdxHead, IdxsTail> Superset<Cons<Head, Tail>, Cons<IdxHead, IdxsTail>>
for S
where
S: Find<Head, IdxHead> + Superset<Tail, IdxsTail>,
{
}
pub trait ScopeTables {
type Tables;
}
impl ScopeTables for Nil {
type Tables = Nil;
}
impl<T: Table, N: Nullability, Tail: ScopeTables> ScopeTables for Cons<TableSlot<T, N>, Tail> {
type Tables = Cons<T, Tail::Tables>;
}
pub trait MapNullable {
type Output;
}
impl MapNullable for Nil {
type Output = Nil;
}
impl<T: Table, N: Nullability, Tail: MapNullable> MapNullable for Cons<TableSlot<T, N>, Tail> {
type Output = Cons<TableSlot<T, MaybeNull>, Tail::Output>;
}
pub(crate) mod wrap {
pub trait Sealed<N> {}
}
pub trait WrapNullable<N: Nullability>: wrap::Sealed<N> {
type Output;
}
pub struct Nullable<T>(PhantomData<T>);
impl<T> wrap::Sealed<NotNull> for T {}
impl<T> WrapNullable<NotNull> for T {
type Output = T;
}
impl<T> wrap::Sealed<MaybeNull> for Nullable<T> {}
impl<T> WrapNullable<MaybeNull> for Nullable<T> {
type Output = Nullable<T>;
}