Repository: microsoft/AzureTRE

Issue:
Implement query builder for queries
comment from @sachinkundu 

IF we are always going to use isDeleted = false we should write a query builder which decorates the query string with an AND clause instead of remembering to put it everywhere



Discussion:

Resolving patch:
diff --git a/management_api_app/db/query_builder.py b/management_api_app/db/query_builder.py
new file mode 100644
--- /dev/null
+++ b/management_api_app/db/query_builder.py
@@ -0,0 +1,26 @@
+from models.domain.resource import ResourceType
+
+
+class QueryBuilder:
+    def __init__(self):
+        self._where_clauses = []
+
+    def select_active_resources(self, resource_type: ResourceType):
+        active_resources = f'c.resourceType = "{resource_type.value}" AND c.isDeleted = false'
+        self._where_clauses.append(active_resources)
+        return self
+
+    def with_id(self, resource_id: str):
+        self._where_clauses.append(f'c.id = "{resource_id}"')
+        return self
+
+    def build(self) -> str:
+        query = 'SELECT * FROM c'
+        if self._where_clauses:
+            query += ' WHERE '
+        for clause in self._where_clauses:
+            query += clause + ' AND '
+        if self._where_clauses:
+            # removes the final ' AND '
+            query = query[:-5]
+        return query
diff --git a/management_api_app/db/repositories/workspaces.py b/management_api_app/db/repositories/workspaces.py
--- a/management_api_app/db/repositories/workspaces.py
+++ b/management_api_app/db/repositories/workspaces.py
@@ -6,10 +6,10 @@
 
 from core.config import STATE_STORE_RESOURCES_CONTAINER
 from db.errors import EntityDoesNotExist
+from db.query_builder import QueryBuilder
 from db.repositories.base import BaseRepository
 from models.domain.resource import Resource, ResourceSpec, ResourceType, Status
 from models.schemas.resource import ResourceInCreate
-from resources import strings
 
 
 class WorkspaceRepository(BaseRepository):
@@ -21,17 +21,12 @@ def container(self) -> ContainerProxy:
         return self._container
 
     def get_all_active_workspaces(self) -> List[Resource]:
-        query = f'SELECT * from c ' \
-                f'WHERE c.resourceType = "{strings.RESOURCE_TYPE_WORKSPACE}" ' \
-                f'AND c.isDeleted = false'
+        query = QueryBuilder().select_active_resources(ResourceType.Workspace).build()
         workspaces = list(self.container.query_items(query=query, enable_cross_partition_query=True))
         return workspaces
 
     def get_workspace_by_workspace_id(self, workspace_id: UUID4) -> Resource:
-        query = f'SELECT * from c ' \
-                f'WHERE c.resourceType = "{strings.RESOURCE_TYPE_WORKSPACE}" ' \
-                f'AND c.isDeleted = false ' \
-                f'AND c.id = "{workspace_id}"'
+        query = QueryBuilder().select_active_resources(ResourceType.Workspace).with_id(workspace_id).build()
         workspaces = list(self.container.query_items(query=query, enable_cross_partition_query=True))
 
         if workspaces:


Test patch:
diff --git a/management_api_app/tests/test_db/__init__.py b/management_api_app/tests/test_db/__init__.py
new file mode 100644
diff --git a/management_api_app/tests/test_db/test_query_builder.py b/management_api_app/tests/test_db/test_query_builder.py
new file mode 100644
--- /dev/null
+++ b/management_api_app/tests/test_db/test_query_builder.py
@@ -0,0 +1,20 @@
+from models.domain.resource import ResourceType
+from db.query_builder import QueryBuilder
+
+
+def test_query_builder_with_no_where_clauses_select_all() -> None:
+    expected_query = 'SELECT * FROM c'
+    actual_query = QueryBuilder().build()
+    assert expected_query == actual_query
+
+
+def test_query_builder_selecting_active_resources() -> None:
+    expected_query = 'SELECT * FROM c WHERE c.resourceType = "workspace" AND c.isDeleted = false'
+    actual_query = QueryBuilder().select_active_resources(ResourceType.Workspace).build()
+    assert expected_query == actual_query
+
+
+def test_query_builder_selecting_active_resources_with_id() -> None:
+    expected_query = 'SELECT * FROM c WHERE c.resourceType = "workspace" AND c.isDeleted = false AND c.id = "some_id"'
+    actual_query = QueryBuilder().select_active_resources(ResourceType.Workspace).with_id("some_id").build()
+    assert expected_query == actual_query