teaql 0.2.2__tar.gz → 0.2.4__tar.gz
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.
- {teaql-0.2.2 → teaql-0.2.4}/PKG-INFO +1 -1
- {teaql-0.2.2 → teaql-0.2.4}/pyproject.toml +1 -1
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/mutation.py +2 -1
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/data_service/__init__.py +2 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/audit.py +8 -1
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/context.py +32 -5
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/module.py +11 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/sql/executor.py +64 -69
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/PKG-INFO +1 -1
- {teaql-0.2.2 → teaql-0.2.4}/LICENSE +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/README.md +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/setup.cfg +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/entity.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/eval.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/expr.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/graph.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/list.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/meta.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/query.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/safe_expression.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/value.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/xls.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/mysql/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/mysql/dialect.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/mysql/transport.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/postgres/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/postgres/dialect.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/postgres/transport.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/redis/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/redis/remote.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/redis/store.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/sqlite/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/sqlite/dialect.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/sqlite/transport.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/tfp_client/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/py.typed +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/_schema_capability.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/builtin-messages-v1.json +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/env.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/i18n.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/opentelemetry.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/store.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/telemetry.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/tools.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/sql/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/sql/dialect.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/sql/types.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/web_integration/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql/web_integration/fastapi/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/SOURCES.txt +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/dependency_links.txt +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/requires.txt +0 -0
- {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/top_level.txt +0 -0
|
@@ -8,6 +8,8 @@ class TraceNode:
|
|
|
8
8
|
entity_type: str = ""
|
|
9
9
|
entity_id: Optional[int] = None
|
|
10
10
|
comment: str = ""
|
|
11
|
+
kind: str = "entity"
|
|
12
|
+
name: str = ""
|
|
11
13
|
|
|
12
14
|
@dataclass
|
|
13
15
|
class InsertCommand:
|
|
@@ -146,4 +148,3 @@ class MutationRequest:
|
|
|
146
148
|
@classmethod
|
|
147
149
|
def Batch(cls, cmds: List['MutationRequest']) -> 'MutationRequest':
|
|
148
150
|
return cls(cmds)
|
|
149
|
-
|
|
@@ -67,6 +67,8 @@ class ExecutionMetadata:
|
|
|
67
67
|
result_count: Optional[int] = None
|
|
68
68
|
trace_chain: List[TraceNode] = field(default_factory=list)
|
|
69
69
|
comment: Optional[str] = None
|
|
70
|
+
purpose: Optional[str] = None
|
|
71
|
+
audit_reason: Optional[str] = None
|
|
70
72
|
backend_request_id: Optional[str] = None
|
|
71
73
|
debug_query: Optional[str] = None
|
|
72
74
|
|
|
@@ -25,6 +25,8 @@ class RawAuditEvent:
|
|
|
25
25
|
entity_id: Any
|
|
26
26
|
changes: tuple[AuditFieldChange, ...]
|
|
27
27
|
trace_chain: tuple[Any, ...] = field(default_factory=tuple)
|
|
28
|
+
actor: Optional[str] = None
|
|
29
|
+
category: Optional[str] = None
|
|
28
30
|
|
|
29
31
|
def safe(self, mask_fields: List[str], max_length: Optional[int]) -> "SafeAuditEvent":
|
|
30
32
|
fields = []
|
|
@@ -37,7 +39,10 @@ class RawAuditEvent:
|
|
|
37
39
|
if truncated:
|
|
38
40
|
value = "*" * max_length if max_length <= 3 else value[:max_length - 3] + "..."
|
|
39
41
|
fields.append(SafeAuditField(change.field, value, masked, truncated))
|
|
40
|
-
return SafeAuditEvent(
|
|
42
|
+
return SafeAuditEvent(
|
|
43
|
+
self.kind, self.entity, self.entity_id, tuple(fields), self.trace_chain,
|
|
44
|
+
self.actor, self.category,
|
|
45
|
+
)
|
|
41
46
|
|
|
42
47
|
|
|
43
48
|
@dataclass(frozen=True)
|
|
@@ -55,6 +60,8 @@ class SafeAuditEvent:
|
|
|
55
60
|
entity_id: Any
|
|
56
61
|
fields: tuple[SafeAuditField, ...]
|
|
57
62
|
trace_chain: tuple[Any, ...] = field(default_factory=tuple)
|
|
63
|
+
actor: Optional[str] = None
|
|
64
|
+
category: Optional[str] = None
|
|
58
65
|
|
|
59
66
|
|
|
60
67
|
def _mask(value: str) -> str:
|
|
@@ -78,6 +78,8 @@ _default_id_set_store = InMemoryIdSetStore()
|
|
|
78
78
|
class UserContext:
|
|
79
79
|
def __init__(self):
|
|
80
80
|
self._resources: Dict[str, Any] = {}
|
|
81
|
+
self._resources["sql_log_options"] = SqlLogOptions.all()
|
|
82
|
+
self._resources["diagnostic_sql_log_sink"] = TextDiagnosticSqlLogSink()
|
|
81
83
|
self._metadata: Optional[Any] = None
|
|
82
84
|
self._user_identifier: str = ""
|
|
83
85
|
self._entities: List[Any] = []
|
|
@@ -437,6 +439,8 @@ class UserContext:
|
|
|
437
439
|
if provider:
|
|
438
440
|
from ._schema_capability import SCHEMA_CAPABILITY
|
|
439
441
|
await provider._ensure_schema(self, SCHEMA_CAPABILITY)
|
|
442
|
+
for bootstrap in self.get_resource("_teaql_generated_bootstraps") or ():
|
|
443
|
+
await bootstrap(self)
|
|
440
444
|
else:
|
|
441
445
|
raise Exception("missing schema provider")
|
|
442
446
|
|
|
@@ -618,7 +622,7 @@ class UserContext:
|
|
|
618
622
|
def sql_log_options(self) -> 'SqlLogOptions':
|
|
619
623
|
opts = self.get_resource("sql_log_options")
|
|
620
624
|
if not opts:
|
|
621
|
-
opts = SqlLogOptions.
|
|
625
|
+
opts = SqlLogOptions.all()
|
|
622
626
|
self.insert_resource("sql_log_options", opts)
|
|
623
627
|
return opts
|
|
624
628
|
|
|
@@ -645,6 +649,14 @@ class UserContext:
|
|
|
645
649
|
self.set_sql_log_options(SqlLogOptions.disabled())
|
|
646
650
|
self.clear_sql_logs()
|
|
647
651
|
|
|
652
|
+
def disable_select_sql_log(self):
|
|
653
|
+
options = self.sql_log_options()
|
|
654
|
+
self.set_sql_log_options(SqlLogOptions(False, options.mutation))
|
|
655
|
+
|
|
656
|
+
def disable_mutation_sql_log(self):
|
|
657
|
+
options = self.sql_log_options()
|
|
658
|
+
self.set_sql_log_options(SqlLogOptions(options.select, False))
|
|
659
|
+
|
|
648
660
|
def sql_logs(self) -> List['SqlLogEntry']:
|
|
649
661
|
return self._resources.get("sql_logs", [])
|
|
650
662
|
|
|
@@ -679,6 +691,10 @@ class UserContext:
|
|
|
679
691
|
ended_at = getattr(metadata, 'ended_at', started_at)
|
|
680
692
|
entry = SqlLogEntry(
|
|
681
693
|
operation=op,
|
|
694
|
+
comment=getattr(metadata, 'comment', None),
|
|
695
|
+
purpose=getattr(metadata, 'purpose', None),
|
|
696
|
+
audit_reason=getattr(metadata, 'audit_reason', None),
|
|
697
|
+
trace_path=list(getattr(metadata, 'trace_chain', [])),
|
|
682
698
|
sql=getattr(metadata, 'parameterized_sql', ''),
|
|
683
699
|
params=list(getattr(metadata, 'parameters', [])),
|
|
684
700
|
debug_sql=getattr(metadata, 'debug_query', '') or '',
|
|
@@ -719,6 +735,10 @@ class UserContext:
|
|
|
719
735
|
|
|
720
736
|
entry = SqlLogEntry(
|
|
721
737
|
operation=operation,
|
|
738
|
+
comment=None,
|
|
739
|
+
purpose=None,
|
|
740
|
+
audit_reason=None,
|
|
741
|
+
trace_path=[],
|
|
722
742
|
sql=getattr(query, 'sql', ""),
|
|
723
743
|
params=getattr(query, 'params', []),
|
|
724
744
|
debug_sql=debug_sql,
|
|
@@ -944,8 +964,8 @@ class SqlLogOperation(Enum):
|
|
|
944
964
|
|
|
945
965
|
@dataclass
|
|
946
966
|
class SqlLogOptions:
|
|
947
|
-
select: bool =
|
|
948
|
-
mutation: bool =
|
|
967
|
+
select: bool = True
|
|
968
|
+
mutation: bool = True
|
|
949
969
|
|
|
950
970
|
@classmethod
|
|
951
971
|
def disabled(cls) -> 'SqlLogOptions':
|
|
@@ -969,6 +989,10 @@ class SqlLogOptions:
|
|
|
969
989
|
@dataclass
|
|
970
990
|
class SqlLogEntry:
|
|
971
991
|
operation: SqlLogOperation
|
|
992
|
+
comment: Optional[str]
|
|
993
|
+
purpose: Optional[str]
|
|
994
|
+
audit_reason: Optional[str]
|
|
995
|
+
trace_path: List[Any]
|
|
972
996
|
sql: str
|
|
973
997
|
params: List[Any]
|
|
974
998
|
debug_sql: str
|
|
@@ -982,7 +1006,7 @@ class SqlLogEntry:
|
|
|
982
1006
|
result_summary: str
|
|
983
1007
|
|
|
984
1008
|
class DiagnosticSqlLogSink:
|
|
985
|
-
"""
|
|
1009
|
+
"""Value-bearing diagnostic SQL destination; the text sink is installed by default."""
|
|
986
1010
|
def write(self, entry: SqlLogEntry) -> None:
|
|
987
1011
|
raise NotImplementedError
|
|
988
1012
|
|
|
@@ -994,7 +1018,10 @@ class TextDiagnosticSqlLogSink(DiagnosticSqlLogSink):
|
|
|
994
1018
|
elapsed_us = int(entry.elapsed.total_seconds() * 1_000_000) if entry.elapsed else 0
|
|
995
1019
|
self._writer(
|
|
996
1020
|
f"[TeaQL SQL][{entry.operation.name.lower()}][{elapsed_us}us] "
|
|
997
|
-
f"{entry.result_summary}
|
|
1021
|
+
f"{entry.result_summary} comment={entry.comment!r} purpose={entry.purpose!r} "
|
|
1022
|
+
f"auditReason={entry.audit_reason!r} tracePath={entry.trace_path!r}\n"
|
|
1023
|
+
f"Parameterized SQL: {entry.sql} params={entry.params!r}\n"
|
|
1024
|
+
f"Debug SQL: {entry.debug_sql}"
|
|
998
1025
|
)
|
|
999
1026
|
|
|
1000
1027
|
@dataclass
|
|
@@ -8,6 +8,7 @@ class RuntimeModule:
|
|
|
8
8
|
self._dependencies: Dict[str, Any] = {}
|
|
9
9
|
self._audit_sinks: List[Any] = []
|
|
10
10
|
self._checkers: Dict[str, Any] = {}
|
|
11
|
+
self._generated_bootstraps: List[Any] = []
|
|
11
12
|
|
|
12
13
|
@classmethod
|
|
13
14
|
def new(cls) -> 'RuntimeModule':
|
|
@@ -48,6 +49,11 @@ class RuntimeModule:
|
|
|
48
49
|
self._checkers[entity] = checker
|
|
49
50
|
return self
|
|
50
51
|
|
|
52
|
+
def generated_bootstrap(self, bootstrap: Any) -> 'RuntimeModule':
|
|
53
|
+
"""Register generated typed data bootstrap; invoked only by ensure_schema()."""
|
|
54
|
+
self._generated_bootstraps.append(bootstrap)
|
|
55
|
+
return self
|
|
56
|
+
|
|
51
57
|
def and_module(self, other: 'RuntimeModule') -> 'RuntimeModule':
|
|
52
58
|
combined = RuntimeModule.new()
|
|
53
59
|
combined._entities = [*self._entities, *other._entities]
|
|
@@ -55,6 +61,10 @@ class RuntimeModule:
|
|
|
55
61
|
combined._dependencies = {**self._dependencies, **other._dependencies}
|
|
56
62
|
combined._audit_sinks = [*self._audit_sinks, *other._audit_sinks]
|
|
57
63
|
combined._checkers = {**self._checkers, **other._checkers}
|
|
64
|
+
combined._generated_bootstraps = [
|
|
65
|
+
*self._generated_bootstraps,
|
|
66
|
+
*other._generated_bootstraps,
|
|
67
|
+
]
|
|
58
68
|
combined._initial_graphs = [
|
|
59
69
|
*getattr(self, '_initial_graphs', []),
|
|
60
70
|
*getattr(other, '_initial_graphs', []),
|
|
@@ -78,6 +88,7 @@ class RuntimeModule:
|
|
|
78
88
|
context.add_root_graph(graph)
|
|
79
89
|
context.insert_resource("entities", self._entities)
|
|
80
90
|
context.insert_resource("behaviors", self._behaviors)
|
|
91
|
+
context.insert_resource("_teaql_generated_bootstraps", tuple(self._generated_bootstraps))
|
|
81
92
|
if self._checkers:
|
|
82
93
|
checkers = dict(self._checkers)
|
|
83
94
|
class GeneratedCheckerRegistry:
|
|
@@ -16,7 +16,7 @@ from teaql.data_service import (
|
|
|
16
16
|
DataServiceOperation, StreamChunk
|
|
17
17
|
)
|
|
18
18
|
from teaql.core.mutation import (
|
|
19
|
-
InsertCommand, UpdateCommand, DeleteCommand, RecoverCommand
|
|
19
|
+
InsertCommand, UpdateCommand, DeleteCommand, RecoverCommand, TraceNode
|
|
20
20
|
)
|
|
21
21
|
from .types import CompiledQuery, SqlCompileError
|
|
22
22
|
from .dialect import SqlDialect
|
|
@@ -187,10 +187,19 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
187
187
|
execution_query, retained_order, retained_empty = await self._prepare_id_set_page(context, request.query)
|
|
188
188
|
if retained_empty:
|
|
189
189
|
now = datetime.now()
|
|
190
|
+
provider = str(self.dialect.kind()).lower()
|
|
190
191
|
metadata = ExecutionMetadata(
|
|
191
|
-
backend=
|
|
192
|
+
backend=provider, operation=DataServiceOperation.Query,
|
|
192
193
|
started_at=now, ended_at=now, result_count=0,
|
|
193
|
-
trace_chain=
|
|
194
|
+
trace_chain=[
|
|
195
|
+
TraceNode(kind="operation", name="query", comment="query"),
|
|
196
|
+
TraceNode(kind="request", name=request.query.entity,
|
|
197
|
+
comment=request.query.entity),
|
|
198
|
+
*request.trace_chain,
|
|
199
|
+
TraceNode(kind="provider", name=provider, comment=provider),
|
|
200
|
+
TraceNode(kind="sql", name="select", comment="select"),
|
|
201
|
+
],
|
|
202
|
+
comment=request._comment, purpose=request._purpose,
|
|
194
203
|
)
|
|
195
204
|
if context is not None:
|
|
196
205
|
context.record_metadata_log(metadata)
|
|
@@ -227,8 +236,8 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
227
236
|
except Exception as e:
|
|
228
237
|
raise TransportError(e)
|
|
229
238
|
|
|
230
|
-
await self._enhance_relations(context, rows, request
|
|
231
|
-
await self._enhance_relation_aggregates(context, rows, request
|
|
239
|
+
await self._enhance_relations(context, rows, request)
|
|
240
|
+
await self._enhance_relation_aggregates(context, rows, request)
|
|
232
241
|
if retained_order:
|
|
233
242
|
by_id = {int(row["id"]): row for row in rows if row.get("id") is not None}
|
|
234
243
|
rows = [by_id[entity_id] for entity_id in retained_order if entity_id in by_id]
|
|
@@ -274,16 +283,25 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
274
283
|
|
|
275
284
|
end = datetime.now()
|
|
276
285
|
|
|
286
|
+
provider = str(self.dialect.kind()).lower()
|
|
287
|
+
trace_path = [
|
|
288
|
+
TraceNode(kind="operation", name="query", comment="query"),
|
|
289
|
+
TraceNode(kind="request", name=request.query.entity, comment=request.query.entity),
|
|
290
|
+
*request.trace_chain,
|
|
291
|
+
TraceNode(kind="provider", name=provider, comment=provider),
|
|
292
|
+
TraceNode(kind="sql", name="select", comment="select"),
|
|
293
|
+
]
|
|
277
294
|
metadata = ExecutionMetadata(
|
|
278
|
-
backend=
|
|
295
|
+
backend=provider,
|
|
279
296
|
operation=DataServiceOperation.Query,
|
|
280
297
|
started_at=start,
|
|
281
298
|
ended_at=end,
|
|
282
299
|
parameterized_sql=compiled.sql_with_comment(),
|
|
283
300
|
parameters=list(compiled.params),
|
|
284
301
|
result_count=len(rows),
|
|
285
|
-
trace_chain=
|
|
302
|
+
trace_chain=trace_path,
|
|
286
303
|
comment=request._comment,
|
|
304
|
+
purpose=request._purpose,
|
|
287
305
|
debug_query=compiled.debug_sql(self.dialect.kind())
|
|
288
306
|
)
|
|
289
307
|
if context is not None:
|
|
@@ -393,7 +411,8 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
393
411
|
_canonical_id_set_value(normalized))
|
|
394
412
|
return "teaql:id-set:v1:" + hashlib.sha256(repr(scope).encode("utf-8")).hexdigest()
|
|
395
413
|
|
|
396
|
-
async def _enhance_relations(self, context, parents: List[Dict[str, Any]],
|
|
414
|
+
async def _enhance_relations(self, context, parents: List[Dict[str, Any]], request: QueryRequest) -> None:
|
|
415
|
+
query = request.query
|
|
397
416
|
if not parents or not query.relations:
|
|
398
417
|
return
|
|
399
418
|
parent_desc = self.schema_provider.get_entity(query.entity)
|
|
@@ -433,7 +452,11 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
433
452
|
probe.and_filter(BinaryExpr(
|
|
434
453
|
ColumnExpr(relation.foreign_key), BinaryOp.Eq,
|
|
435
454
|
ValueExpr(Value.from_any(parent_id))))
|
|
436
|
-
|
|
455
|
+
child_trace = [*request.trace_chain, TraceNode(
|
|
456
|
+
kind="relation", name=f"{query.entity}.{load.name}",
|
|
457
|
+
comment=load.name)]
|
|
458
|
+
children.extend((await self.query(context, QueryRequest(
|
|
459
|
+
probe, child_trace, request._comment, request._purpose))).rows)
|
|
437
460
|
selected_plan = "bounded_probes"
|
|
438
461
|
probe_count = len(parent_ids)
|
|
439
462
|
else:
|
|
@@ -442,7 +465,11 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
442
465
|
ColumnExpr(relation.foreign_key), BinaryOp.In, ValueExpr(values)))
|
|
443
466
|
if limited:
|
|
444
467
|
child_query.partition_by_field(relation.foreign_key)
|
|
445
|
-
|
|
468
|
+
child_trace = [*request.trace_chain, TraceNode(
|
|
469
|
+
kind="relation", name=f"{query.entity}.{load.name}",
|
|
470
|
+
comment=load.name)]
|
|
471
|
+
children = (await self.query(context, QueryRequest(
|
|
472
|
+
child_query, child_trace, request._comment, request._purpose))).rows
|
|
446
473
|
selected_plan = "window" if limited else "batch"
|
|
447
474
|
probe_count = 0
|
|
448
475
|
for child in children:
|
|
@@ -467,7 +494,8 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
467
494
|
relation_scope.failure(error)
|
|
468
495
|
raise
|
|
469
496
|
|
|
470
|
-
async def _enhance_relation_aggregates(self, context, parents: List[Dict[str, Any]],
|
|
497
|
+
async def _enhance_relation_aggregates(self, context, parents: List[Dict[str, Any]], request: QueryRequest) -> None:
|
|
498
|
+
query = request.query
|
|
471
499
|
if not parents or not query.relation_aggregates:
|
|
472
500
|
return
|
|
473
501
|
parent_desc = self.schema_provider.get_entity(query.entity)
|
|
@@ -498,7 +526,11 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
498
526
|
values = Value.List([Value.from_any(value) for value in parent_ids])
|
|
499
527
|
child_query.and_filter(BinaryExpr(
|
|
500
528
|
ColumnExpr(relation.foreign_key), BinaryOp.In, ValueExpr(values)))
|
|
501
|
-
|
|
529
|
+
child_trace = [*request.trace_chain, TraceNode(
|
|
530
|
+
kind="relation", name=f"{query.entity}.{aggregate.relation_name}",
|
|
531
|
+
comment=aggregate.relation_name)]
|
|
532
|
+
rows = (await self.query(context, QueryRequest(
|
|
533
|
+
child_query, child_trace, request._comment, request._purpose))).rows
|
|
502
534
|
child_desc = self.schema_provider.get_entity(relation.target_entity)
|
|
503
535
|
foreign_property = child_desc.property_by_name(relation.foreign_key) if child_desc else None
|
|
504
536
|
if foreign_property and foreign_property.column_name_val != relation.foreign_key:
|
|
@@ -668,8 +700,16 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
668
700
|
f"authoritative persisted row not found for {req_data.entity}"))
|
|
669
701
|
persisted_record = persisted_rows[0]
|
|
670
702
|
|
|
703
|
+
provider = str(self.dialect.kind()).lower()
|
|
704
|
+
trace_path = [
|
|
705
|
+
TraceNode(kind="operation", name="mutation", comment="mutation"),
|
|
706
|
+
TraceNode(kind="entity", name=req_data.entity, comment=req_data.entity),
|
|
707
|
+
*request.trace_chain(),
|
|
708
|
+
TraceNode(kind="provider", name=provider, comment=provider),
|
|
709
|
+
TraceNode(kind="sql", name=op, comment=op),
|
|
710
|
+
]
|
|
671
711
|
metadata = ExecutionMetadata(
|
|
672
|
-
backend=
|
|
712
|
+
backend=provider,
|
|
673
713
|
operation={
|
|
674
714
|
"insert": DataServiceOperation.Insert,
|
|
675
715
|
"update": DataServiceOperation.Update,
|
|
@@ -681,8 +721,9 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
681
721
|
parameterized_sql=compiled.sql_with_comment(),
|
|
682
722
|
parameters=list(compiled.params),
|
|
683
723
|
affected_rows=affected_rows,
|
|
684
|
-
trace_chain=
|
|
724
|
+
trace_chain=trace_path,
|
|
685
725
|
comment=request.comment(),
|
|
726
|
+
audit_reason=request.comment(),
|
|
686
727
|
debug_query=compiled.debug_sql(self.dialect.kind())
|
|
687
728
|
)
|
|
688
729
|
if context is not None:
|
|
@@ -706,7 +747,15 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
706
747
|
kind = MutationAuditKind.RECOVERED
|
|
707
748
|
entity_id = req_data.id
|
|
708
749
|
changes = ()
|
|
709
|
-
await context.send_audit_event(RawAuditEvent(
|
|
750
|
+
await context.send_audit_event(RawAuditEvent(
|
|
751
|
+
kind,
|
|
752
|
+
req_data.entity,
|
|
753
|
+
entity_id,
|
|
754
|
+
changes,
|
|
755
|
+
tuple(request.trace_chain()),
|
|
756
|
+
context.user_identifier(),
|
|
757
|
+
context.get_resource("bootstrapCategory"),
|
|
758
|
+
))
|
|
710
759
|
return MutationResult(
|
|
711
760
|
affected_rows=affected_rows,
|
|
712
761
|
generated_values=generated_values,
|
|
@@ -843,60 +892,6 @@ class SqlDataServiceExecutor(QueryExecutor, MutationExecutor):
|
|
|
843
892
|
"CREATE TABLE IF NOT EXISTS teaql_id_space ("
|
|
844
893
|
"type_name VARCHAR(255) NOT NULL PRIMARY KEY, "
|
|
845
894
|
"current_level BIGINT NOT NULL)", []))
|
|
846
|
-
await self.ensure_initial_graphs(context)
|
|
847
|
-
|
|
848
|
-
async def ensure_initial_graphs(self, context: 'UserContext') -> None:
|
|
849
|
-
from teaql.core.mutation import InsertCommand, UpdateCommand
|
|
850
|
-
graphs = [
|
|
851
|
-
*((graph, False) for graph in context.root_graphs()),
|
|
852
|
-
*((graph, True) for graph in context.initial_graphs()),
|
|
853
|
-
]
|
|
854
|
-
for graph, reconcile in graphs:
|
|
855
|
-
entity_name = getattr(graph, 'entity', None)
|
|
856
|
-
if not entity_name:
|
|
857
|
-
continue
|
|
858
|
-
|
|
859
|
-
entity_desc = self.schema_provider.get_entity(entity_name)
|
|
860
|
-
if not entity_desc:
|
|
861
|
-
entities = context.get_resource("entities") or []
|
|
862
|
-
for e in entities:
|
|
863
|
-
if getattr(e, "_name", None) == entity_name:
|
|
864
|
-
entity_desc = e
|
|
865
|
-
break
|
|
866
|
-
if not entity_desc:
|
|
867
|
-
continue
|
|
868
|
-
|
|
869
|
-
values = getattr(graph, 'fields', getattr(graph, 'values', {}))
|
|
870
|
-
seed_id = values.get('id')
|
|
871
|
-
seed_id = seed_id.val if hasattr(seed_id, 'val') else seed_id
|
|
872
|
-
if seed_id is None:
|
|
873
|
-
raise ValueError(f"bootstrap graph {entity_name} must define id")
|
|
874
|
-
query = SelectQuery(entity_name).filter(BinaryExpr(
|
|
875
|
-
ColumnExpr('id'), BinaryOp.Eq, ValueExpr(Value.from_any(seed_id))))
|
|
876
|
-
current_rows = (await self._query(context, QueryRequest(query))).rows
|
|
877
|
-
if not current_rows:
|
|
878
|
-
mutation = InsertCommand(entity_name)
|
|
879
|
-
for k, v in values.items():
|
|
880
|
-
mutation.value(k, v)
|
|
881
|
-
version_prop = next((p for p in getattr(entity_desc, 'properties', [])
|
|
882
|
-
if getattr(p, '_is_version', False) or getattr(p, 'is_version_val', False)), None)
|
|
883
|
-
if version_prop is not None and version_prop.name not in mutation.values:
|
|
884
|
-
mutation.value(version_prop.name, 1)
|
|
885
|
-
await self.transport.execute_sql(self.dialect.compile_insert(entity_desc, mutation))
|
|
886
|
-
elif reconcile:
|
|
887
|
-
current = current_rows[0]
|
|
888
|
-
changed = {k: v for k, v in values.items() if k != 'id'
|
|
889
|
-
and current.get(k) != (v.val if hasattr(v, 'val') else v)}
|
|
890
|
-
if changed:
|
|
891
|
-
mutation = UpdateCommand(entity_name, Value.from_any(seed_id))
|
|
892
|
-
for k, v in changed.items():
|
|
893
|
-
mutation.value(k, v)
|
|
894
|
-
version = current.get('version')
|
|
895
|
-
version = version.val if hasattr(version, 'val') else version
|
|
896
|
-
if version is not None:
|
|
897
|
-
mutation.expected_version(int(version))
|
|
898
|
-
await self.transport.execute_sql(self.dialect.compile_update(entity_desc, mutation))
|
|
899
|
-
await self.ensure_id_floor(entity_name, int(seed_id))
|
|
900
895
|
|
|
901
896
|
async def begin(self, context: 'UserContext') -> 'teaql.data_service.Transaction':
|
|
902
897
|
if not isinstance(self.transport, SqlTransactionTransport):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|