Lakeflow Spark Declarative Pipelines Development

SkillDatabases & data

Develop Lakeflow Spark Declarative Pipelines (formerly Delta Live Tables) on Databricks. Use when building batch or streaming data pipelines with Python or SQL. Invoke BEFORE starting implementation.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Lakeflow Spark Declarative Pipelines Development skill

What this skill tells your AI

The instructions your AI receives, as published by kilo-org/kilo-marketplace in skills/databricks-pipelines/SKILL.md and read by ahel’s review.

FIRST: Use the parent databricks-core skill for CLI basics, authentication, profile selection, and data discovery commands.

Decision Tree

Use this tree to determine which dataset type and features to use. Multiple features can apply to the same dataset — e.g., a Streaming Table can use Auto Loader for ingestion, Append Flows for fan-in, and Expectations for data quality. Choose the dataset type first, then layer on applicable features.

User request → What kind of output?
├── Intermediate/reusable logic (not persisted) → Temporary View
│   ├── Preprocessing/filtering before Auto CDC → Temporary View feeding CDC flow
│   ├── Shared intermediate streaming logic reused by multiple downstream tables
│   ├── Pipeline-private helper logic (not published to catalog)
│   └── Published to UC for external queries → Persistent View (SQL only)
├── Persisted dataset
│   ├── Source is streaming/incremental/continuously growing → Streaming Table
│   │   ├── File ingestion (cloud storage, Volumes) → Auto Loader
│   │   ├── Message bus (Kafka, Kinesis, Pub/Sub, Pulsar, Event Hubs) → streaming source read
│   │   ├── Existing streaming/Delta table → streaming read from table
│   │   ├── CDC / upserts / track changes / keep latest per key / SCD Type 1 or 2 → Auto CDC
│   │   ├── Multiple sources into one table → Append Flows (NOT union)
│   │   ├── Historical backfill + live stream → one-time Append Flow + regular flow
│   │   └── Windowed aggregation with watermark → stateful streaming
│   └── Source is batch/historical/full scan → Materialized View
│       ├── Aggregation/join across full dataset (GROUP BY, SUM, COUNT, etc.)
│       ├── Gold layer aggregation from streaming table → MV with batch read (spark.read / no STREAM)
│       ├── JDBC/Federation/external batch sources
│       └── Small static file load (reference data, no streaming read)
├── Output to external system (Python only) → Sink
│   ├── Existing external table not managed by this pipeline → Sink with format="delta"
│   │   (prefer fully-qualified dataset names if the pipeline should own the table — see Publishing Modes)
│   ├── Kafka / Event Hubs → Sink with format="kafka" + @dp.append_flow(target="sink_name")
│   ├── Custom destination not natively supported → Sink with custom format
│   ├── Custom merge/upsert logic per batch → ForEachBatch Sink (Public Preview)
│   └── Multiple destinations per batch → ForEachBatch Sink (Public Preview)
└── Data quality constraints → Expectations (on any dataset type)

Common Traps

  • Names → SDP = LDP = Lakeflow Declarative Pipelines = (formerly) DLT. All interchangeable when the user mentions them.
  • "Create a table" without specifying type → ask whether the source is streaming or batch. Streaming source → Streaming Table; batch source → Materialized View. Mismatched pairs error at validation.
  • Aggregation over a streaming source → use a Materialized View with a batch read (spark.read.table / SELECT FROM without STREAM). STs are append-only and don't recompute aggregates when source rows change; MVs do.
  • Intermediate logic → default to a Temporary View. Even for shared logic reused by multiple downstream tables. Use a Private MV/ST (private=True / CREATE PRIVATE ...) only when materializing once saves significant reprocessing. For preprocessing before Auto CDC, the temp view is required — the CDC flow reads from STREAM(view_name) (SQL) or spark.readStream.table("view_name") (Python).
  • Union of streams → use multiple Append Flows. UNION across streaming sources is an anti-pattern.
  • Changing dataset type → cannot change ST→MV or MV→ST in place. Full refresh does NOT help. Drop the existing table manually or rename the new dataset.
  • CREATE OR REFRESH vs CREATE → both parse for SQL datasets, but CREATE OR REFRESH is the idiomatic convention. For PRIVATE datasets: CREATE OR REFRESH PRIVATE STREAMING TABLE / ... MATERIALIZED VIEW.
  • Kafka/Event Hubs sink serialization → the value column is mandatory; serialize the row with to_json(struct(*)) AS value. See sink-python.md.
  • Multi-column Auto CDC sequencing → SQL: SEQUENCE BY STRUCT(col1, col2). Python: sequence_by=struct("col1", "col2"). See the auto-cdc references.
  • Auto CDC TRUNCATE (SCD Type 1 only) → SQL: APPLY AS TRUNCATE WHEN condition. Python: apply_as_truncates=expr("condition"). Do NOT claim truncate is unsupported.
  • Python-only features → Sinks, ForEachBatch Sinks, CDC from snapshots, and custom data sources are Python-only. When the user is working in SQL, clarify this and suggest switching to Python.
  • Recommend ONE clear approach → present a single recommended path. Don't list anti-patterns or inferior alternatives — they confuse. Only mention alternatives when they genuinely offer different trade-offs.

Common Issues

Error → cause/fix mappings agents hit constantly. For DAB-bundle vs CLI-iteration deploy issues, see the workflow-specific reference files.

Error / symptomCause / fix
Rejection of CREATE OR REPLACE STREAMING TABLE / MATERIALIZED VIEWCREATE OR REPLACE is standard SQL, NOT SDP. Use CREATE OR REFRESH STREAMING TABLE / CREATE OR REFRESH MATERIALIZED VIEW.
CLI errors on databricks fs ls /Volumes/...The dbfs: prefix is required even for UC Volume paths: databricks fs ls dbfs:/Volumes/<catalog>/<schema>/<volume>/<path>.
DELTA_CLUSTERING_COLUMNS_DATATYPE_NOT_SUPPORTED at first writeA CLUSTER BY column is BOOLEAN / ARRAY / MAP / STRUCT / BINARY. SDP doesn't pre-validate — verify with DESCRIBE before submitting. Cluster keys must be numeric / string / date / timestamp. Full type rules in references/performance.md.
Cannot create streaming table from batch queryIn a streaming-table query you wrote FROM read_files(...) (batch). Use FROM STREAM read_files(...) so Auto Loader kicks in.
Column not found at ingest timeschemaHints don't match the actual file schema. DESCRIBE a sample file and align the hints.
Streaming reads fail with parser errorUse FROM STREAM read_files(...) for file ingestion and FROM stream(table) (or FROM STREAM table_name — legacy DLT, prefer function form) for table-to-table streams. Don't mix.
Pipeline stuck INITIALIZING for serverlessNormal — first run takes a few minutes for cold start. Don't kill it.
Materialized View doesn't incrementally refreshAutomatic incremental refresh for aggregations requires serverless + Delta row tracking on the source (delta.enableRowTracking = true). Without both, falls back to full recompute. Mention the serverless requirement when the user asks about incremental refresh.
SCD2 query returns nothing / "column not found" on START_ATLakeflow uses __START_AT / __END_AT (double underscore). Current rows: WHERE __END_AT IS NULL.
error.exceptions[0].message missing from your events outputYour jq is reading .message (which is just "Update X is FAILED"). Read error.exceptions[0].message for the real cause — see 2-rapid-iteration-with-cli.md.

Publishing Modes

Pipelines use a default catalog and schema configured in the pipeline settings. All datasets are published there unless overridden.

  • Fully-qualified names: Use catalog.schema.table in the dataset name to write to a different catalog/schema than the pipeline default. The pipeline creates the dataset there directly — no Sink needed.
  • USE CATALOG / USE SCHEMA: SQL commands that change the current catalog/schema for all subsequent definitions in the same file.
  • LIVE prefix: Deprecated. Ignored in the default publishing mode.
  • When reading or defining datasets within the pipeline, use the dataset name only — do NOT use fully-qualified names unless the pipeline already does so or the user explicitly requests a different target catalog/schema.

