Skip to content

Commit df48d71

Browse files
committed
Merge remote-tracking branch 'origin/main' into copilot/fix-mlv-schema-mismatch-216
2 parents 93ae1f6 + 27c99ff commit df48d71

6 files changed

Lines changed: 240 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## v1.12.5
4+
5+
### Bug Fixes
6+
7+
- Fix `UnexpectedJinjaBlockDeprecation` (D023) emitted by `create_table_as.sql`: a stray `{% do %}` tag inside a C-style `/* … */` comment was being parsed as a top-level Jinja block. Switched the wrapper to a Jinja comment `{# … #}`. Added a unit-test regression that scans every macro file for `unexpected_block` warnings. ([#46](https://github.com/microsoft/dbt-fabricspark/issues/46))
8+
- Pin `dbt-core<2.0` in `tools/scripts/run-local-e2e.sh` so the local end-to-end harness keeps resolving a `fabricspark`-aware dbt-core (avoids the unrelated `2.0.0a1` alpha).
9+
- Fix latent typo in `alter_column_set_constraints` dispatcher macro (`create_table_as.sql`): the body was missing its `{{ … }}` wrap, so the macro emitted its own source as text instead of dispatching. Added unit guards: (a) the dispatcher now renders via `adapter.dispatch` correctly, and (b) `FabricSparkRelation` cannot silently re-introduce the legacy `Cannot set database in spark!` runtime check that v1.9.3 removed to enable schema-enabled lakehouses and cross-lakehouse writes. ([#46](https://github.com/microsoft/dbt-fabricspark/issues/46))
10+
311
## v1.12.4
412

513
### Bug Fixes

contrib/bootstrap-dev-env.ps1

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,61 @@ foreach ($distro in $distros) {
4343
wsl --unregister $distro
4444
}
4545

46+
$RECOMMENDED_CORES = 16
47+
$RECOMMENDED_FREE_GB = 128
48+
4649
$memGB=[math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory/1GB)
4750
$cpu=[Environment]::ProcessorCount
4851
$swap=[math]::Floor($memGB/4)
4952

53+
if ($cpu -lt $RECOMMENDED_CORES) {
54+
Write-Host "WARNING: This machine has $cpu cores, which is below the recommended $RECOMMENDED_CORES cores." -ForegroundColor DarkYellow
55+
$requiredResponse = "I am OK with having a subpar development experience"
56+
do {
57+
$response = Read-Host "Please type '$requiredResponse' **EXACTLY** as is to continue"
58+
} while ($response -ne $requiredResponse)
59+
} else {
60+
Write-Host "(detected $cpu cores, ${memGB}GB RAM)" -ForegroundColor Green
61+
}
62+
63+
$driveOptions = @(Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
64+
Sort-Object -Property FreeSpace -Descending |
65+
ForEach-Object {
66+
[PSCustomObject]@{
67+
Path = "$($_.DeviceID)\VHD"
68+
FreeGB = [math]::Floor($_.FreeSpace / 1GB)
69+
}
70+
})
71+
72+
if ($driveOptions.Count -eq 0) {
73+
Write-Error "No local fixed drives found to host the WSL VHD."
74+
exit 1
75+
}
76+
77+
Write-Host ""
78+
Write-Host "Choose a drive to store the WSL VHD (the repo recommends at least ${RECOMMENDED_FREE_GB} GB free):" -ForegroundColor Cyan
79+
for ($i = 0; $i -lt $driveOptions.Count; $i++) {
80+
$opt = $driveOptions[$i]
81+
Write-Host (" [{0}] {1} ({2} GB free)" -f ($i + 1), $opt.Path, $opt.FreeGB)
82+
}
83+
84+
do {
85+
$pick = Read-Host "Pick a drive number [1-$($driveOptions.Count)]"
86+
} while (-not ($pick -as [int]) -or [int]$pick -lt 1 -or [int]$pick -gt $driveOptions.Count)
87+
88+
$wslLocation = $driveOptions[[int]$pick - 1]
89+
90+
if ($wslLocation.FreeGB -lt $RECOMMENDED_FREE_GB) {
91+
Write-Host "WARNING: $($wslLocation.Path) has only $($wslLocation.FreeGB) GB free, which is below the recommended ${RECOMMENDED_FREE_GB} GB." -ForegroundColor DarkYellow
92+
$requiredResponse = "I am OK with having my WSL crash when my hard disk fills up"
93+
do {
94+
$response = Read-Host "Please type '$requiredResponse' **EXACTLY** as is to continue"
95+
} while ($response -ne $requiredResponse)
96+
} else {
97+
Write-Host "Using $($wslLocation.Path) for the WSL VHD." -ForegroundColor Green
98+
}
99+
100+
New-Item -ItemType Directory -Path $wslLocation.Path -Force | Out-Null
50101
@"
51102
[wsl2]
52103
memory=${memGB}GB
@@ -60,5 +111,28 @@ wsl --shutdown
60111

61112
winget install -e --id Microsoft.GitCredentialManagerCore
62113

63-
Write-Host "Installing Ubuntu"
64-
wsl --install -d Ubuntu-24.04
114+
Write-Host "Updating WSL (the --location flag requires WSL 2.4.4+)"
115+
wsl --update
116+
117+
$minWslVersion = [version]'2.4.4'
118+
$prevEncoding = [Console]::OutputEncoding
119+
try {
120+
[Console]::OutputEncoding = [System.Text.Encoding]::Unicode
121+
$wslVersionRaw = (wsl --version) 2>$null
122+
} finally {
123+
[Console]::OutputEncoding = $prevEncoding
124+
}
125+
$versionMatch = [regex]::Match(($wslVersionRaw -join "`n"), '(\d+)\.(\d+)\.(\d+)')
126+
if (-not $versionMatch.Success) {
127+
Write-Error "Could not determine WSL version from 'wsl --version'. Run 'wsl --update' manually and re-run this script."
128+
exit 1
129+
}
130+
$wslVersion = [version]("$($versionMatch.Groups[1].Value).$($versionMatch.Groups[2].Value).$($versionMatch.Groups[3].Value)")
131+
if ($wslVersion -lt $minWslVersion) {
132+
Write-Error "Detected WSL $wslVersion, but $minWslVersion is required for 'wsl --install --location'. Run 'wsl --update' manually and re-run this script."
133+
exit 1
134+
}
135+
Write-Host "WSL version $wslVersion detected." -ForegroundColor Green
136+
137+
Write-Host "Installing Ubuntu to $($wslLocation.Path)"
138+
wsl --install -d Ubuntu-24.04 --location $wslLocation.Path

src/dbt/include/fabricspark/macros/materializations/models/table/create_table_as.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,10 @@
165165
{{ return(adapter.dispatch('persist_constraints', 'dbt')(relation, model)) }}
166166
{% endmacro %}
167167

168-
/* TODO: alter table {{ relation }} change column {{ quoted_name }} set not null;
168+
{# TODO: alter table {{ relation }} change column {{ quoted_name }} set not null;
169169
is not supported in Fabric Runtime 1.2/Spark 3.4.1
170170
{% do alter_column_set_constraints(relation, model.columns) %}
171-
*/
171+
#}
172172
{% macro fabricspark__persist_constraints(relation, model) %}
173173
{%- set contract_config = config.get('contract') -%}
174174
{% if contract_config.enforced and config.get('file_format', 'delta') == 'delta' %}
@@ -194,7 +194,7 @@
194194

195195

196196
{% macro alter_column_set_constraints(relation, column_dict) %}
197-
return(adapter.dispatch('alter_column_set_constraints', 'dbt')(relation, column_dict))
197+
{{ return(adapter.dispatch('alter_column_set_constraints', 'dbt')(relation, column_dict)) }}
198198
{% endmacro %}
199199

200200
{% macro fabricspark__alter_column_set_constraints(relation, column_dict) %}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from unittest import mock
5+
6+
import pytest
7+
from jinja2 import Environment, FileSystemLoader
8+
9+
MACROS_DIR = (
10+
Path(__file__).resolve().parents[2]
11+
/ "src"
12+
/ "dbt"
13+
/ "include"
14+
/ "fabricspark"
15+
/ "macros"
16+
/ "materializations"
17+
/ "models"
18+
/ "table"
19+
)
20+
21+
22+
@pytest.fixture
23+
def env() -> Environment:
24+
return Environment(
25+
loader=FileSystemLoader(str(MACROS_DIR)),
26+
extensions=["jinja2.ext.do"],
27+
)
28+
29+
30+
def _dispatch_to_recorder(recorded: list[tuple]):
31+
def _dispatch(macro_name, macro_namespace=None, packages=None):
32+
def _impl(*args, **kwargs):
33+
recorded.append((macro_name, args, kwargs))
34+
return ""
35+
36+
return _impl
37+
38+
return _dispatch
39+
40+
41+
def test_alter_column_set_constraints_dispatches(env: Environment) -> None:
42+
template = env.get_template(
43+
"create_table_as.sql",
44+
globals={
45+
"validation": mock.Mock(),
46+
"model": mock.Mock(),
47+
"exceptions": mock.Mock(),
48+
"config": mock.Mock(),
49+
"return": lambda r: r,
50+
},
51+
)
52+
recorded: list[tuple] = []
53+
adapter = mock.Mock()
54+
adapter.dispatch = _dispatch_to_recorder(recorded)
55+
56+
template.globals["adapter"] = adapter
57+
58+
rendered = template.module.alter_column_set_constraints("my_relation", {"col": {}})
59+
60+
assert "return(adapter.dispatch" not in str(rendered), (
61+
"alter_column_set_constraints macro is emitting its own source as text — "
62+
"the dispatcher is missing its `{{ … }}` wrap. Got:\n" + str(rendered)
63+
)
64+
assert recorded == [
65+
("alter_column_set_constraints", ("my_relation", {"col": {}}), {}),
66+
], (
67+
"Expected the dispatcher to invoke adapter.dispatch exactly once "
68+
"with the original args. Got: " + repr(recorded)
69+
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
5+
import pytest
6+
from dbt_common.clients._jinja_blocks import (
7+
BlockIterator,
8+
ExtractWarning,
9+
TagIterator,
10+
)
11+
12+
REPO_ROOT = Path(__file__).resolve().parents[2]
13+
MACROS_DIR = REPO_ROOT / "src" / "dbt" / "include" / "fabricspark" / "macros"
14+
ALLOWED_BLOCKS = {"macro", "materialization", "test", "data_test"}
15+
16+
17+
def _macro_files() -> list[Path]:
18+
return sorted(MACROS_DIR.rglob("*.sql"))
19+
20+
21+
@pytest.mark.parametrize(
22+
"macro_path",
23+
_macro_files(),
24+
ids=lambda p: str(p.relative_to(REPO_ROOT)),
25+
)
26+
def test_macro_file_has_no_unexpected_block_warnings(macro_path: Path) -> None:
27+
warnings: list[ExtractWarning] = []
28+
BlockIterator(
29+
TagIterator(macro_path.read_text()),
30+
warning_callback=warnings.append,
31+
).lex_for_blocks(allowed_blocks=ALLOWED_BLOCKS, collect_raw_data=False)
32+
33+
formatted = "\n".join(f" {w.warning_type}: {w.msg}" for w in warnings)
34+
assert not warnings, (
35+
f"{macro_path.relative_to(REPO_ROOT)} emitted unexpected-block warnings "
36+
f"(would trigger UnexpectedJinjaBlockDeprecation in dbt-core):\n{formatted}"
37+
)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from __future__ import annotations
2+
3+
import inspect
4+
5+
from dbt.adapters.contracts.relation import RelationType
6+
from dbt.adapters.fabricspark.relation import FabricSparkRelation
7+
8+
_FORBIDDEN_LITERAL = "Cannot set database in spark!"
9+
10+
11+
def test_relation_with_distinct_database_and_schema_does_not_raise() -> None:
12+
rel = FabricSparkRelation.create(
13+
database="lakehouse_a",
14+
schema="dbo",
15+
identifier="my_table",
16+
type=RelationType.Table,
17+
)
18+
19+
assert rel.database == "lakehouse_a"
20+
assert rel.schema == "dbo"
21+
assert rel.identifier == "my_table"
22+
assert rel.matches(database="lakehouse_a", schema="dbo", identifier="my_table")
23+
24+
25+
def test_relation_with_distinct_database_and_schema_schemas_enabled_does_not_raise() -> None:
26+
FabricSparkRelation._schemas_enabled = True
27+
try:
28+
rel = FabricSparkRelation.create(
29+
database="lakehouse_a",
30+
schema="dbo",
31+
identifier="my_table",
32+
type=RelationType.Table,
33+
)
34+
assert str(rel) == "`lakehouse_a`.`dbo`.my_table"
35+
finally:
36+
FabricSparkRelation._schemas_enabled = False
37+
38+
39+
def test_relation_source_does_not_contain_legacy_database_check_message() -> None:
40+
src = inspect.getsource(FabricSparkRelation)
41+
assert _FORBIDDEN_LITERAL not in src, (
42+
f"FabricSparkRelation source contains the legacy "
43+
f"{_FORBIDDEN_LITERAL!r} message. This guard was removed in v1.9.3 "
44+
"(commit 2dba5bc) to support schema-enabled lakehouses and "
45+
"cross-lakehouse / cross-workspace writes — it must not be "
46+
"re-introduced."
47+
)

0 commit comments

Comments
 (0)