Skip to content

Commit 55b4acb

Browse files
Merge branch 'main' into fix_databricks_column_types
2 parents 9682bd5 + 4dac2b3 commit 55b4acb

18 files changed

Lines changed: 339 additions & 69 deletions

.github/workflows/release.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ jobs:
1212
- uses: actions/checkout@v5
1313
- uses: actions/setup-node@v6
1414
with:
15-
node-version: '20'
15+
node-version: '22'
1616
- uses: pnpm/action-setup@v4
1717
with:
1818
version: latest
1919
- name: Install dependencies
20-
run: pnpm install
20+
run: pnpm install --frozen-lockfile
2121
- name: Build UI
2222
run: pnpm --prefix web/client run build
2323
- name: Upload UI build artifact

.github/workflows/release_extension.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
- name: Setup Node.js
3131
uses: actions/setup-node@v6
3232
with:
33-
node-version: '20'
33+
node-version: '22'
3434
- name: Install pnpm
3535
uses: pnpm/action-setup@v4
3636
with:

.github/workflows/release_shared_js.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
- name: Setup Node.js
3434
uses: actions/setup-node@v6
3535
with:
36-
node-version: '20'
36+
node-version: '22'
3737
registry-url: 'https://registry.npmjs.org'
3838
- name: Update npm
3939
run: npm install -g npm@latest

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20
1+
22

