Query dialects
Learn how to use query dialects
Redis Stack currently supports four query dialects for use with the FT.SEARCH
, FT.AGGREGATE
, and other Redis Query Engine commands.
Dialects provide for enhancing the query API incrementally, introducing innovative behaviors and new features that support new use cases in a way that does not break the API for existing applications.
DIALECT 1
Dialect version 1 was the default query syntax dialect from the first release of search and query until dialect version 2 was introduced with version 2.4. This dialect is also the default dialect. See below for information about changing the default dialect.
DIALECT 2
Dialect version 2 was introduced in the 2.4 release to address query parser inconsistencies found in previous versions of Redis Stack. Dialect version 1 remains the default dialect. To use dialect version 2, append DIALECT 2
to your query command.
Support for vector search also was introduced in the 2.4 release and requires DIALECT 2
. See here for more details.
FT.SEARCH ... DIALECT 2
It was determined that under certain conditions some query parsing rules did not behave as originally intended. Particularly, some queries containing the operators below could return unexpected results.
- AND, multi-word phrases that imply intersection
"..."
(exact),~
(optional),-
(negation), and%
(fuzzy)- OR, words separated by the
|
(pipe) character that imply union - wildcard characters
Existing queries that used dialect 1 may behave differently using dialect 2 if they fall into any of the following categories:
-
Your query has a field modifier followed by multiple words. Consider the sample query:
@name:James Brown
Here, the field modifier
@name
is followed by two words,James
andBrown
.In
DIALECT 1
, this query would be interpreted as findJames Brown
in the@name
field. InDIALECT 2
, this query would be interpreted as findJames
in the@name
field, andBrown
in any text field. In other words, it would be interpreted as(@name:James) Brown
. InDIALECT 2
, you could achieve the dialect 1 behavior by updating your query to@name:(James Brown)
. -
Your query uses
"..."
,~
,-
, and/or%
. Consider a simple query with negation:-hello world
In
DIALECT 1
, this query is interpreted as find values in any field that do not containhello
and do not containworld
; the equivalent of-(hello world)
or-hello -world
. InDIALECT 2
, this query is interpreted as-hello
andworld
(onlyhello
is negated). InDIALECT 2
, you could achieve the dialect 1 behavior by updating your query to-(hello world)
. -
Your query used
|
. Consider the simple query:hello world | "goodbye" moon
In
DIALECT 1
, this query is interpreted as searching for(hello world | "goodbye") moon
. InDIALECT 2
, this query is interpreted as searching for eitherhello world
"goodbye" moon
. -
Your query uses a wildcard pattern. Consider the simple query:
"w'foo*bar?'"
As shown above, you must use double quotes to contain the
w
pattern.
With DIALECT 2
you can use un-escaped spaces in tag queries, even with stopwords.
DIALECT 2
is required with vector searches.DIALECT 2
functionality was enhanced in the 2.10 release.
It introduces support for new comparison operators for NUMERIC
fields:
-
==
(equal).FT.SEARCH idx "@numeric==3456" DIALECT 2
and
FT.SEARCH idx "@numeric:[3456]" DIALECT 2
-
!=
(not equal).FT.SEARCH idx "@numeric!=3456" DIALECT 2
-
>
(greater than).FT.SEARCH idx "@numeric>3456" DIALECT 2
-
>=
(greater than or equal).FT.SEARCH idx "@numeric>=3456" DIALECT 2
-
<
(less than).FT.SEARCH idx "@numeric<3456" DIALECT 2
-
<=
(less than or equal).FT.SEARCH idx "@numeric<=3456" DIALECT 2
The Dialect version 2 enhancements also introduce simplified syntax for logical operations:
-
|
(or).FT.SEARCH idx "@tag:{3d3586fe-0416-4572-8ce1 | 3d3586fe-0416-6758-4ri8}" DIALECT 2
which is equivalent to
FT.SEARCH idx "(@tag:{3d3586fe-0416-4572-8ce1} | @tag{3d3586fe-0416-6758-4ri8})" DIALECT 2
-
<space>
(and).FT.SEARCH idx "(@tag:{3d3586fe-0416-4572-8ce1} @tag{3d3586fe-0416-6758-4ri8})" DIALECT 2
-
-
(negation).FT.SEARCH idx "(@tag:{3d3586fe-0416-4572-8ce1} -@tag{3d3586fe-0416-6758-4ri8})" DIALECT 2
-
~
(optional/proximity).FT.SEARCH idx "(@tag:{3d3586fe-0416-4572-8ce1} ~@tag{3d3586fe-0416-6758-4ri8})" DIALECT 2
DIALECT 3
Dialect version 3 was introduced in the 2.6 release. This version introduced support for multi-value indexing and querying of attributes for any attribute type ( TEXT, TAG, NUMERIC, GEO and VECTOR) defined by a JSONPath leading to an array or multiple scalar values. Support for GEOSHAPE queries was also introduced in this dialect.
The primary difference between dialects version 2 and version 3 is that JSON is returned rather than scalars for multi-value attributes. Apart from specifying DIALECT 3
at the end of a FT.SEARCH
command, there are no other syntactic changes. Dialect version 1 remains the default dialect. To use dialect version 3, append DIALECT 3
to your query command.
FT.SEARCH ... DIALECT 3
Example
Sample JSON:
{
"id": 123,
"underlyings": [
{
"currency": "USD",
"spot": 99,
"underlier": "AAPL UW"
},
{
"currency": "USD",
"spot": 100,
"underlier": "NFLX UW"
}
]
}
Create an index:
FT.CREATE js_idx ON JSON PREFIX 1 js: SCHEMA $.underlyings[*].underlier AS und TAG
Now search, with and without DIALECT 3
.
-
With dialect 1 (default):
ft.search js_idx * return 1 und 1) (integer) 1 2) "js:1" 3) 1) "und" 2) "AAPL UW"
Only the first element of the expected two elements is returned.
-
With dialect 3:
ft.search js_idx * return 1 und DIALECT 3 1) (integer) 1 2) "js:1" 3) 1) "und" 2) "[\"AAPL UW\",\"NFLX UW\"]"
Both elements are returned.
POINT
or POLYGON
) geospatial queries.DIALECT 4
Dialect version 4 was introduced in the 2.8 release. It introduces performance optimizations for sorting operations on FT.SEARCH
and FT.AGGREGATE
. Apart from specifying DIALECT 4
at the end of a FT.SEARCH
command, there are no other syntactic changes. Dialect version 1 remains the default dialect. To use dialect version 4, append DIALECT 4
to your query command.
FT.SEARCH ... DIALECT 4
Dialect version 4 will improve performance in four different scenarios:
- Skip sorter - applied when there is no sorting to be done. The query can return once it reaches the
LIMIT
of requested results. - Partial range - applied when there is a
SORTBY
on a numeric field, either with no filter or with a filter by the same numeric field. Such queries will iterate on a range large enough to satisfy theLIMIT
of requested results. - Hybrid - applied when there is a
SORTBY
on a numeric field in addition to another non-numeric filter. It could be the case that some results will get filtered, leaving too small a range to satisfy any specifiedLIMIT
. In such cases, the iterator then is re-wound and additional iterations occur to collect result up to the requestedLIMIT
. - No optimization - If there is a sort by score or by a non-numeric field, there is no other option but to retrieve all results and compare their values to the search parameters.
Use FT.EXPLAINCLI
to compare dialects
The FT.EXPLAINCLI
command is a powerful tool that provides a window into the inner workings of your queries. It's like a roadmap that details your query's journey from start to finish.
When you run FT.EXPLAINCLI
, it returns an array representing the execution plan of a complex query. This plan is a step-by-step guide of how Redis interprets your query and how it plans to fetch results. It's a behind-the-scenes look at the process, giving you insights into how the search engine works.
The FT.EXPLAINCLI
accepts a DIALECT
argument, allowing you to execute the query using different dialect versions, allowing you to compare the resulting query plans.
To use FT.EXPLAINCLI
, you need to provide an index and a query predicate. The index is the name of the index you created using FT.CREATE
, and the query predicate is the same as if you were sending it to FT.SEARCH
or FT.AGGREGATE
.
Here's an example of how to use FT.EXPLAINCLI
to understand differences in dialect versions 1 and 2.
Negation of the intersection between tokens hello
and world
:
1) NOT {
2) INTERSECT {
3) hello
4) world
5) }
6) }
7)
Intersection of the negation of the token hello
together with token world
:
FT.EXPLAINCLI idx:dialects "-hello world" DIALECT 2
1) INTERSECT {
2) NOT {
3) hello
4) }
5) UNION {
6) world
7) +world(expanded)
8) }
9) }
10)
Same result as DIALECT 1
:
FT.EXPLAINCLI idx:dialects "-(hello world)" DIALECT 2
1) NOT {
2) INTERSECT {
3) hello
4) world
5) }
6) }
7)
FT.EXPLAIN
doesn't execute the query. It only explains the plan. It's a way to understand how your query is interpreted by the query engine, which can be invaluable when you're trying to optimize your searches.Change the default dialect
The default dialect is DIALECT 1
. If you wish to change that, you can do so by using the DEFAULT_DIALECT
parameter when loading the RediSearch module:
$ redis-server --loadmodule ./redisearch.so DEFAULT_DIALECT 2
You can also change the query dialect on an already running server using the FT.CONFIG
command:
FT.CONFIG SET DEFAULT_DIALECT 2