api-foundry-query-engine 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. api_foundry_query_engine/adapters/adapter.py +67 -0
  2. api_foundry_query_engine/adapters/case_change_adapter.py +79 -0
  3. api_foundry_query_engine/adapters/gateway_adapter.py +112 -0
  4. api_foundry_query_engine/connectors/connection.py +32 -0
  5. api_foundry_query_engine/connectors/connection_factory.py +110 -0
  6. api_foundry_query_engine/connectors/oracle_connector.py +29 -0
  7. api_foundry_query_engine/connectors/postgres_connection.py +105 -0
  8. api_foundry_query_engine/dao/dao.py +23 -0
  9. api_foundry_query_engine/dao/operation_dao.py +141 -0
  10. api_foundry_query_engine/dao/sql_custom_query_handler.py +66 -0
  11. api_foundry_query_engine/dao/sql_delete_query_handler.py +36 -0
  12. api_foundry_query_engine/dao/sql_insert_query_handler.py +102 -0
  13. api_foundry_query_engine/dao/sql_query_handler.py +386 -0
  14. api_foundry_query_engine/dao/sql_select_query_handler.py +305 -0
  15. api_foundry_query_engine/dao/sql_subselect_query_handler.py +63 -0
  16. api_foundry_query_engine/dao/sql_update_query_handler.py +59 -0
  17. api_foundry_query_engine/handler.py +48 -0
  18. api_foundry_query_engine/operation.py +15 -0
  19. api_foundry_query_engine/services/service.py +60 -0
  20. api_foundry_query_engine/services/transactional_service.py +43 -0
  21. api_foundry_query_engine/utils/api_model.py +175 -0
  22. api_foundry_query_engine/utils/app_exception.py +22 -0
  23. api_foundry_query_engine/utils/logger.py +60 -0
  24. api_foundry_query_engine-0.0.1.dist-info/METADATA +14 -0
  25. api_foundry_query_engine-0.0.1.dist-info/RECORD +27 -0
  26. api_foundry_query_engine-0.0.1.dist-info/WHEEL +4 -0
  27. api_foundry_query_engine-0.0.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,141 @@
