Skip to content

Commit 70bedb4

Browse files
authored
Merge branch 'main' into chore/update-engine-list
2 parents 66cfdc4 + cf0b176 commit 70bedb4

7 files changed

Lines changed: 114 additions & 17 deletions

File tree

.github/workflows/pr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ jobs:
457457
strategy:
458458
fail-fast: false
459459
matrix:
460-
dbt-version: ['1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '1.10']
460+
dbt-version: ['1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '1.10', '1.11']
461461
steps:
462462
- uses: actions/checkout@v7
463463
- name: Set up Python

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ install-dev-dbt-%:
3333
echo "Installing dbt version: $$version"; \
3434
cp pyproject.toml pyproject.toml.backup; \
3535
$(SED_INPLACE) 's/"pydantic>=2.0.0"/"pydantic"/g' pyproject.toml; \
36-
if [ "$$version" = "1.10.0" ]; then \
37-
echo "Applying special handling for dbt 1.10.0"; \
36+
if [ "$$version" = "1.10.0" ] || [ "$$version" = "1.11.0" ]; then \
37+
echo "Applying special handling for dbt $$version"; \
3838
$(SED_INPLACE) -E 's/"(dbt-core)[^"]*"/"\1~='"$$version"'"/g' pyproject.toml; \
3939
$(SED_INPLACE) -E 's/"(dbt-(bigquery|duckdb|snowflake|athena-community|clickhouse|redshift|trino))[^"]*"/"\1"/g' pyproject.toml; \
4040
$(SED_INPLACE) -E 's/"(dbt-databricks)[^"]*"/"\1~='"$$version"'"/g' pyproject.toml; \

sqlmesh/core/model/meta.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -321,17 +321,19 @@ def _column_descriptions_validator(
321321
if isinstance(vs, (exp.Tuple, exp.Array)):
322322
vs = vs.expressions
323323

324-
raw_col_descriptions = (
325-
vs
324+
# Normalize each part while it is still an identifier, so that a quoted
325+
# column keeps its case on dialects where quoting makes it significant.
326+
col_descriptions = (
327+
{normalize_identifiers(k, dialect=dialect).name: v for k, v in vs.items()}
326328
if isinstance(vs, dict)
327-
else {".".join([part.this for part in v.this.parts]): v.expression.name for v in vs}
329+
else {
330+
".".join(
331+
normalize_identifiers(part, dialect=dialect).name for part in v.this.parts
332+
): v.expression.name
333+
for v in vs
334+
}
328335
)
329336

330-
col_descriptions = {
331-
normalize_identifiers(k, dialect=dialect).name: v
332-
for k, v in raw_col_descriptions.items()
333-
}
334-
335337
columns_to_types = data.get("columns_to_types_")
336338
if columns_to_types:
337339
from sqlmesh.core.console import get_console

tests/core/test_model.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,75 @@ def test_column_descriptions(sushi_context, assert_exp_eq):
10001000
assert model.column_descriptions == {"id": "primary key", "foo": "bar"}
10011001

10021002

1003+
def test_column_descriptions_quoted_identifier():
1004+
expressions = d.parse(
1005+
"""
1006+
MODEL (
1007+
name db.table,
1008+
kind FULL,
1009+
dialect snowflake,
1010+
column_descriptions (
1011+
"myColumn" = 'a case-sensitive column',
1012+
other_column = 'an unquoted column'
1013+
)
1014+
);
1015+
1016+
SELECT 1 AS "myColumn", 2 AS other_column
1017+
"""
1018+
)
1019+
model = load_sql_based_model(expressions, dialect="snowflake")
1020+
1021+
# A quoted key keeps its case, an unquoted one is still normalized.
1022+
assert model.column_descriptions == {
1023+
"myColumn": "a case-sensitive column",
1024+
"OTHER_COLUMN": "an unquoted column",
1025+
}
1026+
assert set(model.column_descriptions) <= set(model.columns_to_types)
1027+
1028+
1029+
def test_column_descriptions_dotted_identifier():
1030+
# A nested field is looked up by its dotted path, so every part normalizes on its own.
1031+
expressions = d.parse(
1032+
"""
1033+
MODEL (
1034+
name db.table,
1035+
kind FULL,
1036+
dialect bigquery,
1037+
column_descriptions (
1038+
record.`myField` = 'a nested field'
1039+
)
1040+
);
1041+
1042+
SELECT STRUCT(1 AS `myField`) AS record
1043+
"""
1044+
)
1045+
model = load_sql_based_model(expressions, dialect="bigquery")
1046+
1047+
assert model.column_descriptions == {"record.myfield": "a nested field"}
1048+
1049+
expressions = d.parse(
1050+
"""
1051+
MODEL (
1052+
name db.table,
1053+
kind FULL,
1054+
dialect snowflake,
1055+
column_descriptions (
1056+
nested.field = 'an unquoted path',
1057+
"MyStruct"."myField" = 'a quoted path'
1058+
)
1059+
);
1060+
1061+
SELECT 1 AS c
1062+
"""
1063+
)
1064+
model = load_sql_based_model(expressions, dialect="snowflake")
1065+
1066+
assert model.column_descriptions == {
1067+
"NESTED.FIELD": "an unquoted path",
1068+
"MyStruct.myField": "a quoted path",
1069+
}
1070+
1071+
10031072
def test_model_jinja_macro_reference_extraction():
10041073
@macro()
10051074
def test_macro(**kwargs) -> None:

tests/web/test_main.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import threading
4-
from pathlib import Path
4+
from pathlib import Path, PureWindowsPath
55

66
import pyarrow as pa # type: ignore
77
import pytest
@@ -75,6 +75,32 @@ def test_get_file(client: TestClient, project_tmp_path: Path) -> None:
7575
}
7676

7777

78+
def test_get_file_nested_path_matches_directory_listing(
79+
client: TestClient, project_tmp_path: Path
80+
) -> None:
81+
models_dir = project_tmp_path / "models"
82+
models_dir.mkdir()
83+
(models_dir / "mymodel.sql").write_text("SELECT 1")
84+
85+
response = client.get("/api/files/models/mymodel.sql")
86+
assert response.status_code == 200
87+
assert response.json()["path"] == "models/mymodel.sql"
88+
89+
90+
def test_get_file_relative_path_uses_posix_separators(tmp_path: Path) -> None:
91+
file_path = tmp_path / "models" / "mymodel.sql"
92+
file_path.parent.mkdir(parents=True)
93+
file_path.write_text("SELECT 1")
94+
95+
windows_relative = PureWindowsPath("models/mymodel.sql")
96+
assert windows_relative.as_posix() == "models/mymodel.sql"
97+
assert str(windows_relative) == "models\\mymodel.sql"
98+
99+
file = _get_file_with_content(file_path, windows_relative.as_posix())
100+
assert file.path == "models/mymodel.sql"
101+
assert file.path != str(windows_relative)
102+
103+
78104
def test_get_file_not_found(client: TestClient) -> None:
79105
response = client.get("/api/files/not_found.txt")
80106
assert response.status_code == 404

web/server/api/endpoints/files.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def get_file(
3737
"""Get a file, including its contents."""
3838
try:
3939
file_path = Path(path)
40-
file = _get_file_with_content(settings.project_path / file_path, str(file_path))
40+
file = _get_file_with_content(settings.project_path / file_path, file_path.as_posix())
4141
except FileNotFoundError:
4242
raise HTTPException(status_code=HTTP_404_NOT_FOUND)
4343

@@ -155,7 +155,7 @@ def walk_path(
155155
return sorted(directories, key=lambda x: x.name), sorted(files, key=lambda x: x.name)
156156

157157
directories, files = walk_path(path)
158-
relative_path = str(Path(path).relative_to(settings.project_path))
158+
relative_path = Path(path).relative_to(settings.project_path).as_posix()
159159

160160
return models.Directory(
161161
name=os.path.basename(path),

web/server/watcher.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async def watch_project() -> None:
5555
changes.append(
5656
models.ArtifactChange(
5757
change=Change.deleted,
58-
path=str(relative_path),
58+
path=relative_path.as_posix(),
5959
)
6060
)
6161
elif change == Change.added:
@@ -69,9 +69,9 @@ async def watch_project() -> None:
6969
models.ArtifactChange(
7070
type=models.ArtifactType.file,
7171
change=change,
72-
path=str(relative_path),
72+
path=relative_path.as_posix(),
7373
file=_get_file_with_content(
74-
settings.project_path / relative_path, str(relative_path)
74+
settings.project_path / relative_path, relative_path.as_posix()
7575
),
7676
)
7777
)

0 commit comments

Comments
 (0)