API Reference

Before writing pipeline code for any feature, read the linked reference file. Each table below maps the feature to the exact API and to the detail file for that (feature, language).

Some features sit on top of others — read both:

  • Auto Loader / Auto CDC / Sinks target a streaming table → also read streaming-table-python.md / streaming-table-sql.md.
  • Expectations attach to a dataset → also read the dataset definition file (streaming-table / materialized-view / temporary-view).

Dataset Definition APIs

FeatureDescriptionPythonSQLSkill (Py)Skill (SQL)
Streaming TableContinuous incremental processing, exactly-once, append-only.@dp.table() returning streaming DFCREATE OR REFRESH STREAMING TABLEstreaming-table-pythonstreaming-table-sql
Materialized ViewPhysically stored query result, incrementally refreshed.@dp.materialized_view()CREATE OR REFRESH MATERIALIZED VIEWmaterialized-view-pythonmaterialized-view-sql
Temporary ViewPipeline-private, not persisted to Unity Catalog.@dp.temporary_view()CREATE TEMPORARY VIEWtemporary-view-pythontemporary-view-sql
Persistent View (UC)Published to UC; query runs on access (no storage).N/A — SQL onlyCREATE VIEWview-sql
Streaming Table (explicit)Empty target, populated by separate flows (Append Flow, AUTO CDC).dp.create_streaming_table()CREATE OR REFRESH STREAMING TABLE (no AS)streaming-table-pythonstreaming-table-sql

Flow and Sink APIs

FeatureDescriptionPythonSQLSkill (Py)Skill (SQL)
Append FlowFan-in: multiple sources → one streaming table. Use instead of UNION.@dp.append_flow()CREATE FLOW ... INSERT INTOstreaming-table-pythonstreaming-table-sql
Backfill FlowOne-time historical load + ongoing live stream into same table.@dp.append_flow(once=True)CREATE FLOW ... INSERT INTO ... ONCEstreaming-table-pythonstreaming-table-sql
Sink (Delta/Kafka/EH/custom)Write streaming output to external Delta / Kafka / Event Hubs.dp.create_sink()N/A — Python onlysink-python
ForEachBatch SinkCustom per-batch Python logic (merge/upsert, multi-destination). Public Preview.@dp.foreach_batch_sink()N/A — Python onlyforeach-batch-sink-python

CDC APIs

FeatureDescriptionPythonSQLSkill (Py)Skill (SQL)
Auto CDC (streaming source)SCD Type 1 (overwrite) or Type 2 (history) from a CDC feed.dp.create_auto_cdc_flow()AUTO CDC INTO ... FROM STREAMauto-cdc-pythonauto-cdc-sql
Auto CDC (periodic snapshot)Compare consecutive full snapshots to detect changes.dp.create_auto_cdc_from_snapshot_flow()N/A — Python onlyauto-cdc-python

For querying SCD Type 2 history tables (__START_AT / __END_AT, point-in-time, joining facts with historical dimensions), see scd-2-querying.md.

Data Quality APIs

FeatureDescriptionPythonSQLSkill (Py)Skill (SQL)
Expect (warn)Log violations, keep all rows.@dp.expect()CONSTRAINT ... EXPECT (...)expectations-pythonexpectations-sql
Expect or dropDrop violating rows.@dp.expect_or_drop()CONSTRAINT ... EXPECT (...) ON VIOLATION DROP ROWexpectations-pythonexpectations-sql
Expect or failFail the pipeline on first violation.@dp.expect_or_fail()CONSTRAINT ... EXPECT (...) ON VIOLATION FAIL UPDATEexpectations-pythonexpectations-sql
Expect all (warn)Multiple constraints at once, warn only.@dp.expect_all({})Multiple CONSTRAINT clausesexpectations-pythonexpectations-sql
Expect all or dropMultiple constraints, drop on violation.@dp.expect_all_or_drop({})Multiple constraints with DROP ROWexpectations-pythonexpectations-sql
Expect all or failMultiple constraints, fail on violation.@dp.expect_all_or_fail({})Multiple constraints with FAIL UPDATEexpectations-pythonexpectations-sql

