use crate::filter::{Filter, FilterValue};
use crate::inputs::scalar::combine_filters;
use crate::inputs::traits::WhereInput;
pub trait RelationFilterMeta {
const PARENT_TABLE: &'static str;
const PARENT_PK: &'static str;
const CHILD_TABLE: &'static str;
const CHILD_FK: &'static str;
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(bound = "W: serde::Serialize + for<'de2> serde::Deserialize<'de2>")]
pub struct ListRelationFilter<W> {
pub some: Option<W>,
pub every: Option<W>,
pub none: Option<W>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(bound = "W: serde::Serialize + for<'de2> serde::Deserialize<'de2>")]
pub struct SingleRelationFilter<W> {
pub is: Option<W>,
pub is_not: Option<W>,
}
pub trait LowerRelationFilter {
fn lower<M: RelationFilterMeta>(self) -> Filter;
}
fn render_inline_filter(inner: Filter) -> (String, Vec<FilterValue>) {
let mut sql = String::new();
let mut params = Vec::<FilterValue>::new();
write_filter(&inner, &mut sql, &mut params);
(sql, params)
}
fn write_filter(f: &Filter, sql: &mut String, params: &mut Vec<FilterValue>) {
match f {
Filter::None => sql.push_str("TRUE"),
Filter::Equals(c, v) => {
if matches!(v, FilterValue::Null) {
sql.push_str(&format!("{} IS NULL", c));
} else {
let idx = params.len();
params.push(v.clone());
sql.push_str(&format!("{} = {{{}}}", c, idx));
}
}
Filter::NotEquals(c, v) => {
if matches!(v, FilterValue::Null) {
sql.push_str(&format!("{} IS NOT NULL", c));
} else {
let idx = params.len();
params.push(v.clone());
sql.push_str(&format!("{} <> {{{}}}", c, idx));
}
}
Filter::Lt(c, v) => {
let i = params.len();
params.push(v.clone());
sql.push_str(&format!("{} < {{{}}}", c, i));
}
Filter::Lte(c, v) => {
let i = params.len();
params.push(v.clone());
sql.push_str(&format!("{} <= {{{}}}", c, i));
}
Filter::Gt(c, v) => {
let i = params.len();
params.push(v.clone());
sql.push_str(&format!("{} > {{{}}}", c, i));
}
Filter::Gte(c, v) => {
let i = params.len();
params.push(v.clone());
sql.push_str(&format!("{} >= {{{}}}", c, i));
}
Filter::IsNull(c) => sql.push_str(&format!("{} IS NULL", c)),
Filter::IsNotNull(c) => sql.push_str(&format!("{} IS NOT NULL", c)),
Filter::Contains(c, FilterValue::String(s)) => {
let i = params.len();
params.push(FilterValue::String(format!("%{}%", s)));
sql.push_str(&format!("{} LIKE {{{}}}", c, i));
}
Filter::StartsWith(c, FilterValue::String(s)) => {
let i = params.len();
params.push(FilterValue::String(format!("{}%", s)));
sql.push_str(&format!("{} LIKE {{{}}}", c, i));
}
Filter::EndsWith(c, FilterValue::String(s)) => {
let i = params.len();
params.push(FilterValue::String(format!("%{}", s)));
sql.push_str(&format!("{} LIKE {{{}}}", c, i));
}
Filter::Contains(_, _) | Filter::StartsWith(_, _) | Filter::EndsWith(_, _) => {
panic!(
"inline relation-filter lowering requires String values for Contains / StartsWith / EndsWith \
(other FilterValue variants must be expressed via Equals / In)"
);
}
Filter::In(c, values) => {
if values.is_empty() {
sql.push_str("FALSE");
return;
}
sql.push_str(&format!("{} IN (", c));
for (n, v) in values.iter().enumerate() {
if n > 0 {
sql.push_str(", ");
}
let i = params.len();
params.push(v.clone());
sql.push_str(&format!("{{{}}}", i));
}
sql.push(')');
}
Filter::NotIn(c, values) => {
if values.is_empty() {
sql.push_str("TRUE");
return;
}
sql.push_str(&format!("{} NOT IN (", c));
for (n, v) in values.iter().enumerate() {
if n > 0 {
sql.push_str(", ");
}
let i = params.len();
params.push(v.clone());
sql.push_str(&format!("{{{}}}", i));
}
sql.push(')');
}
Filter::And(parts) => {
if parts.is_empty() {
sql.push_str("TRUE");
return;
}
sql.push('(');
for (n, p) in parts.iter().enumerate() {
if n > 0 {
sql.push_str(" AND ");
}
write_filter(p, sql, params);
}
sql.push(')');
}
Filter::Or(parts) => {
if parts.is_empty() {
sql.push_str("FALSE");
return;
}
sql.push('(');
for (n, p) in parts.iter().enumerate() {
if n > 0 {
sql.push_str(" OR ");
}
write_filter(p, sql, params);
}
sql.push(')');
}
Filter::Not(inner) => {
sql.push_str("NOT (");
write_filter(inner, sql, params);
sql.push(')');
}
Filter::ScalarSubquery { .. } => {
panic!(
"inline relation-filter lowering does not support nested ScalarSubquery \
(the outer to_sql_with_params handles ScalarSubquery; relation bodies must lower to leaf filters first)"
);
}
}
}
impl<W: WhereInput> LowerRelationFilter for ListRelationFilter<W> {
fn lower<M: RelationFilterMeta>(self) -> Filter {
let mut clauses: Vec<Filter> = Vec::new();
if let Some(w) = self.some {
let (body, params) = render_inline_filter(w.into_ir());
let sql = format!(
"EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND {})",
M::CHILD_TABLE,
M::CHILD_TABLE,
M::CHILD_FK,
M::PARENT_TABLE,
M::PARENT_PK,
body,
);
clauses.push(Filter::ScalarSubquery {
sql: sql.into(),
params,
});
}
if let Some(w) = self.every {
let (body, params) = render_inline_filter(w.into_ir());
let sql = format!(
"NOT EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND NOT ({}))",
M::CHILD_TABLE,
M::CHILD_TABLE,
M::CHILD_FK,
M::PARENT_TABLE,
M::PARENT_PK,
body,
);
clauses.push(Filter::ScalarSubquery {
sql: sql.into(),
params,
});
}
if let Some(w) = self.none {
let (body, params) = render_inline_filter(w.into_ir());
let sql = format!(
"NOT EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND {})",
M::CHILD_TABLE,
M::CHILD_TABLE,
M::CHILD_FK,
M::PARENT_TABLE,
M::PARENT_PK,
body,
);
clauses.push(Filter::ScalarSubquery {
sql: sql.into(),
params,
});
}
combine_filters(clauses)
}
}
impl<W: WhereInput> LowerRelationFilter for SingleRelationFilter<W> {
fn lower<M: RelationFilterMeta>(self) -> Filter {
let mut clauses: Vec<Filter> = Vec::new();
if let Some(w) = self.is {
let (body, params) = render_inline_filter(w.into_ir());
let sql = format!(
"EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND {})",
M::CHILD_TABLE,
M::CHILD_TABLE,
M::CHILD_FK,
M::PARENT_TABLE,
M::PARENT_PK,
body,
);
clauses.push(Filter::ScalarSubquery {
sql: sql.into(),
params,
});
}
if let Some(w) = self.is_not {
let (body, params) = render_inline_filter(w.into_ir());
let sql = format!(
"NOT EXISTS (SELECT 1 FROM {} WHERE {}.{} = {}.{} AND {})",
M::CHILD_TABLE,
M::CHILD_TABLE,
M::CHILD_FK,
M::PARENT_TABLE,
M::PARENT_PK,
body,
);
clauses.push(Filter::ScalarSubquery {
sql: sql.into(),
params,
});
}
combine_filters(clauses)
}
}