Skip to content

Commit dcc9f3b

Browse files
authored
Merge branch 'main' into fix/each-lambda-param-column-name-conflict
2 parents 2448057 + da2e5fc commit dcc9f3b

18 files changed

Lines changed: 284 additions & 66 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ dependencies = [
2424
"requests",
2525
"rich[jupyter]",
2626
"ruamel.yaml",
27-
"sqlglot~=30.2.1",
27+
"sqlglot~=30.4.2",
2828
"tenacity",
2929
"time-machine",
3030
"json-stream"

sqlmesh/core/config/connection.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
ValidationInfo,
3535
field_validator,
3636
model_validator,
37+
validation_data,
3738
validation_error_message,
3839
get_concrete_types_from_typehint,
3940
)
@@ -1081,7 +1082,7 @@ def validate_execution_project(
10811082
v: t.Optional[str],
10821083
info: ValidationInfo,
10831084
) -> t.Optional[str]:
1084-
if v and not info.data.get("project"):
1085+
if v and not validation_data(info).get("project"):
10851086
raise ConfigError(
10861087
"If the `execution_project` field is specified, you must also specify the `project` field to provide a default object location."
10871088
)
@@ -1093,7 +1094,7 @@ def validate_quota_project(
10931094
v: t.Optional[str],
10941095
info: ValidationInfo,
10951096
) -> t.Optional[str]:
1096-
if v and not info.data.get("project"):
1097+
if v and not validation_data(info).get("project"):
10971098
raise ConfigError(
10981099
"If the `quota_project` field is specified, you must also specify the `project` field to provide a default object location."
10991100
)

