Skip to content

Commit fd840a1

Browse files
authored
Adding query cache management (#36)
1 parent f6c1a34 commit fd840a1

File tree

8 files changed

+424
-2
lines changed

8 files changed

+424
-2
lines changed

arangoasync/aql.py

+193-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
__all__ = ["AQL"]
1+
__all__ = ["AQL", "AQLQueryCache"]
22

33

44
from typing import Optional
55

66
from arangoasync.cursor import Cursor
77
from arangoasync.errno import HTTP_NOT_FOUND
88
from arangoasync.exceptions import (
9+
AQLCacheClearError,
10+
AQLCacheConfigureError,
11+
AQLCacheEntriesError,
12+
AQLCachePropertiesError,
913
AQLQueryClearError,
1014
AQLQueryExecuteError,
1115
AQLQueryExplainError,
@@ -23,13 +27,196 @@
2327
from arangoasync.typings import (
2428
Json,
2529
Jsons,
30+
QueryCacheProperties,
2631
QueryExplainOptions,
2732
QueryProperties,
2833
QueryTrackingConfiguration,
2934
Result,
3035
)
3136

3237

38+
class AQLQueryCache:
39+
"""AQL Query Cache API wrapper.
40+
41+
Args:
42+
executor: API executor. Required to execute the API requests.
43+
"""
44+
45+
def __init__(self, executor: ApiExecutor) -> None:
46+
self._executor = executor
47+
48+
@property
49+
def name(self) -> str:
50+
"""Return the name of the current database."""
51+
return self._executor.db_name
52+
53+
@property
54+
def serializer(self) -> Serializer[Json]:
55+
"""Return the serializer."""
56+
return self._executor.serializer
57+
58+
@property
59+
def deserializer(self) -> Deserializer[Json, Jsons]:
60+
"""Return the deserializer."""
61+
return self._executor.deserializer
62+
63+
def __repr__(self) -> str:
64+
return f"<AQLQueryCache in {self.name}>"
65+
66+
async def entries(self) -> Result[Jsons]:
67+
"""Return a list of all AQL query results cache entries.
68+
69+
70+
Returns:
71+
list: List of AQL query results cache entries.
72+
73+
Raises:
74+
AQLCacheEntriesError: If retrieval fails.
75+
76+
References:
77+
- `list-the-entries-of-the-aql-query-results-cache <https://docs.arangodb.com/stable/develop/http-api/queries/aql-query-results-cache/#list-the-entries-of-the-aql-query-results-cache>`__
78+
""" # noqa: E501
79+
request = Request(method=Method.GET, endpoint="/_api/query-cache/entries")
80+
81+
def response_handler(resp: Response) -> Jsons:
82+
if not resp.is_success:
83+
raise AQLCacheEntriesError(resp, request)
84+
return self.deserializer.loads_many(resp.raw_body)
85+
86+
return await self._executor.execute(request, response_handler)
87+
88+
async def plan_entries(self) -> Result[Jsons]:
89+
"""Return a list of all AQL query plan cache entries.
90+
91+
Returns:
92+
list: List of AQL query plan cache entries.
93+
94+
Raises:
95+
AQLCacheEntriesError: If retrieval fails.
96+
97+
References:
98+
- `list-the-entries-of-the-aql-query-plan-cache <https://docs.arangodb.com/stable/develop/http-api/queries/aql-query-plan-cache/#list-the-entries-of-the-aql-query-plan-cache>`__
99+
""" # noqa: E501
100+
request = Request(method=Method.GET, endpoint="/_api/query-plan-cache")
101+
102+
def response_handler(resp: Response) -> Jsons:
103+
if not resp.is_success:
104+
raise AQLCacheEntriesError(resp, request)
105+
return self.deserializer.loads_many(resp.raw_body)
106+
107+
return await self._executor.execute(request, response_handler)
108+
109+
async def clear(self) -> Result[None]:
110+
"""Clear the AQL query results cache.
111+
112+
Raises:
113+
AQLCacheClearError: If clearing the cache fails.
114+
115+
References:
116+
- `clear-the-aql-query-results-cache <https://docs.arangodb.com/stable/develop/http-api/queries/aql-query-results-cache/#clear-the-aql-query-results-cache>`__
117+
""" # noqa: E501
118+
request = Request(method=Method.DELETE, endpoint="/_api/query-cache")
119+
120+
def response_handler(resp: Response) -> None:
121+
if not resp.is_success:
122+
raise AQLCacheClearError(resp, request)
123+
124+
return await self._executor.execute(request, response_handler)
125+
126+
async def clear_plan(self) -> Result[None]:
127+
"""Clear the AQL query plan cache.
128+
129+
Raises:
130+
AQLCacheClearError: If clearing the cache fails.
131+
132+
References:
133+
- `clear-the-aql-query-plan-cache <https://docs.arangodb.com/stable/develop/http-api/queries/aql-query-plan-cache/#clear-the-aql-query-plan-cache>`__
134+
""" # noqa: E501
135+
request = Request(method=Method.DELETE, endpoint="/_api/query-plan-cache")
136+
137+
def response_handler(resp: Response) -> None:
138+
if not resp.is_success:
139+
raise AQLCacheClearError(resp, request)
140+
141+
return await self._executor.execute(request, response_handler)
142+
143+
async def properties(self) -> Result[QueryCacheProperties]:
144+
"""Return the current AQL query results cache configuration.
145+
146+
Returns:
147+
QueryCacheProperties: Current AQL query cache properties.
148+
149+
Raises:
150+
AQLCachePropertiesError: If retrieval fails.
151+
152+
References:
153+
- `get-the-aql-query-results-cache-configuration <https://docs.arangodb.com/stable/develop/http-api/queries/aql-query-results-cache/#get-the-aql-query-results-cache-configuration>`__
154+
""" # noqa: E501
155+
request = Request(method=Method.GET, endpoint="/_api/query-cache/properties")
156+
157+
def response_handler(resp: Response) -> QueryCacheProperties:
158+
if not resp.is_success:
159+
raise AQLCachePropertiesError(resp, request)
160+
return QueryCacheProperties(self.deserializer.loads(resp.raw_body))
161+
162+
return await self._executor.execute(request, response_handler)
163+
164+
async def configure(
165+
self,
166+
mode: Optional[str] = None,
167+
max_results: Optional[int] = None,
168+
max_results_size: Optional[int] = None,
169+
max_entry_size: Optional[int] = None,
170+
include_system: Optional[bool] = None,
171+
) -> Result[QueryCacheProperties]:
172+
"""Configure the AQL query results cache.
173+
174+
Args:
175+
mode (str | None): Cache mode. Allowed values are `"off"`, `"on"`,
176+
and `"demand"`.
177+
max_results (int | None): Max number of query results stored per
178+
database-specific cache.
179+
max_results_size (int | None): Max cumulative size of query results stored
180+
per database-specific cache.
181+
max_entry_size (int | None): Max entry size of each query result stored per
182+
database-specific cache.
183+
include_system (bool | None): Store results of queries in system collections.
184+
185+
Returns:
186+
QueryCacheProperties: Updated AQL query cache properties.
187+
188+
Raises:
189+
AQLCacheConfigureError: If setting the configuration fails.
190+
191+
References:
192+
- `set-the-aql-query-results-cache-configuration <https://docs.arangodb.com/stable/develop/http-api/queries/aql-query-results-cache/#set-the-aql-query-results-cache-configuration>`__
193+
""" # noqa: E501
194+
data: Json = dict()
195+
if mode is not None:
196+
data["mode"] = mode
197+
if max_results is not None:
198+
data["maxResults"] = max_results
199+
if max_results_size is not None:
200+
data["maxResultsSize"] = max_results_size
201+
if max_entry_size is not None:
202+
data["maxEntrySize"] = max_entry_size
203+
if include_system is not None:
204+
data["includeSystem"] = include_system
205+
206+
request = Request(
207+
method=Method.PUT,
208+
endpoint="/_api/query-cache/properties",
209+
data=self.serializer.dumps(data),
210+
)
211+
212+
def response_handler(resp: Response) -> QueryCacheProperties:
213+
if not resp.is_success:
214+
raise AQLCacheConfigureError(resp, request)
215+
return QueryCacheProperties(self.deserializer.loads(resp.raw_body))
216+
217+
return await self._executor.execute(request, response_handler)
218+
219+
33220
class AQL:
34221
"""AQL (ArangoDB Query Language) API wrapper.
35222
@@ -58,6 +245,11 @@ def deserializer(self) -> Deserializer[Json, Jsons]:
58245
"""Return the deserializer."""
59246
return self._executor.deserializer
60247

248+
@property
249+
def cache(self) -> AQLQueryCache:
250+
"""Return the AQL Query Cache API wrapper."""
251+
return AQLQueryCache(self._executor)
252+
61253
def __repr__(self) -> str:
62254
return f"<AQL in {self.name}>"
63255

arangoasync/database.py

+27
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
PermissionResetError,
2828
PermissionUpdateError,
2929
ServerStatusError,
30+
ServerVersionError,
3031
TransactionAbortError,
3132
TransactionCommitError,
3233
TransactionExecuteError,
@@ -1189,6 +1190,32 @@ def response_handler(resp: Response) -> Any:
11891190

11901191
return await self._executor.execute(request, response_handler)
11911192

1193+
async def version(self, details: bool = False) -> Result[Json]:
1194+
"""Return the server version information.
1195+
1196+
Args:
1197+
details (bool): If `True`, return detailed version information.
1198+
1199+
Returns:
1200+
dict: Server version information.
1201+
1202+
Raises:
1203+
ServerVersionError: If the operation fails on the server side.
1204+
1205+
References:
1206+
- `get-the-server-version <https://docs.arangodb.com/stable/develop/http-api/administration/#get-the-server-version>`__
1207+
""" # noqa: E501
1208+
request = Request(
1209+
method=Method.GET, endpoint="/_api/version", params={"details": details}
1210+
)
1211+
1212+
def response_handler(resp: Response) -> Json:
1213+
if not resp.is_success:
1214+
raise ServerVersionError(resp, request)
1215+
return self.deserializer.loads(resp.raw_body)
1216+
1217+
return await self._executor.execute(request, response_handler)
1218+
11921219

11931220
class StandardDatabase(Database):
11941221
"""Standard database API wrapper.

arangoasync/exceptions.py

+20
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ def __init__(
7171
self.http_headers = resp.headers
7272

7373

74+
class AQLCacheClearError(ArangoServerError):
75+
"""Failed to clear the query cache."""
76+
77+
78+
class AQLCacheConfigureError(ArangoServerError):
79+
"""Failed to configure query cache properties."""
80+
81+
82+
class AQLCacheEntriesError(ArangoServerError):
83+
"""Failed to retrieve AQL cache entries."""
84+
85+
86+
class AQLCachePropertiesError(ArangoServerError):
87+
"""Failed to retrieve query cache properties."""
88+
89+
7490
class AQLQueryClearError(ArangoServerError):
7591
"""Failed to clear slow AQL queries."""
7692

@@ -251,6 +267,10 @@ class ServerStatusError(ArangoServerError):
251267
"""Failed to retrieve server status."""
252268

253269

270+
class ServerVersionError(ArangoServerError):
271+
"""Failed to retrieve server version."""
272+
273+
254274
class TransactionAbortError(ArangoServerError):
255275
"""Failed to abort transaction."""
256276

arangoasync/typings.py

+53
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,9 @@ class QueryProperties(JsonWrapper):
10961096
store intermediate and final results temporarily on disk if the number
10971097
of rows produced by the query exceeds the specified value.
10981098
stream (bool | None): Can be enabled to execute the query lazily.
1099+
use_plan_cache (bool | None): Set this option to `True` to utilize
1100+
a cached query plan or add the execution plan of this query to the
1101+
cache if it’s not in the cache yet.
10991102
11001103
Example:
11011104
.. code-block:: json
@@ -1136,6 +1139,7 @@ def __init__(
11361139
spill_over_threshold_memory_usage: Optional[int] = None,
11371140
spill_over_threshold_num_rows: Optional[int] = None,
11381141
stream: Optional[bool] = None,
1142+
use_plan_cache: Optional[bool] = None,
11391143
) -> None:
11401144
data: Json = dict()
11411145
if allow_dirty_reads is not None:
@@ -1178,6 +1182,8 @@ def __init__(
11781182
data["spillOverThresholdNumRows"] = spill_over_threshold_num_rows
11791183
if stream is not None:
11801184
data["stream"] = stream
1185+
if use_plan_cache is not None:
1186+
data["usePlanCache"] = use_plan_cache
11811187
super().__init__(data)
11821188

11831189
@property
@@ -1260,6 +1266,10 @@ def spill_over_threshold_num_rows(self) -> Optional[int]:
12601266
def stream(self) -> Optional[bool]:
12611267
return self._data.get("stream")
12621268

1269+
@property
1270+
def use_plan_cache(self) -> Optional[bool]:
1271+
return self._data.get("usePlanCache")
1272+
12631273

12641274
class QueryExecutionPlan(JsonWrapper):
12651275
"""The execution plan of an AQL query.
@@ -1598,3 +1608,46 @@ def max_plans(self) -> Optional[int]:
15981608
@property
15991609
def optimizer(self) -> Optional[Json]:
16001610
return self._data.get("optimizer")
1611+
1612+
1613+
class QueryCacheProperties(JsonWrapper):
1614+
"""AQL Cache Configuration.
1615+
1616+
Example:
1617+
.. code-block:: json
1618+
1619+
{
1620+
"mode" : "demand",
1621+
"maxResults" : 128,
1622+
"maxResultsSize" : 268435456,
1623+
"maxEntrySize" : 16777216,
1624+
"includeSystem" : false
1625+
}
1626+
1627+
References:
1628+
- `get-the-aql-query-results-cache-configuration <https://docs.arangodb.com/stable/develop/http-api/queries/aql-query-results-cache/#get-the-aql-query-results-cache-configuration>`__
1629+
- `set-the-aql-query-results-cache-configuration <https://docs.arangodb.com/stable/develop/http-api/queries/aql-query-results-cache/#set-the-aql-query-results-cache-configuration>`__
1630+
""" # noqa: E501
1631+
1632+
def __init__(self, data: Json) -> None:
1633+
super().__init__(data)
1634+
1635+
@property
1636+
def mode(self) -> str:
1637+
return cast(str, self._data.get("mode", ""))
1638+
1639+
@property
1640+
def max_results(self) -> int:
1641+
return cast(int, self._data.get("maxResults", 0))
1642+
1643+
@property
1644+
def max_results_size(self) -> int:
1645+
return cast(int, self._data.get("maxResultsSize", 0))
1646+
1647+
@property
1648+
def max_entry_size(self) -> int:
1649+
return cast(int, self._data.get("maxEntrySize", 0))
1650+
1651+
@property
1652+
def include_system(self) -> bool:
1653+
return cast(bool, self._data.get("includeSystem", False))

0 commit comments

Comments
 (0)