SearchFilter#
The SearchFilter
allows you to filter efficiently a list of items with several
common SQL operators (such as =
, IN
, EXISTS
, LIKE
, etc) using
readable query parameter keys. You can filter on nested relations/properties with
complex conditions.
Decorator#
You can register a SearchFilter on an entity using the @Search
decorator.
You can directly pass an array of FilterProperty
, which is an array of property/nested
property path or a tuple of property/nested property path with a specific default StrategyType
. This
list contains the filterable properties
.
Each StrategyType
will allow you to filter a property through different conditions.
You can use any StrategyType
on a filterable property. Specifying one for a property path in the
FilterProperty
list just sets it as the default strategy to use for that property
path and allows you to omit it in query parameters.
Example of use with a property, a property path with another defaultWhereStrategy
and another global
defaultWhereStrategy
for that @Entity
:
In this example the firstName
property will default to STARTS_WITH
unless it is overriden by specifiying another
StrategyType
directly in the request, whereas the role.identifier
property will default
to CONTAINS
strategy, unless overriden directly in the request.
Property decorator#
TODO
Good to know#
Basic Usage#
Once you have enabled the properties you want to filter, you can use them as query parameters on list
operation for
that @Entity
.
On a User
entity, by enabling only the firstName
and role.identifier
properties :
With these query parameters :
On the request: GET:/users/?id=123&firstName=Alex&role=789&role.identifier=abc456
Only the firstName
& role.identifier
query parameters will be used as filtered since they alone were explicitly
specified on @Search filterable properties
.
firstName
strategy is STARTS_WITH
since it is the defaultWhereStrategy
given in options and role.identifier
strategy is CONTAINS
since it was provided in properties (in FilterProperty
tuple).
Strategies#
These strategies can be used both as defaultWhereStrategy
or directly in query parameter with the
advanced syntax.
StrategyType | Shortcut | SQL generated | Inverse SQL |
---|---|---|---|
EXACT | none | prop = value | != |
IS | none | prop IS value | IS NOT |
IN | none | prop IN value | NOT IN |
EXISTS | none | prop IS NOT NULL | IS NULL |
CONTAINS | none | prop LIKE %value% | NOT LIKE %value% |
STARTS_WITH | none | prop LIKE value% | NOT LIKE value% |
ENDS_WITH | none | prop LIKE %value | NOT LIKE %value |
BETWEEN | <> | prop IS BETWEEN value | IS NOT BETWEEN |
BETWEEN_STRICT | >< | (valueMin > prop AND prop < valueMax) | (valueMin <= prop AND prop >= valueMax) |
LESS_THAN | < | prop < value | >= |
LESS_THAN_OR_EQUAL | <| | prop <= value | prop > value |
GREATER_THAN | > | prop > value | <= |
GREATER_THAN_OR_EQUAL | >| | prop >= value | prop < value |
If you're wondering why LESS_THAN_OR_EQUAL
and GREATER_THAN_OR_EQUAL
use the |
instead of =
character, it's
because the =
is already used to define the limit between the query parameter key and value.
Multiple values#
If you need to filter on the same FilterProperty
and
StrategyType
but with different values, you can use comma-separated values as query param
value. Then each of these values will be trimmed and then wrapped in the same SQL condition with a OR
conditions for
each value.
BETWEEN_STRICT
is the only strategy that always requires exactly 2 values. Example: { "id><": "3, 8" }
or
{ "role.id;betweenStrict": "3, 8" }
Relations#
- You can filter a relation property by enabling a
FilterProperty
on its dot delimited path. Example:abc.def.ghi
- You can filter on a relation by either enabling as
FilterProperty
the relation itself or by appending.id
. Example:{ "role": 123 }
is the same as{ "role.id": 123 }
- There is no limit to the nesting, be careful with the SQL performance of every LEFT JOIN made if you nest too far
Advanced usage#
Since any StrategyType
is available for a filterable property
, sometimes you might want
to use another one than the default one. To do so you just need to use the strategy part of the advanced syntax
.
There is a syntax for query parameters keys that makes filters very powerful. There are 4 parts to a
FilterProperty
query parameter. Each part name is wrapped with parenthesis below (for
readability, there are no parenthesis in usage) :
(complex condition)(propertyPath)(;StrategyType|shortcut)(!)
complex condition
: Optional. The complex condition part might be used if you have advanced needs, it allows you to nest and group conditions with theand
|or
operators. More details here.propertyPath
: Using a dot delimited string, you can target a (shallow
|deeply nested
) (relation
|property
) to filter on.StrategyType|shortcut
: Optional. The StrategyType name (can be written camelCased or as UPPERCASE_SNAKE_CASE) prepended by a colon or just the shortcut (without the colon) if there is any available.!
: Optional. The NOT operator, by appending a!
to a query parameter key, the StrategyType will be inversed.
Quick example#
Below is an example which makes no actual sense whatsoever, still, it remains valid and would produce the following query.
With these query parameters :
A query would be produced looking like this :
Full example#
On the user.list
route scope (GET:/users/
): Using all: true
, you can filter on any property path from the User
entity. Here the default strategy used for every FilterProperty
is EXACT
since it's
the global default one and none were specified on the @Search decorator.
Filter description | Query parameter key | Query parameter value |
---|---|---|
Get all users with ids in list [1, 2, 3] | id | 1,2,3 |
Get all users with role equal to 111 | role or role.id | 111 |
Get all super_admin users | role.identifier | super_admin |
Get all admin users | role.identifier;endsWith or role.identifier;ENDS_WITH | _admin |
Complex conditions#
At some point, you might need to have more complex conditions to filter your list, requiring the SQL OR
operator, or
with maybe some nested conditions
. That is possible using the complex conditions
syntax.
All you need for that is to prefix your condition with the and
or or
condition operator. When you have nested
conditions you should append a condition identifier
wrapped in parenthesis after the condition operator
(and
|or
). That condition identifier
can be any string you want, as long as you re-use the same consistently for
the same conditions you want to nest.
You can chain them indefinitely and add as much nested conditions as you want. It is recommended to use names that makes sense to make the query parameter key almost readable like an english sentence.
Example
Using the same entities as the example above.
For these query parameters :
You would have that query generated :
Full example
To get all users with first name that starts with "Alex" that are either in the list [1, 2, 3] or that are admin and with an id above 50 but not super_admin unless his first name is "Max" or "Nath". (Yes this is a complete unrealistic demand, I just needed an example of a complex query.)
The query parameters would look like this :
Reminder: The complex condition names (isAdmin)
and (aboveId)
are completely arbitrary, you just need to use the
same string when you want to nest conditions.
And the SearchFilter
would generate a query like :
Default config#
You can get the default SearchFilter
using the
getSearchFilterDefaultConfig
function. It can be used for example to
retrieve the defaultWhereStrategy
inside the options
key.