Repository: redis/redis-py

Issue:
Add dialect support for RediSearch queries

**Version**: What redis-py and what redis version is the issue happening on?
RediSearch>=2.4.3 

On RediSearch 2.4.3 we will introduce a new `DIALECT` command argument for queries (`FT.SEARCH`, `FT.AGGREGATE`) this will allow to process queries either by the current dialect (RediSearch 2.2.x) or the new dialect introduced on RediSearch 2.4.2.
New dialect features:
1. parameterized queries 
2. VSS
3. Improved syntax behavior as introduced at https://github.com/RediSearch/RediSearch/releases/tag/v2.4.2


Discussion:

Resolving patch:
diff --git a/redis/commands/search/query.py b/redis/commands/search/query.py
--- a/redis/commands/search/query.py
+++ b/redis/commands/search/query.py
@@ -35,6 +35,7 @@ def __init__(self, query_string):
         self._highlight_fields = []
         self._language = None
         self._expander = None
+        self._dialect = None
 
     def query_string(self):
         """Return the query string of this query only."""
@@ -202,6 +203,8 @@ def _get_args_tags(self):
             args += ["LANGUAGE", self._language]
         if self._expander:
             args += ["EXPANDER", self._expander]
+        if self._dialect:
+            args += ["DIALECT", self._dialect]
 
         return args
 
@@ -288,6 +291,15 @@ def expander(self, expander):
         self._expander = expander
         return self
 
+    def dialect(self, dialect: int) -> "Query":
+        """
+        Add a dialect field to the query.
+
+        - **dialect** - dialect version to execute the query under
+        """
+        self._dialect = dialect
+        return self
+
 
 class Filter:
     def __init__(self, keyword, field, *args):


Test patch:
diff --git a/tests/test_search.py b/tests/test_search.py
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -1631,3 +1631,48 @@ def test_search_commands_in_pipeline(client):
     assert "foo baz" == res[3][2]
     assert res[3][5] is None
     assert res[3][3] == res[3][6] == ["txt", "foo bar"]
+
+
+@pytest.mark.redismod
+@pytest.mark.onlynoncluster
+@skip_ifmodversion_lt("2.4.3", "search")
+def test_dialect_config(modclient: redis.Redis):
+    assert modclient.ft().config_get("DEFAULT_DIALECT") == {"DEFAULT_DIALECT": "1"}
+    assert modclient.ft().config_set("DEFAULT_DIALECT", 2)
+    assert modclient.ft().config_get("DEFAULT_DIALECT") == {"DEFAULT_DIALECT": "2"}
+    with pytest.raises(redis.ResponseError):
+        modclient.ft().config_set("DEFAULT_DIALECT", 0)
+
+
+@pytest.mark.redismod
+@skip_ifmodversion_lt("2.4.3", "search")
+def test_dialect(modclient: redis.Redis):
+    modclient.ft().create_index(
+        (
+            TagField("title"),
+            TextField("t1"),
+            TextField("t2"),
+            NumericField("num"),
+            VectorField(
+                "v", "HNSW", {"TYPE": "FLOAT32", "DIM": 1, "DISTANCE_METRIC": "COSINE"}
+            ),
+        )
+    )
+    modclient.hset("h", "t1", "hello")
+    with pytest.raises(redis.ResponseError) as err:
+        modclient.ft().explain(Query("(*)").dialect(1))
+    assert "Syntax error" in str(err)
+    assert "WILDCARD" in modclient.ft().explain(Query("(*)").dialect(2))
+
+    with pytest.raises(redis.ResponseError) as err:
+        modclient.ft().explain(Query("$hello").dialect(1))
+    assert "Syntax error" in str(err)
+    q = Query("$hello").dialect(2)
+    expected = "UNION {\n  hello\n  +hello(expanded)\n}\n"
+    assert expected in modclient.ft().explain(q, query_params={"hello": "hello"})
+
+    expected = "NUMERIC {0.000000 <= @num <= 10.000000}\n"
+    assert expected in modclient.ft().explain(Query("@title:(@num:[0 10])").dialect(1))
+    with pytest.raises(redis.ResponseError) as err:
+        modclient.ft().explain(Query("@title:(@num:[0 10])").dialect(2))
+    assert "Syntax error" in str(err)