sqlmesh 0.211.1.dev4__py3-none-any.whl → 0.211.2.dev2__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.
- sqlmesh/_version.py +2 -2
- sqlmesh/core/console.py +65 -24
- sqlmesh/core/engine_adapter/athena.py +1 -0
- sqlmesh/core/engine_adapter/base.py +62 -10
- sqlmesh/core/engine_adapter/base_postgres.py +1 -0
- sqlmesh/core/engine_adapter/bigquery.py +19 -0
- sqlmesh/core/engine_adapter/clickhouse.py +3 -1
- sqlmesh/core/engine_adapter/duckdb.py +2 -0
- sqlmesh/core/engine_adapter/mssql.py +1 -0
- sqlmesh/core/engine_adapter/mysql.py +1 -0
- sqlmesh/core/engine_adapter/postgres.py +1 -0
- sqlmesh/core/engine_adapter/redshift.py +3 -1
- sqlmesh/core/engine_adapter/snowflake.py +3 -0
- sqlmesh/core/engine_adapter/spark.py +2 -0
- sqlmesh/core/engine_adapter/trino.py +3 -0
- sqlmesh/core/scheduler.py +7 -0
- sqlmesh/core/snapshot/__init__.py +1 -0
- sqlmesh/core/snapshot/definition.py +10 -2
- sqlmesh/core/snapshot/evaluator.py +24 -14
- sqlmesh/core/snapshot/execution_tracker.py +97 -0
- sqlmesh/core/state_sync/db/environment.py +2 -0
- sqlmesh/core/state_sync/db/interval.py +2 -0
- sqlmesh/core/state_sync/db/migrator.py +3 -1
- sqlmesh/core/state_sync/db/snapshot.py +2 -0
- sqlmesh/core/state_sync/db/version.py +1 -0
- {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/METADATA +2 -1
- {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/RECORD +35 -34
- sqlmesh_dbt/cli.py +13 -2
- sqlmesh_dbt/operations.py +7 -2
- sqlmesh_dbt/selectors.py +13 -7
- web/server/console.py +2 -0
- {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/WHEEL +0 -0
- {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/top_level.txt +0 -0
sqlmesh/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.211.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 211,
|
|
31
|
+
__version__ = version = '0.211.2.dev2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 211, 2, 'dev2')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
sqlmesh/core/console.py
CHANGED
|
@@ -7,6 +7,7 @@ import unittest
|
|
|
7
7
|
import uuid
|
|
8
8
|
import logging
|
|
9
9
|
import textwrap
|
|
10
|
+
from humanize import metric, naturalsize
|
|
10
11
|
from itertools import zip_longest
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from hyperscript import h
|
|
@@ -39,6 +40,7 @@ from sqlmesh.core.snapshot import (
|
|
|
39
40
|
SnapshotInfoLike,
|
|
40
41
|
)
|
|
41
42
|
from sqlmesh.core.snapshot.definition import Interval, Intervals, SnapshotTableInfo
|
|
43
|
+
from sqlmesh.core.snapshot.execution_tracker import QueryExecutionStats
|
|
42
44
|
from sqlmesh.core.test import ModelTest
|
|
43
45
|
from sqlmesh.utils import rich as srich
|
|
44
46
|
from sqlmesh.utils import Verbosity
|
|
@@ -439,6 +441,7 @@ class Console(
|
|
|
439
441
|
num_audits_passed: int,
|
|
440
442
|
num_audits_failed: int,
|
|
441
443
|
audit_only: bool = False,
|
|
444
|
+
execution_stats: t.Optional[QueryExecutionStats] = None,
|
|
442
445
|
auto_restatement_triggers: t.Optional[t.List[SnapshotId]] = None,
|
|
443
446
|
) -> None:
|
|
444
447
|
"""Updates the snapshot evaluation progress."""
|
|
@@ -587,6 +590,7 @@ class NoopConsole(Console):
|
|
|
587
590
|
num_audits_passed: int,
|
|
588
591
|
num_audits_failed: int,
|
|
589
592
|
audit_only: bool = False,
|
|
593
|
+
execution_stats: t.Optional[QueryExecutionStats] = None,
|
|
590
594
|
auto_restatement_triggers: t.Optional[t.List[SnapshotId]] = None,
|
|
591
595
|
) -> None:
|
|
592
596
|
pass
|
|
@@ -1032,7 +1036,9 @@ class TerminalConsole(Console):
|
|
|
1032
1036
|
|
|
1033
1037
|
# determine column widths
|
|
1034
1038
|
self.evaluation_column_widths["annotation"] = (
|
|
1035
|
-
_calculate_annotation_str_len(
|
|
1039
|
+
_calculate_annotation_str_len(
|
|
1040
|
+
batched_intervals, self.AUDIT_PADDING, len(" (123.4m rows, 123.4 KiB)")
|
|
1041
|
+
)
|
|
1036
1042
|
+ 3 # brackets and opening escape backslash
|
|
1037
1043
|
)
|
|
1038
1044
|
self.evaluation_column_widths["name"] = max(
|
|
@@ -1077,6 +1083,7 @@ class TerminalConsole(Console):
|
|
|
1077
1083
|
num_audits_passed: int,
|
|
1078
1084
|
num_audits_failed: int,
|
|
1079
1085
|
audit_only: bool = False,
|
|
1086
|
+
execution_stats: t.Optional[QueryExecutionStats] = None,
|
|
1080
1087
|
auto_restatement_triggers: t.Optional[t.List[SnapshotId]] = None,
|
|
1081
1088
|
) -> None:
|
|
1082
1089
|
"""Update the snapshot evaluation progress."""
|
|
@@ -1097,7 +1104,7 @@ class TerminalConsole(Console):
|
|
|
1097
1104
|
).ljust(self.evaluation_column_widths["name"])
|
|
1098
1105
|
|
|
1099
1106
|
annotation = _create_evaluation_model_annotation(
|
|
1100
|
-
snapshot, _format_evaluation_model_interval(snapshot, interval)
|
|
1107
|
+
snapshot, _format_evaluation_model_interval(snapshot, interval), execution_stats
|
|
1101
1108
|
)
|
|
1102
1109
|
audits_str = ""
|
|
1103
1110
|
if num_audits_passed:
|
|
@@ -3668,6 +3675,7 @@ class DatabricksMagicConsole(CaptureTerminalConsole):
|
|
|
3668
3675
|
num_audits_passed: int,
|
|
3669
3676
|
num_audits_failed: int,
|
|
3670
3677
|
audit_only: bool = False,
|
|
3678
|
+
execution_stats: t.Optional[QueryExecutionStats] = None,
|
|
3671
3679
|
auto_restatement_triggers: t.Optional[t.List[SnapshotId]] = None,
|
|
3672
3680
|
) -> None:
|
|
3673
3681
|
view_name, loaded_batches = self.evaluation_batch_progress[snapshot.snapshot_id]
|
|
@@ -3838,6 +3846,7 @@ class DebuggerTerminalConsole(TerminalConsole):
|
|
|
3838
3846
|
num_audits_passed: int,
|
|
3839
3847
|
num_audits_failed: int,
|
|
3840
3848
|
audit_only: bool = False,
|
|
3849
|
+
execution_stats: t.Optional[QueryExecutionStats] = None,
|
|
3841
3850
|
auto_restatement_triggers: t.Optional[t.List[SnapshotId]] = None,
|
|
3842
3851
|
) -> None:
|
|
3843
3852
|
message = f"Evaluated {snapshot.name} | batch={batch_idx} | duration={duration_ms}ms | num_audits_passed={num_audits_passed} | num_audits_failed={num_audits_failed}"
|
|
@@ -4169,33 +4178,62 @@ def _format_evaluation_model_interval(snapshot: Snapshot, interval: Interval) ->
|
|
|
4169
4178
|
return ""
|
|
4170
4179
|
|
|
4171
4180
|
|
|
4172
|
-
def _create_evaluation_model_annotation(
|
|
4181
|
+
def _create_evaluation_model_annotation(
|
|
4182
|
+
snapshot: Snapshot,
|
|
4183
|
+
interval_info: t.Optional[str],
|
|
4184
|
+
execution_stats: t.Optional[QueryExecutionStats],
|
|
4185
|
+
) -> str:
|
|
4186
|
+
annotation = None
|
|
4187
|
+
execution_stats_str = ""
|
|
4188
|
+
if execution_stats:
|
|
4189
|
+
rows_processed = execution_stats.total_rows_processed
|
|
4190
|
+
if rows_processed:
|
|
4191
|
+
# 1.00 and 1.0 to 1
|
|
4192
|
+
rows_processed_str = metric(rows_processed).replace(".00", "").replace(".0", "")
|
|
4193
|
+
execution_stats_str += f"{rows_processed_str} row{'s' if rows_processed > 1 else ''}"
|
|
4194
|
+
|
|
4195
|
+
bytes_processed = execution_stats.total_bytes_processed
|
|
4196
|
+
execution_stats_str += (
|
|
4197
|
+
f"{', ' if execution_stats_str else ''}{naturalsize(bytes_processed, binary=True)}"
|
|
4198
|
+
if bytes_processed
|
|
4199
|
+
else ""
|
|
4200
|
+
)
|
|
4201
|
+
execution_stats_str = f" ({execution_stats_str})" if execution_stats_str else ""
|
|
4202
|
+
|
|
4173
4203
|
if snapshot.is_audit:
|
|
4174
|
-
|
|
4175
|
-
if snapshot.is_model
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4204
|
+
annotation = "run standalone audit"
|
|
4205
|
+
if snapshot.is_model:
|
|
4206
|
+
if snapshot.model.kind.is_external:
|
|
4207
|
+
annotation = "run external audits"
|
|
4208
|
+
if snapshot.model.kind.is_view:
|
|
4209
|
+
annotation = "recreate view"
|
|
4210
|
+
if snapshot.model.kind.is_seed:
|
|
4211
|
+
annotation = f"insert seed file{execution_stats_str}"
|
|
4212
|
+
if snapshot.model.kind.is_full:
|
|
4213
|
+
annotation = f"full refresh{execution_stats_str}"
|
|
4214
|
+
if snapshot.model.kind.is_incremental_by_unique_key:
|
|
4215
|
+
annotation = f"insert/update rows{execution_stats_str}"
|
|
4216
|
+
if snapshot.model.kind.is_incremental_by_partition:
|
|
4217
|
+
annotation = f"insert partitions{execution_stats_str}"
|
|
4218
|
+
|
|
4219
|
+
if annotation:
|
|
4220
|
+
return annotation
|
|
4221
|
+
|
|
4222
|
+
return f"{interval_info}{execution_stats_str}" if interval_info else ""
|
|
4223
|
+
|
|
4224
|
+
|
|
4225
|
+
def _calculate_interval_str_len(
|
|
4226
|
+
snapshot: Snapshot,
|
|
4227
|
+
intervals: t.List[Interval],
|
|
4228
|
+
execution_stats: t.Optional[QueryExecutionStats] = None,
|
|
4229
|
+
) -> int:
|
|
4192
4230
|
interval_str_len = 0
|
|
4193
4231
|
for interval in intervals:
|
|
4194
4232
|
interval_str_len = max(
|
|
4195
4233
|
interval_str_len,
|
|
4196
4234
|
len(
|
|
4197
4235
|
_create_evaluation_model_annotation(
|
|
4198
|
-
snapshot, _format_evaluation_model_interval(snapshot, interval)
|
|
4236
|
+
snapshot, _format_evaluation_model_interval(snapshot, interval), execution_stats
|
|
4199
4237
|
)
|
|
4200
4238
|
),
|
|
4201
4239
|
)
|
|
@@ -4248,13 +4286,16 @@ def _calculate_audit_str_len(snapshot: Snapshot, audit_padding: int = 0) -> int:
|
|
|
4248
4286
|
|
|
4249
4287
|
|
|
4250
4288
|
def _calculate_annotation_str_len(
|
|
4251
|
-
batched_intervals: t.Dict[Snapshot, t.List[Interval]],
|
|
4289
|
+
batched_intervals: t.Dict[Snapshot, t.List[Interval]],
|
|
4290
|
+
audit_padding: int = 0,
|
|
4291
|
+
execution_stats_len: int = 0,
|
|
4252
4292
|
) -> int:
|
|
4253
4293
|
annotation_str_len = 0
|
|
4254
4294
|
for snapshot, intervals in batched_intervals.items():
|
|
4255
4295
|
annotation_str_len = max(
|
|
4256
4296
|
annotation_str_len,
|
|
4257
4297
|
_calculate_interval_str_len(snapshot, intervals)
|
|
4258
|
-
+ _calculate_audit_str_len(snapshot, audit_padding)
|
|
4298
|
+
+ _calculate_audit_str_len(snapshot, audit_padding)
|
|
4299
|
+
+ execution_stats_len,
|
|
4259
4300
|
)
|
|
4260
4301
|
return annotation_str_len
|
|
@@ -45,6 +45,7 @@ class AthenaEngineAdapter(PandasNativeFetchDFSupportMixin, RowDiffMixin):
|
|
|
45
45
|
# >>> self._execute('/* test */ DESCRIBE foo')
|
|
46
46
|
# pyathena.error.OperationalError: FAILED: ParseException line 1:0 cannot recognize input near '/' '*' 'test'
|
|
47
47
|
ATTACH_CORRELATION_ID = False
|
|
48
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = True
|
|
48
49
|
SUPPORTED_DROP_CASCADE_OBJECT_KINDS = ["DATABASE", "SCHEMA"]
|
|
49
50
|
|
|
50
51
|
def __init__(
|
|
@@ -40,6 +40,7 @@ from sqlmesh.core.engine_adapter.shared import (
|
|
|
40
40
|
)
|
|
41
41
|
from sqlmesh.core.model.kind import TimeColumn
|
|
42
42
|
from sqlmesh.core.schema_diff import SchemaDiffer, TableAlterOperation
|
|
43
|
+
from sqlmesh.core.snapshot.execution_tracker import QueryExecutionTracker
|
|
43
44
|
from sqlmesh.utils import (
|
|
44
45
|
CorrelationId,
|
|
45
46
|
columns_to_types_all_known,
|
|
@@ -117,6 +118,7 @@ class EngineAdapter:
|
|
|
117
118
|
QUOTE_IDENTIFIERS_IN_VIEWS = True
|
|
118
119
|
MAX_IDENTIFIER_LENGTH: t.Optional[int] = None
|
|
119
120
|
ATTACH_CORRELATION_ID = True
|
|
121
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = False
|
|
120
122
|
|
|
121
123
|
def __init__(
|
|
122
124
|
self,
|
|
@@ -133,6 +135,7 @@ class EngineAdapter:
|
|
|
133
135
|
shared_connection: bool = False,
|
|
134
136
|
correlation_id: t.Optional[CorrelationId] = None,
|
|
135
137
|
schema_differ_overrides: t.Optional[t.Dict[str, t.Any]] = None,
|
|
138
|
+
query_execution_tracker: t.Optional[QueryExecutionTracker] = None,
|
|
136
139
|
**kwargs: t.Any,
|
|
137
140
|
):
|
|
138
141
|
self.dialect = dialect.lower() or self.DIALECT
|
|
@@ -156,11 +159,16 @@ class EngineAdapter:
|
|
|
156
159
|
self._multithreaded = multithreaded
|
|
157
160
|
self.correlation_id = correlation_id
|
|
158
161
|
self._schema_differ_overrides = schema_differ_overrides
|
|
162
|
+
self._query_execution_tracker = query_execution_tracker
|
|
159
163
|
|
|
160
164
|
def with_settings(self, **kwargs: t.Any) -> EngineAdapter:
|
|
161
165
|
extra_kwargs = {
|
|
162
166
|
"null_connection": True,
|
|
163
167
|
"execute_log_level": kwargs.pop("execute_log_level", self._execute_log_level),
|
|
168
|
+
"correlation_id": kwargs.pop("correlation_id", self.correlation_id),
|
|
169
|
+
"query_execution_tracker": kwargs.pop(
|
|
170
|
+
"query_execution_tracker", self._query_execution_tracker
|
|
171
|
+
),
|
|
164
172
|
**self._extra_config,
|
|
165
173
|
**kwargs,
|
|
166
174
|
}
|
|
@@ -854,6 +862,7 @@ class EngineAdapter:
|
|
|
854
862
|
table_description: t.Optional[str] = None,
|
|
855
863
|
column_descriptions: t.Optional[t.Dict[str, str]] = None,
|
|
856
864
|
table_kind: t.Optional[str] = None,
|
|
865
|
+
track_rows_processed: bool = True,
|
|
857
866
|
**kwargs: t.Any,
|
|
858
867
|
) -> None:
|
|
859
868
|
table = exp.to_table(table_name)
|
|
@@ -899,11 +908,15 @@ class EngineAdapter:
|
|
|
899
908
|
replace=replace,
|
|
900
909
|
table_description=table_description,
|
|
901
910
|
table_kind=table_kind,
|
|
911
|
+
track_rows_processed=track_rows_processed,
|
|
902
912
|
**kwargs,
|
|
903
913
|
)
|
|
904
914
|
else:
|
|
905
915
|
self._insert_append_query(
|
|
906
|
-
table_name,
|
|
916
|
+
table_name,
|
|
917
|
+
query,
|
|
918
|
+
target_columns_to_types or self.columns(table),
|
|
919
|
+
track_rows_processed=track_rows_processed,
|
|
907
920
|
)
|
|
908
921
|
|
|
909
922
|
# Register comments with commands if the engine supports comments and we weren't able to
|
|
@@ -927,6 +940,7 @@ class EngineAdapter:
|
|
|
927
940
|
table_description: t.Optional[str] = None,
|
|
928
941
|
column_descriptions: t.Optional[t.Dict[str, str]] = None,
|
|
929
942
|
table_kind: t.Optional[str] = None,
|
|
943
|
+
track_rows_processed: bool = True,
|
|
930
944
|
**kwargs: t.Any,
|
|
931
945
|
) -> None:
|
|
932
946
|
self.execute(
|
|
@@ -943,7 +957,8 @@ class EngineAdapter:
|
|
|
943
957
|
),
|
|
944
958
|
table_kind=table_kind,
|
|
945
959
|
**kwargs,
|
|
946
|
-
)
|
|
960
|
+
),
|
|
961
|
+
track_rows_processed=track_rows_processed,
|
|
947
962
|
)
|
|
948
963
|
|
|
949
964
|
def _build_create_table_exp(
|
|
@@ -1431,6 +1446,7 @@ class EngineAdapter:
|
|
|
1431
1446
|
table_name: TableName,
|
|
1432
1447
|
query_or_df: QueryOrDF,
|
|
1433
1448
|
target_columns_to_types: t.Optional[t.Dict[str, exp.DataType]] = None,
|
|
1449
|
+
track_rows_processed: bool = True,
|
|
1434
1450
|
source_columns: t.Optional[t.List[str]] = None,
|
|
1435
1451
|
) -> None:
|
|
1436
1452
|
source_queries, target_columns_to_types = self._get_source_queries_and_columns_to_types(
|
|
@@ -1439,19 +1455,27 @@ class EngineAdapter:
|
|
|
1439
1455
|
target_table=table_name,
|
|
1440
1456
|
source_columns=source_columns,
|
|
1441
1457
|
)
|
|
1442
|
-
self._insert_append_source_queries(
|
|
1458
|
+
self._insert_append_source_queries(
|
|
1459
|
+
table_name, source_queries, target_columns_to_types, track_rows_processed
|
|
1460
|
+
)
|
|
1443
1461
|
|
|
1444
1462
|
def _insert_append_source_queries(
|
|
1445
1463
|
self,
|
|
1446
1464
|
table_name: TableName,
|
|
1447
1465
|
source_queries: t.List[SourceQuery],
|
|
1448
1466
|
target_columns_to_types: t.Optional[t.Dict[str, exp.DataType]] = None,
|
|
1467
|
+
track_rows_processed: bool = True,
|
|
1449
1468
|
) -> None:
|
|
1450
1469
|
with self.transaction(condition=len(source_queries) > 0):
|
|
1451
1470
|
target_columns_to_types = target_columns_to_types or self.columns(table_name)
|
|
1452
1471
|
for source_query in source_queries:
|
|
1453
1472
|
with source_query as query:
|
|
1454
|
-
self._insert_append_query(
|
|
1473
|
+
self._insert_append_query(
|
|
1474
|
+
table_name,
|
|
1475
|
+
query,
|
|
1476
|
+
target_columns_to_types,
|
|
1477
|
+
track_rows_processed=track_rows_processed,
|
|
1478
|
+
)
|
|
1455
1479
|
|
|
1456
1480
|
def _insert_append_query(
|
|
1457
1481
|
self,
|
|
@@ -1459,10 +1483,14 @@ class EngineAdapter:
|
|
|
1459
1483
|
query: Query,
|
|
1460
1484
|
target_columns_to_types: t.Dict[str, exp.DataType],
|
|
1461
1485
|
order_projections: bool = True,
|
|
1486
|
+
track_rows_processed: bool = True,
|
|
1462
1487
|
) -> None:
|
|
1463
1488
|
if order_projections:
|
|
1464
1489
|
query = self._order_projections_and_filter(query, target_columns_to_types)
|
|
1465
|
-
self.execute(
|
|
1490
|
+
self.execute(
|
|
1491
|
+
exp.insert(query, table_name, columns=list(target_columns_to_types)),
|
|
1492
|
+
track_rows_processed=track_rows_processed,
|
|
1493
|
+
)
|
|
1466
1494
|
|
|
1467
1495
|
def insert_overwrite_by_partition(
|
|
1468
1496
|
self,
|
|
@@ -1604,7 +1632,7 @@ class EngineAdapter:
|
|
|
1604
1632
|
)
|
|
1605
1633
|
if insert_overwrite_strategy.is_replace_where:
|
|
1606
1634
|
insert_exp.set("where", where or exp.true())
|
|
1607
|
-
self.execute(insert_exp)
|
|
1635
|
+
self.execute(insert_exp, track_rows_processed=True)
|
|
1608
1636
|
|
|
1609
1637
|
def update_table(
|
|
1610
1638
|
self,
|
|
@@ -1625,7 +1653,9 @@ class EngineAdapter:
|
|
|
1625
1653
|
using = exp.alias_(
|
|
1626
1654
|
exp.Subquery(this=query), alias=MERGE_SOURCE_ALIAS, copy=False, table=True
|
|
1627
1655
|
)
|
|
1628
|
-
self.execute(
|
|
1656
|
+
self.execute(
|
|
1657
|
+
exp.Merge(this=this, using=using, on=on, whens=whens), track_rows_processed=True
|
|
1658
|
+
)
|
|
1629
1659
|
|
|
1630
1660
|
def scd_type_2_by_time(
|
|
1631
1661
|
self,
|
|
@@ -2374,6 +2404,7 @@ class EngineAdapter:
|
|
|
2374
2404
|
expressions: t.Union[str, exp.Expression, t.Sequence[exp.Expression]],
|
|
2375
2405
|
ignore_unsupported_errors: bool = False,
|
|
2376
2406
|
quote_identifiers: bool = True,
|
|
2407
|
+
track_rows_processed: bool = False,
|
|
2377
2408
|
**kwargs: t.Any,
|
|
2378
2409
|
) -> None:
|
|
2379
2410
|
"""Execute a sql query."""
|
|
@@ -2395,7 +2426,7 @@ class EngineAdapter:
|
|
|
2395
2426
|
expression=e if isinstance(e, exp.Expression) else None,
|
|
2396
2427
|
quote_identifiers=quote_identifiers,
|
|
2397
2428
|
)
|
|
2398
|
-
self._execute(sql, **kwargs)
|
|
2429
|
+
self._execute(sql, track_rows_processed, **kwargs)
|
|
2399
2430
|
|
|
2400
2431
|
def _attach_correlation_id(self, sql: str) -> str:
|
|
2401
2432
|
if self.ATTACH_CORRELATION_ID and self.correlation_id:
|
|
@@ -2420,9 +2451,29 @@ class EngineAdapter:
|
|
|
2420
2451
|
|
|
2421
2452
|
logger.log(self._execute_log_level, "Executing SQL: %s", sql_to_log)
|
|
2422
2453
|
|
|
2423
|
-
def
|
|
2454
|
+
def _record_execution_stats(
|
|
2455
|
+
self, sql: str, rowcount: t.Optional[int] = None, bytes_processed: t.Optional[int] = None
|
|
2456
|
+
) -> None:
|
|
2457
|
+
if self._query_execution_tracker:
|
|
2458
|
+
self._query_execution_tracker.record_execution(sql, rowcount, bytes_processed)
|
|
2459
|
+
|
|
2460
|
+
def _execute(self, sql: str, track_rows_processed: bool = False, **kwargs: t.Any) -> None:
|
|
2424
2461
|
self.cursor.execute(sql, **kwargs)
|
|
2425
2462
|
|
|
2463
|
+
if (
|
|
2464
|
+
self.SUPPORTS_QUERY_EXECUTION_TRACKING
|
|
2465
|
+
and track_rows_processed
|
|
2466
|
+
and self._query_execution_tracker
|
|
2467
|
+
and self._query_execution_tracker.is_tracking()
|
|
2468
|
+
):
|
|
2469
|
+
if (
|
|
2470
|
+
rowcount := getattr(self.cursor, "rowcount", None)
|
|
2471
|
+
) is not None and rowcount is not None:
|
|
2472
|
+
try:
|
|
2473
|
+
self._record_execution_stats(sql, int(rowcount))
|
|
2474
|
+
except (TypeError, ValueError):
|
|
2475
|
+
return
|
|
2476
|
+
|
|
2426
2477
|
@contextlib.contextmanager
|
|
2427
2478
|
def temp_table(
|
|
2428
2479
|
self,
|
|
@@ -2467,6 +2518,7 @@ class EngineAdapter:
|
|
|
2467
2518
|
exists=True,
|
|
2468
2519
|
table_description=None,
|
|
2469
2520
|
column_descriptions=None,
|
|
2521
|
+
track_rows_processed=False,
|
|
2470
2522
|
**kwargs,
|
|
2471
2523
|
)
|
|
2472
2524
|
|
|
@@ -2718,7 +2770,7 @@ class EngineAdapter:
|
|
|
2718
2770
|
insert_statement.set("where", delete_filter)
|
|
2719
2771
|
insert_statement.set("this", exp.to_table(target_table))
|
|
2720
2772
|
|
|
2721
|
-
self.execute(insert_statement)
|
|
2773
|
+
self.execute(insert_statement, track_rows_processed=True)
|
|
2722
2774
|
finally:
|
|
2723
2775
|
self.drop_table(temp_table)
|
|
2724
2776
|
|
|
@@ -24,6 +24,7 @@ class BasePostgresEngineAdapter(EngineAdapter):
|
|
|
24
24
|
DEFAULT_BATCH_SIZE = 400
|
|
25
25
|
COMMENT_CREATION_TABLE = CommentCreationTable.COMMENT_COMMAND_ONLY
|
|
26
26
|
COMMENT_CREATION_VIEW = CommentCreationView.COMMENT_COMMAND_ONLY
|
|
27
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = True
|
|
27
28
|
SUPPORTED_DROP_CASCADE_OBJECT_KINDS = ["SCHEMA", "TABLE", "VIEW"]
|
|
28
29
|
|
|
29
30
|
def columns(
|
|
@@ -66,6 +66,7 @@ class BigQueryEngineAdapter(InsertOverwriteWithMergeMixin, ClusteredByMixin, Row
|
|
|
66
66
|
SUPPORTS_CLONING = True
|
|
67
67
|
MAX_TABLE_COMMENT_LENGTH = 1024
|
|
68
68
|
MAX_COLUMN_COMMENT_LENGTH = 1024
|
|
69
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = True
|
|
69
70
|
SUPPORTED_DROP_CASCADE_OBJECT_KINDS = ["SCHEMA"]
|
|
70
71
|
|
|
71
72
|
SCHEMA_DIFFER_KWARGS = {
|
|
@@ -1049,6 +1050,7 @@ class BigQueryEngineAdapter(InsertOverwriteWithMergeMixin, ClusteredByMixin, Row
|
|
|
1049
1050
|
def _execute(
|
|
1050
1051
|
self,
|
|
1051
1052
|
sql: str,
|
|
1053
|
+
track_rows_processed: bool = False,
|
|
1052
1054
|
**kwargs: t.Any,
|
|
1053
1055
|
) -> None:
|
|
1054
1056
|
"""Execute a sql query."""
|
|
@@ -1094,6 +1096,23 @@ class BigQueryEngineAdapter(InsertOverwriteWithMergeMixin, ClusteredByMixin, Row
|
|
|
1094
1096
|
self.cursor._set_rowcount(query_results)
|
|
1095
1097
|
self.cursor._set_description(query_results.schema)
|
|
1096
1098
|
|
|
1099
|
+
if (
|
|
1100
|
+
track_rows_processed
|
|
1101
|
+
and self._query_execution_tracker
|
|
1102
|
+
and self._query_execution_tracker.is_tracking()
|
|
1103
|
+
):
|
|
1104
|
+
num_rows = None
|
|
1105
|
+
if query_job.statement_type == "CREATE_TABLE_AS_SELECT":
|
|
1106
|
+
# since table was just created, number rows in table == number rows processed
|
|
1107
|
+
query_table = self.client.get_table(query_job.destination)
|
|
1108
|
+
num_rows = query_table.num_rows
|
|
1109
|
+
elif query_job.statement_type in ["INSERT", "DELETE", "MERGE", "UPDATE"]:
|
|
1110
|
+
num_rows = query_job.num_dml_affected_rows
|
|
1111
|
+
|
|
1112
|
+
self._query_execution_tracker.record_execution(
|
|
1113
|
+
sql, num_rows, query_job.total_bytes_processed
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1097
1116
|
def _get_data_objects(
|
|
1098
1117
|
self, schema_name: SchemaName, object_names: t.Optional[t.Set[str]] = None
|
|
1099
1118
|
) -> t.List[DataObject]:
|
|
@@ -294,7 +294,7 @@ class ClickhouseEngineAdapter(EngineAdapterWithIndexSupport, LogicalMergeMixin):
|
|
|
294
294
|
)
|
|
295
295
|
|
|
296
296
|
try:
|
|
297
|
-
self.execute(existing_records_insert_exp)
|
|
297
|
+
self.execute(existing_records_insert_exp, track_rows_processed=True)
|
|
298
298
|
finally:
|
|
299
299
|
if table_partition_exp:
|
|
300
300
|
self.drop_table(partitions_temp_table_name)
|
|
@@ -489,6 +489,7 @@ class ClickhouseEngineAdapter(EngineAdapterWithIndexSupport, LogicalMergeMixin):
|
|
|
489
489
|
table_description: t.Optional[str] = None,
|
|
490
490
|
column_descriptions: t.Optional[t.Dict[str, str]] = None,
|
|
491
491
|
table_kind: t.Optional[str] = None,
|
|
492
|
+
track_rows_processed: bool = True,
|
|
492
493
|
**kwargs: t.Any,
|
|
493
494
|
) -> None:
|
|
494
495
|
"""Creates a table in the database.
|
|
@@ -525,6 +526,7 @@ class ClickhouseEngineAdapter(EngineAdapterWithIndexSupport, LogicalMergeMixin):
|
|
|
525
526
|
column_descriptions,
|
|
526
527
|
table_kind,
|
|
527
528
|
empty_ctas=(self.engine_run_mode.is_cloud and expression is not None),
|
|
529
|
+
track_rows_processed=track_rows_processed,
|
|
528
530
|
**kwargs,
|
|
529
531
|
)
|
|
530
532
|
|
|
@@ -170,6 +170,7 @@ class DuckDBEngineAdapter(LogicalMergeMixin, GetCurrentCatalogFromFunctionMixin,
|
|
|
170
170
|
table_description: t.Optional[str] = None,
|
|
171
171
|
column_descriptions: t.Optional[t.Dict[str, str]] = None,
|
|
172
172
|
table_kind: t.Optional[str] = None,
|
|
173
|
+
track_rows_processed: bool = True,
|
|
173
174
|
**kwargs: t.Any,
|
|
174
175
|
) -> None:
|
|
175
176
|
catalog = self.get_current_catalog()
|
|
@@ -193,6 +194,7 @@ class DuckDBEngineAdapter(LogicalMergeMixin, GetCurrentCatalogFromFunctionMixin,
|
|
|
193
194
|
table_description,
|
|
194
195
|
column_descriptions,
|
|
195
196
|
table_kind,
|
|
197
|
+
track_rows_processed=track_rows_processed,
|
|
196
198
|
**kwargs,
|
|
197
199
|
)
|
|
198
200
|
|
|
@@ -53,6 +53,7 @@ class MSSQLEngineAdapter(
|
|
|
53
53
|
COMMENT_CREATION_TABLE = CommentCreationTable.UNSUPPORTED
|
|
54
54
|
COMMENT_CREATION_VIEW = CommentCreationView.UNSUPPORTED
|
|
55
55
|
SUPPORTS_REPLACE_TABLE = False
|
|
56
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = True
|
|
56
57
|
SCHEMA_DIFFER_KWARGS = {
|
|
57
58
|
"parameterized_type_defaults": {
|
|
58
59
|
exp.DataType.build("DECIMAL", dialect=DIALECT).this: [(18, 0), (0,)],
|
|
@@ -39,6 +39,7 @@ class MySQLEngineAdapter(
|
|
|
39
39
|
MAX_COLUMN_COMMENT_LENGTH = 1024
|
|
40
40
|
SUPPORTS_REPLACE_TABLE = False
|
|
41
41
|
MAX_IDENTIFIER_LENGTH = 64
|
|
42
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = True
|
|
42
43
|
SCHEMA_DIFFER_KWARGS = {
|
|
43
44
|
"parameterized_type_defaults": {
|
|
44
45
|
exp.DataType.build("BIT", dialect=DIALECT).this: [(1,)],
|
|
@@ -35,6 +35,7 @@ class PostgresEngineAdapter(
|
|
|
35
35
|
CURRENT_CATALOG_EXPRESSION = exp.column("current_catalog")
|
|
36
36
|
SUPPORTS_REPLACE_TABLE = False
|
|
37
37
|
MAX_IDENTIFIER_LENGTH = 63
|
|
38
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = True
|
|
38
39
|
SCHEMA_DIFFER_KWARGS = {
|
|
39
40
|
"parameterized_type_defaults": {
|
|
40
41
|
# DECIMAL without precision is "up to 131072 digits before the decimal point; up to 16383 digits after the decimal point"
|
|
@@ -173,6 +173,7 @@ class RedshiftEngineAdapter(
|
|
|
173
173
|
table_description: t.Optional[str] = None,
|
|
174
174
|
column_descriptions: t.Optional[t.Dict[str, str]] = None,
|
|
175
175
|
table_kind: t.Optional[str] = None,
|
|
176
|
+
track_rows_processed: bool = True,
|
|
176
177
|
**kwargs: t.Any,
|
|
177
178
|
) -> None:
|
|
178
179
|
"""
|
|
@@ -426,7 +427,8 @@ class RedshiftEngineAdapter(
|
|
|
426
427
|
using=using,
|
|
427
428
|
on=on.transform(resolve_target_table),
|
|
428
429
|
whens=whens.transform(resolve_target_table),
|
|
429
|
-
)
|
|
430
|
+
),
|
|
431
|
+
track_rows_processed=True,
|
|
430
432
|
)
|
|
431
433
|
|
|
432
434
|
def _normalize_decimal_value(self, expr: exp.Expression, precision: int) -> exp.Expression:
|
|
@@ -72,6 +72,7 @@ class SnowflakeEngineAdapter(GetCurrentCatalogFromFunctionMixin, ClusteredByMixi
|
|
|
72
72
|
}
|
|
73
73
|
MANAGED_TABLE_KIND = "DYNAMIC TABLE"
|
|
74
74
|
SNOWPARK = "snowpark"
|
|
75
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = True
|
|
75
76
|
|
|
76
77
|
@contextlib.contextmanager
|
|
77
78
|
def session(self, properties: SessionProperties) -> t.Iterator[None]:
|
|
@@ -166,6 +167,7 @@ class SnowflakeEngineAdapter(GetCurrentCatalogFromFunctionMixin, ClusteredByMixi
|
|
|
166
167
|
table_description: t.Optional[str] = None,
|
|
167
168
|
column_descriptions: t.Optional[t.Dict[str, str]] = None,
|
|
168
169
|
table_kind: t.Optional[str] = None,
|
|
170
|
+
track_rows_processed: bool = True,
|
|
169
171
|
**kwargs: t.Any,
|
|
170
172
|
) -> None:
|
|
171
173
|
table_format = kwargs.get("table_format")
|
|
@@ -185,6 +187,7 @@ class SnowflakeEngineAdapter(GetCurrentCatalogFromFunctionMixin, ClusteredByMixi
|
|
|
185
187
|
table_description=table_description,
|
|
186
188
|
column_descriptions=column_descriptions,
|
|
187
189
|
table_kind=table_kind,
|
|
190
|
+
track_rows_processed=False, # snowflake tracks CTAS row counts incorrectly
|
|
188
191
|
**kwargs,
|
|
189
192
|
)
|
|
190
193
|
|
|
@@ -433,6 +433,7 @@ class SparkEngineAdapter(
|
|
|
433
433
|
table_description: t.Optional[str] = None,
|
|
434
434
|
column_descriptions: t.Optional[t.Dict[str, str]] = None,
|
|
435
435
|
table_kind: t.Optional[str] = None,
|
|
436
|
+
track_rows_processed: bool = True,
|
|
436
437
|
**kwargs: t.Any,
|
|
437
438
|
) -> None:
|
|
438
439
|
table_name = (
|
|
@@ -461,6 +462,7 @@ class SparkEngineAdapter(
|
|
|
461
462
|
target_columns_to_types=target_columns_to_types,
|
|
462
463
|
table_description=table_description,
|
|
463
464
|
column_descriptions=column_descriptions,
|
|
465
|
+
track_rows_processed=track_rows_processed,
|
|
464
466
|
**kwargs,
|
|
465
467
|
)
|
|
466
468
|
table_name = (
|
|
@@ -55,6 +55,7 @@ class TrinoEngineAdapter(
|
|
|
55
55
|
SUPPORTED_DROP_CASCADE_OBJECT_KINDS = ["SCHEMA"]
|
|
56
56
|
DEFAULT_CATALOG_TYPE = "hive"
|
|
57
57
|
QUOTE_IDENTIFIERS_IN_VIEWS = False
|
|
58
|
+
SUPPORTS_QUERY_EXECUTION_TRACKING = True
|
|
58
59
|
SCHEMA_DIFFER_KWARGS = {
|
|
59
60
|
"parameterized_type_defaults": {
|
|
60
61
|
# default decimal precision varies across backends
|
|
@@ -357,6 +358,7 @@ class TrinoEngineAdapter(
|
|
|
357
358
|
table_description: t.Optional[str] = None,
|
|
358
359
|
column_descriptions: t.Optional[t.Dict[str, str]] = None,
|
|
359
360
|
table_kind: t.Optional[str] = None,
|
|
361
|
+
track_rows_processed: bool = True,
|
|
360
362
|
**kwargs: t.Any,
|
|
361
363
|
) -> None:
|
|
362
364
|
super()._create_table(
|
|
@@ -368,6 +370,7 @@ class TrinoEngineAdapter(
|
|
|
368
370
|
table_description=table_description,
|
|
369
371
|
column_descriptions=column_descriptions,
|
|
370
372
|
table_kind=table_kind,
|
|
373
|
+
track_rows_processed=track_rows_processed,
|
|
371
374
|
**kwargs,
|
|
372
375
|
)
|
|
373
376
|
|
sqlmesh/core/scheduler.py
CHANGED
|
@@ -20,6 +20,7 @@ from sqlmesh.core.snapshot import (
|
|
|
20
20
|
DeployabilityIndex,
|
|
21
21
|
Snapshot,
|
|
22
22
|
SnapshotId,
|
|
23
|
+
SnapshotIdBatch,
|
|
23
24
|
SnapshotEvaluator,
|
|
24
25
|
apply_auto_restatements,
|
|
25
26
|
earliest_start_date,
|
|
@@ -531,6 +532,11 @@ class Scheduler:
|
|
|
531
532
|
finally:
|
|
532
533
|
num_audits = len(audit_results)
|
|
533
534
|
num_audits_failed = sum(1 for result in audit_results if result.count)
|
|
535
|
+
|
|
536
|
+
execution_stats = self.snapshot_evaluator.execution_tracker.get_execution_stats(
|
|
537
|
+
SnapshotIdBatch(snapshot_id=snapshot.snapshot_id, batch_id=node.batch_index)
|
|
538
|
+
)
|
|
539
|
+
|
|
534
540
|
self.console.update_snapshot_evaluation_progress(
|
|
535
541
|
snapshot,
|
|
536
542
|
batched_intervals[snapshot][node.batch_index],
|
|
@@ -538,6 +544,7 @@ class Scheduler:
|
|
|
538
544
|
evaluation_duration_ms,
|
|
539
545
|
num_audits - num_audits_failed,
|
|
540
546
|
num_audits_failed,
|
|
547
|
+
execution_stats=execution_stats,
|
|
541
548
|
auto_restatement_triggers=auto_restatement_triggers.get(
|
|
542
549
|
snapshot.snapshot_id
|
|
543
550
|
),
|
|
@@ -8,6 +8,7 @@ from sqlmesh.core.snapshot.definition import (
|
|
|
8
8
|
SnapshotDataVersion as SnapshotDataVersion,
|
|
9
9
|
SnapshotFingerprint as SnapshotFingerprint,
|
|
10
10
|
SnapshotId as SnapshotId,
|
|
11
|
+
SnapshotIdBatch as SnapshotIdBatch,
|
|
11
12
|
SnapshotIdLike as SnapshotIdLike,
|
|
12
13
|
SnapshotInfoLike as SnapshotInfoLike,
|
|
13
14
|
SnapshotIntervals as SnapshotIntervals,
|
|
@@ -13,10 +13,13 @@ from pydantic import Field
|
|
|
13
13
|
from sqlglot import exp
|
|
14
14
|
from sqlglot.optimizer.normalize_identifiers import normalize_identifiers
|
|
15
15
|
|
|
16
|
-
from sqlmesh.core.config.common import
|
|
16
|
+
from sqlmesh.core.config.common import (
|
|
17
|
+
TableNamingConvention,
|
|
18
|
+
VirtualEnvironmentMode,
|
|
19
|
+
EnvironmentSuffixTarget,
|
|
20
|
+
)
|
|
17
21
|
from sqlmesh.core import constants as c
|
|
18
22
|
from sqlmesh.core.audit import StandaloneAudit
|
|
19
|
-
from sqlmesh.core.environment import EnvironmentSuffixTarget
|
|
20
23
|
from sqlmesh.core.macros import call_macro
|
|
21
24
|
from sqlmesh.core.model import Model, ModelKindMixin, ModelKindName, ViewKind, CustomKind
|
|
22
25
|
from sqlmesh.core.model.definition import _Model
|
|
@@ -159,6 +162,11 @@ class SnapshotId(PydanticModel, frozen=True):
|
|
|
159
162
|
return f"SnapshotId<{self.name}: {self.identifier}>"
|
|
160
163
|
|
|
161
164
|
|
|
165
|
+
class SnapshotIdBatch(PydanticModel, frozen=True):
|
|
166
|
+
snapshot_id: SnapshotId
|
|
167
|
+
batch_id: int
|
|
168
|
+
|
|
169
|
+
|
|
162
170
|
class SnapshotNameVersion(PydanticModel, frozen=True):
|
|
163
171
|
name: str
|
|
164
172
|
version: str
|
|
@@ -37,7 +37,6 @@ from sqlmesh.core import constants as c
|
|
|
37
37
|
from sqlmesh.core import dialect as d
|
|
38
38
|
from sqlmesh.core.audit import Audit, StandaloneAudit
|
|
39
39
|
from sqlmesh.core.dialect import schema_
|
|
40
|
-
from sqlmesh.core.engine_adapter import EngineAdapter
|
|
41
40
|
from sqlmesh.core.engine_adapter.shared import InsertOverwriteStrategy, DataObjectType
|
|
42
41
|
from sqlmesh.core.macros import RuntimeStage
|
|
43
42
|
from sqlmesh.core.model import (
|
|
@@ -62,9 +61,11 @@ from sqlmesh.core.snapshot import (
|
|
|
62
61
|
Intervals,
|
|
63
62
|
Snapshot,
|
|
64
63
|
SnapshotId,
|
|
64
|
+
SnapshotIdBatch,
|
|
65
65
|
SnapshotInfoLike,
|
|
66
66
|
SnapshotTableCleanupTask,
|
|
67
67
|
)
|
|
68
|
+
from sqlmesh.core.snapshot.execution_tracker import QueryExecutionTracker
|
|
68
69
|
from sqlmesh.utils import random_id, CorrelationId
|
|
69
70
|
from sqlmesh.utils.concurrency import (
|
|
70
71
|
concurrent_apply_to_snapshots,
|
|
@@ -88,6 +89,7 @@ else:
|
|
|
88
89
|
|
|
89
90
|
if t.TYPE_CHECKING:
|
|
90
91
|
from sqlmesh.core.engine_adapter._typing import DF, QueryOrDF
|
|
92
|
+
from sqlmesh.core.engine_adapter.base import EngineAdapter
|
|
91
93
|
from sqlmesh.core.environment import EnvironmentNamingInfo
|
|
92
94
|
|
|
93
95
|
logger = logging.getLogger(__name__)
|
|
@@ -128,6 +130,11 @@ class SnapshotEvaluator:
|
|
|
128
130
|
self.adapters = (
|
|
129
131
|
adapters if isinstance(adapters, t.Dict) else {selected_gateway or "": adapters}
|
|
130
132
|
)
|
|
133
|
+
self.execution_tracker = QueryExecutionTracker()
|
|
134
|
+
self.adapters = {
|
|
135
|
+
gateway: adapter.with_settings(query_execution_tracker=self.execution_tracker)
|
|
136
|
+
for gateway, adapter in self.adapters.items()
|
|
137
|
+
}
|
|
131
138
|
self.adapter = (
|
|
132
139
|
next(iter(self.adapters.values()))
|
|
133
140
|
if not selected_gateway
|
|
@@ -169,19 +176,22 @@ class SnapshotEvaluator:
|
|
|
169
176
|
Returns:
|
|
170
177
|
The WAP ID of this evaluation if supported, None otherwise.
|
|
171
178
|
"""
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
179
|
+
with self.execution_tracker.track_execution(
|
|
180
|
+
SnapshotIdBatch(snapshot_id=snapshot.snapshot_id, batch_id=batch_index)
|
|
181
|
+
):
|
|
182
|
+
result = self._evaluate_snapshot(
|
|
183
|
+
start=start,
|
|
184
|
+
end=end,
|
|
185
|
+
execution_time=execution_time,
|
|
186
|
+
snapshot=snapshot,
|
|
187
|
+
snapshots=snapshots,
|
|
188
|
+
allow_destructive_snapshots=allow_destructive_snapshots or set(),
|
|
189
|
+
allow_additive_snapshots=allow_additive_snapshots or set(),
|
|
190
|
+
deployability_index=deployability_index,
|
|
191
|
+
batch_index=batch_index,
|
|
192
|
+
target_table_exists=target_table_exists,
|
|
193
|
+
**kwargs,
|
|
194
|
+
)
|
|
185
195
|
if result is None or isinstance(result, str):
|
|
186
196
|
return result
|
|
187
197
|
raise SQLMeshError(
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from threading import local
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from sqlmesh.core.snapshot import SnapshotIdBatch
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class QueryExecutionStats:
|
|
12
|
+
snapshot_id_batch: SnapshotIdBatch
|
|
13
|
+
total_rows_processed: t.Optional[int] = None
|
|
14
|
+
total_bytes_processed: t.Optional[int] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class QueryExecutionContext:
|
|
19
|
+
"""
|
|
20
|
+
Container for tracking rows processed or other execution information during snapshot evaluation.
|
|
21
|
+
|
|
22
|
+
It accumulates statistics from multiple cursor.execute() calls during a single snapshot evaluation.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
snapshot_id_batch: Identifier linking this context to a specific snapshot evaluation
|
|
26
|
+
stats: Running sum of cursor.rowcount and possibly bytes processed from all executed queries during evaluation
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
snapshot_id_batch: SnapshotIdBatch
|
|
30
|
+
stats: QueryExecutionStats = field(init=False)
|
|
31
|
+
|
|
32
|
+
def __post_init__(self) -> None:
|
|
33
|
+
self.stats = QueryExecutionStats(snapshot_id_batch=self.snapshot_id_batch)
|
|
34
|
+
|
|
35
|
+
def add_execution(
|
|
36
|
+
self, sql: str, row_count: t.Optional[int], bytes_processed: t.Optional[int]
|
|
37
|
+
) -> None:
|
|
38
|
+
if row_count is not None and row_count >= 0:
|
|
39
|
+
if self.stats.total_rows_processed is None:
|
|
40
|
+
self.stats.total_rows_processed = row_count
|
|
41
|
+
else:
|
|
42
|
+
self.stats.total_rows_processed += row_count
|
|
43
|
+
|
|
44
|
+
# conditional on row_count because we should only count bytes corresponding to
|
|
45
|
+
# DML actions whose rows were captured
|
|
46
|
+
if bytes_processed is not None:
|
|
47
|
+
if self.stats.total_bytes_processed is None:
|
|
48
|
+
self.stats.total_bytes_processed = bytes_processed
|
|
49
|
+
else:
|
|
50
|
+
self.stats.total_bytes_processed += bytes_processed
|
|
51
|
+
|
|
52
|
+
def get_execution_stats(self) -> QueryExecutionStats:
|
|
53
|
+
return self.stats
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class QueryExecutionTracker:
|
|
57
|
+
"""Thread-local context manager for snapshot execution statistics, such as rows processed."""
|
|
58
|
+
|
|
59
|
+
def __init__(self) -> None:
|
|
60
|
+
self._thread_local = local()
|
|
61
|
+
self._contexts: t.Dict[SnapshotIdBatch, QueryExecutionContext] = {}
|
|
62
|
+
|
|
63
|
+
def get_execution_context(
|
|
64
|
+
self, snapshot_id_batch: SnapshotIdBatch
|
|
65
|
+
) -> t.Optional[QueryExecutionContext]:
|
|
66
|
+
return self._contexts.get(snapshot_id_batch)
|
|
67
|
+
|
|
68
|
+
def is_tracking(self) -> bool:
|
|
69
|
+
return getattr(self._thread_local, "context", None) is not None
|
|
70
|
+
|
|
71
|
+
@contextmanager
|
|
72
|
+
def track_execution(
|
|
73
|
+
self, snapshot_id_batch: SnapshotIdBatch
|
|
74
|
+
) -> t.Iterator[t.Optional[QueryExecutionContext]]:
|
|
75
|
+
"""Context manager for tracking snapshot execution statistics such as row counts and bytes processed."""
|
|
76
|
+
context = QueryExecutionContext(snapshot_id_batch=snapshot_id_batch)
|
|
77
|
+
self._thread_local.context = context
|
|
78
|
+
self._contexts[snapshot_id_batch] = context
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
yield context
|
|
82
|
+
finally:
|
|
83
|
+
self._thread_local.context = None
|
|
84
|
+
|
|
85
|
+
def record_execution(
|
|
86
|
+
self, sql: str, row_count: t.Optional[int], bytes_processed: t.Optional[int]
|
|
87
|
+
) -> None:
|
|
88
|
+
context = getattr(self._thread_local, "context", None)
|
|
89
|
+
if context is not None:
|
|
90
|
+
context.add_execution(sql, row_count, bytes_processed)
|
|
91
|
+
|
|
92
|
+
def get_execution_stats(
|
|
93
|
+
self, snapshot_id_batch: SnapshotIdBatch
|
|
94
|
+
) -> t.Optional[QueryExecutionStats]:
|
|
95
|
+
context = self._contexts.get(snapshot_id_batch)
|
|
96
|
+
self._contexts.pop(snapshot_id_batch, None)
|
|
97
|
+
return context.get_execution_stats() if context else None
|
|
@@ -78,6 +78,7 @@ class EnvironmentState:
|
|
|
78
78
|
self.environments_table,
|
|
79
79
|
_environment_to_df(environment),
|
|
80
80
|
target_columns_to_types=self._environment_columns_to_types,
|
|
81
|
+
track_rows_processed=False,
|
|
81
82
|
)
|
|
82
83
|
|
|
83
84
|
def update_environment_statements(
|
|
@@ -108,6 +109,7 @@ class EnvironmentState:
|
|
|
108
109
|
self.environment_statements_table,
|
|
109
110
|
_environment_statements_to_df(environment_name, plan_id, environment_statements),
|
|
110
111
|
target_columns_to_types=self._environment_statements_columns_to_types,
|
|
112
|
+
track_rows_processed=False,
|
|
111
113
|
)
|
|
112
114
|
|
|
113
115
|
def invalidate_environment(self, name: str, protect_prod: bool = True) -> None:
|
|
@@ -115,6 +115,7 @@ class IntervalState:
|
|
|
115
115
|
self.intervals_table,
|
|
116
116
|
_intervals_to_df(intervals_to_remove, is_dev=False, is_removed=True),
|
|
117
117
|
target_columns_to_types=self._interval_columns_to_types,
|
|
118
|
+
track_rows_processed=False,
|
|
118
119
|
)
|
|
119
120
|
|
|
120
121
|
def get_snapshot_intervals(
|
|
@@ -243,6 +244,7 @@ class IntervalState:
|
|
|
243
244
|
self.intervals_table,
|
|
244
245
|
pd.DataFrame(new_intervals),
|
|
245
246
|
target_columns_to_types=self._interval_columns_to_types,
|
|
247
|
+
track_rows_processed=False,
|
|
246
248
|
)
|
|
247
249
|
|
|
248
250
|
def _get_snapshot_intervals(
|
|
@@ -413,7 +413,9 @@ class StateMigrator:
|
|
|
413
413
|
backup_name = _backup_table_name(table)
|
|
414
414
|
self.engine_adapter.drop_table(backup_name)
|
|
415
415
|
self.engine_adapter.create_table_like(backup_name, table)
|
|
416
|
-
self.engine_adapter.insert_append(
|
|
416
|
+
self.engine_adapter.insert_append(
|
|
417
|
+
backup_name, exp.select("*").from_(table), track_rows_processed=False
|
|
418
|
+
)
|
|
417
419
|
|
|
418
420
|
def _restore_table(
|
|
419
421
|
self,
|
|
@@ -103,6 +103,7 @@ class SnapshotState:
|
|
|
103
103
|
self.snapshots_table,
|
|
104
104
|
_snapshots_to_df(snapshots_to_store),
|
|
105
105
|
target_columns_to_types=self._snapshot_columns_to_types,
|
|
106
|
+
track_rows_processed=False,
|
|
106
107
|
)
|
|
107
108
|
|
|
108
109
|
for snapshot in snapshots:
|
|
@@ -406,6 +407,7 @@ class SnapshotState:
|
|
|
406
407
|
self.snapshots_table,
|
|
407
408
|
_snapshots_to_df(snapshots_to_store),
|
|
408
409
|
target_columns_to_types=self._snapshot_columns_to_types,
|
|
410
|
+
track_rows_processed=False,
|
|
409
411
|
)
|
|
410
412
|
|
|
411
413
|
def _get_snapshots(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlmesh
|
|
3
|
-
Version: 0.211.
|
|
3
|
+
Version: 0.211.2.dev2
|
|
4
4
|
Summary: Next-generation data transformation framework
|
|
5
5
|
Author-email: "TobikoData Inc." <engineering@tobikodata.com>
|
|
6
6
|
License: Apache License
|
|
@@ -223,6 +223,7 @@ Requires-Dist: click
|
|
|
223
223
|
Requires-Dist: croniter
|
|
224
224
|
Requires-Dist: duckdb!=0.10.3,>=0.10.0
|
|
225
225
|
Requires-Dist: dateparser<=1.2.1
|
|
226
|
+
Requires-Dist: humanize
|
|
226
227
|
Requires-Dist: hyperscript>=0.1.0
|
|
227
228
|
Requires-Dist: importlib-metadata; python_version < "3.12"
|
|
228
229
|
Requires-Dist: ipywidgets
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
|
|
2
|
-
sqlmesh/_version.py,sha256=
|
|
2
|
+
sqlmesh/_version.py,sha256=_SX7vdymMYLUIT2uNWUZp-U7rXKSg-_tOmix0J3KAwA,721
|
|
3
3
|
sqlmesh/magics.py,sha256=xLh3u4eqpVrKRVN5KF3X84RPRqjygAB9AJP1TXwH8hg,42086
|
|
4
4
|
sqlmesh/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
sqlmesh/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -11,7 +11,7 @@ sqlmesh/cli/options.py,sha256=8qEGWivk4urGikpEmaU9T8WsyaZtjCpmW4Ed25iQ73I,2543
|
|
|
11
11
|
sqlmesh/cli/project_init.py,sha256=-Fhc-wAbKmjo9Ktog2i96nvITFwD4qDqaaiuqONkM5c,17204
|
|
12
12
|
sqlmesh/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
sqlmesh/core/_typing.py,sha256=e1f7xO2aZCy38M_3VoDy-XI3shyd99-IFctgIFSgXWM,497
|
|
14
|
-
sqlmesh/core/console.py,sha256=
|
|
14
|
+
sqlmesh/core/console.py,sha256=mgUrgbwjuR0HEZsXkPBtfpjeKzmFjgWVqJOcROyI74E,168778
|
|
15
15
|
sqlmesh/core/constants.py,sha256=Wg6qApCRhENA1me7ELUS5GnArKlTBH-8192gQYKQnE8,2759
|
|
16
16
|
sqlmesh/core/context.py,sha256=q9n9e8JSpgnrkrJCLzKyipK5BNzJ8cPQuDsAyt3MPwc,128717
|
|
17
17
|
sqlmesh/core/context_diff.py,sha256=pfMcdcV-8fdxkD68dv-c1EYuN-rfSvy3wriEMYSsa3o,21848
|
|
@@ -24,7 +24,7 @@ sqlmesh/core/node.py,sha256=3ChBz1PIMJhp-jzed_GLBrtmEF2v_BVDEM5WNe0ssPk,14658
|
|
|
24
24
|
sqlmesh/core/notification_target.py,sha256=PPGoDrgbRKxr27vJEu03XqNTQLYTw0ZF_b0yAapxGeI,16158
|
|
25
25
|
sqlmesh/core/reference.py,sha256=k7OSkLqTjPR8WJjNeFj0xAJ297nZUMgb_iTVwKRRKjc,4875
|
|
26
26
|
sqlmesh/core/renderer.py,sha256=PMUmmtwqk3FA5lN-16OWl6buCbrVa1M8NIgRXCYEB8k,26805
|
|
27
|
-
sqlmesh/core/scheduler.py,sha256
|
|
27
|
+
sqlmesh/core/scheduler.py,sha256=-RDcvwmU2VhvpnnFYT7KcQMe3VK_thsNzJ1i35c5Kfc,47110
|
|
28
28
|
sqlmesh/core/schema_diff.py,sha256=QRQfiVuptTfDAVf7zkuGo7beqXKRno3MUvRjYB6q0nk,33702
|
|
29
29
|
sqlmesh/core/schema_loader.py,sha256=_Pq2RSw91uthqv1vNi_eHmLlzhtGz_APMJ0wAJZYuvk,3677
|
|
30
30
|
sqlmesh/core/selector.py,sha256=9bqtBx7Onl8zZb-G4OsGVYVwkdm24j_qnp5HgXLFhR4,12837
|
|
@@ -57,24 +57,24 @@ sqlmesh/core/config/scheduler.py,sha256=LVEHIln7dqv8ygo4uniji4j9-gfuJlk-WpQU9FAq
|
|
|
57
57
|
sqlmesh/core/config/ui.py,sha256=jsO-S6_d9NkLZGG5pT4mgKgxMF34KzkDociZAMvCX3U,278
|
|
58
58
|
sqlmesh/core/engine_adapter/__init__.py,sha256=y9jZAFdMBkkkRrf0ymfsJJn6s_7Ya6OpDgR4Bf1OG_U,2383
|
|
59
59
|
sqlmesh/core/engine_adapter/_typing.py,sha256=QSXXdxtTHXTwJyiv-9ArAJHvwmJPfa7tShuD_3vGwfg,1010
|
|
60
|
-
sqlmesh/core/engine_adapter/athena.py,sha256
|
|
61
|
-
sqlmesh/core/engine_adapter/base.py,sha256=
|
|
62
|
-
sqlmesh/core/engine_adapter/base_postgres.py,sha256=
|
|
63
|
-
sqlmesh/core/engine_adapter/bigquery.py,sha256=
|
|
64
|
-
sqlmesh/core/engine_adapter/clickhouse.py,sha256=
|
|
60
|
+
sqlmesh/core/engine_adapter/athena.py,sha256=-HysZHXI9vETmqw6s88sYTUD7_z7LiAiMmi6nulqri0,26801
|
|
61
|
+
sqlmesh/core/engine_adapter/base.py,sha256=xBIBu4S1bhiRLR4Bj2KWETWiZzy5wk82P9tqbiBOCNo,115889
|
|
62
|
+
sqlmesh/core/engine_adapter/base_postgres.py,sha256=V4NwGs1IxOpRDjrDeh16A8TZdkHYfJEoJyVWW96xYQU,7061
|
|
63
|
+
sqlmesh/core/engine_adapter/bigquery.py,sha256=e66jX3T885y5ADytYVNfqH8pAPWe4_j8CJxeQXaswb8,54785
|
|
64
|
+
sqlmesh/core/engine_adapter/clickhouse.py,sha256=Lxtsz-8EoVGPEZpmGbfoU5VXQromkM-6h3QHALdAQ9k,35979
|
|
65
65
|
sqlmesh/core/engine_adapter/databricks.py,sha256=B6OkHelGrxxehZlYno5cZ8MB_bHirQS_imznSZdQGrY,14589
|
|
66
|
-
sqlmesh/core/engine_adapter/duckdb.py,sha256=
|
|
66
|
+
sqlmesh/core/engine_adapter/duckdb.py,sha256=9AXeRhaYXBcYSmIavyFY9LUzfgh94qkTO98v0-suQ8I,7993
|
|
67
67
|
sqlmesh/core/engine_adapter/fabric.py,sha256=nXDYpKtzTN8WgBqOEmSLct7oyXNR3HwMZgOL3OzMvyo,14009
|
|
68
68
|
sqlmesh/core/engine_adapter/mixins.py,sha256=JoITl_xbY5io5LtiTenqEhjWE9VCv2cK4Ue025Bkm7Y,23607
|
|
69
|
-
sqlmesh/core/engine_adapter/mssql.py,sha256=
|
|
70
|
-
sqlmesh/core/engine_adapter/mysql.py,sha256=
|
|
71
|
-
sqlmesh/core/engine_adapter/postgres.py,sha256=
|
|
72
|
-
sqlmesh/core/engine_adapter/redshift.py,sha256=
|
|
69
|
+
sqlmesh/core/engine_adapter/mssql.py,sha256=oUKTUvXUzLxqtrtDsnyOJjV6p6R6pkiVIhL2WmKMs9k,18270
|
|
70
|
+
sqlmesh/core/engine_adapter/mysql.py,sha256=cvWN0HXHxhxxIUHZ6syV8mfpNc3MjQget0SR26qVwSY,6959
|
|
71
|
+
sqlmesh/core/engine_adapter/postgres.py,sha256=prSV9trLqzmFGnquoqfmR20ZtKmAtVvfP8rX6E8ofHA,5128
|
|
72
|
+
sqlmesh/core/engine_adapter/redshift.py,sha256=lWPX1_u6HNUhPgq0WcWlURcgKqidJpoasBV1baPmp3E,17577
|
|
73
73
|
sqlmesh/core/engine_adapter/risingwave.py,sha256=GY0u085TWerl8kTRNJpEutdD5FBsoPijQ0CcOkWOJEE,2359
|
|
74
74
|
sqlmesh/core/engine_adapter/shared.py,sha256=pvYU-sC2WEIOENm4oG4UrOCWXF56h-FEmlAqAR8TYoA,13500
|
|
75
|
-
sqlmesh/core/engine_adapter/snowflake.py,sha256=
|
|
76
|
-
sqlmesh/core/engine_adapter/spark.py,sha256=
|
|
77
|
-
sqlmesh/core/engine_adapter/trino.py,sha256=
|
|
75
|
+
sqlmesh/core/engine_adapter/snowflake.py,sha256=mg1vMwGtXB5fmO_HSm290RsYR0o8zT-QLbNKNCjuTfM,30180
|
|
76
|
+
sqlmesh/core/engine_adapter/spark.py,sha256=EqqFIZmFabdha_Zba2UW1NY77pfn9_bqTP0wvN7Y7Mw,22466
|
|
77
|
+
sqlmesh/core/engine_adapter/trino.py,sha256=_aFxm6GVsd9pZY2CBh_JcZW1gULMndnr91O_EiZHyEE,16432
|
|
78
78
|
sqlmesh/core/linter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
79
|
sqlmesh/core/linter/definition.py,sha256=JDJTkhByUw4Tr3nF-BOHKFAx_eASQqEqzX6AUVQUz8I,5659
|
|
80
80
|
sqlmesh/core/linter/helpers.py,sha256=cwKXP4sL6azRtNVGbMfJ5_6Hqq5Xx2M2rRLCgH3Y3ag,10743
|
|
@@ -100,24 +100,25 @@ sqlmesh/core/plan/definition.py,sha256=NWosaN3wYPwgQU0wLxv___Gx-obvnAdmYzkQN7RGK
|
|
|
100
100
|
sqlmesh/core/plan/evaluator.py,sha256=QvDk2NAuWSGEffOGCPAdp3kN6-mNGR-_GpWTJJEx7wk,22601
|
|
101
101
|
sqlmesh/core/plan/explainer.py,sha256=aBkvtM9eog_2NIglazSDdG_i9TTCdFSVOkCw5N8O-1k,12028
|
|
102
102
|
sqlmesh/core/plan/stages.py,sha256=aAA29iNSZC3CvWh_4zx9NNmS8OcFsfGSXtAadISfE_0,25329
|
|
103
|
-
sqlmesh/core/snapshot/__init__.py,sha256=
|
|
103
|
+
sqlmesh/core/snapshot/__init__.py,sha256=LU-0TGyLTpz6SIPEJr4s0Zvu1d6vuN2YH7m3mR70080,1357
|
|
104
104
|
sqlmesh/core/snapshot/cache.py,sha256=bgqCR2hyf6r2A_8QP1EnXFK25gDX37-Zg0YeMuETWxg,3934
|
|
105
105
|
sqlmesh/core/snapshot/categorizer.py,sha256=qGG5870mu1jR2whKisSBH_GFbsyh96RnzJDgkN7DV9g,2482
|
|
106
|
-
sqlmesh/core/snapshot/definition.py,sha256=
|
|
107
|
-
sqlmesh/core/snapshot/evaluator.py,sha256=
|
|
106
|
+
sqlmesh/core/snapshot/definition.py,sha256=4WuHZsaET6A_aXRLc2Sam7bUPslR1JKBDEOMCATeVTI,91717
|
|
107
|
+
sqlmesh/core/snapshot/evaluator.py,sha256=aE-zOHFuEh6DTd-DZz5Be8IIx58JVodftRAUIjlOzKQ,110080
|
|
108
|
+
sqlmesh/core/snapshot/execution_tracker.py,sha256=Ss1oYgH28Fy1mQ4HriX-luE9MG0eLdecrE1SssUveQI,3651
|
|
108
109
|
sqlmesh/core/state_sync/__init__.py,sha256=B60xj-_Xk-4jas3UphLvwkuf04G-9AyGckcblXE0aRg,869
|
|
109
110
|
sqlmesh/core/state_sync/base.py,sha256=AdqWmbhnvRFqVcFK6L01uCvpBVAYGuMrm4P0D3u_NaU,18334
|
|
110
111
|
sqlmesh/core/state_sync/cache.py,sha256=0AsYdQyBqbLdJvkBQ-hDAipsvnn_jSps-nya-Q4XeL0,5337
|
|
111
112
|
sqlmesh/core/state_sync/common.py,sha256=scuisdmaO3iliuUNFZi16CLDwiSKpW_zkMA0_MCNGLk,7902
|
|
112
113
|
sqlmesh/core/state_sync/export_import.py,sha256=WJ7sFX2b_RPAFE4yspJRi_4JQnaO23xpJgRxwXD0agA,8709
|
|
113
114
|
sqlmesh/core/state_sync/db/__init__.py,sha256=jGCqKYB7npq4eh6H2LyfBAJWdOP-IFu0DC4CMiQgYvw,107
|
|
114
|
-
sqlmesh/core/state_sync/db/environment.py,sha256=
|
|
115
|
+
sqlmesh/core/state_sync/db/environment.py,sha256=Pn7T79JH0WxdWApci75sjfhNPJ0slHF4TkvqsLcCKQo,13773
|
|
115
116
|
sqlmesh/core/state_sync/db/facade.py,sha256=7OMZt_T3TvavxeC95uwBWe7uD_PtgRyf-s7oYrTkVPg,25245
|
|
116
|
-
sqlmesh/core/state_sync/db/interval.py,sha256=
|
|
117
|
-
sqlmesh/core/state_sync/db/migrator.py,sha256=
|
|
118
|
-
sqlmesh/core/state_sync/db/snapshot.py,sha256=
|
|
117
|
+
sqlmesh/core/state_sync/db/interval.py,sha256=gPHDuFU4VJIAP7HfZjZ4_gZo5E3az8Jch43gNO3lh4M,18083
|
|
118
|
+
sqlmesh/core/state_sync/db/migrator.py,sha256=rRwWTETCJ9Mfua-W3fdV_TOqUp1q1Y7i80Oqrd3rO58,18192
|
|
119
|
+
sqlmesh/core/state_sync/db/snapshot.py,sha256=zK-FFADihpK1xgUWcEmC2gabAHQryD5EDOCzy3J_5TY,28300
|
|
119
120
|
sqlmesh/core/state_sync/db/utils.py,sha256=8KjRmOjP5CLuSRkYBUE2k34V-UYB0iSyuO0rWBQ_5pY,4218
|
|
120
|
-
sqlmesh/core/state_sync/db/version.py,sha256=
|
|
121
|
+
sqlmesh/core/state_sync/db/version.py,sha256=q5VDIIvY-585vTbvqPalU0N4qjG6RKs4gr8a51R-_UE,2257
|
|
121
122
|
sqlmesh/core/test/__init__.py,sha256=e83TJPwPRR_rAG29Y0OVbZb-5oWVBzz-_wrcd22Qk10,418
|
|
122
123
|
sqlmesh/core/test/context.py,sha256=9mmskKXpzsIkW_I31uN1n3Ioj8ngvyGAqyL_AZvVHpc,2253
|
|
123
124
|
sqlmesh/core/test/definition.py,sha256=VvjwvBoyd-y6Ucepf_vLuDm2DxqDLJh0X--D8BzFUng,41868
|
|
@@ -293,14 +294,14 @@ sqlmesh/utils/pydantic.py,sha256=o_NsXbIpDqNpUA1Uc5xF0ZzoXQYYB0DfHwdRxBwXPNk,120
|
|
|
293
294
|
sqlmesh/utils/rich.py,sha256=cwQ5nJ6sgz64xHtoh6_ec7ReV5YpsOGhMtUJnwoRfEI,3549
|
|
294
295
|
sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
|
|
295
296
|
sqlmesh/utils/yaml.py,sha256=hvo5lmybYLcIx4p6CQwufS8X4MbwS60lEBnCmYJCXmU,2336
|
|
296
|
-
sqlmesh-0.211.
|
|
297
|
+
sqlmesh-0.211.2.dev2.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
|
|
297
298
|
sqlmesh_dbt/__init__.py,sha256=awYS5y5mz-1NUmx6i5h5NSTJ7tidRl9NC0FAnFWSF6U,350
|
|
298
|
-
sqlmesh_dbt/cli.py,sha256=
|
|
299
|
+
sqlmesh_dbt/cli.py,sha256=wSShIx02GCYz0Ciq808GbbO9tjXYkaM6U6SFoT7rmpA,4117
|
|
299
300
|
sqlmesh_dbt/console.py,sha256=80sCEbrKfR5f3SvxIyI7hw_SuiDiiUwuJYxXbQO2014,830
|
|
300
301
|
sqlmesh_dbt/error.py,sha256=khi7UM_nqH4T47QL0NQbiXUvpr15Amh0F-Rtt4Xov20,885
|
|
301
|
-
sqlmesh_dbt/operations.py,sha256=
|
|
302
|
+
sqlmesh_dbt/operations.py,sha256=EON-uSJaLkjh_uyhHAiRWdT1yuD2SqZq5CnepmJmZZU,5584
|
|
302
303
|
sqlmesh_dbt/options.py,sha256=noB_qK4uGGi7Erqk1XkkMaFz5aUc6lp44wwn1Nv_LI4,737
|
|
303
|
-
sqlmesh_dbt/selectors.py,sha256=
|
|
304
|
+
sqlmesh_dbt/selectors.py,sha256=4w8eE0qAgoYC2K8afk3whfju-u3e5muXGwgkfXxQR3Y,5228
|
|
304
305
|
web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
305
306
|
web/client/dist/index.html,sha256=9Xh9IRDTAsC-XXYlaB1dLXv3dv15cDXnW2qz1z-Dj98,5472
|
|
306
307
|
web/client/dist/assets/Audits-Ucsx1GzF.js,sha256=fZhFbeOvI1RlQt7dBwG-GLDt5SBbCdbTY0i4zfXFGwE,1041
|
|
@@ -397,7 +398,7 @@ web/client/dist/fonts/Sohne/sohne-mono-kraftig.woff2,sha256=DGsWMljiFHztzyb0Gg_T
|
|
|
397
398
|
web/client/src/workers/sqlglot/sqlglot.py,sha256=XAMrvqcRBa7vy8bHVayYbZNyWsf1lNSjVk_odtif-YY,1566
|
|
398
399
|
web/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
399
400
|
web/server/app.py,sha256=b6qEJ4btjaSm2kWwQUGV--S3VE8QW0Um7f4rBdaoB_M,59
|
|
400
|
-
web/server/console.py,sha256=
|
|
401
|
+
web/server/console.py,sha256=s9caTcfLd-0Zhj6cQgkJBYxsl9w_ZDNv3gN6MaBNj0o,14806
|
|
401
402
|
web/server/exceptions.py,sha256=I2PPpChwhDWawQENr5kHg6DjyRGeiJ6OWZsCzEZhVzs,1533
|
|
402
403
|
web/server/main.py,sha256=9PaDa8sigo1z0AxtI3sQ3fHmDqs6uXwP-_IvoMwkznw,3199
|
|
403
404
|
web/server/models.py,sha256=SfuknVLSRzAAiTzUQgGyQbF5aZBt8yZg-sBnQ_8lWJQ,16552
|
|
@@ -418,8 +419,8 @@ web/server/api/endpoints/models.py,sha256=kwj0s7uve3iZSMfmjkoPVMFMeY1sD0peTeyrWf
|
|
|
418
419
|
web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
|
|
419
420
|
web/server/api/endpoints/plan.py,sha256=bbbY50W_2MsZSTxOHWMKz0tbIm75nsRSlPy8GI2fg9Q,9306
|
|
420
421
|
web/server/api/endpoints/table_diff.py,sha256=8XTwgOh6QBbNy_hTM1JuHgRjbnie-pGPrphiW-FNLjQ,6058
|
|
421
|
-
sqlmesh-0.211.
|
|
422
|
-
sqlmesh-0.211.
|
|
423
|
-
sqlmesh-0.211.
|
|
424
|
-
sqlmesh-0.211.
|
|
425
|
-
sqlmesh-0.211.
|
|
422
|
+
sqlmesh-0.211.2.dev2.dist-info/METADATA,sha256=eRMMo1P2DMyeqqYL3g5V9rjB_JN40Tcx0F70pYBKnhU,26773
|
|
423
|
+
sqlmesh-0.211.2.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
424
|
+
sqlmesh-0.211.2.dev2.dist-info/entry_points.txt,sha256=sHAf6tQczIM8xZoduN4qaUjV7QEPVUUW_LCT8EDUMv4,155
|
|
425
|
+
sqlmesh-0.211.2.dev2.dist-info/top_level.txt,sha256=RQ-33FPe2IgL0rgossAfJkCRtqslz9b7wFARqiWLC5Q,24
|
|
426
|
+
sqlmesh-0.211.2.dev2.dist-info/RECORD,,
|
sqlmesh_dbt/cli.py
CHANGED
|
@@ -46,10 +46,19 @@ exclude_option = click.option("--exclude", multiple=True, help="Specify the node
|
|
|
46
46
|
@click.group(invoke_without_command=True)
|
|
47
47
|
@click.option("--profile", help="Which existing profile to load. Overrides output.profile")
|
|
48
48
|
@click.option("-t", "--target", help="Which target to load for the given profile")
|
|
49
|
+
@click.option(
|
|
50
|
+
"-d",
|
|
51
|
+
"--debug/--no-debug",
|
|
52
|
+
default=False,
|
|
53
|
+
help="Display debug logging during dbt execution. Useful for debugging and making bug reports events to help when debugging.",
|
|
54
|
+
)
|
|
49
55
|
@click.pass_context
|
|
50
56
|
@cli_global_error_handler
|
|
51
57
|
def dbt(
|
|
52
|
-
ctx: click.Context,
|
|
58
|
+
ctx: click.Context,
|
|
59
|
+
profile: t.Optional[str] = None,
|
|
60
|
+
target: t.Optional[str] = None,
|
|
61
|
+
debug: bool = False,
|
|
53
62
|
) -> None:
|
|
54
63
|
"""
|
|
55
64
|
An ELT tool for managing your SQL transformations and data models, powered by the SQLMesh engine.
|
|
@@ -61,7 +70,9 @@ def dbt(
|
|
|
61
70
|
|
|
62
71
|
# we have a partially applied function here because subcommands might set extra options like --vars
|
|
63
72
|
# that need to be known before we attempt to load the project
|
|
64
|
-
ctx.obj = functools.partial(
|
|
73
|
+
ctx.obj = functools.partial(
|
|
74
|
+
create, project_dir=Path.cwd(), profile=profile, target=target, debug=debug
|
|
75
|
+
)
|
|
65
76
|
|
|
66
77
|
if not ctx.invoked_subcommand:
|
|
67
78
|
if profile or target:
|
sqlmesh_dbt/operations.py
CHANGED
|
@@ -16,9 +16,10 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class DbtOperations:
|
|
19
|
-
def __init__(self, sqlmesh_context: Context, dbt_project: Project):
|
|
19
|
+
def __init__(self, sqlmesh_context: Context, dbt_project: Project, debug: bool = False):
|
|
20
20
|
self.context = sqlmesh_context
|
|
21
21
|
self.project = dbt_project
|
|
22
|
+
self.debug = debug
|
|
22
23
|
|
|
23
24
|
def list_(
|
|
24
25
|
self,
|
|
@@ -55,6 +56,10 @@ class DbtOperations:
|
|
|
55
56
|
self, select: t.Optional[t.List[str]] = None, exclude: t.Optional[t.List[str]] = None
|
|
56
57
|
) -> t.Dict[str, Model]:
|
|
57
58
|
if sqlmesh_selector := selectors.to_sqlmesh(select or [], exclude or []):
|
|
59
|
+
if self.debug:
|
|
60
|
+
self.console.print(f"dbt --select: {select}")
|
|
61
|
+
self.console.print(f"dbt --exclude: {exclude}")
|
|
62
|
+
self.console.print(f"sqlmesh equivalent: '{sqlmesh_selector}'")
|
|
58
63
|
model_selector = self.context._new_selector()
|
|
59
64
|
selected_models = {
|
|
60
65
|
fqn: model
|
|
@@ -119,7 +124,7 @@ def create(
|
|
|
119
124
|
# so that DbtOperations can query information from the DBT project files in order to invoke SQLMesh correctly
|
|
120
125
|
dbt_project = dbt_loader._projects[0]
|
|
121
126
|
|
|
122
|
-
return DbtOperations(sqlmesh_context, dbt_project)
|
|
127
|
+
return DbtOperations(sqlmesh_context, dbt_project, debug=debug)
|
|
123
128
|
|
|
124
129
|
|
|
125
130
|
def init_project_if_required(project_dir: Path) -> None:
|
sqlmesh_dbt/selectors.py
CHANGED
|
@@ -37,6 +37,10 @@ def to_sqlmesh(dbt_select: t.Collection[str], dbt_exclude: t.Collection[str]) ->
|
|
|
37
37
|
-> "+main.model_a & ^(raw.src_data)"
|
|
38
38
|
--select "+main.model_a" --select "main.*b+" --exclude "raw.src_data"
|
|
39
39
|
-> "(+main.model_a | main.*b+) & ^(raw.src_data)"
|
|
40
|
+
--select "+main.model_a" --select "main.*b+" --exclude "raw.src_data" --exclude "main.model_c"
|
|
41
|
+
-> "(+main.model_a | main.*b+) & ^(raw.src_data | main.model_c)"
|
|
42
|
+
--select "+main.model_a main.*b+" --exclude "raw.src_data main.model_c"
|
|
43
|
+
-> "(+main.model_a | main.*b+) & ^(raw.src_data | main.model_c)"
|
|
40
44
|
"""
|
|
41
45
|
if not dbt_select and not dbt_exclude:
|
|
42
46
|
return None
|
|
@@ -44,8 +48,13 @@ def to_sqlmesh(dbt_select: t.Collection[str], dbt_exclude: t.Collection[str]) ->
|
|
|
44
48
|
select_expr = " | ".join(_to_sqlmesh(expr) for expr in dbt_select)
|
|
45
49
|
select_expr = _wrap(select_expr) if dbt_exclude and len(dbt_select) > 1 else select_expr
|
|
46
50
|
|
|
47
|
-
exclude_expr = "
|
|
48
|
-
|
|
51
|
+
exclude_expr = ""
|
|
52
|
+
|
|
53
|
+
if dbt_exclude:
|
|
54
|
+
exclude_expr = " | ".join(_to_sqlmesh(expr) for expr in dbt_exclude)
|
|
55
|
+
exclude_expr = _negate(
|
|
56
|
+
_wrap(exclude_expr) if dbt_select and len(dbt_exclude) > 1 else exclude_expr
|
|
57
|
+
)
|
|
49
58
|
|
|
50
59
|
main_expr = " & ".join([expr for expr in [select_expr, exclude_expr] if expr])
|
|
51
60
|
|
|
@@ -56,13 +65,9 @@ def to_sqlmesh(dbt_select: t.Collection[str], dbt_exclude: t.Collection[str]) ->
|
|
|
56
65
|
return main_expr
|
|
57
66
|
|
|
58
67
|
|
|
59
|
-
def _to_sqlmesh(selector_str: str
|
|
68
|
+
def _to_sqlmesh(selector_str: str) -> str:
|
|
60
69
|
unions, intersections = _split_unions_and_intersections(selector_str)
|
|
61
70
|
|
|
62
|
-
if negate:
|
|
63
|
-
unions = [_negate(u) for u in unions]
|
|
64
|
-
intersections = [_negate(i) for i in intersections]
|
|
65
|
-
|
|
66
71
|
union_expr = " | ".join(unions)
|
|
67
72
|
intersection_expr = " & ".join(intersections)
|
|
68
73
|
|
|
@@ -79,6 +84,7 @@ def _split_unions_and_intersections(selector_str: str) -> t.Tuple[t.List[str], t
|
|
|
79
84
|
# break space-separated items like: "my_first_model my_second_model" into a list of selectors to union
|
|
80
85
|
# and comma-separated items like: "my_first_model,my_second_model" into a list of selectors to intersect
|
|
81
86
|
# but, take into account brackets, eg "(my_first_model & my_second_model)" should not be split
|
|
87
|
+
# also take into account both types in the same string, eg "my_first_model my_second_model model_3,model_4,model_5"
|
|
82
88
|
|
|
83
89
|
def _split_by(input: str, delimiter: str) -> t.Iterator[str]:
|
|
84
90
|
buf = ""
|
web/server/console.py
CHANGED
|
@@ -10,6 +10,7 @@ from sqlmesh.core.console import TerminalConsole
|
|
|
10
10
|
from sqlmesh.core.environment import EnvironmentNamingInfo
|
|
11
11
|
from sqlmesh.core.plan.definition import EvaluatablePlan
|
|
12
12
|
from sqlmesh.core.snapshot import Snapshot, SnapshotInfoLike, SnapshotTableInfo, SnapshotId
|
|
13
|
+
from sqlmesh.core.snapshot.execution_tracker import QueryExecutionStats
|
|
13
14
|
from sqlmesh.core.test import ModelTest
|
|
14
15
|
from sqlmesh.core.test.result import ModelTextTestResult
|
|
15
16
|
from sqlmesh.utils.date import now_timestamp
|
|
@@ -142,6 +143,7 @@ class ApiConsole(TerminalConsole):
|
|
|
142
143
|
num_audits_passed: int,
|
|
143
144
|
num_audits_failed: int,
|
|
144
145
|
audit_only: bool = False,
|
|
146
|
+
execution_stats: t.Optional[QueryExecutionStats] = None,
|
|
145
147
|
auto_restatement_triggers: t.Optional[t.List[SnapshotId]] = None,
|
|
146
148
|
) -> None:
|
|
147
149
|
if audit_only:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|