Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions graph/src/components/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,17 @@ pub enum EntityFilter {
In(Attribute, Vec<Value>),
NotIn(Attribute, Vec<Value>),
Contains(Attribute, Value),
ContainsNoCase(Attribute, Value),
NotContains(Attribute, Value),
NotContainsNoCase(Attribute, Value),
StartsWith(Attribute, Value),
StartsWithNoCase(Attribute, Value),
NotStartsWith(Attribute, Value),
NotStartsWithNoCase(Attribute, Value),
EndsWith(Attribute, Value),
EndsWithNoCase(Attribute, Value),
NotEndsWith(Attribute, Value),
NotEndsWithNoCase(Attribute, Value),
}

// Define some convenience methods
Expand Down
53 changes: 41 additions & 12 deletions graphql/src/schema/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,17 @@ fn field_scalar_filter_input_values(
"in",
"not_in",
"contains",
"contains_nocase",
"not_contains",
"not_contains_nocase",
"starts_with",
"starts_with_nocase",
"not_starts_with",
"not_starts_with_nocase",
"ends_with",
"ends_with_nocase",
"not_ends_with",
"not_ends_with_nocase",
],
_ => vec!["", "not"],
}
Expand Down Expand Up @@ -341,18 +347,25 @@ fn field_list_filter_input_values(
};

Some(
vec!["", "not", "contains", "not_contains"]
.into_iter()
.map(|filter_type| {
input_value(
&field.name,
filter_type,
Type::ListType(Box::new(Type::NonNullType(Box::new(
input_field_type.clone(),
)))),
)
})
.collect(),
vec![
"",
"not",
"contains",
"contains_nocase",
"not_contains",
"not_contains_nocase",
]
.into_iter()
.map(|filter_type| {
input_value(
&field.name,
filter_type,
Type::ListType(Box::new(Type::NonNullType(Box::new(
input_field_type.clone(),
)))),
)
})
.collect(),
)
})
}
Expand Down Expand Up @@ -864,19 +877,29 @@ mod tests {
"name_in",
"name_not_in",
"name_contains",
"name_contains_nocase",
"name_not_contains",
"name_not_contains_nocase",
"name_starts_with",
"name_starts_with_nocase",
"name_not_starts_with",
"name_not_starts_with_nocase",
"name_ends_with",
"name_ends_with_nocase",
"name_not_ends_with",
"name_not_ends_with_nocase",
"favoritePetNames",
"favoritePetNames_not",
"favoritePetNames_contains",
"favoritePetNames_contains_nocase",
"favoritePetNames_not_contains",
"favoritePetNames_not_contains_nocase",
"pets",
"pets_not",
"pets_contains",
"pets_contains_nocase",
"pets_not_contains",
"pets_not_contains_nocase",
"favoriteFurType",
"favoriteFurType_not",
"favoriteFurType_in",
Expand All @@ -890,11 +913,17 @@ mod tests {
"favoritePet_in",
"favoritePet_not_in",
"favoritePet_contains",
"favoritePet_contains_nocase",
"favoritePet_not_contains",
"favoritePet_not_contains_nocase",
"favoritePet_starts_with",
"favoritePet_starts_with_nocase",
"favoritePet_not_starts_with",
"favoritePet_not_starts_with_nocase",
"favoritePet_ends_with",
"favoritePet_ends_with_nocase",
"favoritePet_not_ends_with",
"favoritePet_not_ends_with_nocase",
]
.iter()
.map(ToString::to_string)
Expand Down
20 changes: 20 additions & 0 deletions graphql/src/schema/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ pub(crate) enum FilterOp {
In,
NotIn,
Contains,
ContainsNoCase,
NotContains,
NotContainsNoCase,
StartsWith,
StartsWithNoCase,
NotStartsWith,
NotStartsWithNoCase,
EndsWith,
EndsWithNoCase,
NotEndsWith,
NotEndsWithNoCase,
Equal,
}

Expand All @@ -40,11 +46,25 @@ pub(crate) fn parse_field_as_filter(key: &str) -> (String, FilterOp) {
k if k.ends_with("_not_in") => ("_not_in", FilterOp::NotIn),
k if k.ends_with("_in") => ("_in", FilterOp::In),
k if k.ends_with("_not_contains") => ("_not_contains", FilterOp::NotContains),
k if k.ends_with("_not_contains_nocase") => {
("_not_contains_nocase", FilterOp::NotContainsNoCase)
}
k if k.ends_with("_contains") => ("_contains", FilterOp::Contains),
k if k.ends_with("_contains_nocase") => ("_contains_nocase", FilterOp::ContainsNoCase),
k if k.ends_with("_not_starts_with") => ("_not_starts_with", FilterOp::NotStartsWith),
k if k.ends_with("_not_starts_with_nocase") => {
("_not_starts_with_nocase", FilterOp::NotStartsWithNoCase)
}
k if k.ends_with("_not_ends_with") => ("_not_ends_with", FilterOp::NotEndsWith),
k if k.ends_with("_not_ends_with_nocase") => {
("_not_ends_with_nocase", FilterOp::NotEndsWithNoCase)
}
k if k.ends_with("_starts_with") => ("_starts_with", FilterOp::StartsWith),
k if k.ends_with("_starts_with_nocase") => {
("_starts_with_nocase", FilterOp::StartsWithNoCase)
}
k if k.ends_with("_ends_with") => ("_ends_with", FilterOp::EndsWith),
k if k.ends_with("_ends_with_nocase") => ("_ends_with_nocase", FilterOp::EndsWithNoCase),
_ => ("", FilterOp::Equal),
};

Expand Down
8 changes: 8 additions & 0 deletions graphql/src/store/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,19 @@ fn build_filter_from_object(
In => EntityFilter::In(field_name, list_values(store_value, "_in")?),
NotIn => EntityFilter::NotIn(field_name, list_values(store_value, "_not_in")?),
Contains => EntityFilter::Contains(field_name, store_value),
ContainsNoCase => EntityFilter::ContainsNoCase(field_name, store_value),
NotContains => EntityFilter::NotContains(field_name, store_value),
NotContainsNoCase => EntityFilter::NotContainsNoCase(field_name, store_value),
StartsWith => EntityFilter::StartsWith(field_name, store_value),
StartsWithNoCase => EntityFilter::StartsWithNoCase(field_name, store_value),
NotStartsWith => EntityFilter::NotStartsWith(field_name, store_value),
NotStartsWithNoCase => {
EntityFilter::NotStartsWithNoCase(field_name, store_value)
}
EndsWith => EntityFilter::EndsWith(field_name, store_value),
EndsWithNoCase => EntityFilter::EndsWithNoCase(field_name, store_value),
NotEndsWith => EntityFilter::NotEndsWith(field_name, store_value),
NotEndsWithNoCase => EntityFilter::NotEndsWithNoCase(field_name, store_value),
Equal => EntityFilter::Equal(field_name, store_value),
})
})
Expand Down
40 changes: 31 additions & 9 deletions store/postgres/src/relational_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,9 @@ impl<'a> QueryFilter<'a> {
}

