Skip to content

Commit 776eca2

Browse files
committed
Add inner query and filter reuse
1 parent 9c938ae commit 776eca2

File tree

4 files changed

+260
-1
lines changed

4 files changed

+260
-1
lines changed

src/inner_statement/bike.rs

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use diesel::{
2+
helper_types::{IntoBoxed, LeftJoin, LeftJoinQuerySource},
3+
prelude::*,
4+
sql_types::{Bool, Nullable},
5+
sqlite::Sqlite,
6+
};
7+
8+
use crate::*;
9+
10+
table! {
11+
bike (id) {
12+
id -> Text,
13+
name -> Text,
14+
owner_id -> Text,
15+
color_id -> Text
16+
}
17+
}
18+
19+
table! {
20+
color (id) {
21+
id -> Text,
22+
name -> Text
23+
}
24+
}
25+
26+
joinable!(bike -> color (color_id));
27+
allow_tables_to_appear_in_same_query!(bike, color);
28+
29+
#[allow(non_camel_case_types)]
30+
pub(super) enum Condition {
31+
name(StringFilter),
32+
color(StringFilter),
33+
And(Vec<Condition>),
34+
Or(Vec<Condition>),
35+
}
36+
37+
type ConditionSource = LeftJoinQuerySource<bike::dsl::bike, color::dsl::color>;
38+
// Need this type for common condition expressions
39+
type BoxedCondition = Box<dyn BoxableExpression<ConditionSource, Sqlite, SqlType = Nullable<Bool>>>;
40+
type QuerySource = LeftJoin<bike::dsl::bike, color::dsl::color>;
41+
type BoxedQuery = IntoBoxed<'static, QuerySource, Sqlite>;
42+
43+
impl Condition {
44+
fn to_boxed_condition(self) -> Option<BoxedCondition> {
45+
Some(match self {
46+
Condition::name(f) => string_filter!(f, bike::dsl::name),
47+
Condition::color(f) => string_filter!(f, color::dsl::name),
48+
Condition::And(conditions) => match create_filter(conditions, AndOr::And) {
49+
Some(boxed_condition) => boxed_condition,
50+
None => return None,
51+
},
52+
Condition::Or(conditions) => match create_filter(conditions, AndOr::Or) {
53+
Some(boxed_condition) => boxed_condition,
54+
None => return None,
55+
},
56+
})
57+
}
58+
}
59+
60+
// This method can also be made into a macro, but it should be fine to just duplicate
61+
fn create_filter(conditions: Vec<Condition>, and_or: AndOr) -> Option<BoxedCondition> {
62+
conditions
63+
.into_iter()
64+
// Map into array of boxed conditions
65+
.filter_map::<BoxedCondition, _>(Condition::to_boxed_condition)
66+
// Reduce to a boxed_condition1.and(boxed_condition2).and(boxed_condition3)...
67+
.fold(None, |boxed_conditions, boxed_condition| {
68+
Some(match boxed_conditions {
69+
Some(bc) => match and_or {
70+
AndOr::And => Box::new(bc.and(boxed_condition)),
71+
AndOr::Or => Box::new(bc.or(boxed_condition)),
72+
},
73+
None => boxed_condition,
74+
})
75+
})
76+
}
77+
pub(super) fn create_filtered_query(conditions: Vec<Condition>) -> BoxedQuery {
78+
let boxed_query = bike::dsl::bike.left_join(color::dsl::color).into_boxed();
79+
80+
match create_filter(conditions, AndOr::And) {
81+
Some(boxed_conditions) => boxed_query.filter(boxed_conditions),
82+
None => boxed_query,
83+
}
84+
}

src/inner_statement/mod.rs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use diesel::{connection::SimpleConnection, prelude::*, SqliteConnection};
2+
3+
use crate::inner_statement::bike::create_filtered_query;
4+
5+
mod bike;
6+
mod person;
7+
8+
#[test]
9+
fn test() {
10+
let mut connection = SqliteConnection::establish("file:test?mode=memory&cache=shared").unwrap();
11+
12+
connection
13+
.batch_execute(
14+
r#"
15+
CREATE TABLE person (
16+
id TEXT PRIMARY KEY,
17+
name TEXT NOT NULL
18+
);
19+
20+
CREATE TABLE color (
21+
id TEXT PRIMARY KEY,
22+
name TEXT NOT NULL
23+
);
24+
25+
CREATE TABLE bike (
26+
id TEXT PRIMARY KEY,
27+
name TEXT NOT NULL,
28+
owner_id TEXT REFERENCES person(id),
29+
color_id TEXT REFERENCES color(id)
30+
);
31+
32+
INSERT INTO color
33+
(id, name)
34+
VALUES
35+
('orange', 'orange');
36+
37+
INSERT INTO color
38+
(id, name)
39+
VALUES
40+
('purple', 'purple');
41+
42+
INSERT INTO color
43+
(id, name)
44+
VALUES
45+
('grey', 'grey');
46+
47+
INSERT INTO person
48+
(id, name)
49+
VALUES
50+
('craig', 'craig');
51+
52+
INSERT INTO person
53+
(id, name)
54+
VALUES
55+
('mark', 'mark');
56+
57+
INSERT INTO bike
58+
(id, name, owner_id, color_id)
59+
VALUES
60+
('c1', 'c1', 'craig', 'orange');
61+
62+
INSERT INTO bike
63+
(id, name, owner_id, color_id)
64+
VALUES
65+
('c2', 'c2', 'craig', 'purple');
66+
67+
INSERT INTO bike
68+
(id, name, owner_id, color_id)
69+
VALUES
70+
('m1', 'm1', 'mark', 'grey');
71+
"#,
72+
)
73+
.unwrap();
74+
75+
use self::person::*;
76+
use super::*;
77+
78+
let condition = vec![Condition::bike(vec![bike::Condition::color(
79+
StringFilter::In(vec!["orange".to_string(), "purple".to_string()]),
80+
)])];
81+
let result = vec!["craig".to_string()];
82+
83+
assert_eq!(
84+
result,
85+
create_filtered_query(condition)
86+
.select(person::dsl::id)
87+
.load::<String>(&mut connection)
88+
.unwrap()
89+
);
90+
}

src/inner_statement/person.rs

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use diesel::{
2+
helper_types::IntoBoxed,
3+
prelude::*,
4+
sql_types::{Bool, Nullable},
5+
sqlite::Sqlite,
6+
};
7+
8+
use super::*;
9+
use crate::*;
10+
11+
table! {
12+
person (id) {
13+
id -> Text,
14+
name -> Text,
15+
}
16+
}
17+
18+
#[allow(non_camel_case_types)]
19+
pub(super) enum Condition {
20+
name(StringFilter),
21+
bike(Vec<bike::Condition>),
22+
And(Vec<Condition>),
23+
Or(Vec<Condition>),
24+
}
25+
26+
type ConditionSource = person::dsl::person;
27+
// Need this type for common condition expressions
28+
type BoxedCondition = Box<dyn BoxableExpression<ConditionSource, Sqlite, SqlType = Nullable<Bool>>>;
29+
30+
type QuerySource = person::dsl::person;
31+
type BoxedQuery = IntoBoxed<'static, QuerySource, Sqlite>;
32+
33+
impl Condition {
34+
fn to_boxed_condition(self) -> Option<BoxedCondition> {
35+
Some(match self {
36+
Condition::name(f) => string_filter!(f, person::dsl::name),
37+
Condition::And(conditions) => match create_filter(conditions, AndOr::And) {
38+
Some(boxed_condition) => boxed_condition,
39+
None => return None,
40+
},
41+
Condition::Or(conditions) => match create_filter(conditions, AndOr::Or) {
42+
Some(boxed_condition) => boxed_condition,
43+
None => return None,
44+
},
45+
Condition::bike(conditions) => {
46+
// Inner statement, reusing conditions defined in bike
47+
let inner_statement = bike::create_filtered_query(conditions);
48+
Box::new(
49+
person::dsl::id
50+
.eq_any(inner_statement.select(bike::bike::dsl::owner_id))
51+
.nullable(),
52+
)
53+
}
54+
})
55+
}
56+
}
57+
58+
// This method can also be made into a macro, but it should be fine to just duplicate
59+
fn create_filter(conditions: Vec<Condition>, and_or: AndOr) -> Option<BoxedCondition> {
60+
conditions
61+
.into_iter()
62+
// Map into array of boxed conditions
63+
.filter_map::<BoxedCondition, _>(Condition::to_boxed_condition)
64+
// Reduce to a boxed_condition1.and(boxed_condition2).and(boxed_condition3)...
65+
.fold(None, |boxed_conditions, boxed_condition| {
66+
Some(match boxed_conditions {
67+
Some(bc) => match and_or {
68+
AndOr::And => Box::new(bc.and(boxed_condition)),
69+
AndOr::Or => Box::new(bc.or(boxed_condition)),
70+
},
71+
None => boxed_condition,
72+
})
73+
})
74+
}
75+
76+
pub(super) fn create_filtered_query(conditions: Vec<Condition>) -> BoxedQuery {
77+
let boxed_query = person::dsl::person.into_boxed();
78+
79+
match create_filter(conditions, AndOr::And) {
80+
Some(boxed_conditions) => boxed_query.filter(boxed_conditions),
81+
None => boxed_query,
82+
}
83+
}

src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mod dynamic_filters;
2-
2+
mod inner_statement;
33
// Filters for "numbers"
44
enum NumberFilter<T> {
55
Equal(T),
@@ -27,6 +27,7 @@ enum StringFilter {
2727
Equal(String),
2828
NotEqual(String),
2929
Like(String),
30+
In(Vec<String>),
3031
}
3132

3233
macro_rules! string_filter {
@@ -35,6 +36,7 @@ macro_rules! string_filter {
3536
StringFilter::Equal(value) => Box::new($dsl_field.eq(value).nullable()),
3637
StringFilter::NotEqual(value) => Box::new($dsl_field.ne(value).nullable()),
3738
StringFilter::Like(value) => Box::new($dsl_field.like(value).nullable()),
39+
StringFilter::In(value) => Box::new($dsl_field.eq_any(value).nullable()),
3840
}
3941
}};
4042
}

0 commit comments

Comments
 (0)