use super::{Expr, IntoExpr, IntoStatement, List};
use crate::schema::{Field, Register};
use std::{fmt, marker::PhantomData};
use toasty_core::{
schema::app::VariantId,
stmt::{self, Direction, OrderByExpr},
};
pub struct Path<T, U> {
pub(super) untyped: stmt::Path,
_p: PhantomData<(T, U)>,
}
impl<T: Register> Path<T, T> {
pub fn root() -> Self {
Self {
untyped: stmt::Path::model(T::id()),
_p: PhantomData,
}
}
}
impl<M: Register> Path<List<M>, List<M>> {
pub fn from_model_list() -> Self {
Self {
untyped: stmt::Path::model(M::id()),
_p: PhantomData,
}
}
}
impl<T, U> Path<T, U> {
pub fn from_field_index(index: usize) -> Self
where
T: Register,
{
Self {
untyped: stmt::Path::from_index(T::id(), index),
_p: PhantomData,
}
}
pub fn into_variant(self, variant_id: VariantId) -> Self {
Self {
untyped: stmt::Path::from_variant(self.untyped, variant_id),
_p: PhantomData,
}
}
pub fn chain<X, V>(mut self, other: impl Into<Path<X, V>>) -> Path<T, V> {
let other = other.into();
self.untyped.chain(&other.untyped);
Path {
untyped: self.untyped,
_p: PhantomData,
}
}
fn build_filter<F>(self, build_body: F) -> Expr<bool>
where
F: FnOnce(stmt::Expr) -> stmt::Expr,
{
let gate = match &self.untyped.root {
stmt::PathRoot::Variant { parent, variant_id } => {
let parent_stmt = parent.as_ref().clone().into_stmt();
Some(stmt::Expr::is_variant(parent_stmt, *variant_id))
}
_ => None,
};
let body = build_body(self.untyped.into_stmt());
let untyped = match gate {
Some(g) => stmt::Expr::and(g, body),
None => body,
};
Expr {
untyped,
_p: PhantomData,
}
}
pub fn eq(self, rhs: impl IntoExpr<U>) -> Expr<bool> {
let rhs = rhs.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::eq(path, rhs))
}
pub fn ne(self, rhs: impl IntoExpr<U>) -> Expr<bool> {
let rhs = rhs.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::ne(path, rhs))
}
pub fn gt(self, rhs: impl IntoExpr<U>) -> Expr<bool> {
let rhs = rhs.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::gt(path, rhs))
}
pub fn ge(self, rhs: impl IntoExpr<U>) -> Expr<bool> {
let rhs = rhs.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::ge(path, rhs))
}
pub fn lt(self, rhs: impl IntoExpr<U>) -> Expr<bool> {
let rhs = rhs.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::lt(path, rhs))
}
pub fn le(self, rhs: impl IntoExpr<U>) -> Expr<bool> {
let rhs = rhs.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::le(path, rhs))
}
pub fn in_list(self, rhs: impl IntoExpr<List<U>>) -> Expr<bool> {
let rhs = rhs.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::in_list(path, rhs))
}
pub fn in_query<Q>(self, rhs: Q) -> Expr<bool>
where
Q: IntoStatement<Returning = List<U>>,
{
let query = rhs.into_statement().into_untyped_query();
self.build_filter(move |path| stmt::Expr::in_subquery(path, query))
}
pub fn asc(self) -> OrderByExpr {
OrderByExpr {
expr: self.untyped.into_stmt(),
order: Some(Direction::Asc),
}
}
pub fn desc(self) -> OrderByExpr {
OrderByExpr {
expr: self.untyped.into_stmt(),
order: Some(Direction::Desc),
}
}
}
impl<T, U> Path<T, List<U>> {
pub fn any(self, filter: Expr<bool>) -> Expr<bool>
where
U: crate::schema::Model,
{
let child_query = super::Query::<List<U>>::filter(filter);
self.build_filter(move |path| stmt::Expr::in_subquery(path, child_query.untyped))
}
pub fn all(self, filter: Expr<bool>) -> Expr<bool>
where
U: crate::schema::Model,
{
let child_query = super::Query::<List<U>>::filter(filter.not());
self.build_filter(move |path| {
stmt::Expr::not(stmt::Expr::in_subquery(path, child_query.untyped))
})
}
}
impl<T, U> Path<T, List<U>>
where
U: crate::schema::Scalar,
{
pub fn contains(self, value: impl IntoExpr<U>) -> Expr<bool> {
let value = value.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::any_op(value, stmt::BinaryOp::Eq, path))
}
pub fn is_superset(self, values: impl IntoExpr<List<U>>) -> Expr<bool> {
let values = values.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::array_is_superset(path, values))
}
pub fn intersects(self, values: impl IntoExpr<List<U>>) -> Expr<bool> {
let values = values.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::array_intersects(path, values))
}
pub fn len(self) -> Expr<i64> {
Expr::from_untyped(stmt::Expr::array_length(self.untyped.into_stmt()))
}
pub fn is_empty(self) -> Expr<bool> {
let untyped = stmt::Expr::eq(
stmt::Expr::array_length(self.untyped.into_stmt()),
stmt::Expr::Value(stmt::Value::I64(0)),
);
Expr::from_untyped(untyped)
}
}
impl<T, U> Path<T, Option<U>> {
pub fn is_none(self) -> Expr<bool> {
self.build_filter(stmt::Expr::is_null)
}
pub fn is_some(self) -> Expr<bool> {
self.build_filter(stmt::Expr::is_not_null)
}
}
impl<T, U> Path<T, U>
where
U: Field<Inner = String>,
{
pub fn starts_with(self, prefix: impl IntoExpr<String>) -> Expr<bool> {
let prefix = prefix.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::starts_with(path, prefix))
}
pub fn like(self, pattern: impl IntoExpr<String>) -> Expr<bool> {
let pattern = pattern.into_expr().untyped;
self.build_filter(move |path| stmt::Expr::like(path, pattern))
}
pub fn ilike(self, pattern: impl IntoExpr<String>) -> Expr<bool> {
Expr {
untyped: stmt::Expr::ilike(self.untyped.into_stmt(), pattern.into_expr().untyped),
_p: PhantomData,
}
}
}
impl<T, U> Clone for Path<T, U> {
fn clone(&self) -> Self {
Self {
untyped: self.untyped.clone(),
_p: PhantomData,
}
}
}
impl<T, U> IntoExpr<U> for Path<T, U> {
fn into_expr(self) -> Expr<U> {
Expr {
untyped: self.untyped.into_stmt(),
_p: PhantomData,
}
}
fn by_ref(&self) -> Expr<U> {
Self::into_expr(self.clone())
}
}
impl<T, U> From<Path<T, U>> for stmt::Path {
fn from(value: Path<T, U>) -> Self {
value.untyped
}
}
impl<T, U> fmt::Debug for Path<T, U> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.untyped)
}
}