sqlmesh 0.217.1.dev1__py3-none-any.whl → 0.227.2.dev4__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/__init__.py +12 -2
- sqlmesh/_version.py +2 -2
- sqlmesh/cli/project_init.py +10 -2
- sqlmesh/core/_typing.py +1 -0
- sqlmesh/core/audit/definition.py +8 -2
- sqlmesh/core/config/__init__.py +1 -1
- sqlmesh/core/config/connection.py +17 -5
- sqlmesh/core/config/dbt.py +13 -0
- sqlmesh/core/config/janitor.py +12 -0
- sqlmesh/core/config/loader.py +7 -0
- sqlmesh/core/config/model.py +2 -0
- sqlmesh/core/config/root.py +3 -0
- sqlmesh/core/console.py +80 -2
- sqlmesh/core/constants.py +1 -1
- sqlmesh/core/context.py +61 -25
- sqlmesh/core/dialect.py +3 -0
- sqlmesh/core/engine_adapter/_typing.py +2 -0
- sqlmesh/core/engine_adapter/base.py +322 -22
- sqlmesh/core/engine_adapter/base_postgres.py +17 -1
- sqlmesh/core/engine_adapter/bigquery.py +146 -7
- sqlmesh/core/engine_adapter/clickhouse.py +17 -13
- sqlmesh/core/engine_adapter/databricks.py +33 -2
- sqlmesh/core/engine_adapter/fabric.py +1 -29
- sqlmesh/core/engine_adapter/mixins.py +142 -48
- sqlmesh/core/engine_adapter/mssql.py +15 -4
- sqlmesh/core/engine_adapter/mysql.py +2 -2
- sqlmesh/core/engine_adapter/postgres.py +9 -3
- sqlmesh/core/engine_adapter/redshift.py +4 -0
- sqlmesh/core/engine_adapter/risingwave.py +1 -0
- sqlmesh/core/engine_adapter/shared.py +6 -0
- sqlmesh/core/engine_adapter/snowflake.py +82 -11
- sqlmesh/core/engine_adapter/spark.py +14 -10
- sqlmesh/core/engine_adapter/trino.py +4 -2
- sqlmesh/core/janitor.py +181 -0
- sqlmesh/core/lineage.py +1 -0
- sqlmesh/core/macros.py +35 -13
- sqlmesh/core/model/common.py +2 -0
- sqlmesh/core/model/definition.py +65 -4
- sqlmesh/core/model/kind.py +66 -2
- sqlmesh/core/model/meta.py +107 -2
- sqlmesh/core/node.py +101 -2
- sqlmesh/core/plan/builder.py +15 -10
- sqlmesh/core/plan/common.py +196 -2
- sqlmesh/core/plan/definition.py +21 -6
- sqlmesh/core/plan/evaluator.py +72 -113
- sqlmesh/core/plan/explainer.py +90 -8
- sqlmesh/core/plan/stages.py +42 -21
- sqlmesh/core/renderer.py +26 -18
- sqlmesh/core/scheduler.py +60 -19
- sqlmesh/core/selector.py +137 -9
- sqlmesh/core/signal.py +64 -1
- sqlmesh/core/snapshot/__init__.py +1 -0
- sqlmesh/core/snapshot/definition.py +109 -25
- sqlmesh/core/snapshot/evaluator.py +610 -50
- sqlmesh/core/state_sync/__init__.py +0 -1
- sqlmesh/core/state_sync/base.py +31 -27
- sqlmesh/core/state_sync/cache.py +12 -4
- sqlmesh/core/state_sync/common.py +216 -111
- sqlmesh/core/state_sync/db/facade.py +30 -15
- sqlmesh/core/state_sync/db/interval.py +27 -7
- sqlmesh/core/state_sync/db/migrator.py +14 -8
- sqlmesh/core/state_sync/db/snapshot.py +119 -87
- sqlmesh/core/table_diff.py +2 -2
- sqlmesh/core/test/definition.py +14 -9
- sqlmesh/dbt/adapter.py +20 -11
- sqlmesh/dbt/basemodel.py +52 -41
- sqlmesh/dbt/builtin.py +27 -11
- sqlmesh/dbt/column.py +17 -5
- sqlmesh/dbt/common.py +4 -2
- sqlmesh/dbt/context.py +14 -1
- sqlmesh/dbt/loader.py +60 -8
- sqlmesh/dbt/manifest.py +136 -8
- sqlmesh/dbt/model.py +105 -25
- sqlmesh/dbt/package.py +16 -1
- sqlmesh/dbt/profile.py +3 -3
- sqlmesh/dbt/project.py +12 -7
- sqlmesh/dbt/seed.py +1 -1
- sqlmesh/dbt/source.py +6 -1
- sqlmesh/dbt/target.py +25 -6
- sqlmesh/dbt/test.py +31 -1
- sqlmesh/migrations/v0000_baseline.py +3 -6
- sqlmesh/migrations/v0061_mysql_fix_blob_text_type.py +2 -5
- sqlmesh/migrations/v0062_add_model_gateway.py +2 -2
- sqlmesh/migrations/v0063_change_signals.py +2 -4
- sqlmesh/migrations/v0064_join_when_matched_strings.py +2 -4
- sqlmesh/migrations/v0065_add_model_optimize.py +2 -2
- sqlmesh/migrations/v0066_add_auto_restatements.py +2 -6
- sqlmesh/migrations/v0067_add_tsql_date_full_precision.py +2 -2
- sqlmesh/migrations/v0068_include_unrendered_query_in_metadata_hash.py +2 -2
- sqlmesh/migrations/v0069_update_dev_table_suffix.py +2 -4
- sqlmesh/migrations/v0070_include_grains_in_metadata_hash.py +2 -2
- sqlmesh/migrations/v0071_add_dev_version_to_intervals.py +2 -6
- sqlmesh/migrations/v0072_add_environment_statements.py +2 -4
- sqlmesh/migrations/v0073_remove_symbolic_disable_restatement.py +2 -4
- sqlmesh/migrations/v0074_add_partition_by_time_column_property.py +2 -2
- sqlmesh/migrations/v0075_remove_validate_query.py +2 -4
- sqlmesh/migrations/v0076_add_cron_tz.py +2 -2
- sqlmesh/migrations/v0077_fix_column_type_hash_calculation.py +2 -2
- sqlmesh/migrations/v0078_warn_if_non_migratable_python_env.py +2 -4
- sqlmesh/migrations/v0079_add_gateway_managed_property.py +7 -9
- sqlmesh/migrations/v0080_add_batch_size_to_scd_type_2_models.py +2 -2
- sqlmesh/migrations/v0081_update_partitioned_by.py +2 -4
- sqlmesh/migrations/v0082_warn_if_incorrectly_duplicated_statements.py +2 -4
- sqlmesh/migrations/v0083_use_sql_for_scd_time_data_type_data_hash.py +2 -2
- sqlmesh/migrations/v0084_normalize_quote_when_matched_and_merge_filter.py +2 -2
- sqlmesh/migrations/v0085_deterministic_repr.py +2 -4
- sqlmesh/migrations/v0086_check_deterministic_bug.py +2 -4
- sqlmesh/migrations/v0087_normalize_blueprint_variables.py +2 -4
- sqlmesh/migrations/v0088_warn_about_variable_python_env_diffs.py +2 -4
- sqlmesh/migrations/v0089_add_virtual_environment_mode.py +2 -2
- sqlmesh/migrations/v0090_add_forward_only_column.py +2 -6
- sqlmesh/migrations/v0091_on_additive_change.py +2 -2
- sqlmesh/migrations/v0092_warn_about_dbt_data_type_diff.py +2 -4
- sqlmesh/migrations/v0093_use_raw_sql_in_fingerprint.py +2 -2
- sqlmesh/migrations/v0094_add_dev_version_and_fingerprint_columns.py +2 -6
- sqlmesh/migrations/v0095_warn_about_dbt_raw_sql_diff.py +2 -4
- sqlmesh/migrations/v0096_remove_plan_dags_table.py +2 -4
- sqlmesh/migrations/v0097_add_dbt_name_in_node.py +2 -2
- sqlmesh/migrations/v0098_add_dbt_node_info_in_node.py +103 -0
- sqlmesh/migrations/v0099_add_last_altered_to_intervals.py +25 -0
- sqlmesh/migrations/v0100_add_grants_and_grants_target_layer.py +9 -0
- sqlmesh/utils/__init__.py +8 -1
- sqlmesh/utils/cache.py +5 -1
- sqlmesh/utils/date.py +1 -1
- sqlmesh/utils/errors.py +4 -0
- sqlmesh/utils/jinja.py +25 -2
- sqlmesh/utils/pydantic.py +6 -6
- sqlmesh/utils/windows.py +13 -3
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/METADATA +5 -5
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/RECORD +181 -176
- sqlmesh_dbt/cli.py +70 -7
- sqlmesh_dbt/console.py +14 -6
- sqlmesh_dbt/operations.py +103 -24
- sqlmesh_dbt/selectors.py +39 -1
- web/client/dist/assets/{Audits-Ucsx1GzF.js → Audits-CBiYyyx-.js} +1 -1
- web/client/dist/assets/{Banner-BWDzvavM.js → Banner-DSRbUlO5.js} +1 -1
- web/client/dist/assets/{ChevronDownIcon-D2VL13Ah.js → ChevronDownIcon-MK_nrjD_.js} +1 -1
- web/client/dist/assets/{ChevronRightIcon-DWGYbf1l.js → ChevronRightIcon-CLWtT22Q.js} +1 -1
- web/client/dist/assets/{Content-DdHDZM3I.js → Content-BNuGZN5l.js} +1 -1
- web/client/dist/assets/{Content-Bikfy8fh.js → Content-CSHJyW0n.js} +1 -1
- web/client/dist/assets/{Data-CzAJH7rW.js → Data-C1oRDbLx.js} +1 -1
- web/client/dist/assets/{DataCatalog-BJF11g8f.js → DataCatalog-HXyX2-_j.js} +1 -1
- web/client/dist/assets/{Editor-s0SBpV2y.js → Editor-BDyfpUuw.js} +1 -1
- web/client/dist/assets/{Editor-DgLhgKnm.js → Editor-D0jNItwC.js} +1 -1
- web/client/dist/assets/{Errors-D0m0O1d3.js → Errors-BfuFLcPi.js} +1 -1
- web/client/dist/assets/{FileExplorer-CEv0vXkt.js → FileExplorer-BR9IE3he.js} +1 -1
- web/client/dist/assets/{Footer-BwzXn8Ew.js → Footer-CgBEtiAh.js} +1 -1
- web/client/dist/assets/{Header-6heDkEqG.js → Header-DSqR6nSO.js} +1 -1
- web/client/dist/assets/{Input-obuJsD6k.js → Input-B-oZ6fGO.js} +1 -1
- web/client/dist/assets/Lineage-DYQVwDbD.js +1 -0
- web/client/dist/assets/{ListboxShow-HM9_qyrt.js → ListboxShow-BE5-xevs.js} +1 -1
- web/client/dist/assets/{ModelLineage-zWdKo0U2.js → ModelLineage-DkIFAYo4.js} +1 -1
- web/client/dist/assets/{Models-Bcu66SRz.js → Models-D5dWr8RB.js} +1 -1
- web/client/dist/assets/{Page-BWEEQfIt.js → Page-C-XfU5BR.js} +1 -1
- web/client/dist/assets/{Plan-C4gXCqlf.js → Plan-ZEuTINBq.js} +1 -1
- web/client/dist/assets/{PlusCircleIcon-CVDO651q.js → PlusCircleIcon-DVXAHG8_.js} +1 -1
- web/client/dist/assets/{ReportErrors-BT6xFwAr.js → ReportErrors-B7FEPzMB.js} +1 -1
- web/client/dist/assets/{Root-ryJoBK4h.js → Root-8aZyhPxF.js} +1 -1
- web/client/dist/assets/{SearchList-DB04sPb9.js → SearchList-W_iT2G82.js} +1 -1
- web/client/dist/assets/{SelectEnvironment-CUYcXUu6.js → SelectEnvironment-C65jALmO.js} +1 -1
- web/client/dist/assets/{SourceList-Doo_9ZGp.js → SourceList-DSLO6nVJ.js} +1 -1
- web/client/dist/assets/{SourceListItem-D5Mj7Dly.js → SourceListItem-BHt8d9-I.js} +1 -1
- web/client/dist/assets/{SplitPane-qHmkD1qy.js → SplitPane-CViaZmw6.js} +1 -1
- web/client/dist/assets/{Tests-DH1Z74ML.js → Tests-DhaVt5t1.js} +1 -1
- web/client/dist/assets/{Welcome-DqUJUNMF.js → Welcome-DvpjH-_4.js} +1 -1
- web/client/dist/assets/context-BctCsyGb.js +71 -0
- web/client/dist/assets/{context-Dr54UHLi.js → context-DFNeGsFF.js} +1 -1
- web/client/dist/assets/{editor-DYIP1yQ4.js → editor-CcO28cqd.js} +1 -1
- web/client/dist/assets/{file-DarlIDVi.js → file-CvJN3aZO.js} +1 -1
- web/client/dist/assets/{floating-ui.react-dom-BH3TFvkM.js → floating-ui.react-dom-CjE-JNW1.js} +1 -1
- web/client/dist/assets/{help-Bl8wqaQc.js → help-DuPhjipa.js} +1 -1
- web/client/dist/assets/{index-D1sR7wpN.js → index-C-dJH7yZ.js} +1 -1
- web/client/dist/assets/{index-O3mjYpnE.js → index-Dj0i1-CA.js} +2 -2
- web/client/dist/assets/{plan-CehRrJUG.js → plan-BTRSbjKn.js} +1 -1
- web/client/dist/assets/{popover-CqgMRE0G.js → popover-_Sf0yvOI.js} +1 -1
- web/client/dist/assets/{project-6gxepOhm.js → project-BvSOI8MY.js} +1 -1
- web/client/dist/index.html +1 -1
- web/client/dist/assets/Lineage-D0Hgdz2v.js +0 -1
- web/client/dist/assets/context-DgX0fp2E.js +0 -68
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/WHEEL +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.217.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/top_level.txt +0 -0
|
@@ -15,10 +15,10 @@ from sqlmesh.core.state_sync.db.utils import (
|
|
|
15
15
|
from sqlmesh.core.snapshot import (
|
|
16
16
|
SnapshotIntervals,
|
|
17
17
|
SnapshotIdLike,
|
|
18
|
+
SnapshotIdAndVersionLike,
|
|
18
19
|
SnapshotNameVersionLike,
|
|
19
20
|
SnapshotTableCleanupTask,
|
|
20
21
|
SnapshotNameVersion,
|
|
21
|
-
SnapshotInfoLike,
|
|
22
22
|
Snapshot,
|
|
23
23
|
)
|
|
24
24
|
from sqlmesh.core.snapshot.definition import Interval
|
|
@@ -60,6 +60,7 @@ class IntervalState:
|
|
|
60
60
|
"is_removed": exp.DataType.build("boolean"),
|
|
61
61
|
"is_compacted": exp.DataType.build("boolean"),
|
|
62
62
|
"is_pending_restatement": exp.DataType.build("boolean"),
|
|
63
|
+
"last_altered_ts": exp.DataType.build("bigint"),
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
def add_snapshots_intervals(self, snapshots_intervals: t.Sequence[SnapshotIntervals]) -> None:
|
|
@@ -68,11 +69,11 @@ class IntervalState:
|
|
|
68
69
|
|
|
69
70
|
def remove_intervals(
|
|
70
71
|
self,
|
|
71
|
-
snapshot_intervals: t.Sequence[t.Tuple[
|
|
72
|
+
snapshot_intervals: t.Sequence[t.Tuple[SnapshotIdAndVersionLike, Interval]],
|
|
72
73
|
remove_shared_versions: bool = False,
|
|
73
74
|
) -> None:
|
|
74
75
|
intervals_to_remove: t.Sequence[
|
|
75
|
-
t.Tuple[t.Union[
|
|
76
|
+
t.Tuple[t.Union[SnapshotIdAndVersionLike, SnapshotIntervals], Interval]
|
|
76
77
|
] = snapshot_intervals
|
|
77
78
|
if remove_shared_versions:
|
|
78
79
|
name_version_mapping = {s.name_version: interval for s, interval in snapshot_intervals}
|
|
@@ -215,13 +216,23 @@ class IntervalState:
|
|
|
215
216
|
for start_ts, end_ts in snapshot.intervals:
|
|
216
217
|
new_intervals.append(
|
|
217
218
|
_interval_to_df(
|
|
218
|
-
snapshot,
|
|
219
|
+
snapshot,
|
|
220
|
+
start_ts,
|
|
221
|
+
end_ts,
|
|
222
|
+
is_dev=False,
|
|
223
|
+
is_compacted=is_compacted,
|
|
224
|
+
last_altered_ts=snapshot.last_altered_ts,
|
|
219
225
|
)
|
|
220
226
|
)
|
|
221
227
|
for start_ts, end_ts in snapshot.dev_intervals:
|
|
222
228
|
new_intervals.append(
|
|
223
229
|
_interval_to_df(
|
|
224
|
-
snapshot,
|
|
230
|
+
snapshot,
|
|
231
|
+
start_ts,
|
|
232
|
+
end_ts,
|
|
233
|
+
is_dev=True,
|
|
234
|
+
is_compacted=is_compacted,
|
|
235
|
+
last_altered_ts=snapshot.dev_last_altered_ts,
|
|
225
236
|
)
|
|
226
237
|
)
|
|
227
238
|
|
|
@@ -236,6 +247,7 @@ class IntervalState:
|
|
|
236
247
|
is_dev=False,
|
|
237
248
|
is_compacted=is_compacted,
|
|
238
249
|
is_pending_restatement=True,
|
|
250
|
+
last_altered_ts=snapshot.last_altered_ts,
|
|
239
251
|
)
|
|
240
252
|
)
|
|
241
253
|
|
|
@@ -284,6 +296,7 @@ class IntervalState:
|
|
|
284
296
|
is_dev,
|
|
285
297
|
is_removed,
|
|
286
298
|
is_pending_restatement,
|
|
299
|
+
last_altered_ts,
|
|
287
300
|
) in rows:
|
|
288
301
|
interval_ids.add(interval_id)
|
|
289
302
|
merge_key = (name, version, dev_version, identifier)
|
|
@@ -318,8 +331,10 @@ class IntervalState:
|
|
|
318
331
|
else:
|
|
319
332
|
if is_dev:
|
|
320
333
|
intervals[merge_key].add_dev_interval(start, end)
|
|
334
|
+
intervals[merge_key].update_dev_last_altered_ts(last_altered_ts)
|
|
321
335
|
else:
|
|
322
336
|
intervals[merge_key].add_interval(start, end)
|
|
337
|
+
intervals[merge_key].update_last_altered_ts(last_altered_ts)
|
|
323
338
|
# Remove all pending restatement intervals recorded before the current interval has been added
|
|
324
339
|
intervals[
|
|
325
340
|
pending_restatement_interval_merge_key
|
|
@@ -340,6 +355,7 @@ class IntervalState:
|
|
|
340
355
|
"is_dev",
|
|
341
356
|
"is_removed",
|
|
342
357
|
"is_pending_restatement",
|
|
358
|
+
"last_altered_ts",
|
|
343
359
|
)
|
|
344
360
|
.from_(exp.to_table(self.intervals_table).as_("intervals"))
|
|
345
361
|
.order_by(
|
|
@@ -431,7 +447,9 @@ class IntervalState:
|
|
|
431
447
|
|
|
432
448
|
|
|
433
449
|
def _intervals_to_df(
|
|
434
|
-
snapshot_intervals: t.Sequence[
|
|
450
|
+
snapshot_intervals: t.Sequence[
|
|
451
|
+
t.Tuple[t.Union[SnapshotIdAndVersionLike, SnapshotIntervals], Interval]
|
|
452
|
+
],
|
|
435
453
|
is_dev: bool,
|
|
436
454
|
is_removed: bool,
|
|
437
455
|
) -> pd.DataFrame:
|
|
@@ -451,13 +469,14 @@ def _intervals_to_df(
|
|
|
451
469
|
|
|
452
470
|
|
|
453
471
|
def _interval_to_df(
|
|
454
|
-
snapshot: t.Union[
|
|
472
|
+
snapshot: t.Union[SnapshotIdAndVersionLike, SnapshotIntervals],
|
|
455
473
|
start_ts: int,
|
|
456
474
|
end_ts: int,
|
|
457
475
|
is_dev: bool = False,
|
|
458
476
|
is_removed: bool = False,
|
|
459
477
|
is_compacted: bool = False,
|
|
460
478
|
is_pending_restatement: bool = False,
|
|
479
|
+
last_altered_ts: t.Optional[int] = None,
|
|
461
480
|
) -> t.Dict[str, t.Any]:
|
|
462
481
|
return {
|
|
463
482
|
"id": random_id(),
|
|
@@ -472,4 +491,5 @@ def _interval_to_df(
|
|
|
472
491
|
"is_removed": is_removed,
|
|
473
492
|
"is_compacted": is_compacted,
|
|
474
493
|
"is_pending_restatement": is_pending_restatement,
|
|
494
|
+
"last_altered_ts": last_altered_ts,
|
|
475
495
|
}
|
|
@@ -30,7 +30,6 @@ from sqlmesh.core.state_sync.base import (
|
|
|
30
30
|
MIN_SCHEMA_VERSION,
|
|
31
31
|
MIN_SQLMESH_VERSION,
|
|
32
32
|
)
|
|
33
|
-
from sqlmesh.core.state_sync.base import StateSync
|
|
34
33
|
from sqlmesh.core.state_sync.db.environment import EnvironmentState
|
|
35
34
|
from sqlmesh.core.state_sync.db.interval import IntervalState
|
|
36
35
|
from sqlmesh.core.state_sync.db.snapshot import SnapshotState
|
|
@@ -85,7 +84,7 @@ class StateMigrator:
|
|
|
85
84
|
|
|
86
85
|
def migrate(
|
|
87
86
|
self,
|
|
88
|
-
|
|
87
|
+
schema: t.Optional[str],
|
|
89
88
|
skip_backup: bool = False,
|
|
90
89
|
promoted_snapshots_only: bool = True,
|
|
91
90
|
) -> None:
|
|
@@ -94,7 +93,7 @@ class StateMigrator:
|
|
|
94
93
|
migration_start_ts = time.perf_counter()
|
|
95
94
|
|
|
96
95
|
try:
|
|
97
|
-
migrate_rows = self._apply_migrations(
|
|
96
|
+
migrate_rows = self._apply_migrations(schema, skip_backup)
|
|
98
97
|
|
|
99
98
|
if not migrate_rows and major_minor(SQLMESH_VERSION) == versions.minor_sqlmesh_version:
|
|
100
99
|
return
|
|
@@ -153,7 +152,7 @@ class StateMigrator:
|
|
|
153
152
|
|
|
154
153
|
def _apply_migrations(
|
|
155
154
|
self,
|
|
156
|
-
|
|
155
|
+
schema: t.Optional[str],
|
|
157
156
|
skip_backup: bool,
|
|
158
157
|
) -> bool:
|
|
159
158
|
versions = self.version_state.get_versions()
|
|
@@ -184,10 +183,10 @@ class StateMigrator:
|
|
|
184
183
|
|
|
185
184
|
for migration in migrations:
|
|
186
185
|
logger.info(f"Applying migration {migration}")
|
|
187
|
-
migration.migrate_schemas(
|
|
186
|
+
migration.migrate_schemas(engine_adapter=self.engine_adapter, schema=schema)
|
|
188
187
|
if state_table_exist:
|
|
189
188
|
# No need to run DML for the initial migration since all tables are empty
|
|
190
|
-
migration.migrate_rows(
|
|
189
|
+
migration.migrate_rows(engine_adapter=self.engine_adapter, schema=schema)
|
|
191
190
|
|
|
192
191
|
snapshot_count_after = self.snapshot_state.count()
|
|
193
192
|
|
|
@@ -229,6 +228,7 @@ class StateMigrator:
|
|
|
229
228
|
"updated_ts": updated_ts,
|
|
230
229
|
"unpaused_ts": unpaused_ts,
|
|
231
230
|
"unrestorable": unrestorable,
|
|
231
|
+
"forward_only": forward_only,
|
|
232
232
|
}
|
|
233
233
|
for where in (
|
|
234
234
|
snapshot_id_filter(
|
|
@@ -237,10 +237,16 @@ class StateMigrator:
|
|
|
237
237
|
if snapshots is not None
|
|
238
238
|
else [None]
|
|
239
239
|
)
|
|
240
|
-
for name, identifier, raw_snapshot, updated_ts, unpaused_ts, unrestorable in fetchall(
|
|
240
|
+
for name, identifier, raw_snapshot, updated_ts, unpaused_ts, unrestorable, forward_only in fetchall(
|
|
241
241
|
self.engine_adapter,
|
|
242
242
|
exp.select(
|
|
243
|
-
"name",
|
|
243
|
+
"name",
|
|
244
|
+
"identifier",
|
|
245
|
+
"snapshot",
|
|
246
|
+
"updated_ts",
|
|
247
|
+
"unpaused_ts",
|
|
248
|
+
"unrestorable",
|
|
249
|
+
"forward_only",
|
|
244
250
|
)
|
|
245
251
|
.from_(self.snapshot_state.snapshots_table)
|
|
246
252
|
.where(where)
|
|
@@ -14,7 +14,6 @@ from sqlmesh.core.state_sync.db.utils import (
|
|
|
14
14
|
snapshot_id_filter,
|
|
15
15
|
fetchone,
|
|
16
16
|
fetchall,
|
|
17
|
-
create_batches,
|
|
18
17
|
)
|
|
19
18
|
from sqlmesh.core.environment import Environment
|
|
20
19
|
from sqlmesh.core.model import SeedModel, ModelKindName
|
|
@@ -30,6 +29,12 @@ from sqlmesh.core.snapshot import (
|
|
|
30
29
|
SnapshotId,
|
|
31
30
|
SnapshotFingerprint,
|
|
32
31
|
)
|
|
32
|
+
from sqlmesh.core.state_sync.common import (
|
|
33
|
+
RowBoundary,
|
|
34
|
+
ExpiredSnapshotBatch,
|
|
35
|
+
ExpiredBatchRange,
|
|
36
|
+
LimitBoundary,
|
|
37
|
+
)
|
|
33
38
|
from sqlmesh.utils.migration import index_text_type, blob_text_type
|
|
34
39
|
from sqlmesh.utils.date import now_timestamp, TimeLike, to_timestamp
|
|
35
40
|
from sqlmesh.utils import unique
|
|
@@ -43,9 +48,6 @@ logger = logging.getLogger(__name__)
|
|
|
43
48
|
|
|
44
49
|
class SnapshotState:
|
|
45
50
|
SNAPSHOT_BATCH_SIZE = 1000
|
|
46
|
-
# Use a smaller batch size for expired snapshots to account for fetching
|
|
47
|
-
# of all snapshots that share the same version.
|
|
48
|
-
EXPIRED_SNAPSHOT_BATCH_SIZE = 200
|
|
49
51
|
|
|
50
52
|
def __init__(
|
|
51
53
|
self,
|
|
@@ -166,53 +168,62 @@ class SnapshotState:
|
|
|
166
168
|
self,
|
|
167
169
|
environments: t.Iterable[Environment],
|
|
168
170
|
current_ts: int,
|
|
169
|
-
ignore_ttl: bool
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
Returns:
|
|
177
|
-
The set of expired snapshot ids.
|
|
178
|
-
The list of table cleanup tasks.
|
|
179
|
-
"""
|
|
180
|
-
all_cleanup_targets = []
|
|
181
|
-
for _, cleanup_targets in self._get_expired_snapshots(
|
|
182
|
-
environments=environments,
|
|
183
|
-
current_ts=current_ts,
|
|
184
|
-
ignore_ttl=ignore_ttl,
|
|
185
|
-
):
|
|
186
|
-
all_cleanup_targets.extend(cleanup_targets)
|
|
187
|
-
return all_cleanup_targets
|
|
188
|
-
|
|
189
|
-
def _get_expired_snapshots(
|
|
190
|
-
self,
|
|
191
|
-
environments: t.Iterable[Environment],
|
|
192
|
-
current_ts: int,
|
|
193
|
-
ignore_ttl: bool = False,
|
|
194
|
-
) -> t.Iterator[t.Tuple[t.Set[SnapshotId], t.List[SnapshotTableCleanupTask]]]:
|
|
195
|
-
expired_query = exp.select("name", "identifier", "version").from_(self.snapshots_table)
|
|
171
|
+
ignore_ttl: bool,
|
|
172
|
+
batch_range: ExpiredBatchRange,
|
|
173
|
+
) -> t.Optional[ExpiredSnapshotBatch]:
|
|
174
|
+
expired_query = exp.select("name", "identifier", "version", "updated_ts").from_(
|
|
175
|
+
self.snapshots_table
|
|
176
|
+
)
|
|
196
177
|
|
|
197
178
|
if not ignore_ttl:
|
|
198
179
|
expired_query = expired_query.where(
|
|
199
180
|
(exp.column("updated_ts") + exp.column("ttl_ms")) <= current_ts
|
|
200
181
|
)
|
|
201
182
|
|
|
183
|
+
expired_query = expired_query.where(batch_range.where_filter)
|
|
184
|
+
|
|
185
|
+
promoted_snapshot_ids = {
|
|
186
|
+
snapshot.snapshot_id
|
|
187
|
+
for environment in environments
|
|
188
|
+
for snapshot in (
|
|
189
|
+
environment.snapshots
|
|
190
|
+
if environment.finalized_ts is not None
|
|
191
|
+
# If the environment is not finalized, check both the current snapshots and the previous finalized snapshots
|
|
192
|
+
else [*environment.snapshots, *(environment.previous_finalized_snapshots or [])]
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if promoted_snapshot_ids:
|
|
197
|
+
not_in_conditions = [
|
|
198
|
+
exp.not_(condition)
|
|
199
|
+
for condition in snapshot_id_filter(
|
|
200
|
+
self.engine_adapter,
|
|
201
|
+
promoted_snapshot_ids,
|
|
202
|
+
batch_size=self.SNAPSHOT_BATCH_SIZE,
|
|
203
|
+
)
|
|
204
|
+
]
|
|
205
|
+
expired_query = expired_query.where(exp.and_(*not_in_conditions))
|
|
206
|
+
|
|
207
|
+
expired_query = expired_query.order_by(
|
|
208
|
+
exp.column("updated_ts"), exp.column("name"), exp.column("identifier")
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if isinstance(batch_range.end, LimitBoundary):
|
|
212
|
+
expired_query = expired_query.limit(batch_range.end.batch_size)
|
|
213
|
+
|
|
214
|
+
rows = fetchall(self.engine_adapter, expired_query)
|
|
215
|
+
|
|
216
|
+
if not rows:
|
|
217
|
+
return None
|
|
218
|
+
|
|
202
219
|
expired_candidates = {
|
|
203
220
|
SnapshotId(name=name, identifier=identifier): SnapshotNameVersion(
|
|
204
221
|
name=name, version=version
|
|
205
222
|
)
|
|
206
|
-
for name, identifier, version in
|
|
223
|
+
for name, identifier, version, _ in rows
|
|
207
224
|
}
|
|
208
225
|
if not expired_candidates:
|
|
209
|
-
return
|
|
210
|
-
|
|
211
|
-
promoted_snapshot_ids = {
|
|
212
|
-
snapshot.snapshot_id
|
|
213
|
-
for environment in environments
|
|
214
|
-
for snapshot in environment.snapshots
|
|
215
|
-
}
|
|
226
|
+
return None
|
|
216
227
|
|
|
217
228
|
def _is_snapshot_used(snapshot: SnapshotIdAndVersion) -> bool:
|
|
218
229
|
return (
|
|
@@ -220,57 +231,73 @@ class SnapshotState:
|
|
|
220
231
|
or snapshot.snapshot_id not in expired_candidates
|
|
221
232
|
)
|
|
222
233
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
234
|
+
# Extract cursor values from last row for pagination
|
|
235
|
+
last_row = rows[-1]
|
|
236
|
+
last_row_boundary = RowBoundary(
|
|
237
|
+
updated_ts=last_row[3],
|
|
238
|
+
name=last_row[0],
|
|
239
|
+
identifier=last_row[1],
|
|
226
240
|
)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
241
|
+
# The returned batch_range represents the actual range of rows in this batch
|
|
242
|
+
result_batch_range = ExpiredBatchRange(
|
|
243
|
+
start=batch_range.start,
|
|
244
|
+
end=last_row_boundary,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
unique_expired_versions = unique(expired_candidates.values())
|
|
248
|
+
expired_snapshot_ids: t.Set[SnapshotId] = set()
|
|
249
|
+
cleanup_tasks: t.List[SnapshotTableCleanupTask] = []
|
|
250
|
+
|
|
251
|
+
snapshots = self._get_snapshots_with_same_version(unique_expired_versions)
|
|
252
|
+
|
|
253
|
+
snapshots_by_version = defaultdict(set)
|
|
254
|
+
snapshots_by_dev_version = defaultdict(set)
|
|
255
|
+
for s in snapshots:
|
|
256
|
+
snapshots_by_version[(s.name, s.version)].add(s.snapshot_id)
|
|
257
|
+
snapshots_by_dev_version[(s.name, s.dev_version)].add(s.snapshot_id)
|
|
258
|
+
|
|
259
|
+
expired_snapshots = [s for s in snapshots if not _is_snapshot_used(s)]
|
|
260
|
+
all_expired_snapshot_ids = {s.snapshot_id for s in expired_snapshots}
|
|
261
|
+
|
|
262
|
+
cleanup_targets: t.List[t.Tuple[SnapshotId, bool]] = []
|
|
263
|
+
for snapshot in expired_snapshots:
|
|
264
|
+
shared_version_snapshots = snapshots_by_version[(snapshot.name, snapshot.version)]
|
|
265
|
+
shared_version_snapshots.discard(snapshot.snapshot_id)
|
|
266
|
+
|
|
267
|
+
shared_dev_version_snapshots = snapshots_by_dev_version[
|
|
268
|
+
(snapshot.name, snapshot.dev_version)
|
|
269
|
+
]
|
|
270
|
+
shared_dev_version_snapshots.discard(snapshot.snapshot_id)
|
|
271
|
+
|
|
272
|
+
if not shared_dev_version_snapshots:
|
|
273
|
+
dev_table_only = bool(shared_version_snapshots)
|
|
274
|
+
cleanup_targets.append((snapshot.snapshot_id, dev_table_only))
|
|
275
|
+
|
|
276
|
+
snapshot_ids_to_cleanup = [snapshot_id for snapshot_id, _ in cleanup_targets]
|
|
277
|
+
full_snapshots = self._get_snapshots(snapshot_ids_to_cleanup)
|
|
278
|
+
for snapshot_id, dev_table_only in cleanup_targets:
|
|
279
|
+
if snapshot_id in full_snapshots:
|
|
280
|
+
cleanup_tasks.append(
|
|
260
281
|
SnapshotTableCleanupTask(
|
|
261
282
|
snapshot=full_snapshots[snapshot_id].table_info,
|
|
262
283
|
dev_table_only=dev_table_only,
|
|
263
284
|
)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
285
|
+
)
|
|
286
|
+
expired_snapshot_ids.add(snapshot_id)
|
|
287
|
+
all_expired_snapshot_ids.discard(snapshot_id)
|
|
288
|
+
|
|
289
|
+
# Add any remaining expired snapshots that don't require cleanup
|
|
290
|
+
if all_expired_snapshot_ids:
|
|
291
|
+
expired_snapshot_ids.update(all_expired_snapshot_ids)
|
|
292
|
+
|
|
293
|
+
if expired_snapshot_ids or cleanup_tasks:
|
|
294
|
+
return ExpiredSnapshotBatch(
|
|
295
|
+
expired_snapshot_ids=expired_snapshot_ids,
|
|
296
|
+
cleanup_tasks=cleanup_tasks,
|
|
297
|
+
batch_range=result_batch_range,
|
|
298
|
+
)
|
|
269
299
|
|
|
270
|
-
|
|
271
|
-
# Remaining expired snapshots for which there are no tables
|
|
272
|
-
# to cleanup
|
|
273
|
-
yield all_expired_snapshot_ids, []
|
|
300
|
+
return None
|
|
274
301
|
|
|
275
302
|
def delete_snapshots(self, snapshot_ids: t.Iterable[SnapshotIdLike]) -> None:
|
|
276
303
|
"""Deletes snapshots.
|
|
@@ -337,6 +364,7 @@ class SnapshotState:
|
|
|
337
364
|
name=name,
|
|
338
365
|
identifier=identifier,
|
|
339
366
|
version=version,
|
|
367
|
+
kind_name=kind_name or None,
|
|
340
368
|
dev_version=dev_version,
|
|
341
369
|
fingerprint=fingerprint,
|
|
342
370
|
)
|
|
@@ -344,9 +372,11 @@ class SnapshotState:
|
|
|
344
372
|
snapshot_names=snapshot_names,
|
|
345
373
|
batch_size=self.SNAPSHOT_BATCH_SIZE,
|
|
346
374
|
)
|
|
347
|
-
for name, identifier, version, dev_version, fingerprint in fetchall(
|
|
375
|
+
for name, identifier, version, kind_name, dev_version, fingerprint in fetchall(
|
|
348
376
|
self.engine_adapter,
|
|
349
|
-
exp.select(
|
|
377
|
+
exp.select(
|
|
378
|
+
"name", "identifier", "version", "kind_name", "dev_version", "fingerprint"
|
|
379
|
+
)
|
|
350
380
|
.from_(self.snapshots_table)
|
|
351
381
|
.where(where)
|
|
352
382
|
.and_(unexpired_expr),
|
|
@@ -661,6 +691,7 @@ class SnapshotState:
|
|
|
661
691
|
"name",
|
|
662
692
|
"identifier",
|
|
663
693
|
"version",
|
|
694
|
+
"kind_name",
|
|
664
695
|
"dev_version",
|
|
665
696
|
"fingerprint",
|
|
666
697
|
)
|
|
@@ -677,10 +708,11 @@ class SnapshotState:
|
|
|
677
708
|
name=name,
|
|
678
709
|
identifier=identifier,
|
|
679
710
|
version=version,
|
|
711
|
+
kind_name=kind_name or None,
|
|
680
712
|
dev_version=dev_version,
|
|
681
713
|
fingerprint=SnapshotFingerprint.parse_raw(fingerprint),
|
|
682
714
|
)
|
|
683
|
-
for name, identifier, version, dev_version, fingerprint in snapshot_rows
|
|
715
|
+
for name, identifier, version, kind_name, dev_version, fingerprint in snapshot_rows
|
|
684
716
|
]
|
|
685
717
|
|
|
686
718
|
|
sqlmesh/core/table_diff.py
CHANGED
|
@@ -367,8 +367,8 @@ class TableDiff:
|
|
|
367
367
|
column_type = matched_columns[name]
|
|
368
368
|
qualified_column = exp.column(name, table)
|
|
369
369
|
|
|
370
|
-
if column_type.is_type(*exp.DataType.
|
|
371
|
-
return
|
|
370
|
+
if column_type.is_type(*exp.DataType.REAL_TYPES):
|
|
371
|
+
return self.adapter._normalize_decimal_value(qualified_column, self.decimals)
|
|
372
372
|
if column_type.is_type(*exp.DataType.NESTED_TYPES):
|
|
373
373
|
return self.adapter._normalize_nested_value(qualified_column)
|
|
374
374
|
|
sqlmesh/core/test/definition.py
CHANGED
|
@@ -100,8 +100,11 @@ class ModelTest(unittest.TestCase):
|
|
|
100
100
|
self._validate_and_normalize_test()
|
|
101
101
|
|
|
102
102
|
if self.engine_adapter.default_catalog:
|
|
103
|
-
self._fixture_catalog: t.Optional[exp.Identifier] =
|
|
104
|
-
|
|
103
|
+
self._fixture_catalog: t.Optional[exp.Identifier] = normalize_identifiers(
|
|
104
|
+
exp.parse_identifier(
|
|
105
|
+
self.engine_adapter.default_catalog, dialect=self._test_adapter_dialect
|
|
106
|
+
),
|
|
107
|
+
dialect=self._test_adapter_dialect,
|
|
105
108
|
)
|
|
106
109
|
else:
|
|
107
110
|
self._fixture_catalog = None
|
|
@@ -451,6 +454,9 @@ class ModelTest(unittest.TestCase):
|
|
|
451
454
|
query = outputs.get("query")
|
|
452
455
|
partial = outputs.pop("partial", None)
|
|
453
456
|
|
|
457
|
+
if ctes is None and query is None:
|
|
458
|
+
_raise_error("Incomplete test, outputs must contain 'query' or 'ctes'", self.path)
|
|
459
|
+
|
|
454
460
|
def _normalize_rows(
|
|
455
461
|
values: t.List[Row] | t.Dict,
|
|
456
462
|
name: str,
|
|
@@ -641,16 +647,16 @@ class ModelTest(unittest.TestCase):
|
|
|
641
647
|
return self._execute(query)
|
|
642
648
|
|
|
643
649
|
rows = values["rows"]
|
|
650
|
+
columns_str: t.Optional[t.List[str]] = None
|
|
644
651
|
if columns:
|
|
652
|
+
columns_str = [str(c) for c in columns]
|
|
645
653
|
referenced_columns = list(dict.fromkeys(col for row in rows for col in row))
|
|
646
654
|
_raise_if_unexpected_columns(columns, referenced_columns)
|
|
647
655
|
|
|
648
656
|
if partial:
|
|
649
|
-
|
|
657
|
+
columns_str = [c for c in columns_str if c in referenced_columns]
|
|
650
658
|
|
|
651
|
-
return pd.DataFrame.from_records(
|
|
652
|
-
rows, columns=[str(c) for c in columns] if columns else None
|
|
653
|
-
)
|
|
659
|
+
return pd.DataFrame.from_records(rows, columns=columns_str)
|
|
654
660
|
|
|
655
661
|
def _add_missing_columns(
|
|
656
662
|
self, query: exp.Query, all_columns: t.Optional[t.Collection[str]] = None
|
|
@@ -801,7 +807,7 @@ class PythonModelTest(ModelTest):
|
|
|
801
807
|
actual_df.reset_index(drop=True, inplace=True)
|
|
802
808
|
expected = self._create_df(values, columns=self.model.columns_to_types, partial=partial)
|
|
803
809
|
|
|
804
|
-
self.assert_equal(expected, actual_df, sort=
|
|
810
|
+
self.assert_equal(expected, actual_df, sort=True, partial=partial)
|
|
805
811
|
|
|
806
812
|
def _execute_model(self) -> pd.DataFrame:
|
|
807
813
|
"""Executes the python model and returns a DataFrame."""
|
|
@@ -919,8 +925,7 @@ def generate_test(
|
|
|
919
925
|
cte_output = test._execute(cte_query)
|
|
920
926
|
ctes[cte.alias] = (
|
|
921
927
|
pandas_timestamp_to_pydatetime(
|
|
922
|
-
cte_output.apply(lambda col: col.map(_normalize_df_value)),
|
|
923
|
-
cte_query.named_selects,
|
|
928
|
+
df=cte_output.apply(lambda col: col.map(_normalize_df_value)),
|
|
924
929
|
)
|
|
925
930
|
.replace({np.nan: None})
|
|
926
931
|
.to_dict(orient="records")
|
sqlmesh/dbt/adapter.py
CHANGED
|
@@ -115,30 +115,39 @@ class BaseAdapter(abc.ABC):
|
|
|
115
115
|
"""Returns the value quoted according to the quote policy."""
|
|
116
116
|
return self.quote(value) if getattr(self.quote_policy, component_type, False) else value
|
|
117
117
|
|
|
118
|
-
def dispatch(
|
|
118
|
+
def dispatch(
|
|
119
|
+
self,
|
|
120
|
+
macro_name: str,
|
|
121
|
+
macro_namespace: t.Optional[str] = None,
|
|
122
|
+
) -> t.Callable:
|
|
119
123
|
"""Returns a dialect-specific version of a macro with the given name."""
|
|
120
124
|
target_type = self.jinja_globals["target"]["type"]
|
|
121
|
-
macro_suffix = f"__{
|
|
125
|
+
macro_suffix = f"__{macro_name}"
|
|
122
126
|
|
|
123
127
|
def _relevance(package_name_pair: t.Tuple[t.Optional[str], str]) -> t.Tuple[int, int]:
|
|
124
128
|
"""Lower scores more relevant."""
|
|
125
|
-
macro_package,
|
|
129
|
+
macro_package, name = package_name_pair
|
|
126
130
|
|
|
127
|
-
package_score = 0 if macro_package ==
|
|
131
|
+
package_score = 0 if macro_package == macro_namespace else 1
|
|
128
132
|
name_score = 1
|
|
129
133
|
|
|
130
|
-
if
|
|
134
|
+
if name.startswith("default"):
|
|
131
135
|
name_score = 2
|
|
132
|
-
elif
|
|
136
|
+
elif name.startswith(target_type):
|
|
133
137
|
name_score = 0
|
|
134
138
|
|
|
135
139
|
return name_score, package_score
|
|
136
140
|
|
|
137
141
|
jinja_env = self.jinja_macros.build_environment(**self.jinja_globals).globals
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
|
|
143
|
+
packages_to_check: t.List[t.Optional[str]] = [None]
|
|
144
|
+
if macro_namespace is not None:
|
|
145
|
+
if macro_namespace in jinja_env:
|
|
146
|
+
packages_to_check = [self.jinja_macros.root_package_name, macro_namespace]
|
|
147
|
+
|
|
148
|
+
# Add dbt packages as fallback
|
|
149
|
+
packages_to_check.extend(k for k in jinja_env if k.startswith("dbt"))
|
|
150
|
+
|
|
142
151
|
candidates = {}
|
|
143
152
|
for macro_package in packages_to_check:
|
|
144
153
|
macros = jinja_env.get(macro_package, {}) if macro_package else jinja_env
|
|
@@ -156,7 +165,7 @@ class BaseAdapter(abc.ABC):
|
|
|
156
165
|
sorted_candidates = sorted(candidates, key=_relevance)
|
|
157
166
|
return candidates[sorted_candidates[0]]
|
|
158
167
|
|
|
159
|
-
raise ConfigError(f"Macro '{
|
|
168
|
+
raise ConfigError(f"Macro '{macro_name}', package '{macro_namespace}' was not found.")
|
|
160
169
|
|
|
161
170
|
def type(self) -> str:
|
|
162
171
|
return self.project_dialect or ""
|