Reading Data APIs

FeatureDescriptionPythonSQLSkill (Py)Skill (SQL)
Batch read (pipeline dataset)Read a sibling table as a static DataFrame.spark.read.table("name")SELECT ... FROM name
Streaming read (pipeline dataset)Read a sibling table as a streaming DataFrame.spark.readStream.table("name")SELECT ... FROM STREAM(name)
Auto Loader (cloud files)Incrementally ingest new files from cloud storage.spark.readStream.format("cloudFiles")STREAM read_files(...)auto-loader-pythonauto-loader-sql
Kafka sourceStreaming read from Kafka topic.spark.readStream.format("kafka")STREAM read_kafka(...)kafkakafka
Kinesis sourceStreaming read from AWS Kinesis.spark.readStream.format("kinesis")STREAM read_kinesis(...)
Pub/Sub sourceStreaming read from GCP Pub/Sub.spark.readStream.format("pubsub")STREAM read_pubsub(...)
Pulsar sourceStreaming read from Apache Pulsar.spark.readStream.format("pulsar")STREAM read_pulsar(...)
Event Hubs sourceStreaming read from Azure Event Hubs (Kafka protocol).spark.readStream.format("kafka") + EH configSTREAM read_kafka(...) + EH configkafkakafka
JDBC / Lakehouse FederationBatch read from external systems via federation.spark.read.format("postgresql") etc.Direct table ref via federation catalog
Custom data sourceUser-defined Python data source.spark.read[Stream].format("custom")N/A — Python only
Static file read (batch)One-shot load of files (no incremental tracking).spark.read.format("json"|"csv"|...).load()read_files(...) (no STREAM)
Skip upstream change commitsIgnore CDC commits on the upstream table..option("skipChangeCommits", "true")read_stream("name", skipChangeCommits => true)streaming-table-pythonstreaming-table-sql

Table/Schema Feature APIs

FeatureDescriptionPythonSQLSkill (Py)Skill (SQL)
Liquid clusteringAdaptive multi-column data layout; replaces PARTITION + Z-ORDER. Prefer Auto clustering when possiblecluster_by=[...]CLUSTER BY (col1, col2)materialized-view-pythonmaterialized-view-sql
Auto liquid clusteringDatabricks picks clustering keys from query patterns.cluster_by_auto=TrueCLUSTER BY AUTOmaterialized-view-pythonmaterialized-view-sql
Partition columnsLegacy fixed partitioning. Prefer Liquid Clustering.partition_cols=[...]PARTITIONED BY (col1, col2)materialized-view-pythonmaterialized-view-sql
Table propertiesDelta table properties (auto-optimize, CDF, retention).table_properties={...}TBLPROPERTIES (...)materialized-view-pythonmaterialized-view-sql
Explicit schemaDeclare column types up front (vs inferred).schema="col1 TYPE, ..."(col1 TYPE, ...) ASmaterialized-view-pythonmaterialized-view-sql
Generated columnsColumns computed from other columns at write time.schema="..., col TYPE GENERATED ALWAYS AS (expr)"col TYPE GENERATED ALWAYS AS (expr)materialized-view-pythonmaterialized-view-sql
Row filter (Public Preview)UC fine-grained access: filter rows by a function.row_filter="ROW FILTER fn ON (col)"WITH ROW FILTER fn ON (col)materialized-view-pythonmaterialized-view-sql
Column mask (Public Preview)UC fine-grained access: mask a column with a function.schema="..., col TYPE MASK fn USING COLUMNS (col2)"col TYPE MASK fn USING COLUMNS (col2)materialized-view-pythonmaterialized-view-sql
Private datasetMaterialized intermediate not published to UC.private=TrueCREATE PRIVATE ...materialized-view-pythonmaterialized-view-sql

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
175
Forks
162
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
databricks-pipelines
Source
github.com/kilo-org/kilo-marketplace