11from __future__ import annotations
22
3+ import logging
34import typing as t
5+ from collections import defaultdict
46from enum import Enum , auto
57
68from sqlglot import exp
79from sqlglot .helper import ensure_list , seq_get
810
911from sqlmesh .utils .pydantic import PydanticModel
1012
13+ if t .TYPE_CHECKING :
14+ from sqlmesh .core ._typing import TableName
15+
16+ logger = logging .getLogger (__name__ )
17+
1118
1219class TableAlterOperationType (Enum ):
1320 ADD = auto ()
@@ -292,6 +299,9 @@ class SchemaDiffer(PydanticModel):
292299 support_nested_operations : bool = False
293300 array_element_selector : str = ""
294301 compatible_types : t .Dict [exp .DataType , t .Set [exp .DataType ]] = {}
302+ support_coercing_compatible_types : bool = False
303+
304+ _coerceable_types : t .Dict [exp .DataType , t .Set [exp .DataType ]] = {}
295305
296306 @classmethod
297307 def _dict_to_struct (cls , value : t .Dict [str , exp .DataType ]) -> exp .DataType :
@@ -303,13 +313,37 @@ def _dict_to_struct(cls, value: t.Dict[str, exp.DataType]) -> exp.DataType:
303313 nested = True ,
304314 )
305315
316+ @property
317+ def coerceable_types (self ) -> t .Dict [exp .DataType , t .Set [exp .DataType ]]:
318+ if not self ._coerceable_types :
319+ if not self .support_coercing_compatible_types or not self .compatible_types :
320+ return {}
321+ coerceable_types = defaultdict (set )
322+ for source_type , target_types in self .compatible_types .items ():
323+ for target_type in target_types :
324+ coerceable_types [target_type ].add (source_type )
325+ self ._coerceable_types = coerceable_types
326+ return self ._coerceable_types
327+
306328 def _is_compatible_type (self , current_type : exp .DataType , new_type : exp .DataType ) -> bool :
307329 if current_type == new_type :
308330 return True
309331 if current_type in self .compatible_types :
310332 return new_type in self .compatible_types [current_type ]
311333 return False
312334
335+ def _is_coerceable_type (self , current_type : exp .DataType , new_type : exp .DataType ) -> bool :
336+ if not self .support_coercing_compatible_types :
337+ return False
338+ if current_type in self .coerceable_types :
339+ is_coerceable = new_type in self .coerceable_types [current_type ]
340+ if is_coerceable :
341+ logger .warning (
342+ f"Coercing type { current_type } to { new_type } which means an alter will not be performed and therefore the resulting table structure will not match what is in the query.\n Update your model to cast the value to { current_type } type in order to remove this warning." ,
343+ )
344+ return is_coerceable
345+ return False
346+
313347 def _get_matching_kwarg (
314348 self ,
315349 current_kwarg : t .Union [str , exp .ColumnDef ],
@@ -438,7 +472,9 @@ def _alter_operation(
438472 new_array_type ,
439473 root_struct ,
440474 )
441- if self ._is_compatible_type (current_type , new_type ):
475+ if self ._is_coerceable_type (current_type , new_type ):
476+ return []
477+ elif self ._is_compatible_type (current_type , new_type ):
442478 struct .expressions .pop (pos )
443479 struct .expressions .insert (pos , new_kwarg )
444480 col_pos = (
@@ -535,7 +571,7 @@ def compare_structs(
535571
536572 def compare_columns (
537573 self ,
538- table_name : t . Union [ str , exp . Table ] ,
574+ table_name : TableName ,
539575 current : t .Dict [str , exp .DataType ],
540576 new : t .Dict [str , exp .DataType ],
541577 ) -> t .List [exp .AlterTable ]:
0 commit comments