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.
Files changed (35) hide show
  1. sqlmesh/_version.py +2 -2
  2. sqlmesh/core/console.py +65 -24
  3. sqlmesh/core/engine_adapter/athena.py +1 -0
  4. sqlmesh/core/engine_adapter/base.py +62 -10
  5. sqlmesh/core/engine_adapter/base_postgres.py +1 -0
  6. sqlmesh/core/engine_adapter/bigquery.py +19 -0
  7. sqlmesh/core/engine_adapter/clickhouse.py +3 -1
  8. sqlmesh/core/engine_adapter/duckdb.py +2 -0
  9. sqlmesh/core/engine_adapter/mssql.py +1 -0
  10. sqlmesh/core/engine_adapter/mysql.py +1 -0
  11. sqlmesh/core/engine_adapter/postgres.py +1 -0
  12. sqlmesh/core/engine_adapter/redshift.py +3 -1
  13. sqlmesh/core/engine_adapter/snowflake.py +3 -0
  14. sqlmesh/core/engine_adapter/spark.py +2 -0
  15. sqlmesh/core/engine_adapter/trino.py +3 -0
  16. sqlmesh/core/scheduler.py +7 -0
  17. sqlmesh/core/snapshot/__init__.py +1 -0
  18. sqlmesh/core/snapshot/definition.py +10 -2
  19. sqlmesh/core/snapshot/evaluator.py +24 -14
  20. sqlmesh/core/snapshot/execution_tracker.py +97 -0
  21. sqlmesh/core/state_sync/db/environment.py +2 -0
  22. sqlmesh/core/state_sync/db/interval.py +2 -0
  23. sqlmesh/core/state_sync/db/migrator.py +3 -1
  24. sqlmesh/core/state_sync/db/snapshot.py +2 -0
  25. sqlmesh/core/state_sync/db/version.py +1 -0
  26. {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/METADATA +2 -1
  27. {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/RECORD +35 -34
  28. sqlmesh_dbt/cli.py +13 -2
  29. sqlmesh_dbt/operations.py +7 -2
  30. sqlmesh_dbt/selectors.py +13 -7
  31. web/server/console.py +2 -0
  32. {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/WHEEL +0 -0
  33. {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/entry_points.txt +0 -0
  34. {sqlmesh-0.211.1.dev4.dist-info → sqlmesh-0.211.2.dev2.dist-info}/licenses/LICENSE +0 -0
  35. {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.1.dev4'
32
- __version_tuple__ = version_tuple = (0, 211, 1, 'dev4')
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(batched_intervals, self.AUDIT_PADDING)
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(snapshot: Snapshot, interval_info: t.Optional[str]) -> str:
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
- return "run standalone audit"
4175
- if snapshot.is_model and snapshot.model.kind.is_external:
4176
- return "run external audits"
4177
- if snapshot.model.kind.is_seed:
4178
- return "insert seed file"
4179
- if snapshot.model.kind.is_full:
4180
- return "full refresh"
4181
- if snapshot.model.kind.is_view:
4182
- return "recreate view"
4183
- if snapshot.model.kind.is_incremental_by_unique_key:
4184
- return "insert/update rows"
4185
- if snapshot.model.kind.is_incremental_by_partition:
4186
- return "insert partitions"
4187
-
4188
- return interval_info if interval_info else ""
4189
-
4190
-
4191
- def _calculate_interval_str_len(snapshot: Snapshot, intervals: t.List[Interval]) -> int:
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]], audit_padding: int = 0
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, query, target_columns_to_types or self.columns(table)
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(table_name, source_queries, target_columns_to_types)
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(table_name, query, target_columns_to_types)
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(exp.insert(query, table_name, columns=list(target_columns_to_types)))
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(exp.Merge(this=this, using=using, on=on, whens=whens))
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 _execute(self, sql: str, **kwargs: t.Any) -> None:
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 TableNamingConvention, VirtualEnvironmentMode
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
- result = self._evaluate_snapshot(
173
- start=start,
174
- end=end,
175
- execution_time=execution_time,
176
- snapshot=snapshot,
177
- snapshots=snapshots,
178
- allow_destructive_snapshots=allow_destructive_snapshots or set(),
179
- allow_additive_snapshots=allow_additive_snapshots or set(),
180
- deployability_index=deployability_index,
181
- batch_index=batch_index,
182
- target_table_exists=target_table_exists,
183
- **kwargs,
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(backup_name, exp.select("*").from_(table))
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(
@@ -55,6 +55,7 @@ class VersionState:
55
55
  ]
56
56
  ),
57
57
  target_columns_to_types=self._version_columns_to_types,
58
+ track_rows_processed=False,
58
59
  )
59
60
 
60
61
  def get_versions(self) -> Versions:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmesh
3
- Version: 0.211.1.dev4
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=qYxwtlsmdtANLFoBZXS_Mq7mAhgSXOczqQ3GgApnbwU,721
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=TclHdPBzufeuM546TcLs24FGupIuJBUN3vqjQFu0sJ0,167042
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=TyRPooWRDIMxZrE44msckQSwUFaPElsXKzF5fURtj1g,46806
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=UlZTHy1bGq0Wqzp9hmbphCpL-P6duOALNGNBSNEHYnk,26756
61
- sqlmesh/core/engine_adapter/base.py,sha256=Nzd6Rg--W0BGuEgWVxgAfarabc-HPJcR9gUQXZjj16g,113607
62
- sqlmesh/core/engine_adapter/base_postgres.py,sha256=U4Y_0-M6_0_RcKbljX57DIUSwIa70w02k-UoDOnWPxQ,7016
63
- sqlmesh/core/engine_adapter/bigquery.py,sha256=dtzRk6BlCLJxnwXeoz1ANAXbIrhtiJEAruasHzJYj0M,53935
64
- sqlmesh/core/engine_adapter/clickhouse.py,sha256=yBVEorfoaHp-O0qi3UXOnGlDwM7_Yo6lw6pOUMEjwB4,35854
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=AyG8mpL1f2cfv4a-_mO_XqJEjMpQZlnLTw4hKhijcvU,7895
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=xLX15SrL-2ukv_YLy9a_4g9pxPga-1WcwxWUBIFY-f4,18225
70
- sqlmesh/core/engine_adapter/mysql.py,sha256=071qYHeJ_7gDdC7YWJoRCorjhxGAVkhtqWL-KetfPCg,6914
71
- sqlmesh/core/engine_adapter/postgres.py,sha256=v6fD7ci22-DrZBtry_03Lfq5m7SNgSPq5X_BZ1dWLhk,5083
72
- sqlmesh/core/engine_adapter/redshift.py,sha256=bfrgxY3Fqcbz5inAF4cxOQCmRdfuC--aPu5U3ks29LA,17494
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=5vlEaDRROI4pUHCkkRUNMCl0-R7p24Q5goJZ-FmJ9EQ,30004
76
- sqlmesh/core/engine_adapter/spark.py,sha256=YTFLBBF7Io6iZAhfsyzqtaqxddCCHxt0J9XiclUalf0,22368
77
- sqlmesh/core/engine_adapter/trino.py,sha256=euroG7zMlQyv8hlo7SBo-deZuj1gti5jRy-9zToNTGE,16289
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=Z5HTetNCZGoKF1WAKXk4LWL9dc8EC8lwS2O4TtoBmt4,1317
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=LPHpFYY4gKaJnQQzOoToeIbrDWvVVObqaLfyRjB2Wz4,91637
107
- sqlmesh/core/snapshot/evaluator.py,sha256=cWAOXiB9Yd8hZpfv4m45Ht-0ix0wJRVF710t4eO8XeA,109534
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=SSH4NzqKxUGZ6g0qg0_vLry5T3UyNNyaWA_ePdE7gFI,13689
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=v2NpzKD3Ff6uQekbXjlZZ_tXjzZfFws2gGCmy1XOkZo,17999
117
- sqlmesh/core/state_sync/db/migrator.py,sha256=av5-dw_0T-u171L8Aepv6nDEg14GFVibdM_hYWq3Hz0,18118
118
- sqlmesh/core/state_sync/db/snapshot.py,sha256=3QpaVGoqCn81Rlk5oKZrTcaj4pbOFrpUHHT5P-6dXJc,28220
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=CcmtJyNOOYsACrncxxyc1WGWlx1vPczm_vC5Ii4uVw4,2217
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.1.dev4.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
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=6CqnLKx3DJmacvKtKHmade3RPNBfao2YSi5dh0SI9j4,3853
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=1mA9MqVQB2bqk9JnZCNZ1hxIhp1lau05ANUqEHok8V4,5290
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=RJITA13d2hILvQYSiWFi_4aZeu0YzxREERb23GVm5P8,4846
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=GsxIYu-oAlXhsSpWohNHYLAO9lOz617wOW1oJI3fR8I,14669
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.1.dev4.dist-info/METADATA,sha256=RYC-W7lOS9dtSnU_Mov0UyR6Z3YcmL8DuGEVOCFM190,26749
422
- sqlmesh-0.211.1.dev4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
423
- sqlmesh-0.211.1.dev4.dist-info/entry_points.txt,sha256=sHAf6tQczIM8xZoduN4qaUjV7QEPVUUW_LCT8EDUMv4,155
424
- sqlmesh-0.211.1.dev4.dist-info/top_level.txt,sha256=RQ-33FPe2IgL0rgossAfJkCRtqslz9b7wFARqiWLC5Q,24
425
- sqlmesh-0.211.1.dev4.dist-info/RECORD,,
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, profile: t.Optional[str] = None, target: t.Optional[str] = None
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(create, project_dir=Path.cwd(), profile=profile, target=target)
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 = " | ".join(_to_sqlmesh(expr, negate=True) for expr in dbt_exclude)
48
- exclude_expr = _wrap(exclude_expr) if dbt_select and len(dbt_exclude) > 1 else exclude_expr
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, negate: bool = False) -> 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: