teaql 0.2.2__tar.gz → 0.2.3__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.3}/PKG-INFO +1 -1
- {teaql-0.2.2 → teaql-0.2.3}/pyproject.toml +1 -1
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/mutation.py +2 -1
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/data_service/__init__.py +2 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/context.py +30 -5
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/sql/executor.py +55 -14
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql.egg-info/PKG-INFO +1 -1
- {teaql-0.2.2 → teaql-0.2.3}/LICENSE +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/README.md +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/setup.cfg +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/entity.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/eval.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/expr.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/graph.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/list.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/meta.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/query.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/safe_expression.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/value.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/core/xls.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/mysql/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/mysql/dialect.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/mysql/transport.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/postgres/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/postgres/dialect.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/postgres/transport.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/redis/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/redis/remote.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/redis/store.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/sqlite/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/sqlite/dialect.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/sqlite/transport.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/provider/tfp_client/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/py.typed +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/_schema_capability.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/audit.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/builtin-messages-v1.json +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/env.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/i18n.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/module.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/opentelemetry.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/store.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/telemetry.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/runtime/tools.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/sql/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/sql/dialect.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/sql/types.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/web_integration/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql/web_integration/fastapi/__init__.py +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql.egg-info/SOURCES.txt +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql.egg-info/dependency_links.txt +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/src/teaql.egg-info/requires.txt +0 -0
- {teaql-0.2.2 → teaql-0.2.3}/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
|
|
|
@@ -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] = []
|
|
@@ -618,7 +620,7 @@ class UserContext:
|
|
|
618
620
|
def sql_log_options(self) -> 'SqlLogOptions':
|
|
619
621
|
opts = self.get_resource("sql_log_options")
|
|
620
622
|
if not opts:
|
|
621
|
-
opts = SqlLogOptions.
|
|
623
|
+
opts = SqlLogOptions.all()
|
|
622
624
|
self.insert_resource("sql_log_options", opts)
|
|
623
625
|
return opts
|
|
624
626
|
|
|
@@ -645,6 +647,14 @@ class UserContext:
|
|
|
645
647
|
self.set_sql_log_options(SqlLogOptions.disabled())
|
|
646
648
|
self.clear_sql_logs()
|
|
647
649
|
|
|
650
|
+
def disable_select_sql_log(self):
|
|
651
|
+
options = self.sql_log_options()
|
|
652
|
+
self.set_sql_log_options(SqlLogOptions(False, options.mutation))
|
|
653
|
+
|
|
654
|
+
def disable_mutation_sql_log(self):
|
|
655
|
+
options = self.sql_log_options()
|
|
656
|
+
self.set_sql_log_options(SqlLogOptions(options.select, False))
|
|
657
|
+
|
|
648
658
|
def sql_logs(self) -> List['SqlLogEntry']:
|
|
649
659
|
return self._resources.get("sql_logs", [])
|
|
650
660
|
|
|
@@ -679,6 +689,10 @@ class UserContext:
|
|
|
679
689
|
ended_at = getattr(metadata, 'ended_at', started_at)
|
|
680
690
|
entry = SqlLogEntry(
|
|
681
691
|
operation=op,
|
|
692
|
+
comment=getattr(metadata, 'comment', None),
|
|
693
|
+
purpose=getattr(metadata, 'purpose', None),
|
|
694
|
+
audit_reason=getattr(metadata, 'audit_reason', None),
|
|
695
|
+
trace_path=list(getattr(metadata, 'trace_chain', [])),
|
|
682
696
|
sql=getattr(metadata, 'parameterized_sql', ''),
|
|
683
697
|
params=list(getattr(metadata, 'parameters', [])),
|
|
684
698
|
debug_sql=getattr(metadata, 'debug_query', '') or '',
|
|
@@ -719,6 +733,10 @@ class UserContext:
|
|
|
719
733
|
|
|
720
734
|
entry = SqlLogEntry(
|
|
721
735
|
operation=operation,
|
|
736
|
+
comment=None,
|
|
737
|
+
purpose=None,
|
|
738
|
+
audit_reason=None,
|
|
739
|
+
trace_path=[],
|
|
722
740
|
sql=getattr(query, 'sql', ""),
|
|
723
741
|
params=getattr(query, 'params', []),
|
|
724
742
|
debug_sql=debug_sql,
|
|
@@ -944,8 +962,8 @@ class SqlLogOperation(Enum):
|
|
|
944
962
|
|
|
945
963
|
@dataclass
|
|
946
964
|
class SqlLogOptions:
|
|
947
|
-
select: bool =
|
|
948
|
-
mutation: bool =
|
|
965
|
+
select: bool = True
|
|
966
|
+
mutation: bool = True
|
|
949
967
|
|
|
950
968
|
@classmethod
|
|
951
969
|
def disabled(cls) -> 'SqlLogOptions':
|
|
@@ -969,6 +987,10 @@ class SqlLogOptions:
|
|
|
969
987
|
@dataclass
|
|
970
988
|
class SqlLogEntry:
|
|
971
989
|
operation: SqlLogOperation
|
|
990
|
+
comment: Optional[str]
|
|
991
|
+
purpose: Optional[str]
|
|
992
|
+
audit_reason: Optional[str]
|
|
993
|
+
trace_path: List[Any]
|
|
972
994
|
sql: str
|
|
973
995
|
params: List[Any]
|
|
974
996
|
debug_sql: str
|
|
@@ -982,7 +1004,7 @@ class SqlLogEntry:
|
|
|
982
1004
|
result_summary: str
|
|
983
1005
|
|
|
984
1006
|
class DiagnosticSqlLogSink:
|
|
985
|
-
"""
|
|
1007
|
+
"""Value-bearing diagnostic SQL destination; the text sink is installed by default."""
|
|
986
1008
|
def write(self, entry: SqlLogEntry) -> None:
|
|
987
1009
|
raise NotImplementedError
|
|
988
1010
|
|
|
@@ -994,7 +1016,10 @@ class TextDiagnosticSqlLogSink(DiagnosticSqlLogSink):
|
|
|
994
1016
|
elapsed_us = int(entry.elapsed.total_seconds() * 1_000_000) if entry.elapsed else 0
|
|
995
1017
|
self._writer(
|
|
996
1018
|
f"[TeaQL SQL][{entry.operation.name.lower()}][{elapsed_us}us] "
|
|
997
|
-
f"{entry.result_summary}
|
|
1019
|
+
f"{entry.result_summary} comment={entry.comment!r} purpose={entry.purpose!r} "
|
|
1020
|
+
f"auditReason={entry.audit_reason!r} tracePath={entry.trace_path!r}\n"
|
|
1021
|
+
f"Parameterized SQL: {entry.sql} params={entry.params!r}\n"
|
|
1022
|
+
f"Debug SQL: {entry.debug_sql}"
|
|
998
1023
|
)
|
|
999
1024
|
|
|
1000
1025
|
@dataclass
|
|
@@ -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:
|
|
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
|
|
File without changes
|
|
File without changes
|