Skip to content

Commit 0905be8

Browse files
committed
Adding usePlanCache option for AQL queries.
1 parent a9e278e commit 0905be8

File tree

2 files changed

+119
-2
lines changed

2 files changed

+119
-2
lines changed

arango/aql.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,36 @@ def response_handler(resp: Response) -> bool:
144144

145145
return self._execute(request, response_handler)
146146

147+
def plan_entries(self) -> Result[Jsons]:
148+
"""Return a list of all AQL query plan cache entries.
149+
150+
:return: List of AQL query plan cache entries.
151+
:rtype: list
152+
:raise arango.exceptions.AQLCacheEntriesError: If retrieval fails.
153+
"""
154+
request = Request(method="get", endpoint="/_api/query-plan-cache")
155+
156+
def response_handler(resp: Response) -> Jsons:
157+
if not resp.is_success:
158+
raise AQLCacheEntriesError(resp, request)
159+
result: Jsons = resp.body
160+
return result
161+
162+
return self._execute(request, response_handler)
163+
164+
def clear_plan(self) -> Result[None]:
165+
"""Clear the AQL query plan cache.
166+
167+
:raises arango.exceptions.AQLCacheClearError: If clearing the cache fails.
168+
"""
169+
request = Request(method="delete", endpoint="/_api/query-plan-cache")
170+
171+
def response_handler(resp: Response) -> None:
172+
if not resp.is_success:
173+
raise AQLCacheClearError(resp, request)
174+
175+
return self._execute(request, response_handler)
176+
147177

148178
class AQL(ApiGroup):
149179
"""AQL (ArangoDB Query Language) API wrapper.
@@ -277,6 +307,7 @@ def execute(
277307
allow_dirty_read: bool = False,
278308
allow_retry: bool = False,
279309
force_one_shard_attribute_value: Optional[str] = None,
310+
use_plan_cache: Optional[bool] = None,
280311
) -> Result[Cursor]:
281312
"""Execute the query and return the result cursor.
282313
@@ -388,6 +419,8 @@ def execute(
388419
shipped to a wrong DB server and may not return results
389420
(i.e. empty result set). Use at your own risk.
390421
:param force_one_shard_attribute_value: str | None
422+
:param use_plan_cache: If set to True, the query plan cache is used.
423+
:param use_plan_cache: bool | None
391424
:return: Result cursor.
392425
:rtype: arango.cursor.Cursor
393426
:raise arango.exceptions.AQLQueryExecuteError: If execute fails.
@@ -399,8 +432,6 @@ def execute(
399432
data["ttl"] = ttl
400433
if bind_vars is not None:
401434
data["bindVars"] = bind_vars
402-
if cache is not None:
403-
data["cache"] = cache
404435
if memory_limit is not None:
405436
data["memoryLimit"] = memory_limit
406437

@@ -437,6 +468,10 @@ def execute(
437468
options["allowRetry"] = allow_retry
438469
if force_one_shard_attribute_value is not None:
439470
options["forceOneShardAttributeValue"] = force_one_shard_attribute_value
471+
if cache is not None:
472+
options["cache"] = cache
473+
if use_plan_cache is not None:
474+
options["usePlanCache"] = use_plan_cache
440475

441476
if options:
442477
data["options"] = options

tests/test_aql.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import pytest
12
from packaging import version
23

4+
from arango.errno import FORBIDDEN
35
from arango.exceptions import (
46
AQLCacheClearError,
57
AQLCacheConfigureError,
@@ -346,6 +348,86 @@ def test_aql_function_management(db, bad_db):
346348
assert db.aql.functions() == []
347349

348350

351+
def test_cache_results_management(db, bad_db, col, docs, cluster):
352+
if cluster:
353+
pytest.skip("Cluster mode does not support query result cache management")
354+
355+
aql = db.aql
356+
cache = aql.cache
357+
358+
# Sanity check, just see if the response is OK.
359+
_ = cache.properties()
360+
with pytest.raises(AQLCachePropertiesError) as err:
361+
_ = bad_db.aql.cache.properties()
362+
assert err.value.error_code == FORBIDDEN
363+
364+
# Turn on caching
365+
result = cache.configure(mode="on")
366+
assert result["mode"] == "on"
367+
result = cache.properties()
368+
assert result["mode"] == "on"
369+
with pytest.raises(AQLCacheConfigureError) as err:
370+
_ = bad_db.aql.cache.configure(mode="on")
371+
assert err.value.error_code == FORBIDDEN
372+
373+
# Run a simple query to use the cache
374+
col.insert(docs[0])
375+
_ = aql.execute(
376+
query="FOR doc IN @@collection RETURN doc",
377+
bind_vars={"@collection": col.name},
378+
cache=True,
379+
)
380+
381+
# Check the entries
382+
entries = cache.entries()
383+
assert isinstance(entries, list)
384+
assert len(entries) > 0
385+
386+
with pytest.raises(AQLCacheEntriesError) as err:
387+
_ = bad_db.aql.cache.entries()
388+
assert err.value.error_code == FORBIDDEN
389+
390+
# Clear the cache
391+
cache.clear()
392+
entries = cache.entries()
393+
assert len(entries) == 0
394+
with pytest.raises(AQLCacheClearError) as err:
395+
bad_db.aql.cache.clear()
396+
assert err.value.error_code == FORBIDDEN
397+
398+
399+
def test_cache_plan_management(db, bad_db, col, docs, db_version):
400+
if db_version < version.parse("3.12.4"):
401+
pytest.skip("Query plan cache is supported in ArangoDB 3.12.4+")
402+
403+
aql = db.aql
404+
cache = aql.cache
405+
406+
# Run a simple query to use the cache
407+
col.insert(docs[0])
408+
_ = aql.execute(
409+
query="FOR doc IN @@collection RETURN doc",
410+
bind_vars={"@collection": col.name},
411+
use_plan_cache=True,
412+
)
413+
414+
# Check the entries
415+
entries = cache.plan_entries()
416+
assert isinstance(entries, list)
417+
assert len(entries) > 0
418+
with pytest.raises(AQLCacheEntriesError) as err:
419+
_ = bad_db.aql.cache.plan_entries()
420+
assert err.value.error_code == FORBIDDEN
421+
422+
# Clear the cache
423+
cache.clear_plan()
424+
entries = cache.plan_entries()
425+
assert len(entries) == 0
426+
with pytest.raises(AQLCacheClearError) as err:
427+
bad_db.aql.cache.clear_plan()
428+
assert err.value.error_code == FORBIDDEN
429+
430+
349431
def test_aql_cache_management(db, bad_db):
350432
# Test get AQL cache properties
351433
properties = db.aql.cache.properties()

0 commit comments

Comments
 (0)