docs/integrations/engines/duckdb.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ SQLMesh will place models with the explicit catalog "ephemeral", such as `epheme
7979
type: ducklake
8080
path: 'catalog.ducklake'
8181
data_path: data/ducklake
82+
override_data_path: true
8283
encrypted: True
8384
data_inlining_row_limit: 10
8485
metadata_schema: main
@@ -105,6 +106,7 @@ SQLMesh will place models with the explicit catalog "ephemeral", such as `epheme
105106
type="ducklake",
106107
path="catalog.ducklake",
107108
data_path="data/ducklake",
109+
override_data_path=False,
108110
encrypted=True,
109111
data_inlining_row_limit=10,
110112
metadata_schema="main",
@@ -120,6 +122,7 @@ SQLMesh will place models with the explicit catalog "ephemeral", such as `epheme
120122

121123
- `path`: Path to the DuckLake catalog file
122124
- `data_path`: Path where DuckLake data files are stored
125+
- `override_data_path`: Whether data_override_path option is set
123126
- `encrypted`: Whether to enable encryption for the catalog (default: `False`)
124127
- `data_inlining_row_limit`: Maximum number of rows to inline in the catalog (default: `0`)
125128
- `metadata_schema`: The schema in the catalog server in which to store the DuckLake metadata tables (default: `main`)
@@ -364,6 +367,7 @@ The `filesystems` accepts a list of file systems to register in the DuckDB conne
364367
type: ducklake
365368
path: myducklakecatalog.duckdb
366369
data_path: abfs://MyFabricWorkspace/MyFabricLakehouse.Lakehouse/Files/DuckLake.Files
370+
override_data_path: False
367371
extensions:
368372
- ducklake
369373
filesystems:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"engines": {
3-
"node": ">=20.0.0",
3+
"node": ">=22.0.0",
44
"pnpm": ">=10.0.0"
55
},
66
"scripts": {

pnpm-workspace.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@ packages:
44
- vscode/react
55
- web/client
66
- web/common
7+
allowBuilds:
8+
'@swc/core': true
9+
'@tailwindcss/oxide': true
10+
'@vscode/vsce-sign': true
11+
esbuild: true
12+
keytar: true

sqlmesh/cli/main.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,16 +631,22 @@ def invalidate(ctx: click.Context, environment: str, **kwargs: t.Any) -> None:
631631
is_flag=True,
632632
help="Cleanup snapshots that are not referenced in any environment, regardless of when they're set to expire",
633633
)
634+
@click.option(
635+
"--force-delete",
636+
is_flag=True,
637+
help="Delete expired environment and snapshot state records even when the physical table or view drops fail. "
638+
"Any objects that could not be dropped become orphaned and must be removed manually.",
639+
)
634640
@click.pass_context
635641
@error_handler
636642
@cli_analytics
637-
def janitor(ctx: click.Context, ignore_ttl: bool, **kwargs: t.Any) -> None:
643+
def janitor(ctx: click.Context, ignore_ttl: bool, force_delete: bool, **kwargs: t.Any) -> None:
638644
"""
639645
Run the janitor process on-demand.
640646
641647
The janitor cleans up old environments and expired snapshots.
642648
"""
643-
ctx.obj.run_janitor(ignore_ttl, **kwargs)
649+
ctx.obj.run_janitor(ignore_ttl, force_delete=force_delete, **kwargs)
644650

645651

646652
@cli.command("destroy")

sqlmesh/core/config/connection.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ class DuckDBAttachOptions(BaseConfig):
238238

239239
# DuckLake specific options
240240
data_path: t.Optional[str] = None
241+
override_data_path: t.Optional[bool] = False
241242
encrypted: bool = False
242243
data_inlining_row_limit: t.Optional[int] = None
243244
metadata_schema: t.Optional[str] = None
@@ -258,6 +259,8 @@ def to_sql(self, alias: str) -> str:
258259
path = f"ducklake:{path}"
259260
if self.data_path is not None:
260261
options.append(f"DATA_PATH '{self.data_path}'")
262+
if self.override_data_path:
263+
options.append("OVERRIDE_DATA_PATH true")
261264
if self.encrypted:
262265
options.append("ENCRYPTED")
263266
if self.data_inlining_row_limit is not None:
@@ -2097,6 +2100,7 @@ class ClickhouseConnectionConfig(ConnectionConfig):
20972100
https_proxy: t.Optional[str] = None
20982101
server_host_name: t.Optional[str] = None
20992102
tls_mode: t.Optional[str] = None
2103+
secure: bool = False
21002104

21012105
concurrent_tasks: int = 1
21022106
register_comments: bool = True
@@ -2133,6 +2137,7 @@ def _connection_kwargs_keys(self) -> t.Set[str]:
21332137
"https_proxy",
21342138
"server_host_name",
21352139
"tls_mode",
2140+
"secure",
21362141
}
21372142
return kwargs
21382143

sqlmesh/core/context.py

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -887,12 +887,12 @@ def _has_environment_changed() -> bool:
887887
return completion_status
888888

889889
@python_api_analytics
890-
def run_janitor(self, ignore_ttl: bool) -> bool:
890+
def run_janitor(self, ignore_ttl: bool, force_delete: bool = False) -> bool:
891891
success = False
892892

893893
if self.console.start_cleanup(ignore_ttl):
894894
try:
895-
self._run_janitor(ignore_ttl)
895+
self._run_janitor(ignore_ttl, force_delete=force_delete)
896896
success = True
897897
finally:
898898
self.console.stop_cleanup(success=success)
@@ -2896,24 +2896,43 @@ def _destroy(self) -> bool:
28962896

28972897
return True
28982898

2899-
def _run_janitor(self, ignore_ttl: bool = False) -> None:
2899+
def _run_janitor(self, ignore_ttl: bool = False, force_delete: bool = False) -> None:
29002900
current_ts = now_timestamp()
2901+
failures: t.List[str] = []
29012902

29022903
# Clean up expired environments by removing their views and schemas
2903-
self._cleanup_environments(current_ts=current_ts)
2904+
failures.extend(
2905+
self._cleanup_environments(current_ts=current_ts, force_delete=force_delete)
2906+
)
29042907

2905-
delete_expired_snapshots(
2906-
self.state_sync,
2907-
self.snapshot_evaluator,
2908-
current_ts=current_ts,
2909-
ignore_ttl=ignore_ttl,
2910-
console=self.console,
2911-
batch_size=self.config.janitor.expired_snapshots_batch_size,
2908+
failures.extend(
2909+
delete_expired_snapshots(
2910+
self.state_sync,
2911+
self.snapshot_evaluator,
2912+
current_ts=current_ts,
2913+
ignore_ttl=ignore_ttl,
2914+
force_delete=force_delete,
2915+
console=self.console,
2916+
batch_size=self.config.janitor.expired_snapshots_batch_size,
2917+
)
29122918
)
29132919
self.state_sync.compact_intervals()
29142920

2915-
def _cleanup_environments(self, current_ts: t.Optional[int] = None) -> None:
2921+
if failures:
2922+
failure_string = "\n - ".join(failures)
2923+
summary = f"Janitor completed with failures:\n {failure_string}"
2924+
if force_delete:
2925+
summary += "\nState records have been deleted, but the underlying objects may still exist in the database.\nPlease investigate and clean up manually the above if necessary."
2926+
if self.config.janitor.warn_on_delete_failure:
2927+
self.console.log_warning(summary)
2928+
else:
2929+
raise SQLMeshError(summary)
2930+
2931+
def _cleanup_environments(
2932+
self, current_ts: t.Optional[int] = None, force_delete: bool = False
2933+
) -> t.List[str]:
29162934
current_ts = current_ts or now_timestamp()
2935+
failures: t.List[str] = []
29172936

29182937
expired_environments_summaries = self.state_sync.get_expired_environments(
29192938
current_ts=current_ts
@@ -2923,15 +2942,20 @@ def _cleanup_environments(self, current_ts: t.Optional[int] = None) -> None:
29232942
expired_env = self.state_reader.get_environment(expired_env_summary.name)
29242943

29252944
if expired_env:
2926-
cleanup_expired_views(
2927-
default_adapter=self.engine_adapter,
2928-
engine_adapters=self.engine_adapters,
2929-
environments=[expired_env],
2930-
warn_on_delete_failure=self.config.janitor.warn_on_delete_failure,
2931-
console=self.console,
2945+
failures.extend(
2946+
cleanup_expired_views(
2947+
default_adapter=self.engine_adapter,
2948+
engine_adapters=self.engine_adapters,
2949+
environments=[expired_env],
2950+
console=self.console,
2951+
)
29322952
)
29332953

2934-
self.state_sync.delete_expired_environments(current_ts=current_ts)
2954+
# we want to retry on the next janitor pass if drops failed, unless
2955+
# force_delete is set in which case we purge state records regardless
2956+
if not failures or force_delete:
2957+
self.state_sync.delete_expired_environments(current_ts=current_ts)
2958+
return failures
29352959

29362960
def _try_connection(self, connection_name: str, validator: t.Callable[[], None]) -> None:
29372961
connection_name = connection_name.capitalize()

0 commit comments

Comments
 (0)