#![allow(dead_code)]
use crate::sql::SqlKeywords;
use super::{Filter, Operator};
#[derive(Debug, Clone)]
pub enum ArrayOperator<I> {
Eq(Option<Vec<I>>),
Not(Option<Vec<I>>),
Gt(Option<Vec<I>>),
Gte(Option<Vec<I>>),
Lt(Option<Vec<I>>),
Lte(Option<Vec<I>>),
Contains(Option<Vec<I>>),
IsContainedBy(Option<Vec<I>>),
Overlaps(Option<Vec<I>>),
}
impl<I> ArrayOperator<I> {
#[inline]
pub fn is_some(&self) -> bool {
!self.is_none()
}
#[inline]
pub fn is_none(&self) -> bool {
self.values().as_ref().map_or(true, |x| x.is_empty())
}
#[inline]
pub fn values(&self) -> &Option<Vec<I>> {
match self {
Self::Eq(x) => x,
Self::Not(x) => x,
Self::Gt(x) => x,
Self::Gte(x) => x,
Self::Lt(x) => x,
Self::Lte(x) => x,
Self::Contains(x) => x,
Self::IsContainedBy(x) => x,
Self::Overlaps(x) => x,
}
}
#[inline]
pub fn into_value(self) -> Option<Vec<I>> {
match self {
Self::Eq(x) => x,
Self::Not(x) => x,
Self::Gt(x) => x,
Self::Gte(x) => x,
Self::Lt(x) => x,
Self::Lte(x) => x,
Self::Contains(x) => x,
Self::IsContainedBy(x) => x,
Self::Overlaps(x) => x,
}
}
#[inline]
pub fn merge(&mut self, other: Self) {
match (self, other) {
(ArrayOperator::Eq(this), ArrayOperator::Eq(Some(other))) => {
this.get_or_insert(other);
}
(ArrayOperator::Not(this), ArrayOperator::Not(Some(other))) => {
this.get_or_insert(other);
}
(ArrayOperator::Gt(this), ArrayOperator::Gt(Some(other))) => {
this.get_or_insert(other);
}
(ArrayOperator::Gte(this), ArrayOperator::Gte(Some(other))) => {
this.get_or_insert(other);
}
(ArrayOperator::Lt(this), ArrayOperator::Lt(Some(other))) => {
this.get_or_insert(other);
}
(ArrayOperator::Lte(this), ArrayOperator::Lte(Some(other))) => {
this.get_or_insert(other);
}
(ArrayOperator::Contains(this), ArrayOperator::Contains(Some(other))) => {
this.get_or_insert(other);
}
(ArrayOperator::IsContainedBy(this), ArrayOperator::IsContainedBy(Some(other))) => {
this.get_or_insert(other);
}
(ArrayOperator::Overlaps(this), ArrayOperator::Overlaps(Some(other))) => {
this.get_or_insert(other);
}
_ => {}
}
}
#[inline(always)]
fn create(f: fn(Option<Vec<I>>) -> Self, vals: impl IntoIterator<Item = I>) -> Self {
let x = vals.into_iter().collect::<Vec<_>>();
f(x.is_empty().then_some(x))
}
#[inline]
pub fn eq(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::Eq, vals)
}
#[inline]
pub fn ne(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::Not, vals)
}
#[inline]
pub fn gt(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::Gt, vals)
}
#[inline]
pub fn gte(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::Gte, vals)
}
#[inline]
pub fn lt(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::Lt, vals)
}
#[inline]
pub fn lte(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::Lte, vals)
}
#[inline]
pub fn contains(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::Contains, vals)
}
#[inline]
pub fn is_contained_by(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::IsContainedBy, vals)
}
#[inline]
pub fn overlaps(vals: impl IntoIterator<Item = I>) -> Self {
Self::create(Self::Overlaps, vals)
}
}
impl<I: Send + Sync + sqlx::postgres::PgHasArrayType> Operator for ArrayOperator<I> {
type Primitive = I;
type Primitives = Vec<I>;
fn sql_op(&self) -> &'static str
where
Self::Primitive:
for<'a> sqlx::Encode<'a, sqlx::Postgres> + sqlx::Type<sqlx::Postgres> + Send + Sync,
{
match self {
ArrayOperator::Eq(_) => SqlKeywords::Eq,
ArrayOperator::Not(_) => " <> ",
ArrayOperator::Gt(_) => SqlKeywords::Gt,
ArrayOperator::Gte(_) => SqlKeywords::Gte,
ArrayOperator::Lt(_) => SqlKeywords::Lt,
ArrayOperator::Lte(_) => SqlKeywords::Lte,
ArrayOperator::Contains(_) => " @> ",
ArrayOperator::IsContainedBy(_) => " <@ ",
ArrayOperator::Overlaps(_) => " && ",
}
}
fn bind<'q>(
&'q self,
field: &str,
builder: &mut sqlx::QueryBuilder<'q, sqlx::Postgres>,
and: &mut bool,
) -> Result<(), async_graphql::Error>
where
Self::Primitive:
for<'a> sqlx::Encode<'a, sqlx::Postgres> + sqlx::Type<sqlx::Postgres> + Send + Sync,
{
let values = self.values();
if let Some(values) = values {
if *and {
builder.push(" AND ");
}
builder.push(field);
builder.push(self.sql_op());
builder.push_bind(values);
*and = true;
}
Ok(())
}
fn to_value(&self) -> async_graphql::Value
where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType,
{
let values = self.values();
async_graphql::InputType::to_value(values)
}
fn graphql_suffix(&self) -> &'static str
where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType,
{
match self {
ArrayOperator::Eq(_) => "",
ArrayOperator::Not(_) => "_not",
ArrayOperator::Gt(_) => "_gt",
ArrayOperator::Gte(_) => "_gte",
ArrayOperator::Lt(_) => "_lt",
ArrayOperator::Lte(_) => "_lte",
ArrayOperator::Contains(_) => "_contains",
ArrayOperator::IsContainedBy(_) => "_is_contained_by",
ArrayOperator::Overlaps(_) => "_overlaps",
}
}
}
#[derive(Debug, Clone)]
pub struct ArrayFilter<I>([ArrayOperator<I>; 9]);
impl<I> Default for ArrayFilter<I> {
fn default() -> Self {
Self::new()
}
}
impl<I> ArrayFilter<I> {
pub fn new() -> Self {
Self([
ArrayOperator::Eq(None),
ArrayOperator::Not(None),
ArrayOperator::Gt(None),
ArrayOperator::Gte(None),
ArrayOperator::Lt(None),
ArrayOperator::Lte(None),
ArrayOperator::Contains(None),
ArrayOperator::IsContainedBy(None),
ArrayOperator::Overlaps(None),
])
}
}
impl<I> From<Vec<I>> for ArrayFilter<I> {
fn from(v: Vec<I>) -> Self {
Self([
ArrayOperator::Eq(Some(v)),
ArrayOperator::Not(None),
ArrayOperator::Gt(None),
ArrayOperator::Gte(None),
ArrayOperator::Lt(None),
ArrayOperator::Lte(None),
ArrayOperator::Contains(None),
ArrayOperator::IsContainedBy(None),
ArrayOperator::Overlaps(None),
])
}
}
impl<I: Send + Sync + sqlx::postgres::PgHasArrayType + Clone> Filter for ArrayFilter<I> {
type Primitive = I;
type Primitives = Vec<I>;
fn primitive_type_info(registry: &mut async_graphql::registry::Registry) -> String
where
Self::Primitive: async_graphql::InputType,
{
<Option<Self::Primitive> as async_graphql::InputType>::create_type_info(registry)
}
fn primitives_type_info(registry: &mut async_graphql::registry::Registry) -> String
where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
{
<Option<Self::Primitives> as async_graphql::InputType>::create_type_info(registry)
}
fn match_operator(
name: &'static str,
op: &'static str,
registry: &mut async_graphql::registry::Registry,
) -> Option<async_graphql::registry::MetaInputValue>
where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
{
match op {
"" | "not" | "gt" | "gte" | "lt" | "lte" | "contains" | "is_contained_by" | "overlaps" => {
Some(async_graphql::registry::MetaInputValue {
name: name.into(),
description: None,
ty: <Option<Self::Primitives> as async_graphql::InputType>::create_type_info(registry),
default_value: None,
visible: None,
is_secret: false,
inaccessible: false,
tags: vec![],
directive_invocations: vec![],
deprecation: async_graphql::registry::Deprecation::NoDeprecated,
})
}
_ => None,
}
}
fn is_collection() -> bool {
true
}
fn merge(&mut self, other: Self) {
self.0.iter_mut().zip(other.0).for_each(|(a, b)| match a {
ArrayOperator::Eq(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
ArrayOperator::Not(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
ArrayOperator::Gt(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
ArrayOperator::Gte(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
ArrayOperator::Lt(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
ArrayOperator::Lte(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
ArrayOperator::Contains(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
ArrayOperator::IsContainedBy(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
ArrayOperator::Overlaps(val) => {
if let Some(other) = b.into_value() {
val.get_or_insert(other);
}
}
});
}
fn parse<T: async_graphql::InputType>(
field_name: &'static str,
obj: &async_graphql::indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
) -> Result<Option<Self>, async_graphql::InputValueError<T>>
where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
{
let eq: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(format!("{}{}", field_name, ArrayOperator::Eq(None).graphql_suffix()).as_str())
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
let not: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(
format!(
"{}{}",
field_name,
ArrayOperator::Not(None).graphql_suffix()
)
.as_str(),
)
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
let gt: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(format!("{}{}", field_name, ArrayOperator::Gt(None).graphql_suffix()).as_str())
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
let gte: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(
format!(
"{}{}",
field_name,
ArrayOperator::Gte(None).graphql_suffix()
)
.as_str(),
)
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
let lt: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(format!("{}{}", field_name, ArrayOperator::Lt(None).graphql_suffix()).as_str())
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
let lte: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(
format!(
"{}{}",
field_name,
ArrayOperator::Lte(None).graphql_suffix()
)
.as_str(),
)
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
let contains: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(
format!(
"{}{}",
field_name,
ArrayOperator::Contains(None).graphql_suffix()
)
.as_str(),
)
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
let is_contained_by: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(
format!(
"{}{}",
field_name,
ArrayOperator::IsContainedBy(None).graphql_suffix()
)
.as_str(),
)
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
let overlaps: Option<Self::Primitives> = async_graphql::InputType::parse(
obj
.get(
format!(
"{}{}",
field_name,
ArrayOperator::Overlaps(None).graphql_suffix()
)
.as_str(),
)
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
Ok(Some(Self([
ArrayOperator::Eq(eq),
ArrayOperator::Not(not),
ArrayOperator::Gt(gt),
ArrayOperator::Gte(gte),
ArrayOperator::Lt(lt),
ArrayOperator::Lte(lte),
ArrayOperator::Contains(contains),
ArrayOperator::IsContainedBy(is_contained_by),
ArrayOperator::Overlaps(overlaps),
])))
}
fn to_value(
&self,
field_name: &'static str,
map: &mut async_graphql::indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
) where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
{
self.0.iter().for_each(|v| {
map.insert(
async_graphql::Name::new(format!("{}{}", field_name, v.graphql_suffix())),
v.to_value(),
);
});
}
fn pg_type() -> Option<sqlx::postgres::PgTypeInfo>
where
Self::Primitive: sqlx::Type<sqlx::Postgres>,
{
Some(<Self::Primitive as sqlx::Type<sqlx::Postgres>>::type_info())
}
fn is_pg_array() -> bool {
true
}
fn apply_filter<'q, QB: core::borrow::BorrowMut<sqlx::QueryBuilder<'q, sqlx::Postgres>>>(
&'q self,
field: &str,
mut builder: QB,
) -> Result<(), async_graphql::Error>
where
Self::Primitive:
for<'a> sqlx::Encode<'a, sqlx::Postgres> + sqlx::Type<sqlx::Postgres> + Send + Sync,
{
let builder = builder.borrow_mut();
let mut and = false;
for val in self.0.iter().filter(|x| x.is_some()) {
val.bind(field, builder, &mut and)?;
}
Ok(())
}
fn is_empty(&self) -> bool {
self.0.iter().all(|x| x.is_none())
}
}
impl<I: async_graphql::InputType> ArrayOperator<I> {
pub fn try_map<U, E, F: Fn(I) -> Result<U, E>>(self, f: &F) -> Result<ArrayOperator<U>, E> {
try_map_operator!(self::ArrayOperator::f, null {}, primitive {}, primitives {
Eq,
Not,
Gt,
Gte,
Lt,
Lte,
Contains,
IsContainedBy,
Overlaps
})
}
}
impl<I: async_graphql::InputType + sqlx::postgres::PgHasArrayType> ArrayFilter<I> {
pub fn try_map<U, E, F>(self, f: F) -> Result<ArrayFilter<U>, E>
where
Self: Filter<Primitive = I>,
U: for<'a> sqlx::Encode<'a, sqlx::Postgres>
+ sqlx::Type<sqlx::Postgres>
+ sqlx::postgres::PgHasArrayType
+ Send
+ Sync,
F: Fn(I) -> Result<U, E>,
{
try_map_filter!(self::ArrayFilter::f, {eq, not, gt, gte, lt, lte, contains, is_contained_by, overlaps})
}
}
impl<I: async_graphql::InputType + sqlx::postgres::PgHasArrayType + Clone> OptionalArrayFilter<I> {
pub fn try_map<U, E, F>(self, f: F) -> Result<OptionalArrayFilter<U>, E>
where
Self: Filter<Primitive = I>,
U: for<'a> sqlx::Encode<'a, sqlx::Postgres>
+ sqlx::Type<sqlx::Postgres>
+ sqlx::postgres::PgHasArrayType
+ Send
+ Sync,
F: Fn(I) -> Result<U, E>,
{
Ok(OptionalArrayFilter {
filter: self.filter.try_map(f)?,
null: self.null,
})
}
}
#[derive(Debug, Clone)]
pub struct OptionalArrayFilter<I> {
filter: ArrayFilter<I>,
null: Option<bool>,
}
impl<I> Default for OptionalArrayFilter<I> {
fn default() -> Self {
Self::new()
}
}
impl<I> OptionalArrayFilter<I> {
pub fn new() -> Self {
Self {
filter: ArrayFilter::new(),
null: None,
}
}
}
impl<I> From<Vec<I>> for OptionalArrayFilter<I> {
fn from(v: Vec<I>) -> Self {
Self {
filter: ArrayFilter::from(v),
null: None,
}
}
}
impl<I> From<Option<Vec<I>>> for OptionalArrayFilter<I> {
fn from(v: Option<Vec<I>>) -> Self {
if let Some(v) = v {
Self {
filter: ArrayFilter::from(v),
null: None,
}
} else {
Self::new()
}
}
}
impl<I: Send + Sync + sqlx::postgres::PgHasArrayType + Clone> Filter for OptionalArrayFilter<I> {
type Primitive = I;
type Primitives = Vec<I>;
fn is_empty(&self) -> bool {
self.filter.is_empty() && self.null.is_none()
}
fn primitive_type_info(registry: &mut async_graphql::registry::Registry) -> String
where
Self::Primitive: async_graphql::InputType,
{
<Option<Self::Primitive> as async_graphql::InputType>::create_type_info(registry)
}
fn primitives_type_info(registry: &mut async_graphql::registry::Registry) -> String
where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
{
<Option<Self::Primitives> as async_graphql::InputType>::create_type_info(registry)
}
fn match_operator(
name: &'static str,
op: &'static str,
registry: &mut async_graphql::registry::Registry,
) -> Option<async_graphql::registry::MetaInputValue>
where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
{
match op {
"null" => Some(async_graphql::registry::MetaInputValue {
name: name.into(),
description: None,
ty: <Option<bool> as async_graphql::InputType>::create_type_info(registry),
default_value: None,
visible: None,
is_secret: false,
inaccessible: false,
tags: vec![],
directive_invocations: vec![],
deprecation: async_graphql::registry::Deprecation::NoDeprecated,
}),
op => ArrayFilter::<I>::match_operator(name, op, registry),
}
}
fn parse<T: async_graphql::InputType>(
field_name: &'static str,
obj: &async_graphql::indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
) -> Result<Option<Self>, async_graphql::InputValueError<T>>
where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
{
let null: Option<bool> = async_graphql::InputType::parse(
obj
.get(format!("{}{}", field_name, "_null").as_str())
.cloned(),
)
.map_err(async_graphql::InputValueError::propagate)?;
ArrayFilter::<I>::parse(field_name, obj).map(|f| f.map(|f| Self { filter: f, null }))
}
fn to_value(
&self,
field_name: &'static str,
map: &mut async_graphql::indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
) where
Self::Primitive: async_graphql::InputType,
Self::Primitives: async_graphql::InputType + IntoIterator<Item = Self::Primitive>,
{
map.insert(
async_graphql::Name::new(format!("{}{}", field_name, "_null")),
async_graphql::InputType::to_value(&self.null),
);
self.filter.to_value(field_name, map);
}
fn type_name() -> &'static str {
std::any::type_name::<Self>()
}
fn pg_type() -> Option<sqlx::postgres::PgTypeInfo>
where
Self::Primitive: sqlx::Type<sqlx::Postgres>,
{
ArrayFilter::<I>::pg_type()
}
fn is_collection() -> bool {
true
}
fn is_pg_array() -> bool
where
Self::Primitive: sqlx::Type<sqlx::Postgres>,
{
ArrayFilter::<I>::is_pg_array()
}
fn apply_filter<'q, QB: core::borrow::BorrowMut<sqlx::QueryBuilder<'q, sqlx::Postgres>>>(
&'q self,
field: &str,
mut builder: QB,
) -> Result<(), async_graphql::Error>
where
Self::Primitive:
for<'a> sqlx::Encode<'a, sqlx::Postgres> + sqlx::Type<sqlx::Postgres> + Send + Sync,
{
let builder = builder.borrow_mut();
let mut and = false;
if let Some(null) = self.null {
if and {
builder.push(" AND ");
}
builder.push(field);
if null {
builder.push(" IS NULL ");
} else {
builder.push(" IS NOT NULL ");
}
and = true;
}
for val in self.filter.0.iter().filter(|x| x.is_some()) {
val.bind(field, builder, &mut and)?;
}
Ok(())
}
fn merge(&mut self, other: Self) {
self.filter.merge(other.filter);
if let Some(null) = other.null {
self.null = Some(null);
}
}
}