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.
Files changed (57) hide show
  1. {teaql-0.2.2 → teaql-0.2.4}/PKG-INFO +1 -1
  2. {teaql-0.2.2 → teaql-0.2.4}/pyproject.toml +1 -1
  3. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/mutation.py +2 -1
  4. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/data_service/__init__.py +2 -0
  5. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/audit.py +8 -1
  6. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/context.py +32 -5
  7. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/module.py +11 -0
  8. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/sql/executor.py +64 -69
  9. {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/PKG-INFO +1 -1
  10. {teaql-0.2.2 → teaql-0.2.4}/LICENSE +0 -0
  11. {teaql-0.2.2 → teaql-0.2.4}/README.md +0 -0
  12. {teaql-0.2.2 → teaql-0.2.4}/setup.cfg +0 -0
  13. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/__init__.py +0 -0
  14. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/__init__.py +0 -0
  15. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/entity.py +0 -0
  16. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/eval.py +0 -0
  17. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/expr.py +0 -0
  18. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/graph.py +0 -0
  19. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/list.py +0 -0
  20. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/meta.py +0 -0
  21. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/query.py +0 -0
  22. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/safe_expression.py +0 -0
  23. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/value.py +0 -0
  24. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/core/xls.py +0 -0
  25. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/__init__.py +0 -0
  26. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/mysql/__init__.py +0 -0
  27. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/mysql/dialect.py +0 -0
  28. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/mysql/transport.py +0 -0
  29. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/postgres/__init__.py +0 -0
  30. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/postgres/dialect.py +0 -0
  31. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/postgres/transport.py +0 -0
  32. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/redis/__init__.py +0 -0
  33. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/redis/remote.py +0 -0
  34. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/redis/store.py +0 -0
  35. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/sqlite/__init__.py +0 -0
  36. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/sqlite/dialect.py +0 -0
  37. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/sqlite/transport.py +0 -0
  38. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/provider/tfp_client/__init__.py +0 -0
  39. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/py.typed +0 -0
  40. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/__init__.py +0 -0
  41. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/_schema_capability.py +0 -0
  42. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/builtin-messages-v1.json +0 -0
  43. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/env.py +0 -0
  44. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/i18n.py +0 -0
  45. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/opentelemetry.py +0 -0
  46. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/store.py +0 -0
  47. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/telemetry.py +0 -0
  48. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/runtime/tools.py +0 -0
  49. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/sql/__init__.py +0 -0
  50. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/sql/dialect.py +0 -0
  51. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/sql/types.py +0 -0
  52. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/web_integration/__init__.py +0 -0
  53. {teaql-0.2.2 → teaql-0.2.4}/src/teaql/web_integration/fastapi/__init__.py +0 -0
  54. {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/SOURCES.txt +0 -0
  55. {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/dependency_links.txt +0 -0
  56. {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/requires.txt +0 -0
  57. {teaql-0.2.2 → teaql-0.2.4}/src/teaql.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teaql
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: TeaQL Runtime and Data Service for Python
5
5
  Author: TeaQL
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "teaql"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "TeaQL Runtime and Data Service for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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(self.kind, self.entity, self.entity_id, tuple(fields), self.trace_chain)
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.disabled()
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 = False
948
- mutation: bool = False
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
- """Explicit value-bearing diagnostic SQL destination; never installed by default."""
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}\n{entry.debug_sql}"
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="sql", operation=DataServiceOperation.Query,
192
+ backend=provider, operation=DataServiceOperation.Query,
192
193
  started_at=now, ended_at=now, result_count=0,
193
- trace_chain=request.trace_chain, comment=request._comment,
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.query)
231
- await self._enhance_relation_aggregates(context, rows, request.query)
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="sql",
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=request.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]], query: SelectQuery) -> None:
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
- children.extend((await self.query(context, QueryRequest(probe))).rows)
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
- children = (await self.query(context, QueryRequest(child_query))).rows
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]], query: SelectQuery) -> None:
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
- rows = (await self.query(context, QueryRequest(child_query))).rows
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="sql",
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=request.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(kind, req_data.entity, entity_id, changes, tuple(request.trace_chain())))
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teaql
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: TeaQL Runtime and Data Service for Python
5
5
  Author: TeaQL
6
6
  License-Expression: Apache-2.0
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