Capabilities and limitations¶
This page summarises what delta-engine can and cannot manage. Each row links to the page with the detail.
Platform¶
Requirement |
Supported |
|---|---|
Backend |
Delta Lake tables on Databricks with Unity Catalog — the supported target today; the reader reads managed and external Delta tables, plus Delta streaming tables for comment and tag management, and any other relation a registered name resolves to (view, materialized view, foreign table, non-Delta format) fails its read (architecture) |
Python |
3.12 or later |
PySpark |
Supplied by Databricks Runtime for the Spark backend and not installed by delta-engine; the SQL warehouse backend needs none. Declaring and planning are pure Python either way (installation) |
Reads (both backends) |
Unity Catalog only — every existing-table read uses one |
Identifier handling¶
Catalog, schema, table, and ordinary column references are case-insensitive, whether backticked or not. Unity Catalog stores catalog, schema, and table names in lowercase, so object name parts are normalized to lowercase exactly as the catalog stores them. Constraint-name DDL has an exception described below.
Column names and nested struct field names keep their declared or observed spelling. Constraint names do not: Unity Catalog stores them in lowercase. Case never distinguishes two identifiers: names differing only in case are the same column and collide as duplicates within one schema.
Within one declaration, case is free. A partition, clustering,
primary-key, or foreign-key reference may use any casing; once attached to a
table it uses the referenced Column.name when the public declaration is
lowered. Thus Column("requestId", ...) and primary_key=["REQUESTID"]
are accepted together, while DeltaTable.primary_key returns
("requestId",).
Against the catalog, case is exact. A declaration naming an existing
column with different casing fails validation with
ColumnSpellingMustMatchCatalog, which reports both spellings; a foreign key
that spells the referenced table’s key differently from that table’s own
declaration fails resolution with REFERENCED_COLUMN_CASE_MISMATCH. Nothing
is rewritten to compensate: the engine emits the spelling the declaration
carries, and this rule is what makes that safe on the Databricks DDL paths
that resolve case-sensitively. Columns the sync creates are spelled as
declared — a table that does not yet exist has no catalog spelling to agree
with.
Exactness holds at every scope. A scope="metadata", scope="annotations",
or scope="tags" declaration still names the columns it keys, comments, or
tags, so it is judged the same way; narrowing the scope does not narrow this
rule.
DESCRIBE TABLE shows the spelling to copy. Callers that relied on lowercase
accessor values should apply their own presentation policy.
Constraint names occupy one case-insensitive namespace per schema, across
tables and constraint kinds, and information_schema exposes their normalized
lowercase spelling. Despite that case-insensitive collision rule,
DROP CONSTRAINT requires the exact catalog spelling; with IF EXISTS, a
case mismatch silently does nothing. Databricks generates a name when raw SQL
omits one. Delta-engine likewise omits the name unless primary_key_name or
ForeignKey(name=...) is supplied. Either form accepts any structurally
matching observed constraint: an explicit name is a creation preference, not
ongoing managed state. Changing only that preference does not rename an
existing constraint; Databricks has no direct constraint-rename clause.
Declared catalog, schema, and table names are validated against Unity Catalog’s object-name rules at declaration time: at most 255 characters, and no periods, spaces, forward slashes, control characters, or DEL. Column names are exempt from those rules; column names that require special characters need column mapping as described in column mapping and dropping columns.
What a sync manages¶
Aspect |
Managed |
Notes |
|---|---|---|
Create table |
✓ |
Missing tables are created from the declaration |
Add column |
✓ |
Must be nullable on an existing table (rules) |
Drop column |
✓ |
Requires |
Loosen nullability |
✓ |
|
Tighten nullability |
✗ |
Top-level columns are blocked — backfill first, then tighten; nested field changes are blocked as struct type changes (rules) |
Change column type |
Widening only |
Safe widenings (e.g. |
Rename column |
✓ |
Declare |
Rename partition column |
✓ |
Column mapping preserves the partition column’s physical identity, so its layout metadata follows the rename (rules) |
Table and column comments |
✓ |
Always managed; an empty declaration clears the comment (comments) |
Table properties |
✓ |
Six managed |
Table and column tags |
✓ |
Full-state: undeclared tags are removed (tags) |
Primary keys |
✓ |
Declared at table level; creation names may be requested (primary keys) |
Foreign keys |
✓ |
Must target the referenced table’s primary key; orders the sync; creation names may be requested (foreign keys) |
Partitioning |
Create only |
Fixed after creation; changes are blocked (rules) |
Clustering |
✓ |
Liquid clustering keys are reconciled in place, unlike partitioning (clustering) |
Metadata-only scope |
✓ |
|
Tag-only scope |
✓ |
|
Annotations scope |
✓ |
|
Streaming tables |
Comments and tags |
Discovered at read time; only |
Dry run |
✓ |
Full plan and validation, zero mutations (guide) |
Outside the model¶
These features are not modeled at all: the engine never reads, creates, changes, or drops them, and they produce no drift.
Not modeled |
Meaning |
|---|---|
CHECK constraints |
Cannot be declared; a constraint that references a renamed column must be changed before the rename |
Key constraint options ( |
Keys are created with Databricks defaults ( |
|
Cannot be declared or used as a registered foreign-key target, even on Databricks versions that support them |
Identity and generated columns |
Generation expressions are invisible; one that references a renamed column must be changed before the rename |
Views and materialized views |
Unsupported; a registered name that resolves to a view or materialized view fails its read rather than being planned against (streaming tables, by contrast, are read for comment and tag management) |
External table creation |
Existing external Delta tables are read and reconciled like managed ones, but the engine creates managed tables only: a location cannot be declared, and an absent table is created managed |
Grants, row filters, column masks |
Governance beyond comments and tags is out of scope |
Data |
The engine runs DDL only; it never reads, writes, or backfills rows |
A comment this engine sets on a streaming table can be reverted by the owning
pipeline. CREATE OR REFRESH is fully declarative, so a refresh re-applies
the pipeline’s own comment and deletes metadata the refresh does not specify.
The engine cannot read the pipeline’s defining SQL, so it cannot warn: a
contested comment re-drifts on every pipeline update and each sync sets it
again. Manage a given comment from one place only.
Type support¶
The full matrix is in data types. The limitations in brief:
Limitation |
Behaviour |
|---|---|
Unsupported Spark types |
Any column whose type the engine cannot model fails that table’s read; columns are never skipped, so drift is never invisible |
|
Treated as |
Struct fields |
Structs change as a whole: any field change is a blocked column type change |
|
Maximum 38, enforced at declaration |
Clustering limits¶
Liquid clustering (clustering) has its own set of declaration-time and execution-time limits, distinct from partitioning:
Limitation |
Behaviour |
|---|---|
Key count |
At most four |
Mutual exclusivity |
A table cannot declare both |
Unsupported key types |
|
Nested struct-field keys |
Clustering by a field inside a |
Statistics |
Clustering keys need collected file statistics. Unity Catalog external tables default to the first 32 columns; managed tables with predictive optimization choose statistics automatically. Verify that every declared key has statistics. |
Runtime compatibility |
Liquid clustering is available in Databricks SQL and Databricks Runtime 13.3 LTS or later; delta-engine does not preflight this — see runtime features |
Concurrent catalog changes¶
A sync is not transactional across its read, plan, and execute phases, and it never re-reads a table after executing its statements. The operating contract is single-writer: while a sync runs, no other actor should run DDL against the tables it manages. A successful table report means every planned statement executed, not that the desired state was verified afterwards — DDL from another actor between the read and a statement is invisible to the run unless it makes a statement fail (how a sync works).
Table creation compiles as a plain CREATE TABLE: if another writer creates
the same name after the reader observed it missing, that statement errors and
the table is reported as an execution failure rather than a false success. The
next sync reads the table that actually exists and reports any resulting
drift. Avoid concurrent creators for the same qualified table name.
Runtime features¶
delta-engine does not preflight Databricks Runtime or Delta protocol versions. Declaring a feature the workspace or table protocol does not support — key constraints, tags, change data feed — fails at execution with the original Databricks error. See runtime and Delta feature compatibility.
Reading a table relies on DESCRIBE TABLE EXTENDED … AS JSON, which needs
Databricks Runtime 16.2 or later, or any SQL warehouse. Primary and foreign keys
are then read from information_schema; because key constraints are available
from Databricks Runtime 13.3 (GA 15.2) — below the AS JSON read floor — any
runtime new enough to read a table can also observe its keys. As with every
other runtime feature, delta-engine documents this floor rather than
preflighting it — an unsupported runtime surfaces as a read failure.
This project does not yet have live Spark-backend evidence for a numbered Databricks Runtime. The recurring live suite exercises the SQL warehouse backend; validate the Spark path in your own production environment until Spark-runtime evidence is published. Databricks Runtime 16.2 is a technical floor, not a runtime allowlist, and compatible newer runtimes remain allowed. See the runtime compatibility policy.
The Spark backend does not currently work on Dedicated access-mode compute
(data_security_mode value SINGLE_USER, formerly called single-user access mode).
Treat this mode as unsupported.
Delta table features¶
Declaring a type that needs a Delta table feature — TimestampNtz or
Variant anywhere in a column’s type tree — makes the engine plan the
enablement itself when the table exists without it: an explicit
SET TBLPROPERTIES ('delta.feature.…' = 'supported') statement, ordered
before the dependent column change and always visible in a dry run. Creating
a table needs no planned enablement; Databricks enables required features
from the created schema.
Feature enablement is a permanent protocol upgrade — older Delta clients may
lose access to the table. The engine never plans DROP FEATURE, and features
it does not model — deletion vectors, row tracking, and every other
platform-managed or property-gated feature — are left entirely to the
platform and its properties.