sqlmesh/core/context.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,9 +1605,11 @@ def plan_builder(
16051605
backfill_models = None
16061606

16071607
models_override: t.Optional[UniqueKeyDict[str, Model]] = None
1608+
selected_fqns: t.Set[str] = set()
1609+
selected_deletion_fqns: t.Set[str] = set()
16081610
if select_models:
16091611
try:
1610-
models_override = model_selector.select_models(
1612+
models_override, selected_fqns = model_selector.select_models(
16111613
select_models,
16121614
environment,
16131615
fallback_env_name=create_from or c.PROD,
@@ -1622,12 +1624,17 @@ def plan_builder(
16221624
# Only backfill selected models unless explicitly specified.
16231625
backfill_models = model_selector.expand_model_selections(select_models)
16241626

1627+
if not backfill_models:
1628+
# The selection matched nothing locally. Check whether it matched models
1629+
# in the deployed environment that were deleted locally.
1630+
selected_deletion_fqns = selected_fqns - set(self._models)
1631+
16251632
expanded_restate_models = None
16261633
if restate_models is not None:
16271634
expanded_restate_models = model_selector.expand_model_selections(restate_models)
16281635

16291636
if (restate_models is not None and not expanded_restate_models) or (
1630-
backfill_models is not None and not backfill_models
1637+
backfill_models is not None and not backfill_models and not selected_deletion_fqns
16311638
):
16321639
raise PlanError(
16331640
"Selector did not return any models. Please check your model selection and try again."
@@ -1636,7 +1643,7 @@ def plan_builder(
16361643
if always_include_local_changes is None:
16371644
# default behaviour - if restatements are detected; we operate entirely out of state and ignore local changes
16381645
force_no_diff = restate_models is not None or (
1639-
backfill_models is not None and not backfill_models
1646+
backfill_models is not None and not backfill_models and not selected_deletion_fqns
16401647
)
16411648
else:
16421649
force_no_diff = not always_include_local_changes

sqlmesh/core/engine_adapter/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2920,7 +2920,11 @@ def _replace_by_key(
29202920
target_columns_to_types = self.columns(target_table)
29212921

29222922
temp_table = self._get_temp_table(target_table)
2923-
key_exp = exp.func("CONCAT_WS", "'__SQLMESH_DELIM__'", *key) if len(key) > 1 else key[0]
2923+
key_exp = (
2924+
exp.func("CONCAT_WS", "'__SQLMESH_DELIM__'", *key, dialect=self.dialect)
2925+
if len(key) > 1
2926+
else key[0]
2927+
)
29242928
column_names = list(target_columns_to_types or [])
29252929

29262930
with self.transaction():

sqlmesh/core/engine_adapter/clickhouse.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,11 @@ def _replace_by_key(
423423
source_columns=source_columns,
424424
)
425425

426-
key_exp = exp.func("CONCAT_WS", "'__SQLMESH_DELIM__'", *key) if len(key) > 1 else key[0]
426+
key_exp = (
427+
exp.func("CONCAT_WS", "'__SQLMESH_DELIM__'", *key, dialect=self.dialect)
428+
if len(key) > 1
429+
else key[0]
430+
)
427431

428432
self._insert_overwrite_by_condition(
429433
target_table,

sqlmesh/core/environment.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ def _sanitize_name(cls, v: str) -> str:
5656
@classmethod
5757
def _validate_boolean_field(cls, v: t.Any, info: ValidationInfo) -> bool:
5858
if v is None:
59-
return info.field_name == "normalize_name"
59+
# Pydantic 2.13+ sets field_name to None during model_validate_json()
60+
return (info.field_name or "") == "normalize_name"
6061
return bool(v)
6162

6263
@t.overload

sqlmesh/core/metric/definition.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from sqlmesh.core.node import str_or_exp_to_str
1111
from sqlmesh.utils import UniqueKeyDict
1212
from sqlmesh.utils.errors import ConfigError
13-
from sqlmesh.utils.pydantic import PydanticModel, ValidationInfo, field_validator
13+
from sqlmesh.utils.pydantic import PydanticModel, ValidationInfo, field_validator, validation_data
1414

1515
MeasureAndDimTables = t.Tuple[str, t.Tuple[str, ...]]
1616

@@ -89,7 +89,7 @@ def _string_validator(cls, v: t.Any) -> t.Optional[str]:
8989
@field_validator("expression", mode="before")
9090
def _validate_expression(cls, v: t.Any, info: ValidationInfo) -> exp.Expr:
9191
if isinstance(v, str):
92-
dialect = info.data.get("dialect")
92+
dialect = validation_data(info).get("dialect")
9393
return d.parse_one(v, dialect=dialect)
9494
if isinstance(v, exp.Expr):
9595
return v

sqlmesh/core/model/common.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@
2121
prepare_env,
2222
serialize_env,
2323
)
24-
from sqlmesh.utils.pydantic import PydanticModel, ValidationInfo, field_validator, get_dialect
24+
from sqlmesh.utils.pydantic import (
25+
PydanticModel,
26+
ValidationInfo,
27+
field_validator,
28+
get_dialect,
29+
validation_data,
30+
)
2531

2632
if t.TYPE_CHECKING:
2733
from sqlglot.dialects.dialect import DialectType
@@ -479,7 +485,7 @@ def parse_expression(
479485
if callable(v):
480486
return v
481487

482-
dialect = info.data.get("dialect") if info else ""
488+
dialect = validation_data(info).get("dialect") if info else ""
483489

484490
if isinstance(v, list):
485491
return [
@@ -519,7 +525,7 @@ def parse_properties(
519525
if v is None:
520526
return v
521527

522-
dialect = info.data.get("dialect") if info else ""
528+
dialect = validation_data(info).get("dialect") if info else ""
523529

524530
if isinstance(v, str):
525531
v = d.parse_one(v, dialect=dialect)
@@ -557,8 +563,9 @@ def default_catalog(cls: t.Type, v: t.Any) -> t.Optional[str]:
557563

558564

559565
def depends_on(cls: t.Type, v: t.Any, info: ValidationInfo) -> t.Optional[t.Set[str]]:
560-
dialect = info.data.get("dialect")
561-
default_catalog = info.data.get("default_catalog")
566+
data = validation_data(info)
567+
dialect = data.get("dialect")
568+
default_catalog = data.get("default_catalog")
562569

563570
if isinstance(v, exp.Paren):
564571
v = v.unnest()

sqlmesh/core/model/kind.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ def data_hash_values(self) -> t.List[t.Optional[str]]:
786786
gen(self.valid_to_name),
787787
str(self.invalidate_hard_deletes),
788788
self.time_data_type.sql(self.dialect),
789-
gen(self.batch_size) if self.batch_size is not None else None,
789+
str(self.batch_size) if self.batch_size is not None else None,
790790
]
791791

792792
@property

sqlmesh/core/model/meta.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
list_of_fields_validator,
4545
model_validator,
4646
get_dialect,
47+
validation_data,
4748
)
4849

4950
if t.TYPE_CHECKING:
@@ -135,7 +136,7 @@ def _func_call_validator(cls, v: t.Any, field: t.Any) -> t.Any:
135136

136137
@field_validator("tags", mode="before")
137138
def _value_or_tuple_validator(cls, v: t.Any, info: ValidationInfo) -> t.Any:
138-
return ensure_list(cls._validate_value_or_tuple(v, info.data))
139+
return ensure_list(cls._validate_value_or_tuple(v, validation_data(info)))
139140

140141
@classmethod
141142
def _validate_value_or_tuple(
@@ -164,7 +165,7 @@ def _normalize(value: t.Any) -> t.Any:
164165
@field_validator("table_format", "storage_format", mode="before")
165166
def _format_validator(cls, v: t.Any, info: ValidationInfo) -> t.Optional[str]:
166167
if isinstance(v, exp.Expr) and not (isinstance(v, (exp.Literal, exp.Identifier))):
167-
return v.sql(info.data.get("dialect"))
168+
return v.sql(validation_data(info).get("dialect"))
168169
return str_or_exp_to_str(v)
169170

170171
@field_validator("dialect", mode="before")
@@ -192,7 +193,7 @@ def _partition_and_cluster_validator(cls, v: t.Any, info: ValidationInfo) -> t.L
192193
if (
193194
isinstance(v, list)
194195
and all(isinstance(i, str) for i in v)
195-
and info.field_name == "partitioned_by_"
196+
and (info.field_name or "") == "partitioned_by_"
196197
):
197198
# this branch gets hit when we are deserializing from json because `partitioned_by` is stored as a List[str]
198199
# however, we should only invoke this if the list contains strings because this validator is also
@@ -205,7 +206,7 @@ def _partition_and_cluster_validator(cls, v: t.Any, info: ValidationInfo) -> t.L
205206
)
206207
v = parsed.this.expressions if isinstance(parsed.this, exp.Schema) else v
207208

208-
expressions = list_of_fields_validator(v, info.data)
209+
expressions = list_of_fields_validator(v, validation_data(info))
209210

210211
for expression in expressions:
211212
num_cols = len(list(expression.find_all(exp.Column)))
@@ -228,7 +229,7 @@ def _columns_validator(
228229
cls, v: t.Any, info: ValidationInfo
229230
) -> t.Optional[t.Dict[str, exp.DataType]]:
230231
columns_to_types = {}
231-
dialect = info.data.get("dialect")
232+
dialect = validation_data(info).get("dialect")
232233

233234
if isinstance(v, exp.Schema):
234235
for column in v.expressions:
@@ -280,7 +281,8 @@ def _columns_validator(
280281
def _column_descriptions_validator(
281282
cls, vs: t.Any, info: ValidationInfo
282283
) -> t.Optional[t.Dict[str, str]]:
283-
dialect = info.data.get("dialect")
284+
data = validation_data(info)
285+
dialect = data.get("dialect")
284286

285287
if vs is None:
286288
return None
@@ -302,23 +304,23 @@ def _column_descriptions_validator(
302304
for k, v in raw_col_descriptions.items()
303305
}
304306

305-
columns_to_types = info.data.get("columns_to_types_")
307+
columns_to_types = data.get("columns_to_types_")
306308
if columns_to_types:
307309
from sqlmesh.core.console import get_console
308310

309311
console = get_console()
310312
for column_name in list(col_descriptions):
311313
if column_name not in columns_to_types:
312314
console.log_warning(
313-
f"In model '{info.data['name']}', a description is provided for column '{column_name}' but it is not a column in the model."
315+
f"In model '{data.get('name', '<unknown>')}', a description is provided for column '{column_name}' but it is not a column in the model."
314316
)
315317
del col_descriptions[column_name]
316318

317319
return col_descriptions
318320

319321
@field_validator("grains", "references", mode="before")
320322
def _refs_validator(cls, vs: t.Any, info: ValidationInfo) -> t.List[exp.Expr]:
321-
dialect = info.data.get("dialect")
323+
dialect = validation_data(info).get("dialect")
322324

323325
if isinstance(vs, exp.Paren):
324326
vs = vs.unnest()

0 commit comments

Comments
 (0)