use core::marker::PhantomData;
use crate::SQLParam;
use crate::prelude::*;
use crate::relation::{CardWrap, RelationDef};
use super::handle::RelationHandle;
use super::row::QueryRow;
use super::store::RelEntry;
pub struct NoWhere;
pub struct HasWhere;
pub struct NoOrderBy;
pub struct HasOrderBy;
pub struct NoLimit;
pub struct HasLimit;
pub struct HasOffset;
pub struct Clauses<W = NoWhere, Ord = NoOrderBy, Lim = NoLimit> {
_marker: PhantomData<(W, Ord, Lim)>,
}
pub struct AllColumns;
pub struct PartialColumns {
pub columns: Vec<&'static str>,
}
pub trait ResolveSelect<T: QueryTable> {
type Model;
}
impl<T: QueryTable> ResolveSelect<T> for AllColumns {
type Model = T::Select;
}
impl<T: QueryTable> ResolveSelect<T> for PartialColumns {
type Model = T::PartialSelect;
}
pub trait IntoColumnSelection {
fn into_column_names(self) -> Vec<&'static str>;
}
pub struct QueryBuilder<V: SQLParam, T, Rels = (), Cols = AllColumns, Cl = Clauses> {
#[doc(hidden)]
pub where_clause: String,
#[doc(hidden)]
pub where_params: Vec<V>,
#[doc(hidden)]
pub order_by_clause: String,
#[doc(hidden)]
pub limit: Option<u32>,
#[doc(hidden)]
pub offset: Option<u32>,
#[doc(hidden)]
pub relations: Rels,
#[doc(hidden)]
pub cols: Cols,
_marker: PhantomData<(T, Cl)>,
}
impl<V: SQLParam, T> QueryBuilder<V, T> {
#[must_use]
pub const fn new() -> Self {
Self {
where_clause: String::new(),
where_params: Vec::new(),
order_by_clause: String::new(),
limit: None,
offset: None,
relations: (),
cols: AllColumns,
_marker: PhantomData,
}
}
}
impl<V: SQLParam, T> Default for QueryBuilder<V, T> {
fn default() -> Self {
Self::new()
}
}
impl<V: SQLParam, T, Rels, Cols, Cl> QueryBuilder<V, T, Rels, Cols, Cl> {
#[allow(clippy::type_complexity)]
pub fn with<R, N, C, RCl>(
self,
handle: RelationHandle<V, R, N, C, RCl>,
) -> QueryBuilder<V, T, (RelationHandle<V, R, N, C, RCl>, Rels), Cols, Cl>
where
R: RelationDef<Source = T> + 'static,
{
QueryBuilder {
where_clause: self.where_clause,
where_params: self.where_params,
order_by_clause: self.order_by_clause,
limit: self.limit,
offset: self.offset,
relations: (handle, self.relations),
cols: self.cols,
_marker: PhantomData,
}
}
}
impl<V: SQLParam, T, Rels, Cols, Ord, Lim>
QueryBuilder<V, T, Rels, Cols, Clauses<NoWhere, Ord, Lim>>
{
pub fn r#where<'a, E>(
self,
condition: E,
) -> QueryBuilder<V, T, Rels, Cols, Clauses<HasWhere, Ord, Lim>>
where
E: crate::expr::Expr<'a, V>,
E::SQLType: crate::types::BooleanLike,
V: 'a,
{
let sql = condition.to_sql();
let (text, params) = sql.build();
QueryBuilder {
where_clause: text,
where_params: params.into_iter().cloned().collect(),
order_by_clause: self.order_by_clause,
limit: self.limit,
offset: self.offset,
relations: self.relations,
cols: self.cols,
_marker: PhantomData,
}
}
}
impl<V: SQLParam, T, Rels, Cols, W, Lim>
QueryBuilder<V, T, Rels, Cols, Clauses<W, NoOrderBy, Lim>>
{
pub fn order_by<'a, E>(
self,
expr: E,
) -> QueryBuilder<V, T, Rels, Cols, Clauses<W, HasOrderBy, Lim>>
where
E: crate::traits::ToSQL<'a, V>,
V: 'a,
{
let sql = expr.to_sql();
let (text, _) = sql.build();
QueryBuilder {
where_clause: self.where_clause,
where_params: self.where_params,
order_by_clause: text,
limit: self.limit,
offset: self.offset,
relations: self.relations,
cols: self.cols,
_marker: PhantomData,
}
}
}
impl<V: SQLParam, T, Rels, Cols, W, Ord> QueryBuilder<V, T, Rels, Cols, Clauses<W, Ord, NoLimit>> {
pub fn limit(self, n: u32) -> QueryBuilder<V, T, Rels, Cols, Clauses<W, Ord, HasLimit>> {
QueryBuilder {
where_clause: self.where_clause,
where_params: self.where_params,
order_by_clause: self.order_by_clause,
limit: Some(n),
offset: self.offset,
relations: self.relations,
cols: self.cols,
_marker: PhantomData,
}
}
}
impl<V: SQLParam, T, Rels, Cols, W, Ord> QueryBuilder<V, T, Rels, Cols, Clauses<W, Ord, HasLimit>> {
pub fn offset(self, n: u32) -> QueryBuilder<V, T, Rels, Cols, Clauses<W, Ord, HasOffset>> {
QueryBuilder {
where_clause: self.where_clause,
where_params: self.where_params,
order_by_clause: self.order_by_clause,
limit: self.limit,
offset: Some(n),
relations: self.relations,
cols: self.cols,
_marker: PhantomData,
}
}
}
impl<V: SQLParam, T: QueryTable, Rels, Cl> QueryBuilder<V, T, Rels, AllColumns, Cl> {
pub fn columns<S: IntoColumnSelection>(
self,
selector: S,
) -> QueryBuilder<V, T, Rels, PartialColumns, Cl> {
QueryBuilder {
where_clause: self.where_clause,
where_params: self.where_params,
order_by_clause: self.order_by_clause,
limit: self.limit,
offset: self.offset,
relations: self.relations,
cols: PartialColumns {
columns: selector.into_column_names(),
},
_marker: PhantomData,
}
}
pub fn omit<S: IntoColumnSelection>(
self,
selector: S,
) -> QueryBuilder<V, T, Rels, PartialColumns, Cl> {
let omitted = selector.into_column_names();
let columns = T::COLUMN_NAMES
.iter()
.copied()
.filter(|c| !omitted.contains(c))
.collect();
QueryBuilder {
where_clause: self.where_clause,
where_params: self.where_params,
order_by_clause: self.order_by_clause,
limit: self.limit,
offset: self.offset,
relations: self.relations,
cols: PartialColumns { columns },
_marker: PhantomData,
}
}
}
pub trait BuildStore {
type Store;
}
impl BuildStore for () {
type Store = ();
}
impl<V: SQLParam, R, Nested, Rest, Cols, Cl> BuildStore
for (RelationHandle<V, R, Nested, Cols, Cl>, Rest)
where
R: RelationDef,
R::Target: QueryTable,
Cols: ResolveSelect<R::Target>,
Nested: BuildStore,
Rest: BuildStore,
{
type Store = RelEntry<
R,
<R::Card as CardWrap>::Wrap<
QueryRow<<Cols as ResolveSelect<R::Target>>::Model, <Nested as BuildStore>::Store>,
>,
<Rest as BuildStore>::Store,
>;
}
pub trait QueryTable {
type Select;
type PartialSelect;
const TABLE_NAME: &'static str;
const COLUMN_NAMES: &'static [&'static str];
const BLOB_COLUMNS: &'static [&'static str] = &[];
}