Skip to content

Commit d057ccc

Browse files
committed
feat: add ignore destructive support
1 parent eed4c26 commit d057ccc

File tree

30 files changed

+3074
-219
lines changed

30 files changed

+3074
-219
lines changed

docs/concepts/models/overview.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,11 +507,15 @@ Some properties are only available in specific model kinds - see the [model conf
507507
: Set this to true to indicate that all changes to this model should be [forward-only](../plans.md#forward-only-plans).
508508

509509
### on_destructive_change
510-
: What should happen when a change to a [forward-only model](../../guides/incremental_time.md#forward-only-models) or incremental model in a [forward-only plan](../plans.md#forward-only-plans) causes a destructive modification to the table schema (i.e., requires dropping an existing column).
510+
: What should happen when a change to a [forward-only model](../../guides/incremental_time.md#forward-only-models) or incremental model in a [forward-only plan](../plans.md#forward-only-plans) causes a destructive modification to the table schema (i.e., requires dropping an existing column or modifying column constraints in ways that could cause data loss).
511511

512512
SQLMesh checks for destructive changes at plan time based on the model definition and run time based on the model's underlying physical tables.
513513

514-
Must be one of the following values: `allow`, `warn`, or `error` (default).
514+
Must be one of the following values: `allow`, `warn`, `error` (default), or `ignore`.
515+
516+
!!! warning "Ignore is Dangerous"
517+
518+
`ignore` is dangerous since it can result in error or data loss. It likely should never be used but could be useful as an "escape-hatch" or a way to workaround unexpected behavior.
515519

516520
### disable_restatement
517521
: Set this to true to indicate that [data restatement](../plans.md#restatement-plans) is disabled for this model.

docs/guides/incremental_time.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,12 @@ The check is performed at plan time based on the model definition. SQLMesh may n
171171

172172
A model's `on_destructive_change` [configuration setting](../reference/model_configuration.md#incremental-models) determines what happens when SQLMesh detects a destructive change.
173173

174-
By default, SQLMesh will error so no data is lost. You can set `on_destructive_change` to `warn` or `allow` in the model's `MODEL` block to allow destructive changes.
174+
By default, SQLMesh will error so no data is lost. You can set `on_destructive_change` to `warn` or `allow` in the model's `MODEL` block to allow destructive changes.
175+
`ignore` can be used to not perform the schema change and allow the table's definition to diverge from the model definition.
176+
177+
!!! warning "Ignore is Dangerous"
178+
179+
`ignore` is dangerous since it can result in error or data loss. It likely should never be used but could be useful as an "escape-hatch" or a way to workaround unexpected behavior.
175180

176181
This example configures a model to silently `allow` destructive changes:
177182

examples/custom_materializations/custom_materializations/custom_kind.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ def insert(
2424
query_or_df: QueryOrDF,
2525
model: Model,
2626
is_first_insert: bool,
27+
render_kwargs: t.Dict[str, t.Any],
2728
**kwargs: t.Any,
2829
) -> None:
2930
assert type(model.kind).__name__ == "ExtendedCustomKind"
3031

31-
self._replace_query_for_model(model, table_name, query_or_df)
32+
self._replace_query_for_model(model, table_name, query_or_df, render_kwargs)

examples/custom_materializations/custom_materializations/full.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def insert(
1717
query_or_df: QueryOrDF,
1818
model: Model,
1919
is_first_insert: bool,
20+
render_kwargs: t.Dict[str, t.Any],
2021
**kwargs: t.Any,
2122
) -> None:
22-
self._replace_query_for_model(model, table_name, query_or_df)
23+
self._replace_query_for_model(model, table_name, query_or_df, render_kwargs)

sqlmesh/core/dialect.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from sqlglot.schema import MappingSchema
2323
from sqlglot.tokens import Token
2424

25+
from sqlmesh import EngineAdapter
2526
from sqlmesh.core.constants import MAX_MODEL_DEFINITION_SIZE
2627
from sqlmesh.utils.errors import SQLMeshError, ConfigError
2728
from sqlmesh.utils.pandas import columns_to_types_from_df
@@ -1134,31 +1135,45 @@ def select_from_values_for_batch_range(
11341135
batch_start: int,
11351136
batch_end: int,
11361137
alias: str = "t",
1138+
source_columns: t.Optional[t.Set[str]] = None,
11371139
) -> exp.Select:
1138-
casted_columns = [
1139-
exp.alias_(exp.cast(exp.column(column), to=kind), column, copy=False)
1140-
for column, kind in columns_to_types.items()
1141-
]
1140+
source_columns = source_columns or set(columns_to_types)
1141+
source_columns_to_types = EngineAdapter.get_source_columns_to_types(
1142+
columns_to_types, source_columns
1143+
)
11421144

11431145
if not values:
11441146
# Ensures we don't generate an empty VALUES clause & forces a zero-row output
11451147
where = exp.false()
1146-
expressions = [tuple(exp.cast(exp.null(), to=kind) for kind in columns_to_types.values())]
1148+
expressions = [
1149+
tuple(exp.cast(exp.null(), to=kind) for kind in source_columns_to_types.values())
1150+
]
11471151
else:
11481152
where = None
11491153
expressions = [
1150-
tuple(transform_values(v, columns_to_types)) for v in values[batch_start:batch_end]
1154+
tuple(transform_values(v, source_columns_to_types))
1155+
for v in values[batch_start:batch_end]
11511156
]
11521157

1153-
values_exp = exp.values(expressions, alias=alias, columns=columns_to_types)
1158+
values_exp = exp.values(expressions, alias=alias, columns=source_columns_to_types)
11541159
if values:
11551160
# BigQuery crashes on `SELECT CAST(x AS TIMESTAMP) FROM UNNEST([NULL]) AS x`, but not
11561161
# on `SELECT CAST(x AS TIMESTAMP) FROM UNNEST([CAST(NULL AS TIMESTAMP)]) AS x`. This
11571162
# ensures nulls under the `Values` expression are cast to avoid similar issues.
1158-
for value, kind in zip(values_exp.expressions[0].expressions, columns_to_types.values()):
1163+
for value, kind in zip(
1164+
values_exp.expressions[0].expressions, source_columns_to_types.values()
1165+
):
11591166
if isinstance(value, exp.Null):
11601167
value.replace(exp.cast(value, to=kind))
11611168

1169+
casted_columns = [
1170+
exp.alias_(
1171+
exp.cast(exp.column(column) if column in source_columns else exp.Null(), to=kind),
1172+
column,
1173+
copy=False,
1174+
)
1175+
for column, kind in columns_to_types.items()
1176+
]
11621177
return exp.select(*casted_columns).from_(values_exp, copy=False).where(where, copy=False)
11631178

11641179

sqlmesh/core/engine_adapter/_typing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
snowpark = optional_import("snowflake.snowpark")
1515

16-
Query = t.Union[exp.Query, exp.DerivedTable]
16+
Query = exp.Query
1717
PySparkSession = t.Union[pyspark.sql.SparkSession, pyspark.sql.connect.dataframe.SparkSession]
1818
PySparkDataFrame = t.Union[pyspark.sql.DataFrame, pyspark.sql.connect.dataframe.DataFrame]
1919

sqlmesh/core/engine_adapter/athena.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ def replace_query(
434434
columns_to_types: t.Optional[t.Dict[str, exp.DataType]] = None,
435435
table_description: t.Optional[str] = None,
436436
column_descriptions: t.Optional[t.Dict[str, str]] = None,
437+
source_columns: t.Optional[t.Set[str]] = None,
437438
**kwargs: t.Any,
438439
) -> None:
439440
table = exp.to_table(table_name)
@@ -447,6 +448,7 @@ def replace_query(
447448
columns_to_types=columns_to_types,
448449
table_description=table_description,
449450
column_descriptions=column_descriptions,
451+
source_columns=source_columns,
450452
**kwargs,
451453
)
452454

0 commit comments

Comments
 (0)