Skip to content

Commit

Permalink
FT.EXPLAIN and FT.EXPLAINCLI commands added (#2508)
Browse files Browse the repository at this point in the history
* FT.EXPLAIN and FT.EXPLAINCLI commands added

---------

Signed-off-by: Prateek Kumar <prateek.kumar@improving.com>
  • Loading branch information
prateek-kumar-improving authored Oct 24, 2024
1 parent b44368e commit 50786a3
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### Changes
* Python: FT.EXPLAIN and FT.EXPLAINCLI commands added([#2508](https://github.com/valkey-io/valkey-glide/pull/2508))
* Python: Python FT.INFO command added([#2429](https://github.com/valkey-io/valkey-glide/pull/2494))
* Python: Add FT.SEARCH command([#2470](https://github.com/valkey-io/valkey-glide/pull/2470))
* Python: Add commands FT.ALIASADD, FT.ALIASDEL, FT.ALIASUPDATE([#2471](https://github.com/valkey-io/valkey-glide/pull/2471))
Expand Down
46 changes: 46 additions & 0 deletions python/python/glide/async_commands/server_modules/ft.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,49 @@ async def info(client: TGlideClient, indexName: TEncodable) -> FtInfoResponse:
"""
args: List[TEncodable] = [CommandNames.FT_INFO, indexName]
return cast(FtInfoResponse, await client.custom_command(args))


async def explain(
client: TGlideClient, indexName: TEncodable, query: TEncodable
) -> TEncodable:
"""
Parse a query and return information about how that query was parsed.
Args:
client (TGlideClient): The client to execute the command.
indexName (TEncodable): The index name for which the query is written.
query (TEncodable): The search query, same as the query passed as an argument to FT.SEARCH.
Returns:
TEncodable: A string containing the parsed results representing the execution plan.
Examples:
>>> from glide import ft
>>> result = await ft.explain(glide_client, indexName="myIndex", query="@price:[0 10]")
b'Field {\n price\n 0\n 10\n}\n' # Parsed results.
"""
args: List[TEncodable] = [CommandNames.FT_EXPLAIN, indexName, query]
return cast(TEncodable, await client.custom_command(args))


async def explaincli(
client: TGlideClient, indexName: TEncodable, query: TEncodable
) -> List[TEncodable]:
"""
Same as the FT.EXPLAIN command except that the results are displayed in a different format. More useful with cli.
Args:
client (TGlideClient): The client to execute the command.
indexName (TEncodable): The index name for which the query is written.
query (TEncodable): The search query, same as the query passed as an argument to FT.SEARCH.
Returns:
List[TEncodable]: An array containing the execution plan.
Examples:
>>> from glide import ft
>>> result = await ft.explaincli(glide_client, indexName="myIndex", query="@price:[0 10]")
[b'Field {', b' price', b' 0', b' 10', b'}', b''] # Parsed results.
"""
args: List[TEncodable] = [CommandNames.FT_EXPLAINCLI, indexName, query]
return cast(List[TEncodable], await client.custom_command(args))
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class CommandNames:
FT_ALIASADD = "FT.ALIASADD"
FT_ALIASDEL = "FT.ALIASDEL"
FT_ALIASUPDATE = "FT.ALIASUPDATE"
FT_EXPLAIN = "FT.EXPLAIN"
FT_EXPLAINCLI = "FT.EXPLAINCLI"


class FtCreateKeywords:
Expand Down
107 changes: 105 additions & 2 deletions python/python/tests/tests_server_modules/test_ft.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DistanceMetricType,
Field,
FtCreateOptions,
NumericField,
TextField,
VectorAlgorithm,
VectorField,
Expand Down Expand Up @@ -107,10 +108,10 @@ async def _create_test_index_hash_type(
):
# Helper function used for creating a basic index with hash data type with one text field.
fields: List[Field] = []
text_field_title: TextField = TextField("$title")
text_field_title: TextField = TextField("title")
fields.append(text_field_title)

prefix = "{json-search-" + str(uuid.uuid4()) + "}:"
prefix = "{hash-search-" + str(uuid.uuid4()) + "}:"
prefixes: List[TEncodable] = []
prefixes.append(prefix)

Expand Down Expand Up @@ -198,3 +199,105 @@ async def _create_test_index_with_vector_field(
schema=fields,
options=FtCreateOptions(DataType.JSON, prefixes=prefixes),
)

@pytest.mark.parametrize("cluster_mode", [True])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_ft_explain(self, glide_client: GlideClusterClient):
indexName = str(uuid.uuid4())
await TestFt._create_test_index_for_ft_explain_commands(
self=self, glide_client=glide_client, index_name=indexName
)

# FT.EXPLAIN on a search query containing numeric field.
query = "@price:[0 10]"
result = await ft.explain(glide_client, indexName=indexName, query=query)
resultString = cast(bytes, result).decode(encoding="utf-8")
assert "price" in resultString and "0" in resultString and "10" in resultString

# FT.EXPLAIN on a search query containing numeric field and having bytes type input to the command.
result = await ft.explain(
glide_client, indexName=indexName.encode(), query=query.encode()
)
resultString = cast(bytes, result).decode(encoding="utf-8")
assert "price" in resultString and "0" in resultString and "10" in resultString

# FT.EXPLAIN on a search query that returns all data.
result = await ft.explain(glide_client, indexName=indexName, query="*")
resultString = cast(bytes, result).decode(encoding="utf-8")
assert "*" in resultString

assert await ft.dropindex(glide_client, indexName=indexName)

# FT.EXPLAIN on a missing index throws an error.
with pytest.raises(RequestError):
await ft.explain(glide_client, str(uuid.uuid4()), "*")

@pytest.mark.parametrize("cluster_mode", [True])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_ft_explaincli(self, glide_client: GlideClusterClient):
indexName = str(uuid.uuid4())
await TestFt._create_test_index_for_ft_explain_commands(
self=self, glide_client=glide_client, index_name=indexName
)

# FT.EXPLAINCLI on a search query containing numeric field.
query = "@price:[0 10]"
result = await ft.explaincli(glide_client, indexName=indexName, query=query)
resultStringArr = []
for i in result:
resultStringArr.append(cast(bytes, i).decode(encoding="utf-8").strip())
assert (
"price" in resultStringArr
and "0" in resultStringArr
and "10" in resultStringArr
)

# FT.EXPLAINCLI on a search query containing numeric field and having bytes type input to the command.
result = await ft.explaincli(
glide_client, indexName=indexName.encode(), query=query.encode()
)
resultStringArr = []
for i in result:
resultStringArr.append(cast(bytes, i).decode(encoding="utf-8").strip())
assert (
"price" in resultStringArr
and "0" in resultStringArr
and "10" in resultStringArr
)

# FT.EXPLAINCLI on a search query that returns all data.
result = await ft.explaincli(glide_client, indexName=indexName, query="*")
resultStringArr = []
for i in result:
resultStringArr.append(cast(bytes, i).decode(encoding="utf-8").strip())
assert "*" in resultStringArr

assert await ft.dropindex(glide_client, indexName=indexName)

# FT.EXPLAINCLI on a missing index throws an error.
with pytest.raises(RequestError):
await ft.explaincli(glide_client, str(uuid.uuid4()), "*")

async def _create_test_index_for_ft_explain_commands(
self, glide_client: GlideClusterClient, index_name: TEncodable
):
# Helper function used for creating an index having hash data type, one text field and one numeric field.
fields: List[Field] = []
numeric_field: NumericField = NumericField("price")
text_field: TextField = TextField("title")
fields.append(text_field)
fields.append(numeric_field)

prefix = "{hash-search-" + str(uuid.uuid4()) + "}:"
prefixes: List[TEncodable] = []
prefixes.append(prefix)

assert (
await ft.create(
glide_client,
index_name,
fields,
FtCreateOptions(DataType.HASH, prefixes),
)
== OK
)

0 comments on commit 50786a3

Please sign in to comment.