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
|
@@ -32,12 +32,14 @@ from functools import reduce
|
|
|
32
32
|
|
|
33
33
|
from sqlglot import exp, select
|
|
34
34
|
from sqlglot.executor import execute
|
|
35
|
+
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_not_exception_type
|
|
35
36
|
|
|
36
37
|
from sqlmesh.core import constants as c
|
|
37
38
|
from sqlmesh.core import dialect as d
|
|
38
39
|
from sqlmesh.core.audit import Audit, StandaloneAudit
|
|
39
40
|
from sqlmesh.core.dialect import schema_
|
|
40
41
|
from sqlmesh.core.engine_adapter.shared import InsertOverwriteStrategy, DataObjectType, DataObject
|
|
42
|
+
from sqlmesh.core.model.meta import GrantsTargetLayer
|
|
41
43
|
from sqlmesh.core.macros import RuntimeStage
|
|
42
44
|
from sqlmesh.core.model import (
|
|
43
45
|
AuditResult,
|
|
@@ -49,7 +51,7 @@ from sqlmesh.core.model import (
|
|
|
49
51
|
ViewKind,
|
|
50
52
|
CustomKind,
|
|
51
53
|
)
|
|
52
|
-
from sqlmesh.core.model.kind import _Incremental
|
|
54
|
+
from sqlmesh.core.model.kind import _Incremental, DbtCustomKind
|
|
53
55
|
from sqlmesh.utils import CompletionStatus, columns_to_types_all_known
|
|
54
56
|
from sqlmesh.core.schema_diff import (
|
|
55
57
|
has_drop_alteration,
|
|
@@ -66,7 +68,7 @@ from sqlmesh.core.snapshot import (
|
|
|
66
68
|
SnapshotTableCleanupTask,
|
|
67
69
|
)
|
|
68
70
|
from sqlmesh.core.snapshot.execution_tracker import QueryExecutionTracker
|
|
69
|
-
from sqlmesh.utils import random_id, CorrelationId
|
|
71
|
+
from sqlmesh.utils import random_id, CorrelationId, AttributeDict
|
|
70
72
|
from sqlmesh.utils.concurrency import (
|
|
71
73
|
concurrent_apply_to_snapshots,
|
|
72
74
|
concurrent_apply_to_values,
|
|
@@ -76,11 +78,13 @@ from sqlmesh.utils.date import TimeLike, now, time_like_to_str
|
|
|
76
78
|
from sqlmesh.utils.errors import (
|
|
77
79
|
ConfigError,
|
|
78
80
|
DestructiveChangeError,
|
|
81
|
+
MigrationNotSupportedError,
|
|
79
82
|
SQLMeshError,
|
|
80
83
|
format_destructive_change_msg,
|
|
81
84
|
format_additive_change_msg,
|
|
82
85
|
AdditiveChangeError,
|
|
83
86
|
)
|
|
87
|
+
from sqlmesh.utils.jinja import MacroReturnVal
|
|
84
88
|
|
|
85
89
|
if sys.version_info >= (3, 12):
|
|
86
90
|
from importlib import metadata
|
|
@@ -304,6 +308,9 @@ class SnapshotEvaluator:
|
|
|
304
308
|
]
|
|
305
309
|
self._create_schemas(gateway_table_pairs=gateway_table_pairs)
|
|
306
310
|
|
|
311
|
+
# Fetch the view data objects for the promoted snapshots to get them cached
|
|
312
|
+
self._get_virtual_data_objects(target_snapshots, environment_naming_info)
|
|
313
|
+
|
|
307
314
|
deployability_index = deployability_index or DeployabilityIndex.all_deployable()
|
|
308
315
|
with self.concurrent_context():
|
|
309
316
|
concurrent_apply_to_snapshots(
|
|
@@ -422,7 +429,9 @@ class SnapshotEvaluator:
|
|
|
422
429
|
target_snapshots: Target snapshots.
|
|
423
430
|
deployability_index: Determines snapshots that are deployable / representative in the context of this creation.
|
|
424
431
|
"""
|
|
425
|
-
existing_data_objects = self.
|
|
432
|
+
existing_data_objects = self._get_physical_data_objects(
|
|
433
|
+
target_snapshots, deployability_index
|
|
434
|
+
)
|
|
426
435
|
snapshots_to_create = []
|
|
427
436
|
for snapshot in target_snapshots:
|
|
428
437
|
if not snapshot.is_model or snapshot.is_symbolic:
|
|
@@ -479,7 +488,7 @@ class SnapshotEvaluator:
|
|
|
479
488
|
deployability_index: Determines snapshots that are deployable in the context of this evaluation.
|
|
480
489
|
"""
|
|
481
490
|
deployability_index = deployability_index or DeployabilityIndex.all_deployable()
|
|
482
|
-
target_data_objects = self.
|
|
491
|
+
target_data_objects = self._get_physical_data_objects(target_snapshots, deployability_index)
|
|
483
492
|
if not target_data_objects:
|
|
484
493
|
return
|
|
485
494
|
|
|
@@ -489,15 +498,14 @@ class SnapshotEvaluator:
|
|
|
489
498
|
allow_destructive_snapshots = allow_destructive_snapshots or set()
|
|
490
499
|
allow_additive_snapshots = allow_additive_snapshots or set()
|
|
491
500
|
snapshots_by_name = {s.name: s for s in snapshots.values()}
|
|
492
|
-
snapshots_with_data_objects = [snapshots[s_id] for s_id in target_data_objects]
|
|
493
501
|
with self.concurrent_context():
|
|
494
502
|
# Only migrate snapshots for which there's an existing data object
|
|
495
503
|
concurrent_apply_to_snapshots(
|
|
496
|
-
|
|
504
|
+
target_snapshots,
|
|
497
505
|
lambda s: self._migrate_snapshot(
|
|
498
506
|
s,
|
|
499
507
|
snapshots_by_name,
|
|
500
|
-
target_data_objects
|
|
508
|
+
target_data_objects.get(s.snapshot_id),
|
|
501
509
|
allow_destructive_snapshots,
|
|
502
510
|
allow_additive_snapshots,
|
|
503
511
|
self.get_adapter(s.model_gateway),
|
|
@@ -517,10 +525,12 @@ class SnapshotEvaluator:
|
|
|
517
525
|
target_snapshots: Snapshots to cleanup.
|
|
518
526
|
on_complete: A callback to call on each successfully deleted database object.
|
|
519
527
|
"""
|
|
528
|
+
target_snapshots = [
|
|
529
|
+
t for t in target_snapshots if t.snapshot.is_model and not t.snapshot.is_symbolic
|
|
530
|
+
]
|
|
520
531
|
snapshots_to_dev_table_only = {
|
|
521
532
|
t.snapshot.snapshot_id: t.dev_table_only for t in target_snapshots
|
|
522
533
|
}
|
|
523
|
-
|
|
524
534
|
with self.concurrent_context():
|
|
525
535
|
concurrent_apply_to_snapshots(
|
|
526
536
|
[t.snapshot for t in target_snapshots],
|
|
@@ -740,38 +750,51 @@ class SnapshotEvaluator:
|
|
|
740
750
|
**render_statements_kwargs
|
|
741
751
|
)
|
|
742
752
|
|
|
753
|
+
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
|
|
754
|
+
evaluation_strategy.run_pre_statements(
|
|
755
|
+
snapshot=snapshot,
|
|
756
|
+
render_kwargs={**render_statements_kwargs, "inside_transaction": False},
|
|
757
|
+
)
|
|
758
|
+
|
|
743
759
|
with (
|
|
744
760
|
adapter.transaction(),
|
|
745
761
|
adapter.session(snapshot.model.render_session_properties(**render_statements_kwargs)),
|
|
746
762
|
):
|
|
747
|
-
|
|
763
|
+
evaluation_strategy.run_pre_statements(
|
|
764
|
+
snapshot=snapshot,
|
|
765
|
+
render_kwargs={**render_statements_kwargs, "inside_transaction": True},
|
|
766
|
+
)
|
|
748
767
|
|
|
749
768
|
if not target_table_exists or (model.is_seed and not snapshot.intervals):
|
|
750
|
-
|
|
769
|
+
# Only create the empty table if the columns were provided explicitly by the user
|
|
770
|
+
should_create_empty_table = (
|
|
751
771
|
model.kind.is_materialized
|
|
752
772
|
and model.columns_to_types_
|
|
753
773
|
and columns_to_types_all_known(model.columns_to_types_)
|
|
754
774
|
)
|
|
775
|
+
if not should_create_empty_table:
|
|
776
|
+
# Or if the model is self-referential and its query is fully annotated with types
|
|
777
|
+
should_create_empty_table = model.depends_on_self and model.annotated
|
|
755
778
|
if self._can_clone(snapshot, deployability_index):
|
|
756
779
|
self._clone_snapshot_in_dev(
|
|
757
780
|
snapshot=snapshot,
|
|
758
781
|
snapshots=snapshots,
|
|
759
782
|
deployability_index=deployability_index,
|
|
760
783
|
render_kwargs=create_render_kwargs,
|
|
761
|
-
rendered_physical_properties=rendered_physical_properties,
|
|
784
|
+
rendered_physical_properties=rendered_physical_properties.copy(),
|
|
762
785
|
allow_destructive_snapshots=allow_destructive_snapshots,
|
|
763
786
|
allow_additive_snapshots=allow_additive_snapshots,
|
|
764
787
|
)
|
|
765
788
|
runtime_stage = RuntimeStage.EVALUATING
|
|
766
789
|
target_table_exists = True
|
|
767
|
-
elif
|
|
790
|
+
elif should_create_empty_table or model.is_seed or model.kind.is_scd_type_2:
|
|
768
791
|
self._execute_create(
|
|
769
792
|
snapshot=snapshot,
|
|
770
793
|
table_name=target_table_name,
|
|
771
794
|
is_table_deployable=is_snapshot_deployable,
|
|
772
795
|
deployability_index=deployability_index,
|
|
773
796
|
create_render_kwargs=create_render_kwargs,
|
|
774
|
-
rendered_physical_properties=rendered_physical_properties,
|
|
797
|
+
rendered_physical_properties=rendered_physical_properties.copy(),
|
|
775
798
|
dry_run=False,
|
|
776
799
|
run_pre_post_statements=False,
|
|
777
800
|
)
|
|
@@ -788,6 +811,7 @@ class SnapshotEvaluator:
|
|
|
788
811
|
if (
|
|
789
812
|
snapshot.is_materialized
|
|
790
813
|
and target_table_exists
|
|
814
|
+
and adapter.wap_enabled
|
|
791
815
|
and (model.wap_supported or adapter.wap_supported(target_table_name))
|
|
792
816
|
):
|
|
793
817
|
wap_id = random_id()[0:8]
|
|
@@ -809,9 +833,17 @@ class SnapshotEvaluator:
|
|
|
809
833
|
batch_index=batch_index,
|
|
810
834
|
)
|
|
811
835
|
|
|
812
|
-
|
|
836
|
+
evaluation_strategy.run_post_statements(
|
|
837
|
+
snapshot=snapshot,
|
|
838
|
+
render_kwargs={**render_statements_kwargs, "inside_transaction": True},
|
|
839
|
+
)
|
|
813
840
|
|
|
814
|
-
|
|
841
|
+
evaluation_strategy.run_post_statements(
|
|
842
|
+
snapshot=snapshot,
|
|
843
|
+
render_kwargs={**render_statements_kwargs, "inside_transaction": False},
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
return wap_id
|
|
815
847
|
|
|
816
848
|
def create_snapshot(
|
|
817
849
|
self,
|
|
@@ -845,6 +877,11 @@ class SnapshotEvaluator:
|
|
|
845
877
|
deployability_index=deployability_index,
|
|
846
878
|
)
|
|
847
879
|
|
|
880
|
+
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
|
|
881
|
+
evaluation_strategy.run_pre_statements(
|
|
882
|
+
snapshot=snapshot, render_kwargs={**create_render_kwargs, "inside_transaction": False}
|
|
883
|
+
)
|
|
884
|
+
|
|
848
885
|
with (
|
|
849
886
|
adapter.transaction(),
|
|
850
887
|
adapter.session(snapshot.model.render_session_properties(**create_render_kwargs)),
|
|
@@ -862,6 +899,7 @@ class SnapshotEvaluator:
|
|
|
862
899
|
rendered_physical_properties=rendered_physical_properties,
|
|
863
900
|
allow_destructive_snapshots=allow_destructive_snapshots,
|
|
864
901
|
allow_additive_snapshots=allow_additive_snapshots,
|
|
902
|
+
run_pre_post_statements=True,
|
|
865
903
|
)
|
|
866
904
|
else:
|
|
867
905
|
is_table_deployable = deployability_index.is_deployable(snapshot)
|
|
@@ -875,6 +913,10 @@ class SnapshotEvaluator:
|
|
|
875
913
|
dry_run=True,
|
|
876
914
|
)
|
|
877
915
|
|
|
916
|
+
evaluation_strategy.run_post_statements(
|
|
917
|
+
snapshot=snapshot, render_kwargs={**create_render_kwargs, "inside_transaction": False}
|
|
918
|
+
)
|
|
919
|
+
|
|
878
920
|
if on_complete is not None:
|
|
879
921
|
on_complete(snapshot)
|
|
880
922
|
|
|
@@ -912,6 +954,7 @@ class SnapshotEvaluator:
|
|
|
912
954
|
model = snapshot.model
|
|
913
955
|
adapter = self.get_adapter(model.gateway)
|
|
914
956
|
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
|
|
957
|
+
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
|
|
915
958
|
|
|
916
959
|
queries_or_dfs = self._render_snapshot_for_evaluation(
|
|
917
960
|
snapshot,
|
|
@@ -935,6 +978,7 @@ class SnapshotEvaluator:
|
|
|
935
978
|
execution_time=execution_time,
|
|
936
979
|
physical_properties=rendered_physical_properties,
|
|
937
980
|
render_kwargs=create_render_kwargs,
|
|
981
|
+
is_snapshot_deployable=is_snapshot_deployable,
|
|
938
982
|
)
|
|
939
983
|
else:
|
|
940
984
|
logger.info(
|
|
@@ -957,6 +1001,7 @@ class SnapshotEvaluator:
|
|
|
957
1001
|
execution_time=execution_time,
|
|
958
1002
|
physical_properties=rendered_physical_properties,
|
|
959
1003
|
render_kwargs=create_render_kwargs,
|
|
1004
|
+
is_snapshot_deployable=is_snapshot_deployable,
|
|
960
1005
|
)
|
|
961
1006
|
|
|
962
1007
|
# DataFrames, unlike SQL expressions, can provide partial results by yielding dataframes. As a result,
|
|
@@ -976,6 +1021,11 @@ class SnapshotEvaluator:
|
|
|
976
1021
|
):
|
|
977
1022
|
import pandas as pd
|
|
978
1023
|
|
|
1024
|
+
try:
|
|
1025
|
+
first_query_or_df = next(queries_or_dfs)
|
|
1026
|
+
except StopIteration:
|
|
1027
|
+
return
|
|
1028
|
+
|
|
979
1029
|
query_or_df = reduce(
|
|
980
1030
|
lambda a, b: (
|
|
981
1031
|
pd.concat([a, b], ignore_index=True) # type: ignore
|
|
@@ -983,6 +1033,7 @@ class SnapshotEvaluator:
|
|
|
983
1033
|
else a.union_all(b) # type: ignore
|
|
984
1034
|
), # type: ignore
|
|
985
1035
|
queries_or_dfs,
|
|
1036
|
+
first_query_or_df,
|
|
986
1037
|
)
|
|
987
1038
|
apply(query_or_df, index=0)
|
|
988
1039
|
else:
|
|
@@ -1021,6 +1072,7 @@ class SnapshotEvaluator:
|
|
|
1021
1072
|
rendered_physical_properties: t.Dict[str, exp.Expression],
|
|
1022
1073
|
allow_destructive_snapshots: t.Set[str],
|
|
1023
1074
|
allow_additive_snapshots: t.Set[str],
|
|
1075
|
+
run_pre_post_statements: bool = False,
|
|
1024
1076
|
) -> None:
|
|
1025
1077
|
adapter = self.get_adapter(snapshot.model.gateway)
|
|
1026
1078
|
|
|
@@ -1032,7 +1084,6 @@ class SnapshotEvaluator:
|
|
|
1032
1084
|
adapter.clone_table(
|
|
1033
1085
|
target_table_name,
|
|
1034
1086
|
snapshot.table_name(),
|
|
1035
|
-
replace=True,
|
|
1036
1087
|
rendered_physical_properties=rendered_physical_properties,
|
|
1037
1088
|
)
|
|
1038
1089
|
self._migrate_target_table(
|
|
@@ -1044,7 +1095,9 @@ class SnapshotEvaluator:
|
|
|
1044
1095
|
rendered_physical_properties=rendered_physical_properties,
|
|
1045
1096
|
allow_destructive_snapshots=allow_destructive_snapshots,
|
|
1046
1097
|
allow_additive_snapshots=allow_additive_snapshots,
|
|
1098
|
+
run_pre_post_statements=run_pre_post_statements,
|
|
1047
1099
|
)
|
|
1100
|
+
|
|
1048
1101
|
except Exception:
|
|
1049
1102
|
adapter.drop_table(target_table_name)
|
|
1050
1103
|
raise
|
|
@@ -1059,7 +1112,7 @@ class SnapshotEvaluator:
|
|
|
1059
1112
|
adapter: EngineAdapter,
|
|
1060
1113
|
deployability_index: DeployabilityIndex,
|
|
1061
1114
|
) -> None:
|
|
1062
|
-
if not snapshot.
|
|
1115
|
+
if not snapshot.is_model or snapshot.is_symbolic:
|
|
1063
1116
|
return
|
|
1064
1117
|
|
|
1065
1118
|
deployability_index = DeployabilityIndex.all_deployable()
|
|
@@ -1071,6 +1124,11 @@ class SnapshotEvaluator:
|
|
|
1071
1124
|
)
|
|
1072
1125
|
target_table_name = snapshot.table_name()
|
|
1073
1126
|
|
|
1127
|
+
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
|
|
1128
|
+
evaluation_strategy.run_pre_statements(
|
|
1129
|
+
snapshot=snapshot, render_kwargs={**render_kwargs, "inside_transaction": False}
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1074
1132
|
with (
|
|
1075
1133
|
adapter.transaction(),
|
|
1076
1134
|
adapter.session(snapshot.model.render_session_properties(**render_kwargs)),
|
|
@@ -1081,6 +1139,10 @@ class SnapshotEvaluator:
|
|
|
1081
1139
|
):
|
|
1082
1140
|
table_exists = False
|
|
1083
1141
|
|
|
1142
|
+
rendered_physical_properties = snapshot.model.render_physical_properties(
|
|
1143
|
+
**render_kwargs
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1084
1146
|
if table_exists:
|
|
1085
1147
|
self._migrate_target_table(
|
|
1086
1148
|
target_table_name=target_table_name,
|
|
@@ -1088,14 +1150,35 @@ class SnapshotEvaluator:
|
|
|
1088
1150
|
snapshots=snapshots,
|
|
1089
1151
|
deployability_index=deployability_index,
|
|
1090
1152
|
render_kwargs=render_kwargs,
|
|
1091
|
-
rendered_physical_properties=
|
|
1092
|
-
**render_kwargs
|
|
1093
|
-
),
|
|
1153
|
+
rendered_physical_properties=rendered_physical_properties,
|
|
1094
1154
|
allow_destructive_snapshots=allow_destructive_snapshots,
|
|
1095
1155
|
allow_additive_snapshots=allow_additive_snapshots,
|
|
1096
1156
|
run_pre_post_statements=True,
|
|
1097
1157
|
)
|
|
1158
|
+
else:
|
|
1159
|
+
self._execute_create(
|
|
1160
|
+
snapshot=snapshot,
|
|
1161
|
+
table_name=snapshot.table_name(is_deployable=True),
|
|
1162
|
+
is_table_deployable=True,
|
|
1163
|
+
deployability_index=deployability_index,
|
|
1164
|
+
create_render_kwargs=render_kwargs,
|
|
1165
|
+
rendered_physical_properties=rendered_physical_properties,
|
|
1166
|
+
dry_run=True,
|
|
1167
|
+
)
|
|
1098
1168
|
|
|
1169
|
+
evaluation_strategy.run_post_statements(
|
|
1170
|
+
snapshot=snapshot, render_kwargs={**render_kwargs, "inside_transaction": False}
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
# Retry in case when the table is migrated concurrently from another plan application
|
|
1174
|
+
@retry(
|
|
1175
|
+
reraise=True,
|
|
1176
|
+
stop=stop_after_attempt(5),
|
|
1177
|
+
wait=wait_exponential(min=1, max=16),
|
|
1178
|
+
retry=retry_if_not_exception_type(
|
|
1179
|
+
(DestructiveChangeError, AdditiveChangeError, MigrationNotSupportedError)
|
|
1180
|
+
),
|
|
1181
|
+
)
|
|
1099
1182
|
def _migrate_target_table(
|
|
1100
1183
|
self,
|
|
1101
1184
|
target_table_name: str,
|
|
@@ -1110,7 +1193,10 @@ class SnapshotEvaluator:
|
|
|
1110
1193
|
) -> None:
|
|
1111
1194
|
adapter = self.get_adapter(snapshot.model.gateway)
|
|
1112
1195
|
|
|
1113
|
-
|
|
1196
|
+
tmp_table = exp.to_table(target_table_name)
|
|
1197
|
+
tmp_table.this.set("this", f"{tmp_table.name}_schema_tmp")
|
|
1198
|
+
tmp_table_name = tmp_table.sql()
|
|
1199
|
+
|
|
1114
1200
|
if snapshot.is_materialized:
|
|
1115
1201
|
self._execute_create(
|
|
1116
1202
|
snapshot=snapshot,
|
|
@@ -1121,6 +1207,7 @@ class SnapshotEvaluator:
|
|
|
1121
1207
|
rendered_physical_properties=rendered_physical_properties,
|
|
1122
1208
|
dry_run=False,
|
|
1123
1209
|
run_pre_post_statements=run_pre_post_statements,
|
|
1210
|
+
skip_grants=True, # skip grants for tmp table
|
|
1124
1211
|
)
|
|
1125
1212
|
try:
|
|
1126
1213
|
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
|
|
@@ -1138,6 +1225,7 @@ class SnapshotEvaluator:
|
|
|
1138
1225
|
allow_additive_snapshots=allow_additive_snapshots,
|
|
1139
1226
|
ignore_destructive=snapshot.model.on_destructive_change.is_ignore,
|
|
1140
1227
|
ignore_additive=snapshot.model.on_additive_change.is_ignore,
|
|
1228
|
+
deployability_index=deployability_index,
|
|
1141
1229
|
)
|
|
1142
1230
|
finally:
|
|
1143
1231
|
if snapshot.is_materialized:
|
|
@@ -1187,6 +1275,7 @@ class SnapshotEvaluator:
|
|
|
1187
1275
|
model=snapshot.model,
|
|
1188
1276
|
environment=environment_naming_info.name,
|
|
1189
1277
|
snapshots=snapshots,
|
|
1278
|
+
snapshot=snapshot,
|
|
1190
1279
|
**render_kwargs,
|
|
1191
1280
|
)
|
|
1192
1281
|
|
|
@@ -1386,6 +1475,7 @@ class SnapshotEvaluator:
|
|
|
1386
1475
|
rendered_physical_properties: t.Dict[str, exp.Expression],
|
|
1387
1476
|
dry_run: bool,
|
|
1388
1477
|
run_pre_post_statements: bool = True,
|
|
1478
|
+
skip_grants: bool = False,
|
|
1389
1479
|
) -> None:
|
|
1390
1480
|
adapter = self.get_adapter(snapshot.model.gateway)
|
|
1391
1481
|
evaluation_strategy = _evaluation_strategy(snapshot, adapter)
|
|
@@ -1399,19 +1489,28 @@ class SnapshotEvaluator:
|
|
|
1399
1489
|
"table_mapping": {snapshot.name: table_name},
|
|
1400
1490
|
}
|
|
1401
1491
|
if run_pre_post_statements:
|
|
1402
|
-
|
|
1492
|
+
evaluation_strategy.run_pre_statements(
|
|
1493
|
+
snapshot=snapshot,
|
|
1494
|
+
render_kwargs={**create_render_kwargs, "inside_transaction": True},
|
|
1495
|
+
)
|
|
1403
1496
|
evaluation_strategy.create(
|
|
1404
1497
|
table_name=table_name,
|
|
1405
1498
|
model=snapshot.model,
|
|
1406
1499
|
is_table_deployable=is_table_deployable,
|
|
1500
|
+
skip_grants=skip_grants,
|
|
1407
1501
|
render_kwargs=create_render_kwargs,
|
|
1408
1502
|
is_snapshot_deployable=is_snapshot_deployable,
|
|
1409
1503
|
is_snapshot_representative=is_snapshot_representative,
|
|
1410
1504
|
dry_run=dry_run,
|
|
1411
1505
|
physical_properties=rendered_physical_properties,
|
|
1506
|
+
snapshot=snapshot,
|
|
1507
|
+
deployability_index=deployability_index,
|
|
1412
1508
|
)
|
|
1413
1509
|
if run_pre_post_statements:
|
|
1414
|
-
|
|
1510
|
+
evaluation_strategy.run_post_statements(
|
|
1511
|
+
snapshot=snapshot,
|
|
1512
|
+
render_kwargs={**create_render_kwargs, "inside_transaction": True},
|
|
1513
|
+
)
|
|
1415
1514
|
|
|
1416
1515
|
def _can_clone(self, snapshot: Snapshot, deployability_index: DeployabilityIndex) -> bool:
|
|
1417
1516
|
adapter = self.get_adapter(snapshot.model.gateway)
|
|
@@ -1420,13 +1519,15 @@ class SnapshotEvaluator:
|
|
|
1420
1519
|
and snapshot.is_materialized
|
|
1421
1520
|
and bool(snapshot.previous_versions)
|
|
1422
1521
|
and adapter.SUPPORTS_CLONING
|
|
1423
|
-
# managed models cannot have their schema mutated because
|
|
1522
|
+
# managed models cannot have their schema mutated because they're based on queries, so clone + alter won't work
|
|
1424
1523
|
and not snapshot.is_managed
|
|
1425
|
-
|
|
1524
|
+
and not snapshot.is_dbt_custom
|
|
1426
1525
|
and not deployability_index.is_deployable(snapshot)
|
|
1526
|
+
# If the deployable table is missing we can't clone it
|
|
1527
|
+
and adapter.table_exists(snapshot.table_name())
|
|
1427
1528
|
)
|
|
1428
1529
|
|
|
1429
|
-
def
|
|
1530
|
+
def _get_physical_data_objects(
|
|
1430
1531
|
self,
|
|
1431
1532
|
target_snapshots: t.Iterable[Snapshot],
|
|
1432
1533
|
deployability_index: DeployabilityIndex,
|
|
@@ -1442,18 +1543,70 @@ class SnapshotEvaluator:
|
|
|
1442
1543
|
A dictionary of snapshot IDs to existing data objects of their physical tables. If the data object
|
|
1443
1544
|
for a snapshot is not found, it will not be included in the dictionary.
|
|
1444
1545
|
"""
|
|
1546
|
+
return self._get_data_objects(
|
|
1547
|
+
target_snapshots,
|
|
1548
|
+
lambda s: exp.to_table(
|
|
1549
|
+
s.table_name(deployability_index.is_deployable(s)), dialect=s.model.dialect
|
|
1550
|
+
),
|
|
1551
|
+
)
|
|
1552
|
+
|
|
1553
|
+
def _get_virtual_data_objects(
|
|
1554
|
+
self,
|
|
1555
|
+
target_snapshots: t.Iterable[Snapshot],
|
|
1556
|
+
environment_naming_info: EnvironmentNamingInfo,
|
|
1557
|
+
) -> t.Dict[SnapshotId, DataObject]:
|
|
1558
|
+
"""Returns a dictionary of snapshot IDs to existing data objects of their virtual views.
|
|
1559
|
+
|
|
1560
|
+
Args:
|
|
1561
|
+
target_snapshots: Target snapshots.
|
|
1562
|
+
environment_naming_info: The environment naming info of the target virtual environment.
|
|
1563
|
+
|
|
1564
|
+
Returns:
|
|
1565
|
+
A dictionary of snapshot IDs to existing data objects of their virtual views. If the data object
|
|
1566
|
+
for a snapshot is not found, it will not be included in the dictionary.
|
|
1567
|
+
"""
|
|
1568
|
+
|
|
1569
|
+
def _get_view_name(s: Snapshot) -> exp.Table:
|
|
1570
|
+
adapter = (
|
|
1571
|
+
self.get_adapter(s.model_gateway)
|
|
1572
|
+
if environment_naming_info.gateway_managed
|
|
1573
|
+
else self.adapter
|
|
1574
|
+
)
|
|
1575
|
+
return exp.to_table(
|
|
1576
|
+
s.qualified_view_name.for_environment(
|
|
1577
|
+
environment_naming_info, dialect=adapter.dialect
|
|
1578
|
+
),
|
|
1579
|
+
dialect=adapter.dialect,
|
|
1580
|
+
)
|
|
1581
|
+
|
|
1582
|
+
return self._get_data_objects(target_snapshots, _get_view_name)
|
|
1583
|
+
|
|
1584
|
+
def _get_data_objects(
|
|
1585
|
+
self,
|
|
1586
|
+
target_snapshots: t.Iterable[Snapshot],
|
|
1587
|
+
table_name_callable: t.Callable[[Snapshot], exp.Table],
|
|
1588
|
+
) -> t.Dict[SnapshotId, DataObject]:
|
|
1589
|
+
"""Returns a dictionary of snapshot IDs to existing data objects.
|
|
1590
|
+
|
|
1591
|
+
Args:
|
|
1592
|
+
target_snapshots: Target snapshots.
|
|
1593
|
+
table_name_callable: A function that takes a snapshot and returns the table to look for.
|
|
1594
|
+
|
|
1595
|
+
Returns:
|
|
1596
|
+
A dictionary of snapshot IDs to existing data objects. If the data object for a snapshot is not found,
|
|
1597
|
+
it will not be included in the dictionary.
|
|
1598
|
+
"""
|
|
1445
1599
|
tables_by_gateway_and_schema: t.Dict[t.Union[str, None], t.Dict[exp.Table, set[str]]] = (
|
|
1446
1600
|
defaultdict(lambda: defaultdict(set))
|
|
1447
1601
|
)
|
|
1448
|
-
snapshots_by_table_name: t.Dict[str, Snapshot] =
|
|
1602
|
+
snapshots_by_table_name: t.Dict[exp.Table, t.Dict[str, Snapshot]] = defaultdict(dict)
|
|
1449
1603
|
for snapshot in target_snapshots:
|
|
1450
1604
|
if not snapshot.is_model or snapshot.is_symbolic:
|
|
1451
1605
|
continue
|
|
1452
|
-
|
|
1453
|
-
table = exp.to_table(snapshot.table_name(is_deployable), dialect=snapshot.model.dialect)
|
|
1606
|
+
table = table_name_callable(snapshot)
|
|
1454
1607
|
table_schema = d.schema_(table.db, catalog=table.catalog)
|
|
1455
1608
|
tables_by_gateway_and_schema[snapshot.model_gateway][table_schema].add(table.name)
|
|
1456
|
-
snapshots_by_table_name[table.name] = snapshot
|
|
1609
|
+
snapshots_by_table_name[table_schema][table.name] = snapshot
|
|
1457
1610
|
|
|
1458
1611
|
def _get_data_objects_in_schema(
|
|
1459
1612
|
schema: exp.Table,
|
|
@@ -1461,26 +1614,30 @@ class SnapshotEvaluator:
|
|
|
1461
1614
|
gateway: t.Optional[str] = None,
|
|
1462
1615
|
) -> t.List[DataObject]:
|
|
1463
1616
|
logger.info("Listing data objects in schema %s", schema.sql())
|
|
1464
|
-
return self.get_adapter(gateway).get_data_objects(
|
|
1617
|
+
return self.get_adapter(gateway).get_data_objects(
|
|
1618
|
+
schema, object_names, safe_to_cache=True
|
|
1619
|
+
)
|
|
1465
1620
|
|
|
1466
1621
|
with self.concurrent_context():
|
|
1467
|
-
|
|
1622
|
+
snapshot_id_to_obj: t.Dict[SnapshotId, DataObject] = {}
|
|
1468
1623
|
# A schema can be shared across multiple engines, so we need to group tables by both gateway and schema
|
|
1469
1624
|
for gateway, tables_by_schema in tables_by_gateway_and_schema.items():
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
)
|
|
1479
|
-
for obj in objs
|
|
1480
|
-
]
|
|
1481
|
-
existing_objects.extend(objs_for_gateway)
|
|
1625
|
+
schema_list = list(tables_by_schema.keys())
|
|
1626
|
+
results = concurrent_apply_to_values(
|
|
1627
|
+
schema_list,
|
|
1628
|
+
lambda s: _get_data_objects_in_schema(
|
|
1629
|
+
schema=s, object_names=tables_by_schema.get(s), gateway=gateway
|
|
1630
|
+
),
|
|
1631
|
+
self.ddl_concurrent_tasks,
|
|
1632
|
+
)
|
|
1482
1633
|
|
|
1483
|
-
|
|
1634
|
+
for schema, objs in zip(schema_list, results):
|
|
1635
|
+
snapshots_by_name = snapshots_by_table_name.get(schema, {})
|
|
1636
|
+
for obj in objs:
|
|
1637
|
+
if obj.name in snapshots_by_name:
|
|
1638
|
+
snapshot_id_to_obj[snapshots_by_name[obj.name].snapshot_id] = obj
|
|
1639
|
+
|
|
1640
|
+
return snapshot_id_to_obj
|
|
1484
1641
|
|
|
1485
1642
|
|
|
1486
1643
|
def _evaluation_strategy(snapshot: SnapshotInfoLike, adapter: EngineAdapter) -> EvaluationStrategy:
|
|
@@ -1505,6 +1662,19 @@ def _evaluation_strategy(snapshot: SnapshotInfoLike, adapter: EngineAdapter) ->
|
|
|
1505
1662
|
klass = ViewStrategy
|
|
1506
1663
|
elif snapshot.is_scd_type_2:
|
|
1507
1664
|
klass = SCDType2Strategy
|
|
1665
|
+
elif snapshot.is_dbt_custom:
|
|
1666
|
+
if hasattr(snapshot, "model") and isinstance(
|
|
1667
|
+
(model_kind := snapshot.model.kind), DbtCustomKind
|
|
1668
|
+
):
|
|
1669
|
+
return DbtCustomMaterializationStrategy(
|
|
1670
|
+
adapter=adapter,
|
|
1671
|
+
materialization_name=model_kind.materialization,
|
|
1672
|
+
materialization_template=model_kind.definition,
|
|
1673
|
+
)
|
|
1674
|
+
|
|
1675
|
+
raise SQLMeshError(
|
|
1676
|
+
f"Expected DbtCustomKind for dbt custom materialization in model '{snapshot.name}'"
|
|
1677
|
+
)
|
|
1508
1678
|
elif snapshot.is_custom:
|
|
1509
1679
|
if snapshot.custom_materialization is None:
|
|
1510
1680
|
raise SQLMeshError(
|
|
@@ -1572,6 +1742,7 @@ class EvaluationStrategy(abc.ABC):
|
|
|
1572
1742
|
model: Model,
|
|
1573
1743
|
is_table_deployable: bool,
|
|
1574
1744
|
render_kwargs: t.Dict[str, t.Any],
|
|
1745
|
+
skip_grants: bool,
|
|
1575
1746
|
**kwargs: t.Any,
|
|
1576
1747
|
) -> None:
|
|
1577
1748
|
"""Creates the target table or view.
|
|
@@ -1644,6 +1815,84 @@ class EvaluationStrategy(abc.ABC):
|
|
|
1644
1815
|
view_name: The name of the target view in the virtual layer.
|
|
1645
1816
|
"""
|
|
1646
1817
|
|
|
1818
|
+
@abc.abstractmethod
|
|
1819
|
+
def run_pre_statements(self, snapshot: Snapshot, render_kwargs: t.Any) -> None:
|
|
1820
|
+
"""Executes the snapshot's pre statements.
|
|
1821
|
+
|
|
1822
|
+
Args:
|
|
1823
|
+
snapshot: The target snapshot.
|
|
1824
|
+
render_kwargs: Additional key-value arguments to pass when rendering the statements.
|
|
1825
|
+
"""
|
|
1826
|
+
|
|
1827
|
+
@abc.abstractmethod
|
|
1828
|
+
def run_post_statements(self, snapshot: Snapshot, render_kwargs: t.Any) -> None:
|
|
1829
|
+
"""Executes the snapshot's post statements.
|
|
1830
|
+
|
|
1831
|
+
Args:
|
|
1832
|
+
snapshot: The target snapshot.
|
|
1833
|
+
render_kwargs: Additional key-value arguments to pass when rendering the statements.
|
|
1834
|
+
"""
|
|
1835
|
+
|
|
1836
|
+
def _apply_grants(
|
|
1837
|
+
self,
|
|
1838
|
+
model: Model,
|
|
1839
|
+
table_name: str,
|
|
1840
|
+
target_layer: GrantsTargetLayer,
|
|
1841
|
+
is_snapshot_deployable: bool = False,
|
|
1842
|
+
) -> None:
|
|
1843
|
+
"""Apply grants for a model if grants are configured.
|
|
1844
|
+
|
|
1845
|
+
This method provides consistent grants application across all evaluation strategies.
|
|
1846
|
+
It ensures that whenever a physical database object (table, view, materialized view)
|
|
1847
|
+
is created or modified, the appropriate grants are applied.
|
|
1848
|
+
|
|
1849
|
+
Args:
|
|
1850
|
+
model: The SQLMesh model containing grants configuration
|
|
1851
|
+
table_name: The target table/view name to apply grants to
|
|
1852
|
+
target_layer: The grants application layer (physical or virtual)
|
|
1853
|
+
is_snapshot_deployable: Whether the snapshot is deployable (targeting production)
|
|
1854
|
+
"""
|
|
1855
|
+
grants_config = model.grants
|
|
1856
|
+
if grants_config is None:
|
|
1857
|
+
return
|
|
1858
|
+
|
|
1859
|
+
if not self.adapter.SUPPORTS_GRANTS:
|
|
1860
|
+
logger.warning(
|
|
1861
|
+
f"Engine {self.adapter.__class__.__name__} does not support grants. "
|
|
1862
|
+
f"Skipping grants application for model {model.name}"
|
|
1863
|
+
)
|
|
1864
|
+
return
|
|
1865
|
+
|
|
1866
|
+
model_grants_target_layer = model.grants_target_layer
|
|
1867
|
+
deployable_vde_dev_only = (
|
|
1868
|
+
is_snapshot_deployable and model.virtual_environment_mode.is_dev_only
|
|
1869
|
+
)
|
|
1870
|
+
|
|
1871
|
+
# table_type is always a VIEW in the virtual layer unless model is deployable and VDE is dev_only
|
|
1872
|
+
# in which case we fall back to the model's model_grants_table_type
|
|
1873
|
+
if target_layer == GrantsTargetLayer.VIRTUAL and not deployable_vde_dev_only:
|
|
1874
|
+
model_grants_table_type = DataObjectType.VIEW
|
|
1875
|
+
else:
|
|
1876
|
+
model_grants_table_type = model.grants_table_type
|
|
1877
|
+
|
|
1878
|
+
if (
|
|
1879
|
+
model_grants_target_layer.is_all
|
|
1880
|
+
or model_grants_target_layer == target_layer
|
|
1881
|
+
# Always apply grants in production when VDE is dev_only regardless of target_layer
|
|
1882
|
+
# since only physical tables are created in production
|
|
1883
|
+
or deployable_vde_dev_only
|
|
1884
|
+
):
|
|
1885
|
+
logger.info(f"Applying grants for model {model.name} to table {table_name}")
|
|
1886
|
+
self.adapter.sync_grants_config(
|
|
1887
|
+
exp.to_table(table_name, dialect=self.adapter.dialect),
|
|
1888
|
+
grants_config,
|
|
1889
|
+
model_grants_table_type,
|
|
1890
|
+
)
|
|
1891
|
+
else:
|
|
1892
|
+
logger.debug(
|
|
1893
|
+
f"Skipping grants application for model {model.name} in {target_layer} layer"
|
|
1894
|
+
)
|
|
1895
|
+
|
|
1647
1896
|
|
|
1648
1897
|
class SymbolicStrategy(EvaluationStrategy):
|
|
1649
1898
|
def insert(
|
|
@@ -1673,6 +1922,7 @@ class SymbolicStrategy(EvaluationStrategy):
|
|
|
1673
1922
|
model: Model,
|
|
1674
1923
|
is_table_deployable: bool,
|
|
1675
1924
|
render_kwargs: t.Dict[str, t.Any],
|
|
1925
|
+
skip_grants: bool,
|
|
1676
1926
|
**kwargs: t.Any,
|
|
1677
1927
|
) -> None:
|
|
1678
1928
|
pass
|
|
@@ -1705,6 +1955,12 @@ class SymbolicStrategy(EvaluationStrategy):
|
|
|
1705
1955
|
def demote(self, view_name: str, **kwargs: t.Any) -> None:
|
|
1706
1956
|
pass
|
|
1707
1957
|
|
|
1958
|
+
def run_pre_statements(self, snapshot: Snapshot, render_kwargs: t.Dict[str, t.Any]) -> None:
|
|
1959
|
+
pass
|
|
1960
|
+
|
|
1961
|
+
def run_post_statements(self, snapshot: Snapshot, render_kwargs: t.Dict[str, t.Any]) -> None:
|
|
1962
|
+
pass
|
|
1963
|
+
|
|
1708
1964
|
|
|
1709
1965
|
class EmbeddedStrategy(SymbolicStrategy):
|
|
1710
1966
|
def promote(
|
|
@@ -1748,10 +2004,27 @@ class PromotableStrategy(EvaluationStrategy, abc.ABC):
|
|
|
1748
2004
|
view_properties=model.render_virtual_properties(**render_kwargs),
|
|
1749
2005
|
)
|
|
1750
2006
|
|
|
2007
|
+
snapshot = kwargs.get("snapshot")
|
|
2008
|
+
deployability_index = kwargs.get("deployability_index")
|
|
2009
|
+
is_snapshot_deployable = (
|
|
2010
|
+
deployability_index.is_deployable(snapshot)
|
|
2011
|
+
if snapshot and deployability_index
|
|
2012
|
+
else False
|
|
2013
|
+
)
|
|
2014
|
+
|
|
2015
|
+
# Apply grants to the virtual layer (view) after promotion
|
|
2016
|
+
self._apply_grants(model, view_name, GrantsTargetLayer.VIRTUAL, is_snapshot_deployable)
|
|
2017
|
+
|
|
1751
2018
|
def demote(self, view_name: str, **kwargs: t.Any) -> None:
|
|
1752
2019
|
logger.info("Dropping view '%s'", view_name)
|
|
1753
2020
|
self.adapter.drop_view(view_name, cascade=False)
|
|
1754
2021
|
|
|
2022
|
+
def run_pre_statements(self, snapshot: Snapshot, render_kwargs: t.Any) -> None:
|
|
2023
|
+
self.adapter.execute(snapshot.model.render_pre_statements(**render_kwargs))
|
|
2024
|
+
|
|
2025
|
+
def run_post_statements(self, snapshot: Snapshot, render_kwargs: t.Any) -> None:
|
|
2026
|
+
self.adapter.execute(snapshot.model.render_post_statements(**render_kwargs))
|
|
2027
|
+
|
|
1755
2028
|
|
|
1756
2029
|
class MaterializableStrategy(PromotableStrategy, abc.ABC):
|
|
1757
2030
|
def create(
|
|
@@ -1760,6 +2033,7 @@ class MaterializableStrategy(PromotableStrategy, abc.ABC):
|
|
|
1760
2033
|
model: Model,
|
|
1761
2034
|
is_table_deployable: bool,
|
|
1762
2035
|
render_kwargs: t.Dict[str, t.Any],
|
|
2036
|
+
skip_grants: bool,
|
|
1763
2037
|
**kwargs: t.Any,
|
|
1764
2038
|
) -> None:
|
|
1765
2039
|
ctas_query = model.ctas_query(**render_kwargs)
|
|
@@ -1804,6 +2078,13 @@ class MaterializableStrategy(PromotableStrategy, abc.ABC):
|
|
|
1804
2078
|
column_descriptions=model.column_descriptions if is_table_deployable else None,
|
|
1805
2079
|
)
|
|
1806
2080
|
|
|
2081
|
+
# Apply grants after table creation (unless explicitly skipped by caller)
|
|
2082
|
+
if not skip_grants:
|
|
2083
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2084
|
+
self._apply_grants(
|
|
2085
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2086
|
+
)
|
|
2087
|
+
|
|
1807
2088
|
def migrate(
|
|
1808
2089
|
self,
|
|
1809
2090
|
target_table_name: str,
|
|
@@ -1829,6 +2110,15 @@ class MaterializableStrategy(PromotableStrategy, abc.ABC):
|
|
|
1829
2110
|
)
|
|
1830
2111
|
self.adapter.alter_table(alter_operations)
|
|
1831
2112
|
|
|
2113
|
+
# Apply grants after schema migration
|
|
2114
|
+
deployability_index = kwargs.get("deployability_index")
|
|
2115
|
+
is_snapshot_deployable = (
|
|
2116
|
+
deployability_index.is_deployable(snapshot) if deployability_index else False
|
|
2117
|
+
)
|
|
2118
|
+
self._apply_grants(
|
|
2119
|
+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2120
|
+
)
|
|
2121
|
+
|
|
1832
2122
|
def delete(self, name: str, **kwargs: t.Any) -> None:
|
|
1833
2123
|
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
|
|
1834
2124
|
self.adapter.drop_table(name, cascade=kwargs.pop("cascade", False))
|
|
@@ -1840,6 +2130,7 @@ class MaterializableStrategy(PromotableStrategy, abc.ABC):
|
|
|
1840
2130
|
name: str,
|
|
1841
2131
|
query_or_df: QueryOrDF,
|
|
1842
2132
|
render_kwargs: t.Dict[str, t.Any],
|
|
2133
|
+
skip_grants: bool = False,
|
|
1843
2134
|
**kwargs: t.Any,
|
|
1844
2135
|
) -> None:
|
|
1845
2136
|
"""Replaces the table for the given model.
|
|
@@ -1876,6 +2167,11 @@ class MaterializableStrategy(PromotableStrategy, abc.ABC):
|
|
|
1876
2167
|
source_columns=source_columns,
|
|
1877
2168
|
)
|
|
1878
2169
|
|
|
2170
|
+
# Apply grants after table replacement (unless explicitly skipped by caller)
|
|
2171
|
+
if not skip_grants:
|
|
2172
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2173
|
+
self._apply_grants(model, name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
|
|
2174
|
+
|
|
1879
2175
|
def _get_target_and_source_columns(
|
|
1880
2176
|
self,
|
|
1881
2177
|
model: Model,
|
|
@@ -1897,7 +2193,13 @@ class MaterializableStrategy(PromotableStrategy, abc.ABC):
|
|
|
1897
2193
|
if model.on_destructive_change.is_ignore or model.on_additive_change.is_ignore:
|
|
1898
2194
|
# We need to identify the columns that are only in the source so we create an empty table with
|
|
1899
2195
|
# the user query to determine that
|
|
1900
|
-
|
|
2196
|
+
temp_table_name = exp.table_(
|
|
2197
|
+
"diff",
|
|
2198
|
+
db=model.physical_schema,
|
|
2199
|
+
)
|
|
2200
|
+
with self.adapter.temp_table(
|
|
2201
|
+
model.ctas_query(**render_kwargs), name=temp_table_name
|
|
2202
|
+
) as temp_table:
|
|
1901
2203
|
source_columns = list(self.adapter.columns(temp_table))
|
|
1902
2204
|
else:
|
|
1903
2205
|
source_columns = None
|
|
@@ -2123,6 +2425,7 @@ class SeedStrategy(MaterializableStrategy):
|
|
|
2123
2425
|
model: Model,
|
|
2124
2426
|
is_table_deployable: bool,
|
|
2125
2427
|
render_kwargs: t.Dict[str, t.Any],
|
|
2428
|
+
skip_grants: bool,
|
|
2126
2429
|
**kwargs: t.Any,
|
|
2127
2430
|
) -> None:
|
|
2128
2431
|
model = t.cast(SeedModel, model)
|
|
@@ -2136,20 +2439,53 @@ class SeedStrategy(MaterializableStrategy):
|
|
|
2136
2439
|
)
|
|
2137
2440
|
return
|
|
2138
2441
|
|
|
2139
|
-
super().create(
|
|
2442
|
+
super().create(
|
|
2443
|
+
table_name,
|
|
2444
|
+
model,
|
|
2445
|
+
is_table_deployable,
|
|
2446
|
+
render_kwargs,
|
|
2447
|
+
skip_grants=True, # Skip grants; they're applied after data insertion
|
|
2448
|
+
**kwargs,
|
|
2449
|
+
)
|
|
2140
2450
|
# For seeds we insert data at the time of table creation.
|
|
2141
2451
|
try:
|
|
2142
2452
|
for index, df in enumerate(model.render_seed()):
|
|
2143
2453
|
if index == 0:
|
|
2144
|
-
self._replace_query_for_model(
|
|
2454
|
+
self._replace_query_for_model(
|
|
2455
|
+
model,
|
|
2456
|
+
table_name,
|
|
2457
|
+
df,
|
|
2458
|
+
render_kwargs,
|
|
2459
|
+
skip_grants=True, # Skip grants; they're applied after data insertion
|
|
2460
|
+
**kwargs,
|
|
2461
|
+
)
|
|
2145
2462
|
else:
|
|
2146
2463
|
self.adapter.insert_append(
|
|
2147
2464
|
table_name, df, target_columns_to_types=model.columns_to_types
|
|
2148
2465
|
)
|
|
2466
|
+
|
|
2467
|
+
if not skip_grants:
|
|
2468
|
+
# Apply grants after seed table creation and data insertion
|
|
2469
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2470
|
+
self._apply_grants(
|
|
2471
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2472
|
+
)
|
|
2149
2473
|
except Exception:
|
|
2150
2474
|
self.adapter.drop_table(table_name)
|
|
2151
2475
|
raise
|
|
2152
2476
|
|
|
2477
|
+
def migrate(
|
|
2478
|
+
self,
|
|
2479
|
+
target_table_name: str,
|
|
2480
|
+
source_table_name: str,
|
|
2481
|
+
snapshot: Snapshot,
|
|
2482
|
+
*,
|
|
2483
|
+
ignore_destructive: bool,
|
|
2484
|
+
ignore_additive: bool,
|
|
2485
|
+
**kwargs: t.Any,
|
|
2486
|
+
) -> None:
|
|
2487
|
+
raise NotImplementedError("Seeds do not support migrations.")
|
|
2488
|
+
|
|
2153
2489
|
def insert(
|
|
2154
2490
|
self,
|
|
2155
2491
|
table_name: str,
|
|
@@ -2181,6 +2517,7 @@ class SCDType2Strategy(IncrementalStrategy):
|
|
|
2181
2517
|
model: Model,
|
|
2182
2518
|
is_table_deployable: bool,
|
|
2183
2519
|
render_kwargs: t.Dict[str, t.Any],
|
|
2520
|
+
skip_grants: bool,
|
|
2184
2521
|
**kwargs: t.Any,
|
|
2185
2522
|
) -> None:
|
|
2186
2523
|
assert isinstance(model.kind, (SCDType2ByTimeKind, SCDType2ByColumnKind))
|
|
@@ -2210,9 +2547,17 @@ class SCDType2Strategy(IncrementalStrategy):
|
|
|
2210
2547
|
model,
|
|
2211
2548
|
is_table_deployable,
|
|
2212
2549
|
render_kwargs,
|
|
2550
|
+
skip_grants,
|
|
2213
2551
|
**kwargs,
|
|
2214
2552
|
)
|
|
2215
2553
|
|
|
2554
|
+
if not skip_grants:
|
|
2555
|
+
# Apply grants after SCD Type 2 table creation
|
|
2556
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2557
|
+
self._apply_grants(
|
|
2558
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2559
|
+
)
|
|
2560
|
+
|
|
2216
2561
|
def insert(
|
|
2217
2562
|
self,
|
|
2218
2563
|
table_name: str,
|
|
@@ -2280,6 +2625,10 @@ class SCDType2Strategy(IncrementalStrategy):
|
|
|
2280
2625
|
f"Unexpected SCD Type 2 kind: {model.kind}. This is not expected and please report this as a bug."
|
|
2281
2626
|
)
|
|
2282
2627
|
|
|
2628
|
+
# Apply grants after SCD Type 2 table recreation
|
|
2629
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2630
|
+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
|
|
2631
|
+
|
|
2283
2632
|
def append(
|
|
2284
2633
|
self,
|
|
2285
2634
|
table_name: str,
|
|
@@ -2336,6 +2685,10 @@ class ViewStrategy(PromotableStrategy):
|
|
|
2336
2685
|
column_descriptions=model.column_descriptions,
|
|
2337
2686
|
)
|
|
2338
2687
|
|
|
2688
|
+
# Apply grants after view creation / replacement
|
|
2689
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2690
|
+
self._apply_grants(model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable)
|
|
2691
|
+
|
|
2339
2692
|
def append(
|
|
2340
2693
|
self,
|
|
2341
2694
|
table_name: str,
|
|
@@ -2352,12 +2705,21 @@ class ViewStrategy(PromotableStrategy):
|
|
|
2352
2705
|
model: Model,
|
|
2353
2706
|
is_table_deployable: bool,
|
|
2354
2707
|
render_kwargs: t.Dict[str, t.Any],
|
|
2708
|
+
skip_grants: bool,
|
|
2355
2709
|
**kwargs: t.Any,
|
|
2356
2710
|
) -> None:
|
|
2711
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2712
|
+
|
|
2357
2713
|
if self.adapter.table_exists(table_name):
|
|
2358
2714
|
# Make sure we don't recreate the view to prevent deletion of downstream views in engines with no late
|
|
2359
2715
|
# binding support (because of DROP CASCADE).
|
|
2360
2716
|
logger.info("View '%s' already exists", table_name)
|
|
2717
|
+
|
|
2718
|
+
if not skip_grants:
|
|
2719
|
+
# Always apply grants when present, even if view exists, to handle grants updates
|
|
2720
|
+
self._apply_grants(
|
|
2721
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2722
|
+
)
|
|
2361
2723
|
return
|
|
2362
2724
|
|
|
2363
2725
|
logger.info("Creating view '%s'", table_name)
|
|
@@ -2381,6 +2743,12 @@ class ViewStrategy(PromotableStrategy):
|
|
|
2381
2743
|
column_descriptions=model.column_descriptions if is_table_deployable else None,
|
|
2382
2744
|
)
|
|
2383
2745
|
|
|
2746
|
+
if not skip_grants:
|
|
2747
|
+
# Apply grants after view creation
|
|
2748
|
+
self._apply_grants(
|
|
2749
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2750
|
+
)
|
|
2751
|
+
|
|
2384
2752
|
def migrate(
|
|
2385
2753
|
self,
|
|
2386
2754
|
target_table_name: str,
|
|
@@ -2407,6 +2775,15 @@ class ViewStrategy(PromotableStrategy):
|
|
|
2407
2775
|
column_descriptions=model.column_descriptions,
|
|
2408
2776
|
)
|
|
2409
2777
|
|
|
2778
|
+
# Apply grants after view migration
|
|
2779
|
+
deployability_index = kwargs.get("deployability_index")
|
|
2780
|
+
is_snapshot_deployable = (
|
|
2781
|
+
deployability_index.is_deployable(snapshot) if deployability_index else False
|
|
2782
|
+
)
|
|
2783
|
+
self._apply_grants(
|
|
2784
|
+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2785
|
+
)
|
|
2786
|
+
|
|
2410
2787
|
def delete(self, name: str, **kwargs: t.Any) -> None:
|
|
2411
2788
|
cascade = kwargs.pop("cascade", False)
|
|
2412
2789
|
try:
|
|
@@ -2546,6 +2923,169 @@ def get_custom_materialization_type_or_raise(
|
|
|
2546
2923
|
raise SQLMeshError(f"Custom materialization '{name}' not present in the Python environment")
|
|
2547
2924
|
|
|
2548
2925
|
|
|
2926
|
+
class DbtCustomMaterializationStrategy(MaterializableStrategy):
|
|
2927
|
+
def __init__(
|
|
2928
|
+
self,
|
|
2929
|
+
adapter: EngineAdapter,
|
|
2930
|
+
materialization_name: str,
|
|
2931
|
+
materialization_template: str,
|
|
2932
|
+
):
|
|
2933
|
+
super().__init__(adapter)
|
|
2934
|
+
self.materialization_name = materialization_name
|
|
2935
|
+
self.materialization_template = materialization_template
|
|
2936
|
+
|
|
2937
|
+
def create(
|
|
2938
|
+
self,
|
|
2939
|
+
table_name: str,
|
|
2940
|
+
model: Model,
|
|
2941
|
+
is_table_deployable: bool,
|
|
2942
|
+
render_kwargs: t.Dict[str, t.Any],
|
|
2943
|
+
skip_grants: bool,
|
|
2944
|
+
**kwargs: t.Any,
|
|
2945
|
+
) -> None:
|
|
2946
|
+
original_query = model.render_query_or_raise(**render_kwargs)
|
|
2947
|
+
self._execute_materialization(
|
|
2948
|
+
table_name=table_name,
|
|
2949
|
+
query_or_df=original_query.limit(0),
|
|
2950
|
+
model=model,
|
|
2951
|
+
is_first_insert=True,
|
|
2952
|
+
render_kwargs=render_kwargs,
|
|
2953
|
+
create_only=True,
|
|
2954
|
+
**kwargs,
|
|
2955
|
+
)
|
|
2956
|
+
|
|
2957
|
+
# Apply grants after dbt custom materialization table creation
|
|
2958
|
+
if not skip_grants:
|
|
2959
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2960
|
+
self._apply_grants(
|
|
2961
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2962
|
+
)
|
|
2963
|
+
|
|
2964
|
+
def insert(
|
|
2965
|
+
self,
|
|
2966
|
+
table_name: str,
|
|
2967
|
+
query_or_df: QueryOrDF,
|
|
2968
|
+
model: Model,
|
|
2969
|
+
is_first_insert: bool,
|
|
2970
|
+
render_kwargs: t.Dict[str, t.Any],
|
|
2971
|
+
**kwargs: t.Any,
|
|
2972
|
+
) -> None:
|
|
2973
|
+
self._execute_materialization(
|
|
2974
|
+
table_name=table_name,
|
|
2975
|
+
query_or_df=query_or_df,
|
|
2976
|
+
model=model,
|
|
2977
|
+
is_first_insert=is_first_insert,
|
|
2978
|
+
render_kwargs=render_kwargs,
|
|
2979
|
+
**kwargs,
|
|
2980
|
+
)
|
|
2981
|
+
|
|
2982
|
+
# Apply grants after custom materialization insert (only on first insert)
|
|
2983
|
+
if is_first_insert:
|
|
2984
|
+
is_snapshot_deployable = kwargs.get("is_snapshot_deployable", False)
|
|
2985
|
+
self._apply_grants(
|
|
2986
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
2987
|
+
)
|
|
2988
|
+
|
|
2989
|
+
def append(
|
|
2990
|
+
self,
|
|
2991
|
+
table_name: str,
|
|
2992
|
+
query_or_df: QueryOrDF,
|
|
2993
|
+
model: Model,
|
|
2994
|
+
render_kwargs: t.Dict[str, t.Any],
|
|
2995
|
+
**kwargs: t.Any,
|
|
2996
|
+
) -> None:
|
|
2997
|
+
return self.insert(
|
|
2998
|
+
table_name,
|
|
2999
|
+
query_or_df,
|
|
3000
|
+
model,
|
|
3001
|
+
is_first_insert=False,
|
|
3002
|
+
render_kwargs=render_kwargs,
|
|
3003
|
+
**kwargs,
|
|
3004
|
+
)
|
|
3005
|
+
|
|
3006
|
+
def run_pre_statements(self, snapshot: Snapshot, render_kwargs: t.Any) -> None:
|
|
3007
|
+
# in dbt custom materialisations it's up to the user to run the pre hooks inside the transaction
|
|
3008
|
+
if not render_kwargs.get("inside_transaction", True):
|
|
3009
|
+
super().run_pre_statements(
|
|
3010
|
+
snapshot=snapshot,
|
|
3011
|
+
render_kwargs=render_kwargs,
|
|
3012
|
+
)
|
|
3013
|
+
|
|
3014
|
+
def run_post_statements(self, snapshot: Snapshot, render_kwargs: t.Any) -> None:
|
|
3015
|
+
# in dbt custom materialisations it's up to the user to run the post hooks inside the transaction
|
|
3016
|
+
if not render_kwargs.get("inside_transaction", True):
|
|
3017
|
+
super().run_post_statements(
|
|
3018
|
+
snapshot=snapshot,
|
|
3019
|
+
render_kwargs=render_kwargs,
|
|
3020
|
+
)
|
|
3021
|
+
|
|
3022
|
+
def _execute_materialization(
|
|
3023
|
+
self,
|
|
3024
|
+
table_name: str,
|
|
3025
|
+
query_or_df: QueryOrDF,
|
|
3026
|
+
model: Model,
|
|
3027
|
+
is_first_insert: bool,
|
|
3028
|
+
render_kwargs: t.Dict[str, t.Any],
|
|
3029
|
+
create_only: bool = False,
|
|
3030
|
+
**kwargs: t.Any,
|
|
3031
|
+
) -> None:
|
|
3032
|
+
jinja_macros = model.jinja_macros
|
|
3033
|
+
|
|
3034
|
+
# For vdes we need to use the table, since we don't know the schema/table at parse time
|
|
3035
|
+
parts = exp.to_table(table_name, dialect=self.adapter.dialect)
|
|
3036
|
+
|
|
3037
|
+
existing_globals = jinja_macros.global_objs
|
|
3038
|
+
relation_info = existing_globals.get("this")
|
|
3039
|
+
if isinstance(relation_info, dict):
|
|
3040
|
+
relation_info["database"] = parts.catalog
|
|
3041
|
+
relation_info["identifier"] = parts.name
|
|
3042
|
+
relation_info["name"] = parts.name
|
|
3043
|
+
|
|
3044
|
+
jinja_globals = {
|
|
3045
|
+
**existing_globals,
|
|
3046
|
+
"this": relation_info,
|
|
3047
|
+
"database": parts.catalog,
|
|
3048
|
+
"schema": parts.db,
|
|
3049
|
+
"identifier": parts.name,
|
|
3050
|
+
"target": existing_globals.get("target", {"type": self.adapter.dialect}),
|
|
3051
|
+
"execution_dt": kwargs.get("execution_time"),
|
|
3052
|
+
"engine_adapter": self.adapter,
|
|
3053
|
+
"sql": str(query_or_df),
|
|
3054
|
+
"is_first_insert": is_first_insert,
|
|
3055
|
+
"create_only": create_only,
|
|
3056
|
+
"pre_hooks": [
|
|
3057
|
+
AttributeDict({"sql": s.this.this, "transaction": transaction})
|
|
3058
|
+
for s in model.pre_statements
|
|
3059
|
+
if (transaction := s.args.get("transaction", True))
|
|
3060
|
+
],
|
|
3061
|
+
"post_hooks": [
|
|
3062
|
+
AttributeDict({"sql": s.this.this, "transaction": transaction})
|
|
3063
|
+
for s in model.post_statements
|
|
3064
|
+
if (transaction := s.args.get("transaction", True))
|
|
3065
|
+
],
|
|
3066
|
+
"model_instance": model,
|
|
3067
|
+
**kwargs,
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
try:
|
|
3071
|
+
jinja_env = jinja_macros.build_environment(**jinja_globals)
|
|
3072
|
+
template = jinja_env.from_string(self.materialization_template)
|
|
3073
|
+
|
|
3074
|
+
try:
|
|
3075
|
+
template.render()
|
|
3076
|
+
except MacroReturnVal as ret:
|
|
3077
|
+
# this is a successful return from a macro call (dbt uses this list of Relations to update their relation cache)
|
|
3078
|
+
returned_relations = ret.value.get("relations", [])
|
|
3079
|
+
logger.info(
|
|
3080
|
+
f"Materialization {self.materialization_name} returned relations: {returned_relations}"
|
|
3081
|
+
)
|
|
3082
|
+
|
|
3083
|
+
except Exception as e:
|
|
3084
|
+
raise SQLMeshError(
|
|
3085
|
+
f"Failed to execute dbt materialization '{self.materialization_name}': {e}"
|
|
3086
|
+
) from e
|
|
3087
|
+
|
|
3088
|
+
|
|
2549
3089
|
class EngineManagedStrategy(MaterializableStrategy):
|
|
2550
3090
|
def create(
|
|
2551
3091
|
self,
|
|
@@ -2553,6 +3093,7 @@ class EngineManagedStrategy(MaterializableStrategy):
|
|
|
2553
3093
|
model: Model,
|
|
2554
3094
|
is_table_deployable: bool,
|
|
2555
3095
|
render_kwargs: t.Dict[str, t.Any],
|
|
3096
|
+
skip_grants: bool,
|
|
2556
3097
|
**kwargs: t.Any,
|
|
2557
3098
|
) -> None:
|
|
2558
3099
|
is_snapshot_deployable: bool = kwargs["is_snapshot_deployable"]
|
|
@@ -2571,6 +3112,13 @@ class EngineManagedStrategy(MaterializableStrategy):
|
|
|
2571
3112
|
column_descriptions=model.column_descriptions,
|
|
2572
3113
|
table_format=model.table_format,
|
|
2573
3114
|
)
|
|
3115
|
+
|
|
3116
|
+
# Apply grants after managed table creation
|
|
3117
|
+
if not skip_grants:
|
|
3118
|
+
self._apply_grants(
|
|
3119
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
3120
|
+
)
|
|
3121
|
+
|
|
2574
3122
|
elif not is_table_deployable:
|
|
2575
3123
|
# Only create the dev preview table as a normal table.
|
|
2576
3124
|
# For the main table, if the snapshot is cant be deployed to prod (eg upstream is forward-only) do nothing.
|
|
@@ -2581,6 +3129,7 @@ class EngineManagedStrategy(MaterializableStrategy):
|
|
|
2581
3129
|
model=model,
|
|
2582
3130
|
is_table_deployable=is_table_deployable,
|
|
2583
3131
|
render_kwargs=render_kwargs,
|
|
3132
|
+
skip_grants=skip_grants,
|
|
2584
3133
|
**kwargs,
|
|
2585
3134
|
)
|
|
2586
3135
|
|
|
@@ -2596,7 +3145,6 @@ class EngineManagedStrategy(MaterializableStrategy):
|
|
|
2596
3145
|
deployability_index: DeployabilityIndex = kwargs["deployability_index"]
|
|
2597
3146
|
snapshot: Snapshot = kwargs["snapshot"]
|
|
2598
3147
|
is_snapshot_deployable = deployability_index.is_deployable(snapshot)
|
|
2599
|
-
|
|
2600
3148
|
if is_first_insert and is_snapshot_deployable and not self.adapter.table_exists(table_name):
|
|
2601
3149
|
self.adapter.create_managed_table(
|
|
2602
3150
|
table_name=table_name,
|
|
@@ -2609,6 +3157,9 @@ class EngineManagedStrategy(MaterializableStrategy):
|
|
|
2609
3157
|
column_descriptions=model.column_descriptions,
|
|
2610
3158
|
table_format=model.table_format,
|
|
2611
3159
|
)
|
|
3160
|
+
self._apply_grants(
|
|
3161
|
+
model, table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
3162
|
+
)
|
|
2612
3163
|
elif not is_snapshot_deployable:
|
|
2613
3164
|
# Snapshot isnt deployable; update the preview table instead
|
|
2614
3165
|
# If the snapshot was deployable, then data would have already been loaded in create() because a managed table would have been created
|
|
@@ -2653,10 +3204,19 @@ class EngineManagedStrategy(MaterializableStrategy):
|
|
|
2653
3204
|
)
|
|
2654
3205
|
if len(potential_alter_operations) > 0:
|
|
2655
3206
|
# this can happen if a user changes a managed model and deliberately overrides a plan to be forward only, eg `sqlmesh plan --forward-only`
|
|
2656
|
-
raise
|
|
3207
|
+
raise MigrationNotSupportedError(
|
|
2657
3208
|
f"The schema of the managed model '{target_table_name}' cannot be updated in a forward-only fashion."
|
|
2658
3209
|
)
|
|
2659
3210
|
|
|
3211
|
+
# Apply grants after verifying no schema changes
|
|
3212
|
+
deployability_index = kwargs.get("deployability_index")
|
|
3213
|
+
is_snapshot_deployable = (
|
|
3214
|
+
deployability_index.is_deployable(snapshot) if deployability_index else False
|
|
3215
|
+
)
|
|
3216
|
+
self._apply_grants(
|
|
3217
|
+
snapshot.model, target_table_name, GrantsTargetLayer.PHYSICAL, is_snapshot_deployable
|
|
3218
|
+
)
|
|
3219
|
+
|
|
2660
3220
|
def delete(self, name: str, **kwargs: t.Any) -> None:
|
|
2661
3221
|
# a dev preview table is created as a normal table, so it needs to be dropped as a normal table
|
|
2662
3222
|
_check_table_db_is_physical_schema(name, kwargs["physical_schema"])
|