Contains(attr, _)
| ContainsNoCase(attr, _)
| NotContains(attr, _)
| NotContainsNoCase(attr, _)
| Equal(attr, _)
| Not(attr, _)
| GreaterThan(attr, _)
Expand All @@ -840,9 +842,13 @@ impl<'a> QueryFilter<'a> {
| In(attr, _)
| NotIn(attr, _)
| StartsWith(attr, _)
| StartsWithNoCase(attr, _)
| NotStartsWith(attr, _)
| NotStartsWithNoCase(attr, _)
| EndsWith(attr, _)
| NotEndsWith(attr, _) => {
| EndsWithNoCase(attr, _)
| NotEndsWith(attr, _)
| NotEndsWithNoCase(attr, _) => {
table.column_for_field(attr)?;
}
}
Expand Down Expand Up @@ -889,18 +895,20 @@ impl<'a> QueryFilter<'a> {
attribute: &Attribute,
value: &Value,
negated: bool,
strict: bool,
mut out: AstPass<Pg>,
) -> QueryResult<()> {
let column = self.column(attribute);

let operation = match (strict, negated) {
(true, true) => " not like ",
(true, false) => " like ",
(false, true) => " not ilike ",
(false, false) => " ilike ",
};
match value {
Value::String(s) => {
out.push_identifier(column.name.as_str())?;
if negated {
out.push_sql(" not like ");
} else {
out.push_sql(" like ")
};
out.push_sql(operation);
Comment on lines -895 to +911
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

if s.starts_with('%') || s.ends_with('%') {
out.push_bind_param::<Text, _>(s)?;
} else {
Expand Down Expand Up @@ -1158,8 +1166,10 @@ impl<'a> QueryFragment<Pg> for QueryFilter<'a> {
And(filters) => self.binary_op(filters, " and ", " true ", out)?,
Or(filters) => self.binary_op(filters, " or ", " false ", out)?,

Contains(attr, value) => self.contains(attr, value, false, out)?,
NotContains(attr, value) => self.contains(attr, value, true, out)?,
Contains(attr, value) => self.contains(attr, value, false, true, out)?,
ContainsNoCase(attr, value) => self.contains(attr, value, false, false, out)?,
NotContains(attr, value) => self.contains(attr, value, true, true, out)?,
NotContainsNoCase(attr, value) => self.contains(attr, value, true, false, out)?,

Equal(attr, value) => self.equals(attr, value, c::Equal, out)?,
Not(attr, value) => self.equals(attr, value, c::NotEqual, out)?,
Expand All @@ -1175,13 +1185,25 @@ impl<'a> QueryFragment<Pg> for QueryFilter<'a> {
StartsWith(attr, value) => {
self.starts_or_ends_with(attr, value, " like ", true, out)?
}
StartsWithNoCase(attr, value) => {
self.starts_or_ends_with(attr, value, " ilike ", true, out)?
}
NotStartsWith(attr, value) => {
self.starts_or_ends_with(attr, value, " not like ", true, out)?
}
NotStartsWithNoCase(attr, value) => {
self.starts_or_ends_with(attr, value, " not ilike ", true, out)?
}
EndsWith(attr, value) => self.starts_or_ends_with(attr, value, " like ", false, out)?,
EndsWithNoCase(attr, value) => {
self.starts_or_ends_with(attr, value, " ilike ", false, out)?
}
NotEndsWith(attr, value) => {
self.starts_or_ends_with(attr, value, " not like ", false, out)?
}
NotEndsWithNoCase(attr, value) => {
self.starts_or_ends_with(attr, value, " not ilike ", false, out)?
}
}
Ok(())
}
Expand Down