Skip to content

Commit 002f846

Browse files
authored
Fix: Support coercion of BIGNUMERIC values into FLOAT64 for BigQuery (#3417)
1 parent ba33416 commit 002f846

File tree

4 files changed

+68
-5
lines changed

4 files changed

+68
-5
lines changed

sqlmesh/core/engine_adapter/bigquery.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ class BigQueryEngineAdapter(InsertOverwriteWithMergeMixin, ClusteredByMixin, Row
7878
exp.DataType.build("DATETIME", dialect=DIALECT),
7979
},
8080
},
81+
coerceable_types={
82+
exp.DataType.build("FLOAT64", dialect=DIALECT): {
83+
exp.DataType.build("BIGNUMERIC", dialect=DIALECT),
84+
},
85+
},
8186
support_coercing_compatible_types=True,
8287
parameterized_type_defaults={
8388
exp.DataType.build("DECIMAL", dialect=DIALECT).this: [(38, 9), (0,)],

sqlmesh/core/schema_diff.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
import logging
44
import typing as t
5-
from collections import defaultdict
65
from enum import Enum, auto
6+
from collections import defaultdict
7+
from pydantic import Field
78
from sqlglot import exp
89
from sqlglot.helper import ensure_list, seq_get
910

@@ -305,6 +306,11 @@ class SchemaDiffer(PydanticModel):
305306
columns of nested STRUCTs.
306307
compatible_types: Types that are compatible and automatically coerced in actions like UNION ALL. Dict key is data
307308
type, and value is the set of types that are compatible with it.
309+
coerceable_types: The mapping from a current type to all types that can be safely coerced to the current one without
310+
altering the column type. NOTE: usually callers should not specify this attribute manually and set the
311+
`support_coercing_compatible_types` flag instead. Some engines are inconsistent about their type coercion rules.
312+
For example, in BigQuery a BIGNUMERIC column can't be altered to be FLOAT64, while BIGNUMERIC values can be inserted
313+
into a FLOAT64 column just fine.
308314
support_coercing_compatible_types: Whether or not the engine for which the diff is being computed supports direct
309315
coercion of compatible types.
310316
parameterized_type_defaults: Default values for parameterized data types. Dict key is a sqlglot exp.DataType.Type,
@@ -326,6 +332,9 @@ class SchemaDiffer(PydanticModel):
326332
support_nested_drop: bool = False
327333
array_element_selector: str = ""
328334
compatible_types: t.Dict[exp.DataType, t.Set[exp.DataType]] = {}
335+
coerceable_types_: t.Dict[exp.DataType, t.Set[exp.DataType]] = Field(
336+
default_factory=dict, alias="coerceable_types"
337+
)
329338
support_coercing_compatible_types: bool = False
330339
parameterized_type_defaults: t.Dict[
331340
exp.DataType.Type, t.List[t.Tuple[t.Union[int, float], ...]]
@@ -339,8 +348,9 @@ class SchemaDiffer(PydanticModel):
339348
def coerceable_types(self) -> t.Dict[exp.DataType, t.Set[exp.DataType]]:
340349
if not self._coerceable_types:
341350
if not self.support_coercing_compatible_types or not self.compatible_types:
342-
return {}
343-
coerceable_types = defaultdict(set)
351+
return self.coerceable_types_
352+
coerceable_types: t.Dict[exp.DataType, t.Set[exp.DataType]] = defaultdict(set)
353+
coerceable_types.update(self.coerceable_types_)
344354
for source_type, target_types in self.compatible_types.items():
345355
for target_type in target_types:
346356
coerceable_types[target_type].add(source_type)
@@ -361,8 +371,6 @@ def _is_compatible_type(self, current_type: exp.DataType, new_type: exp.DataType
361371
return False
362372

363373
def _is_coerceable_type(self, current_type: exp.DataType, new_type: exp.DataType) -> bool:
364-
if not self.support_coercing_compatible_types:
365-
return False
366374
if current_type in self.coerceable_types:
367375
is_coerceable = new_type in self.coerceable_types[current_type]
368376
if is_coerceable:

tests/core/engine_adapter/test_base.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,26 @@ def test_comments(make_mocked_engine_adapter: t.Callable, mocker: MockerFixture)
563563
'ALTER TABLE "test_table" ADD COLUMN "array_col"."element"."array_b" INT AFTER "array_a"',
564564
],
565565
),
566+
(
567+
{
568+
"coerceable_types": {
569+
exp.DataType.build("FLOAT"): {exp.DataType.build("INT")},
570+
},
571+
},
572+
{
573+
"a": "FLOAT",
574+
"b": "TEXT",
575+
},
576+
{
577+
"a": "INT",
578+
"b": "TEXT",
579+
},
580+
{
581+
"a": "FLOAT",
582+
"b": "TEXT",
583+
},
584+
[],
585+
),
566586
(
567587
{
568588
"support_nested_operations": True,
@@ -865,6 +885,8 @@ def _from_structs(
865885
current_struct: exp.DataType, new_struct: exp.DataType
866886
) -> t.List[TableAlterOperation]:
867887
operations = original_from_structs(current_struct, new_struct)
888+
if not operations:
889+
return operations
868890
assert (
869891
operations[-1].expected_table_struct.sql()
870892
== columns_to_types_to_struct(expected_final_structure).sql()

tests/core/test_schema_diff.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,34 @@ def test_schema_diff_calculate_type_transitions():
11081108
},
11091109
),
11101110
),
1111+
(
1112+
"STRUCT<id INT, name STRING, revenue FLOAT>",
1113+
"STRUCT<id INT, name STRING, revenue INT>",
1114+
[],
1115+
dict(
1116+
support_positional_add=True,
1117+
support_nested_operations=True,
1118+
coerceable_types={
1119+
exp.DataType.build("FLOAT"): {exp.DataType.build("INT")},
1120+
},
1121+
),
1122+
),
1123+
(
1124+
"STRUCT<id INT, name STRING, revenue FLOAT>",
1125+
"STRUCT<id INT, name INT, revenue INT>",
1126+
[],
1127+
dict(
1128+
support_positional_add=True,
1129+
support_nested_operations=True,
1130+
support_coercing_compatible_types=True,
1131+
compatible_types={
1132+
exp.DataType.build("INT"): {exp.DataType.build("FLOAT")},
1133+
},
1134+
coerceable_types={
1135+
exp.DataType.build("STRING"): {exp.DataType.build("INT")},
1136+
},
1137+
),
1138+
),
11111139
# Coercion with an alter results in a single alter
11121140
(
11131141
"STRUCT<id INT, name STRING, revenue FLOAT, total INT>",

0 commit comments

Comments
 (0)