Skip to content

Commit 017f4cd

Browse files
authored
Merge pull request #139 from multinet-app/137-aql-queries
2 parents 40144d9 + 0a5ee18 commit 017f4cd

File tree

9 files changed

+89
-26
lines changed

9 files changed

+89
-26
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.2.12 on 2022-08-02 21:03
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0011_auto_20210927_2228'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='aqlquery',
15+
name='bind_vars',
16+
field=models.JSONField(blank=True, default=dict),
17+
),
18+
]

multinet/api/models/tasks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ class AqlQuery(Task):
4444
"""An object to track AQL queries."""
4545

4646
query = models.TextField()
47+
bind_vars = models.JSONField(blank=True, default=dict)
4748
results = models.JSONField(blank=True, null=True)

multinet/api/tasks/aql/aql_query.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@ class AqlQueryTask(MultinetCeleryTask):
1616
def execute_query(task_id: int) -> None:
1717
query_task: AqlQuery = AqlQuery.objects.select_related('workspace').get(id=task_id)
1818
workspace: Workspace = query_task.workspace
19-
query_str = query_task.query
19+
2020
# Run the query on Arango DB
2121
database = workspace.get_arango_db()
22-
query = ArangoQuery(database, query_str, time_limit_secs=60)
22+
query = ArangoQuery(
23+
database,
24+
query_str=query_task.query,
25+
bind_vars=query_task.bind_vars,
26+
time_limit_secs=60,
27+
)
2328
cursor: Cursor = query.execute()
2429

2530
# Store the results on the task object