1
+ from typing import Union
2
+
3
+ from api_foundry_query_engine.dao.sql_custom_query_handler import SQLCustomQueryHandler
4
+ from api_foundry_query_engine.dao.sql_delete_query_handler import SQLDeleteSchemaQueryHandler
5
+ from api_foundry_query_engine.dao.sql_insert_query_handler import SQLInsertSchemaQueryHandler
6
+ from api_foundry_query_engine.dao.sql_select_query_handler import SQLSelectSchemaQueryHandler
7
+ from api_foundry_query_engine.dao.sql_subselect_query_handler import SQLSubselectSchemaQueryHandler
8
+ from api_foundry_query_engine.dao.sql_update_query_handler import SQLUpdateSchemaQueryHandler
9
+ from api_foundry_query_engine.utils.app_exception import ApplicationException
10
+ from api_foundry_query_engine.dao.dao import DAO
11
+ from api_foundry_query_engine.connectors.connection import Cursor
12
+ from api_foundry_query_engine.operation import Operation
13
+ from api_foundry_query_engine.utils.api_model import get_schema_object, get_path_operation
14
+ from api_foundry_query_engine.dao.sql_query_handler import SQLQueryHandler
15
+
16
+
17
+ class OperationDAO(DAO):
18
+ """
19
+ A class to handle database operations based on the provided
20
+ Operation object.
21
+
22
+ Attributes:
23
+ operation (Operation): The operation to perform.
24
+ """
25
+
26
+ def __init__(self, operation: Operation, engine: str) -> None:
27
+ """
28
+ Initialize the OperationDAO with the provided Operation object.
29
+
30
+ Args:
31
+ operation (Operation): The operation to perform.
32
+ """
33
+ super().__init__()
34
+ self.operation = operation
35
+ self.engine = engine
36
+
37
+ @property
38
+ def query_handler(self) -> SQLQueryHandler:
39
+ if not hasattr(self, "_query_handler"):
40
+ path_operation = get_path_operation(
41
+ self.operation.entity, self.operation.action
42
+ )
43
+ if path_operation:
44
+ self._query_handler = SQLCustomQueryHandler(
45
+ self.operation, path_operation, self.engine
46
+ )
47
+ return self._query_handler
48
+
49
+ schema_object = get_schema_object(self.operation.entity)
50
+ if self.operation.action == "read":
51
+ self._query_handler = SQLSelectSchemaQueryHandler(
52
+ self.operation, schema_object, self.engine
53
+ )
54
+ elif self.operation.action == "create":
55
+ self._query_handler = SQLInsertSchemaQueryHandler(
56
+ self.operation, schema_object, self.engine
57
+ )
58
+ elif self.operation.action == "update":
59
+ self._query_handler = SQLUpdateSchemaQueryHandler(
60
+ self.operation, schema_object, self.engine
61
+ )
62
+ elif self.operation.action == "delete":
63
+ self._query_handler = SQLDeleteSchemaQueryHandler(
64
+ self.operation, schema_object, self.engine
65
+ )
66
+ else:
67
+ raise ApplicationException(
68
+ 400, f"Invalid operation action: {self.operation.action}"
69
+ )
70
+ return self._query_handler
71
+
72
+ def execute(self, cursor: Cursor) -> Union[list[dict], dict]:
73
+ """
74
+ Execute the database operation based on the provided cursor.
75
+
76
+ Args:
77
+ cursor (Cursor): The database cursor.
78
+
79
+ Returns:
80
+ list[dict]: A list of dictionaries containing the results
81
+ of the operation.
82
+ """
83
+
84
+ result = self.__fetch_record_set(self.query_handler, cursor)
85
+
86
+ if self.operation.action == "read":
87
+ if self.operation.metadata_params.get("count", False):
88
+ return result[0]
89
+ self.__fetch_many(result, cursor)
90
+ elif self.operation.action in ["update", "delete"] and len(result) == 0:
91
+ raise ApplicationException(400, "No records were modified")
92
+
93
+ return result
94
+
95
+ def __fetch_many(self, parent_set: list[dict], cursor: Cursor):
96
+ if "properties" not in self.operation.metadata_params:
97
+ return
98
+
99
+ schema_object = get_schema_object(self.operation.entity)
100
+ for name, relation in schema_object.relations.items():
101
+ if relation.type == "object":
102
+ continue
103
+
104
+ child_set = self.__fetch_record_set(
105
+ SQLSubselectSchemaQueryHandler(
106
+ self.operation, relation, self.query_handler # type: ignore
107
+ ),
108
+ cursor,
109
+ )
110
+ if len(child_set) == 0:
111
+ continue
112
+
113
+ for parent in parent_set:
114
+ parent[name] = []
115
+
116
+ parents = {}
117
+ for parent in parent_set:
118
+ parents[parent[relation.parent_property]] = parent
119
+
120
+ for child in child_set:
121
+ parent_id = child[relation.child_property]
122
+ parent = parents.get(parent_id)
123
+ if parent:
124
+ parent[name].append(child)
125
+
126
+ def __fetch_record_set(
127
+ self, query_handler: SQLQueryHandler, cursor: Cursor
128
+ ) -> list[dict]:
129
+ sql = query_handler.sql
130
+ if not sql:
131
+ return []
132
+
133
+ record_set = cursor.execute(
134
+ sql, query_handler.placeholders, query_handler.selection_results
135
+ )
136
+ result = []
137
+ for record in record_set:
138
+ object = query_handler.marshal_record(record)
139
+ result.append(object)
140
+
141
+ return result
@@ -0,0 +1,66 @@
1
+ from typing import List, Dict
2
+ import re
3
+
4
+ from api_foundry_query_engine.dao.sql_query_handler import SQLQueryHandler
5
+ from api_foundry_query_engine.utils.api_model import SchemaObjectProperty, PathOperation
6
+ from api_foundry_query_engine.operation import Operation
7
+ from api_foundry_query_engine.utils.app_exception import ApplicationException
8
+ from api_foundry_query_engine.utils.logger import logger
9
+
10
+ log = logger(__name__)
11
+
12
+
13
+ class SQLCustomQueryHandler(SQLQueryHandler):
14
+ def __init__(
15
+ self, operation: Operation, path_operation: PathOperation, engine: str
16
+ ) -> None:
17
+ super().__init__(operation, engine)
18
+ self.path_operation = path_operation
19
+
20
+ @property
21
+ def sql(self) -> str:
22
+ if not hasattr(self, "_sql"):
23
+ self._compile()
24
+ return self._sql
25
+
26
+ @property
27
+ def placeholders(self) -> Dict[str, SchemaObjectProperty]:
28
+ if not hasattr(self, "_placeholders"):
29
+ self._compile()
30
+ return self._placeholders
31
+
32
+ @property
33
+ def select_list_columns(self) -> List[SchemaObjectProperty]:
34
+ raise NotImplementedError()
35
+
36
+ def selection_result_map(self) -> Dict:
37
+ log.info(f"outputs: {self.path_operation.outputs}")
38
+ return self.path_operation.outputs
39
+
40
+ def _compile(self):
41
+ placeholder_pattern = re.compile(r":(\w+)")
42
+ self._placeholders = dict()
43
+ result_sql = placeholder_pattern.sub(
44
+ self._get_placeholder_text, self.path_operation.sql
45
+ )
46
+ self._sql = re.sub(r"\s+", " ", result_sql).strip()
47
+
48
+ def _get_placeholder_text(self, match) -> str:
49
+ placeholder_name = match.group(1)
50
+ property = self.path_operation.inputs.get(placeholder_name)
51
+ if not property:
52
+ raise ApplicationException(
53
+ 500,
54
+ f"Input parameter not defined for the placeholder: {placeholder_name}",
55
+ )
56
+
57
+ value = (
58
+ self.operation.query_params[placeholder_name]
59
+ if placeholder_name in self.operation.query_params
60
+ else property.default
61
+ )
62
+ log.info(f"placeholder_name: {placeholder_name}")
63
+ log.info(f"value: {value}, default: {property.default}")
64
+ log.info(f"placeholders: {self.generate_placeholders(property, value)}")
65
+ self._placeholders.update(self.generate_placeholders(property, value))
66
+ return self.placeholder(property, placeholder_name)
@@ -0,0 +1,36 @@
1
+ from api_foundry_query_engine.dao.sql_query_handler import SQLSchemaQueryHandler
2
+ from api_foundry_query_engine.operation import Operation
3
+ from api_foundry_query_engine.utils.app_exception import ApplicationException
4
+ from api_foundry_query_engine.utils.api_model import SchemaObject
5
+
6
+
7
+ class SQLDeleteSchemaQueryHandler(SQLSchemaQueryHandler):
8
+ def __init__(
9
+ self, operation: Operation, schema_object: SchemaObject, engine: str
10
+ ) -> None:
11
+ super().__init__(operation, schema_object, engine)
12
+
13
+ @property
14
+ def sql(self) -> str:
15
+ concurrency_property = self.schema_object.concurrency_property
16
+ if concurrency_property:
17
+ if not self.operation.query_params.get(concurrency_property.api_name):
18
+ raise ApplicationException(
19
+ 400,
20
+ "Missing required concurrency management property. "
21
+ + f"schema_object: {self.schema_object.api_name}, "
22
+ + f"property: {concurrency_property.api_name}",
23
+ )
24
+ if self.operation.store_params.get(concurrency_property.api_name):
25
+ raise ApplicationException(
26
+ 400,
27
+ "For updating concurrency managed schema objects the current "
28
+ + "version may not be supplied as a storage parameter. "
29
+ + f"schema_object: {self.schema_object.api_name}, "
30
+ + f"property: {concurrency_property.api_name}",
31
+ )
32
+
33
+ return (
34
+ f"DELETE FROM {self.table_expression}{self.search_condition} "
35
+ + f"RETURNING {self.select_list}"
36
+ )
@@ -0,0 +1,102 @@
1
+ from typing import Optional
2
+ from api_foundry_query_engine.dao.sql_query_handler import SQLSchemaQueryHandler
3
+ from api_foundry_query_engine.operation import Operation
4
+ from api_foundry_query_engine.utils.app_exception import ApplicationException
5
+ from api_foundry_query_engine.utils.api_model import SchemaObject, SchemaObjectProperty
6
+
7
+
8
+ class SQLInsertSchemaQueryHandler(SQLSchemaQueryHandler):
9
+ key_property: Optional[SchemaObjectProperty]
10
+
11
+ def __init__(
12
+ self, operation: Operation, schema_object: SchemaObject, engine: str
13
+ ) -> None:
14
+ super().__init__(operation, schema_object, engine)
15
+ self.key_property = schema_object.primary_key
16
+ if self.key_property:
17
+ if self.key_property.key_type == "auto":
18
+ if operation.store_params.get(self.key_property.column_name):
19
+ raise ApplicationException(
20
+ 400,
21
+ "Primary key values cannot be inserted when key type"
22
+ + f" is auto. schema_object: {schema_object.api_name}",
23
+ )
24
+ elif self.key_property.key_type == "required":
25
+ if not operation.store_params.get(self.key_property.column_name):
26
+ raise ApplicationException(
27
+ 400,
28
+ "Primary key values must be provided when key type is"
29
+ + f" required. schema_object: {schema_object.api_name}",
30
+ )
31
+ self.concurrency_property = schema_object.concurrency_property
32
+ if self.concurrency_property and operation.store_params.get(
33
+ self.concurrency_property.api_name
34
+ ):
35
+ raise ApplicationException(
36
+ 400,
37
+ "Versioned properties can not be supplied a store parameters. "
38
+ + f"schema_object: {schema_object.api_name}, "
39
+ + f"property: {self.concurrency_property.api_name}",
40
+ )
41
+
42
+ @property
43
+ def sql(self) -> str:
44
+ self.concurrency_property = self.schema_object.concurrency_property
45
+ if not self.concurrency_property:
46
+ return (
47
+ f"INSERT INTO {self.table_expression}{self.insert_values} "
48
+ + f"RETURNING {self.select_list}"
49
+ )
50
+
51
+ if self.operation.store_params.get(self.concurrency_property.api_name):
52
+ raise ApplicationException(
53
+ 400,
54
+ "When inserting schema objects with a version property "
55
+ + "the a version must not be supplied as a storage parameter."
56
+ + f" schema_object: {self.schema_object.api_name}, "
57
+ + f"property: {self.concurrency_property.api_name}",
58
+ )
59
+ return (
60
+ f"INSERT INTO {self.table_expression}{self.insert_values}"
61
+ + f" RETURNING {self.select_list}"
62
+ )
63
+
64
+ @property
65
+ def insert_values(self) -> str:
66
+ self.store_placeholders = {}
67
+ placeholders = []
68
+ columns = []
69
+
70
+ for name, value in self.operation.store_params.items():
71
+ parts = name.split(".")
72
+
73
+ try:
74
+ if len(parts) > 1:
75
+ raise ApplicationException(
76
+ 400,
77
+ "Properties can not be set on associated objects " + name,
78
+ )
79
+
80
+ property = self.schema_object.properties[parts[0]]
81
+ except KeyError:
82
+ raise ApplicationException(400, f"Invalid property: {name}")
83
+
84
+ columns.append(property.column_name)
85
+ placeholders.append(self.placeholder(property, property.api_name))
86
+ self.store_placeholders[property.api_name] = property.convert_to_db_value(value)
87
+
88
+ if self.key_property:
89
+ if self.key_property.key_type == "sequence":
90
+ columns.append(self.key_property.column_name)
91
+ placeholders.append(f"nextval('{self.key_property.sequence_name}')")
92
+
93
+ if self.concurrency_property:
94
+ columns.append(self.concurrency_property.column_name)
95
+ if self.concurrency_property.column_type == "integer":
96
+ placeholders.append("1")
97
+ else:
98
+ placeholders.append(
99
+ self.concurrency_generator(self.concurrency_property)
100
+ )
101
+
102
+ return f" ( {', '.join(columns)} ) VALUES ( {', '.join(placeholders)})"