Skip to content

Commit 66480a1

Browse files
committed
Introducing AQL query management operations
1 parent b102051 commit 66480a1

File tree

5 files changed

+683
-3
lines changed

5 files changed

+683
-3
lines changed

arangoasync/aql.py

+331-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,30 @@
44
from typing import Optional
55

66
from arangoasync.cursor import Cursor
7-
from arangoasync.exceptions import AQLQueryExecuteError
7+
from arangoasync.errno import HTTP_NOT_FOUND
8+
from arangoasync.exceptions import (
9+
AQLQueryClearError,
10+
AQLQueryExecuteError,
11+
AQLQueryExplainError,
12+
AQLQueryKillError,
13+
AQLQueryListError,
14+
AQLQueryRulesGetError,
15+
AQLQueryTrackingGetError,
16+
AQLQueryTrackingSetError,
17+
AQLQueryValidateError,
18+
)
819
from arangoasync.executor import ApiExecutor
920
from arangoasync.request import Method, Request
1021
from arangoasync.response import Response
1122
from arangoasync.serialization import Deserializer, Serializer
12-
from arangoasync.typings import Json, Jsons, QueryProperties, Result
23+
from arangoasync.typings import (
24+
Json,
25+
Jsons,
26+
QueryExplainOptions,
27+
QueryProperties,
28+
QueryTrackingConfiguration,
29+
Result,
30+
)
1331

1432

1533
class AQL:
@@ -75,6 +93,9 @@ async def execute(
7593
allow_dirty_read (bool | None): Allow reads from followers in a cluster.
7694
options (QueryProperties | dict | None): Extra options for the query.
7795
96+
Returns:
97+
Cursor: Result cursor.
98+
7899
References:
79100
- `create-a-cursor <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#create-a-cursor>`__
80101
""" # noqa: E501
@@ -113,3 +134,311 @@ def response_handler(resp: Response) -> Cursor:
113134
return Cursor(self._executor, self.deserializer.loads(resp.raw_body))
114135

115136
return await self._executor.execute(request, response_handler)
137+
138+
async def tracking(self) -> Result[QueryTrackingConfiguration]:
139+
"""Returns the current query tracking configuration.
140+
141+
Returns:
142+
QueryTrackingConfiguration: Returns the current query tracking configuration.
143+
144+
Raises:
145+
AQLQueryTrackingGetError: If retrieval fails.
146+
147+
References:
148+
- `get-the-aql-query-tracking-configuration <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#get-the-aql-query-tracking-configuration>`__
149+
""" # noqa: E501
150+
request = Request(method=Method.GET, endpoint="/_api/query/properties")
151+
152+
def response_handler(resp: Response) -> QueryTrackingConfiguration:
153+
if not resp.is_success:
154+
raise AQLQueryTrackingGetError(resp, request)
155+
return QueryTrackingConfiguration(self.deserializer.loads(resp.raw_body))
156+
157+
return await self._executor.execute(request, response_handler)
158+
159+
async def set_tracking(
160+
self,
161+
enabled: Optional[bool] = None,
162+
max_slow_queries: Optional[int] = None,
163+
slow_query_threshold: Optional[int] = None,
164+
max_query_string_length: Optional[int] = None,
165+
track_bind_vars: Optional[bool] = None,
166+
track_slow_queries: Optional[int] = None,
167+
) -> Result[QueryTrackingConfiguration]:
168+
"""Configure AQL query tracking properties.
169+
170+
Args:
171+
enabled (bool | None): If set to `True`, then queries will be tracked.
172+
If set to `False`, neither queries nor slow queries will be tracked.
173+
max_slow_queries (int | None): Maximum number of slow queries to track. Oldest
174+
entries are discarded first.
175+
slow_query_threshold (int | None): Runtime threshold (in seconds) for treating a
176+
query as slow.
177+
max_query_string_length (int | None): The maximum query string length (in bytes)
178+
to keep in the list of queries.
179+
track_bind_vars (bool | None): If set to `True`, track bind variables used in
180+
queries.
181+
track_slow_queries (int | None): If set to `True`, then slow queries will be
182+
tracked in the list of slow queries if their runtime exceeds the
183+
value set in `slowQueryThreshold`.
184+
185+
Returns:
186+
QueryTrackingConfiguration: Returns the updated query tracking configuration.
187+
188+
Raises:
189+
AQLQueryTrackingSetError: If setting the configuration fails.
190+
191+
References:
192+
- `update-the-aql-query-tracking-configuration <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#update-the-aql-query-tracking-configuration>`__
193+
""" # noqa: E501
194+
data: Json = dict()
195+
196+
if enabled is not None:
197+
data["enabled"] = enabled
198+
if max_slow_queries is not None:
199+
data["maxSlowQueries"] = max_slow_queries
200+
if max_query_string_length is not None:
201+
data["maxQueryStringLength"] = max_query_string_length
202+
if slow_query_threshold is not None:
203+
data["slowQueryThreshold"] = slow_query_threshold
204+
if track_bind_vars is not None:
205+
data["trackBindVars"] = track_bind_vars
206+
if track_slow_queries is not None:
207+
data["trackSlowQueries"] = track_slow_queries
208+
209+
request = Request(
210+
method=Method.PUT,
211+
endpoint="/_api/query/properties",
212+
data=self.serializer.dumps(data),
213+
)
214+
215+
def response_handler(resp: Response) -> QueryTrackingConfiguration:
216+
if not resp.is_success:
217+
raise AQLQueryTrackingSetError(resp, request)
218+
return QueryTrackingConfiguration(self.deserializer.loads(resp.raw_body))
219+
220+
return await self._executor.execute(request, response_handler)
221+
222+
async def queries(self, all_queries: bool = False) -> Result[Jsons]:
223+
"""Return a list of currently running queries.
224+
225+
Args:
226+
all_queries (bool): If set to `True`, will return the currently
227+
running queries in all databases, not just the selected one.
228+
Using the parameter is only allowed in the `_system` database
229+
and with superuser privileges.
230+
231+
Returns:
232+
list: List of currently running queries and their properties.
233+
234+
Raises:
235+
AQLQueryListError: If retrieval fails.
236+
237+
References:
238+
- `list-the-running-queries <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#list-the-running-queries>`__
239+
""" # noqa: E501
240+
request = Request(
241+
method=Method.GET,
242+
endpoint="/_api/query/current",
243+
params={"all": all_queries},
244+
)
245+
246+
def response_handler(resp: Response) -> Jsons:
247+
if not resp.is_success:
248+
raise AQLQueryListError(resp, request)
249+
return self.deserializer.loads_many(resp.raw_body)
250+
251+
return await self._executor.execute(request, response_handler)
252+
253+
async def slow_queries(self, all_queries: bool = False) -> Result[Jsons]:
254+
"""Returns a list containing the last AQL queries that are finished and
255+
have exceeded the slow query threshold in the selected database.
256+
257+
Args:
258+
all_queries (bool): If set to `True`, will return the slow queries
259+
in all databases, not just the selected one. Using the parameter
260+
is only allowed in the `_system` database and with superuser privileges.
261+
262+
Returns:
263+
list: List of slow queries.
264+
265+
Raises:
266+
AQLQueryListError: If retrieval fails.
267+
268+
References:
269+
- `list-the-slow-aql-queries <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#list-the-slow-aql-queries>`__
270+
""" # noqa: E501
271+
request = Request(
272+
method=Method.GET,
273+
endpoint="/_api/query/slow",
274+
params={"all": all_queries},
275+
)
276+
277+
def response_handler(resp: Response) -> Jsons:
278+
if not resp.is_success:
279+
raise AQLQueryListError(resp, request)
280+
return self.deserializer.loads_many(resp.raw_body)
281+
282+
return await self._executor.execute(request, response_handler)
283+
284+
async def clear_slow_queries(self, all_queries: bool = False) -> Result[None]:
285+
"""Clears the list of slow queries.
286+
287+
Args:
288+
all_queries (bool): If set to `True`, will clear the slow queries
289+
in all databases, not just the selected one. Using the parameter
290+
is only allowed in the `_system` database and with superuser privileges.
291+
292+
Returns:
293+
dict: Empty dictionary.
294+
295+
Raises:
296+
AQLQueryClearError: If retrieval fails.
297+
298+
References:
299+
- `clear-the-list-of-slow-aql-queries <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#clear-the-list-of-slow-aql-queries>`__
300+
""" # noqa: E501
301+
request = Request(
302+
method=Method.DELETE,
303+
endpoint="/_api/query/slow",
304+
params={"all": all_queries},
305+
)
306+
307+
def response_handler(resp: Response) -> None:
308+
if not resp.is_success:
309+
raise AQLQueryClearError(resp, request)
310+
311+
return await self._executor.execute(request, response_handler)
312+
313+
async def kill(
314+
self,
315+
query_id: str,
316+
ignore_missing: bool = False,
317+
all_queries: bool = False,
318+
) -> Result[bool]:
319+
"""Kill a running query.
320+
321+
Args:
322+
query_id (str): Thea ID of the query to kill.
323+
ignore_missing (bool): If set to `True`, will not raise an exception
324+
if the query is not found.
325+
all_queries (bool): If set to `True`, will kill the query in all databases,
326+
not just the selected one. Using the parameter is only allowed in the
327+
`_system` database and with superuser privileges.
328+
329+
Returns:
330+
bool: `True` if the query was killed successfully.
331+
332+
Raises:
333+
AQLQueryKillError: If killing the query fails.
334+
335+
References:
336+
- `kill-a-running-aql-query <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#kill-a-running-aql-query>`__
337+
""" # noqa: E501
338+
request = Request(
339+
method=Method.DELETE,
340+
endpoint=f"/_api/query/{query_id}",
341+
params={"all": all_queries},
342+
)
343+
344+
def response_handler(resp: Response) -> bool:
345+
if resp.is_success:
346+
return True
347+
if resp.status_code == HTTP_NOT_FOUND and ignore_missing:
348+
return False
349+
raise AQLQueryKillError(resp, request)
350+
351+
return await self._executor.execute(request, response_handler)
352+
353+
async def explain(
354+
self,
355+
query: str,
356+
bind_vars: Optional[Json] = None,
357+
options: Optional[QueryExplainOptions | Json] = None,
358+
) -> Result[Json]:
359+
"""Inspect the query and return its metadata without executing it.
360+
361+
Args:
362+
query (str): Query string to be explained.
363+
bind_vars (dict | None): An object with key/value pairs representing
364+
the bind parameters.
365+
options (QueryExplainOptions | dict | None): Extra options for the query.
366+
367+
Returns:
368+
dict: Query execution plan.
369+
370+
Raises:
371+
AQLQueryExplainError: If retrieval fails.
372+
373+
References:
374+
- `explain-an-aql-query <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#explain-an-aql-query>`__
375+
""" # noqa: E501
376+
data: Json = dict(query=query)
377+
if bind_vars is not None:
378+
data["bindVars"] = bind_vars
379+
if options is not None:
380+
if isinstance(options, QueryExplainOptions):
381+
options = options.to_dict()
382+
data["options"] = options
383+
384+
request = Request(
385+
method=Method.POST,
386+
endpoint="/_api/explain",
387+
data=self.serializer.dumps(data),
388+
)
389+
390+
def response_handler(resp: Response) -> Json:
391+
if not resp.is_success:
392+
raise AQLQueryExplainError(resp, request)
393+
return self.deserializer.loads(resp.raw_body)
394+
395+
return await self._executor.execute(request, response_handler)
396+
397+
async def validate(self, query: str) -> Result[Json]:
398+
"""Parse and validate the query without executing it.
399+
400+
Args:
401+
query (str): Query string to be validated.
402+
403+
Returns:
404+
dict: Query information.
405+
406+
Raises:
407+
AQLQueryValidateError: If validation fails.
408+
409+
References:
410+
- `parse-an-aql-query <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#parse-an-aql-query>`__
411+
""" # noqa: E501
412+
request = Request(
413+
method=Method.POST,
414+
endpoint="/_api/query",
415+
data=self.serializer.dumps(dict(query=query)),
416+
)
417+
418+
def response_handler(resp: Response) -> Json:
419+
if not resp.is_success:
420+
raise AQLQueryValidateError(resp, request)
421+
return self.deserializer.loads(resp.raw_body)
422+
423+
return await self._executor.execute(request, response_handler)
424+
425+
async def query_rules(self) -> Result[Jsons]:
426+
"""A list of all optimizer rules and their properties.
427+
428+
Returns:
429+
list: Available optimizer rules.
430+
431+
Raises:
432+
AQLQueryRulesGetError: If retrieval fails.
433+
434+
References:
435+
- `list-all-aql-optimizer-rules <https://docs.arangodb.com/stable/develop/http-api/queries/aql-queries/#list-all-aql-optimizer-rules>`__
436+
""" # noqa: E501
437+
request = Request(method=Method.GET, endpoint="/_api/query/rules")
438+
439+
def response_handler(resp: Response) -> Jsons:
440+
if not resp.is_success:
441+
raise AQLQueryRulesGetError(resp, request)
442+
return self.deserializer.loads_many(resp.raw_body)
443+
444+
return await self._executor.execute(request, response_handler)

arangoasync/exceptions.py

+32
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,42 @@ def __init__(
7171
self.http_headers = resp.headers
7272

7373

74+
class AQLQueryClearError(ArangoServerError):
75+
"""Failed to clear slow AQL queries."""
76+
77+
7478
class AQLQueryExecuteError(ArangoServerError):
7579
"""Failed to execute query."""
7680

7781

82+
class AQLQueryExplainError(ArangoServerError):
83+
"""Failed to parse and explain query."""
84+
85+
86+
class AQLQueryKillError(ArangoServerError):
87+
"""Failed to kill the query."""
88+
89+
90+
class AQLQueryListError(ArangoServerError):
91+
"""Failed to retrieve running AQL queries."""
92+
93+
94+
class AQLQueryRulesGetError(ArangoServerError):
95+
"""Failed to retrieve AQL query rules."""
96+
97+
98+
class AQLQueryTrackingGetError(ArangoServerError):
99+
"""Failed to retrieve AQL tracking properties."""
100+
101+
102+
class AQLQueryTrackingSetError(ArangoServerError):
103+
"""Failed to configure AQL tracking properties."""
104+
105+
106+
class AQLQueryValidateError(ArangoServerError):
107+
"""Failed to parse and validate query."""
108+
109+
78110
class AuthHeaderError(ArangoClientError):
79111
"""The authentication header could not be determined."""
80112

0 commit comments

Comments
 (0)