multinet/api/tests/test_query.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,45 @@ def valid_query(workspace: Workspace, user: User, authenticated_api_client: APIC
1515
"""Create a fixture representing the response of a POST request for AQL queries."""
1616
workspace.set_user_permission(user, WorkspaceRoleChoice.READER)
1717
node_table = populated_table(workspace, False)
18-
query_str = f'FOR document IN {node_table.name} RETURN document'
18+
19+
query_str = 'FOR document IN @@TABLE RETURN document'
20+
bind_vars = {'@TABLE': node_table.name}
1921
r: Response = authenticated_api_client.post(
20-
f'/api/workspaces/{workspace.name}/queries/', {'query': query_str}, format='json'
22+
f'/api/workspaces/{workspace.name}/queries/',
23+
{'query': query_str, 'bind_vars': bind_vars},
24+
format='json',
2125
)
2226
WorkspaceRole.objects.filter(workspace=workspace, user=user).delete()
23-
return {'response': r, 'query': query_str, 'nodes': list(node_table.get_rows())}
27+
28+
return {
29+
'response': r,
30+
'query': query_str,
31+
'bind_vars': bind_vars,
32+
'nodes': list(node_table.get_rows()),
33+
}
2434

2535

2636
@pytest.fixture
2737
def mutating_query(workspace: Workspace, user: User, authenticated_api_client: APIClient):
2838
"""Create a fixture for a mutating AQL query that will have an error message post processing."""
2939
workspace.set_user_permission(user, WorkspaceRoleChoice.READER)
3040
node_table = populated_table(workspace, False)
31-
fake = Faker()
32-
query_str = f"INSERT {{ 'name': {fake.pystr()} }} INTO {node_table.name}"
41+
42+
query_str = 'INSERT {name: @DOCNAME} INTO @@TABLE'
43+
bind_vars = {'@TABLE': node_table.name, 'DOCNAME': Faker().pystr()}
3344
r: Response = authenticated_api_client.post(
34-
f'/api/workspaces/{workspace.name}/queries/', {'query': query_str}, format='json'
45+
f'/api/workspaces/{workspace.name}/queries/',
46+
{'query': query_str, 'bind_vars': bind_vars},
47+
format='json',
3548
)
3649
WorkspaceRole.objects.filter(workspace=workspace, user=user).delete()
37-
return {'response': r, 'query': query_str, 'nodes': list(node_table.get_rows())}
50+
51+
return {
52+
'response': r,
53+
'query': query_str,
54+
'bind_vars': bind_vars,
55+
'nodes': list(node_table.get_rows()),
56+
}
3857

3958

4059
@pytest.mark.django_db
@@ -45,6 +64,7 @@ def test_query_rest_create(workspace: Workspace, user: User, valid_query):
4564
'id': INTEGER_ID_RE,
4665
'workspace': workspace_re(workspace),
4766
'query': valid_query['query'],
67+
'bind_vars': valid_query['bind_vars'],
4868
'user': user.username,
4969
'error_messages': None,
5070
'status': AqlQuery.Status.PENDING,
@@ -63,6 +83,7 @@ def test_query_rest_create_mutating(workspace: Workspace, user: User, mutating_q
6383
'id': INTEGER_ID_RE,
6484
'workspace': workspace_re(workspace),
6585
'query': mutating_query['query'],
86+
'bind_vars': mutating_query['bind_vars'],
6687
'user': user.username,
6788
'error_messages': None,
6889
'status': AqlQuery.Status.PENDING,

multinet/api/tests/test_workspace.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -447,10 +447,16 @@ def test_workspace_rest_aql(
447447
node_table = populated_table(workspace, False)
448448
nodes: Cursor = node_table.get_rows()
449449
nodes_list = list(nodes)
450+
450451
# try and execute a valid non-mutating query on the data
451-
query = f'FOR document IN {node_table.name} RETURN document'
452-
r = authenticated_api_client.get(
453-
f'/api/workspaces/{workspace.name}/aql/', data={'query': query}
452+
r = authenticated_api_client.post(
453+
f'/api/workspaces/{workspace.name}/aql/',
454+
{
455+
'query': 'FOR doc IN @@TABLE RETURN doc',
456+
'bind_vars': {
457+
'@TABLE': node_table.name,
458+
},
459+
},
454460
)
455461
assert r.status_code == status_code
456462

@@ -466,11 +472,17 @@ def test_workspace_rest_aql_mutating_query(
466472
):
467473
workspace.set_user_permission(user, WorkspaceRoleChoice.READER)
468474
fake = Faker()
469-
470475
node_table = populated_table(workspace, False)
476+
471477
# Mutating query
472-
query = f"INSERT {{ 'name': {fake.pystr()} }} INTO {node_table.name}"
473-
r = authenticated_api_client.get(
474-
f'/api/workspaces/{workspace.name}/aql/', data={'query': query}
478+
r = authenticated_api_client.post(
479+
f'/api/workspaces/{workspace.name}/aql/',
480+
data={
481+
'query': 'INSERT {name: @DOCNAME} INTO @@TABLE',
482+
'bind_vars': {
483+
'@TABLE': node_table.name,
484+
'DOCNAME': fake.pystr(),
485+
},
486+
},
475487
)
476488
assert r.status_code == 400

multinet/api/views/query.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ def create(self, request, parent_lookup_workspace__name: str):
2929
workspace: Workspace = get_object_or_404(Workspace, name=parent_lookup_workspace__name)
3030
serializer = AqlQuerySerializer(data=request.data)
3131
serializer.is_valid(raise_exception=True)
32-
query_str = serializer.validated_data['query']
33-
3432
query: AqlQuery = AqlQuery.objects.create(
35-
workspace=workspace, user=request.user, query=query_str
33+
workspace=workspace,
34+
user=request.user,
35+
query=serializer.validated_data['query'],
36+
bind_vars=serializer.validated_data['bind_vars'],
3637
)
3738

3839
execute_query.delay(task_id=query.pk)
39-
4040
return Response(AqlQueryTaskSerializer(query).data, status=status.HTTP_200_OK)
4141

4242
@swagger_auto_schema(responses={200: AqlQueryResultsSerializer()})

multinet/api/views/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class SingleUserWorkspacePermissionSerializer(serializers.Serializer):
8989

9090
class AqlQuerySerializer(serializers.Serializer):
9191
query = serializers.CharField()
92+
bind_vars = serializers.DictField(child=serializers.CharField())
9293

9394

9495
class AqlQueryTaskSerializer(serializers.ModelSerializer):

multinet/api/views/workspace.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,17 +178,22 @@ def put_workspace_permissions(self, request, name: str):
178178

179179
return Response(PermissionsReturnSerializer(workspace).data, status=status.HTTP_200_OK)
180180

181-
@swagger_auto_schema(query_serializer=AqlQuerySerializer())
182-
@action(detail=True)
181+
@swagger_auto_schema(request_body=AqlQuerySerializer())
182+
@action(detail=True, methods=['POST'])
183183
@require_workspace_permission(WorkspaceRoleChoice.READER)
184184
def aql(self, request, name: str):
185185
"""Execute AQL in a workspace."""
186-
serializer = AqlQuerySerializer(data=request.query_params)
186+
serializer = AqlQuerySerializer(data=request.data)
187187
serializer.is_valid(raise_exception=True)
188-
query_str = serializer.validated_data['query']
188+
189+
# Retrieve workspace and db
189190
workspace: Workspace = get_object_or_404(Workspace, name=name)
190191
database = workspace.get_arango_db()
191-
query = ArangoQuery(database, query_str)
192+
193+
# Form query
194+
query_str = serializer.validated_data['query']
195+
bind_vars = serializer.validated_data['bind_vars']
196+
query = ArangoQuery(database, query_str=query_str, bind_vars=bind_vars)
192197

193198
try:
194199
cursor: Cursor = query.execute()

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ envlist =
88
skipsdist = true
99
skip_install = true
1010
deps =
11-
flake8
11+
flake8 < 5
1212
flake8-black
1313
flake8-bugbear
1414
flake8-docstrings

0 commit comments

Comments
 (0)