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.
- api_foundry_query_engine/adapters/adapter.py +67 -0
- api_foundry_query_engine/adapters/case_change_adapter.py +79 -0
- api_foundry_query_engine/adapters/gateway_adapter.py +112 -0
- api_foundry_query_engine/connectors/connection.py +32 -0
- api_foundry_query_engine/connectors/connection_factory.py +110 -0
- api_foundry_query_engine/connectors/oracle_connector.py +29 -0
- api_foundry_query_engine/connectors/postgres_connection.py +105 -0
- api_foundry_query_engine/dao/dao.py +23 -0
- api_foundry_query_engine/dao/operation_dao.py +141 -0
- api_foundry_query_engine/dao/sql_custom_query_handler.py +66 -0
- api_foundry_query_engine/dao/sql_delete_query_handler.py +36 -0
- api_foundry_query_engine/dao/sql_insert_query_handler.py +102 -0
- api_foundry_query_engine/dao/sql_query_handler.py +386 -0
- api_foundry_query_engine/dao/sql_select_query_handler.py +305 -0
- api_foundry_query_engine/dao/sql_subselect_query_handler.py +63 -0
- api_foundry_query_engine/dao/sql_update_query_handler.py +59 -0
- api_foundry_query_engine/handler.py +48 -0
- api_foundry_query_engine/operation.py +15 -0
- api_foundry_query_engine/services/service.py +60 -0
- api_foundry_query_engine/services/transactional_service.py +43 -0
- api_foundry_query_engine/utils/api_model.py +175 -0
- api_foundry_query_engine/utils/app_exception.py +22 -0
- api_foundry_query_engine/utils/logger.py +60 -0
- api_foundry_query_engine-0.0.1.dist-info/METADATA +14 -0
- api_foundry_query_engine-0.0.1.dist-info/RECORD +27 -0
- api_foundry_query_engine-0.0.1.dist-info/WHEEL +4 -0
- 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)})"
|