Skip to content

Janitor cannot delete snapshots on DuckLake: DROP ... CASCADE is unsupported, and fails silently until a snapshot expires #6032

Description

@aydingeeringh

Summary

sqlmesh janitor can never reclaim space on a DuckLake catalog. SnapshotEvaluator forces
cascade=True when deleting a snapshot's tables, and DuckLake does not implement cascade drops:

Not implemented Error: Cascade Drop not supported in DuckLake

The cause is a granularity mismatch: cascade support is declared per engine adapter, but on
DuckDB it varies per catalog. A plain DuckDB catalog supports DROP TABLE ... CASCADE; a
DuckLake catalog attached in the same connection does not.

This fails quietly. sqlmesh janitor returns Cleanup complete. having deleted nothing while
no snapshot has yet reached its TTL, so a DuckLake project looks healthy and then silently stops
reclaiming space once snapshots start expiring. Only --ignore-ttl surfaces it immediately.

Reproduction

import duckdb
con = duckdb.connect(); con.execute("INSTALL ducklake; LOAD ducklake")
con.execute("ATTACH 'ducklake:t.ducklake' AS lk (DATA_PATH 'storage/')")
con.execute("CREATE SCHEMA lk.phys; CREATE SCHEMA lk.virt")
con.execute("CREATE TABLE lk.phys.t (i INTEGER); INSERT INTO lk.phys.t VALUES (1)")
con.execute("CREATE VIEW lk.virt.v AS SELECT * FROM lk.phys.t")   # as the virtual layer builds

con.execute("DROP TABLE lk.phys.t CASCADE")   # FAIL — Cascade Drop not supported in DuckLake
con.execute("DROP TABLE lk.phys.t")           # OK   — the dependent view does NOT block it
con.execute("DROP SCHEMA lk.virt CASCADE")    # OK   — schema cascade is supported

In a real project, sqlmesh janitor --ignore-ttl gives:

Cleanup failed!
Error: Execution failed for node SnapshotId<"unlisted"."du"."prefix_map": 259425956>
  caused by: _duckdb.NotImplementedException:
  Not implemented Error: Cascade Drop not supported in DuckLake

Root cause

sqlmesh/core/snapshot/evaluator.py:1366 (0.236.1):

evaluation_strategy.delete(
    table_name,
    ...
    # we need to set cascade=true or we will get a 'cant drop because other objects depend on it'-style
    # error on engines that enforce referential integrity, such as Postgres
    cascade=True,
)

sqlmesh/core/engine_adapter/duckdb.py:39:

SUPPORTED_DROP_CASCADE_OBJECT_KINDS = ["SCHEMA", "TABLE", "VIEW"]

This looks like collateral from #4767 ("Janitor fails to delete object with dependencies in
Postgres"), which is a genuine Postgres problem — but DuckDB does not enforce referential
integrity
, so TABLE/VIEW cascade was never needed on this engine, and declaring it breaks
DuckLake catalogs. The reproduction above shows the dependent view does not block a plain
DROP TABLE.

Suggested fix

Make the capability catalog-aware rather than adapter-wide. The adapter already has this exact
pattern at duckdb.py:176 in _create_table:

catalog_type_tuple = self.fetchone(
    exp.select("type").from_("duckdb_databases()")
    .where(exp.column("database_name").eq(catalog))
)
catalog_type = catalog_type_tuple[0] if catalog_type_tuple else None
if catalog_type == "ducklake":
    ...

So an _drop_object override on DuckDBEngineAdapter that drops cascade for TABLE/VIEW
when the target's catalog type is ducklake would fix it, keeping SCHEMA (which works, and
which environment cleanup relies on).

One caveat: the existing pattern resolves get_current_catalog(), which is not right for
_drop_object — it receives a fully-qualified name whose catalog may differ from the session's,
so the catalog should be resolved from the table name.

Workaround

Setting DuckDBEngineAdapter.SUPPORTED_DROP_CASCADE_OBJECT_KINDS = ["SCHEMA"] before building a
Context makes the built-in janitor work correctly. On a real project this dropped 78 physical
tables and reclaimed 3.64 GB, after which plan --skip-backfill reported no changes and all
blocking audits still passed. It is blunter than the fix above — it disables cascade for plain
DuckDB catalogs too — but harmless for the reason given.

Environment

  • sqlmesh 0.233.0 and 0.236.1 (verified the declaration and cascade=True are unchanged in both)
  • duckdb 1.5.5, ducklake extension d8a1881e (DuckLake format 1.0)
  • macOS 15, Python 3.11 / 3.12

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions