Reusable & Dynamic Query Pieces #4507
Unanswered
ahmed-said-jax
asked this question in
Q&A
Replies: 1 comment
-
So it turns out that using trait bounds, I can actually accomplish this: use diesel::{RunQueryDsl, pg::Pg, prelude::*, sql_types::Bool};
use serde::Deserialize;
use uuid::Uuid;
type BoxedFilterExpression<'a, T> = Box<dyn BoxableExpression<T, Pg, SqlType = Bool> + 'a>;
#[derive(Deserialize, Debug, Default)]
struct MetadataFilter {
name: Option<String>,
nationality: Option<String>,
}
impl MetadataFilter {
fn as_diesel_expression<'a, T: 'a>(&'a self) -> Option<BoxedFilterExpression<'a, T>>
where
player::id: SelectableExpression<T>,
player::name: SelectableExpression<T>,
player::nationality: SelectableExpression<T>,
{
use player::dsl::{name as name_col, nationality as nationality_col};
let Self { name, nationality } = self;
if matches!((name, nationality), (None, None)) {
return None;
}
let mut query: Vec<BoxedFilterExpression<T>> = Vec::new();
if let Some(player_name) = &self.name {
let ilike_string = format!("%{player_name}%");
let q = name_col.ilike(ilike_string);
query.push(Box::new(q));
}
if let Some(player_country) = &self.nationality {
let q = nationality_col.eq(player_country);
query.push(Box::new(q));
}
query.into_iter().reduce(|q1, q2| Box::new(q1.and(q2)))
}
}
#[derive(Deserialize, Default)]
struct GoalKeeperFilter {
#[serde(flatten)]
#[serde(default)]
metadata_filter: MetadataFilter,
more_saves_than: Option<i32>,
less_saves_than: Option<i32>,
}
impl GoalKeeperFilter {
fn as_diesel_expression<'a, T: 'a>(&'a self) -> Option<BoxedFilterExpression<'a, T>>
where
player::id: SelectableExpression<T>,
player::name: SelectableExpression<T>,
player::nationality: SelectableExpression<T>,
goalkeeper::n_saves: SelectableExpression<T>,
{
use goalkeeper::dsl::n_saves;
let Self {
metadata_filter,
more_saves_than,
less_saves_than,
} = self;
let metadata_filter = metadata_filter.as_diesel_expression();
if matches!(
(&metadata_filter, more_saves_than, less_saves_than),
(None, None, None)
) {
return None;
}
let mut query: Vec<Box<dyn BoxableExpression<T, Pg, SqlType = Bool>>> = Vec::new();
if let Some(metadata_filter) = metadata_filter {
query.push(metadata_filter);
}
if let Some(less_saves_than) = &self.less_saves_than {
query.push(Box::new(n_saves.lt(less_saves_than)));
}
if let Some(more_saves_than) = &self.more_saves_than {
query.push(Box::new(n_saves.gt(more_saves_than)));
}
query.into_iter().reduce(|q1, q2| Box::new(q1.and(q2)))
}
} This is not bad, but there are a couple problems I'd like to improve:
|
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I'm using a sort of "composition" in my database schema to extract fields that are common to many tables. Here's a contrived example schema in an unrelated domain (for funsies):
It's not exactly conventional, but I think my actual use-case merits it (I'm open to changing my schema though). Now, I want to be able to filter either goalkeepers or strikers based on fields specific to those tables, but also common fields, like
name
ornationality
. I've gotten this far:Now, I know I might use
into_boxed
, and then call.filter(..)
multiple times like so:But then I cannot use that result in an
inner_join
withgoalkeeper
due to a type error (happy to add it if it's useful, but I'm pretty sure it would just be noise). Any suggestions on how to achieve this behavior?Beta Was this translation helpful? Give feedback.
All reactions