Repository navigation
fix(snowflake): change the clustering key before dropping a clustered column #6176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,15 +16,18 @@ | |
| ClusteredByMixin, | ||
| RowDiffMixin, | ||
| GrantsFromInfoSchemaMixin, | ||
| TableAlterChangeClusterKeyOperation, | ||
| TableAlterClusterByOperation, | ||
| TableAlterDropClusterKeyOperation, | ||
| ) | ||
| from sqlmesh.core.schema_diff import TableAlterDropColumnOperation, TableAlterOperation | ||
| from sqlmesh.core.engine_adapter.shared import ( | ||
| CatalogSupport, | ||
| DataObject, | ||
| DataObjectType, | ||
| SourceQuery, | ||
| set_catalog, | ||
| ) | ||
| from sqlmesh.core.schema_diff import TableAlterOperation | ||
| from sqlmesh.utils import optional_import, get_source_columns_to_types | ||
| from sqlmesh.utils.errors import SQLMeshError | ||
| from sqlmesh.utils.pandas import columns_to_types_from_dtypes | ||
|
|
@@ -692,6 +695,63 @@ def clone_table( | |
| **kwargs, | ||
| ) | ||
|
|
||
| def _apply_clustering_operation( | ||
| self, | ||
| operations: t.List[TableAlterOperation], | ||
| clustering_operation: TableAlterClusterByOperation, | ||
| current_table_info: t.Any, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
| ) -> None: | ||
| """Apply clustering DDL in the order Snowflake requires. | ||
|
|
||
| Snowflake rejects DROP COLUMN while the column is still named in the clustering | ||
| key, and also rejects CLUSTER BY on a column that has not yet been added. The | ||
| required sequence when the old key names a column being dropped is: | ||
|
|
||
| 1. DROP CLUSTERING KEY — before any drop of a column the old key still names. | ||
| 2. Column changes (drops and adds) — in their existing schema-diff order. | ||
| 3. CLUSTER BY — after all column changes, if the target table is clustered. | ||
|
|
||
| When no column being dropped belongs to the current key the historical order is | ||
| kept: column changes first, then the single clustering change at the end. | ||
| """ | ||
| clustering_key = getattr(current_table_info, "clustering_key", None) | ||
| if clustering_key: | ||
| referenced = self._clustering_key_column_names(clustering_key) | ||
| drop_index = next( | ||
| ( | ||
| i | ||
| for i, op in enumerate(operations) | ||
| if isinstance(op, TableAlterDropColumnOperation) | ||
| and op.column_parts | ||
| and normalize_identifiers( | ||
| exp.to_identifier(op.column_parts[0].name), dialect=self.dialect | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Treating the column name as quoted ( |
||
| ).name | ||
| in referenced | ||
| ), | ||
| None, | ||
| ) | ||
| if drop_index is not None: | ||
| drop_key_op = TableAlterDropClusterKeyOperation( | ||
| target_table=clustering_operation.target_table | ||
| ) | ||
| operations.insert(drop_index, drop_key_op) | ||
| if isinstance(clustering_operation, TableAlterChangeClusterKeyOperation): | ||
| operations.append(clustering_operation) | ||
| return | ||
| # No key column is being dropped: append at end (historical order). | ||
| operations.append(clustering_operation) | ||
|
|
||
| def _clustering_key_column_names(self, clustering_key: str) -> t.Set[str]: | ||
| expressions = TableAlterChangeClusterKeyOperation( | ||
| target_table=exp.to_table("_"), | ||
| clustering_key=clustering_key, | ||
| dialect=self.dialect, | ||
| ).cluster_key_expressions | ||
| return { | ||
| normalize_identifiers(expression, dialect=self.dialect).name | ||
| for expression in expressions | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
return {
normalize_identifiers(column, dialect=self.dialect).name
for expression in expressions
for column in expression.find_all(exp.Column)
}Please add a test with an expression key, e.g. |
||
| } | ||
|
nicksspirit marked this conversation as resolved.
Resolved
|
||
|
|
||
| def alter_table( | ||
| self, | ||
| alter_expressions: t.Union[t.List[exp.Alter], t.List[TableAlterOperation]], | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related case: changing a clustered column's type in a way that can't be altered in place (e.g. VARCHAR → INT) while the key stays the same. Schema diff turns that into DROP + ADD, but since the key didn't change,
clustering_operationisNoneand this hook never runs. Snowflake still seesDROP COLUMNon a column that's in the key. That already happens onmain, so it isn't a regression, but it's the same bug as #5813. Happy for it to go in here or in a follow-up issue, whichever you prefer.