Templates in development

Written for Databricks, Snowflake and Fabric and tested on DuckDB. None has run on a real platform yet, and none is released.

Ingest (batch)

Ingest (batch): land every file once.

Read a batch of files, extracts or database tables, type the columns, add load metadata and land it in a bronze table, exactly once. A malformed file is reported, not half-loaded.

Template in development
Load
Databricks first

Shape card

01 / 10

  1. Source (used)
  2. Bronze (used)
  3. Silver
  4. Gold
  5. Apps
Group
Load
Works across
Source to Bronze
Reads
Files, extracts, source tables
Writes
Typed bronze tables
Slots
6 typed slots
Tested by
Golden tests on DuckDB
Platforms
Databricks first, then Snowflake and Microsoft Fabric

Overview

What Ingest (batch) does.

Land files, extracts and database tables in bronze tables, typed and checked on load.

What it does

Ingest (batch) reads one complete batch of source data, such as files in cloud storage, an API extract or a table from an operational database, and lands it in a bronze table. Columns are typed from an inferred or declared schema, and every row gets load metadata, such as the source file and the load time.

Exact duplicates are dropped on load. A file that cannot be parsed is reported with its name and the reason instead of failing the run halfway. The template keeps a record of what it has loaded, so running it again on the same files changes nothing.

Use it when

  • The first landing of a new source.
  • Scheduled file drops from a partner, a vendor or an upstream system.
  • Daily or hourly extracts where each batch is complete on its own.
  • Any time raw data should be kept as it arrived, with its origin, before it is cleaned.

Use another shape when

  • The source only adds rows and you want just the new ones on every run:

    02Incremental append
  • Rows in the source change or are deleted after they are written:

    03CDC merge
  • You need one row per key, with consistent types, across sources:

    08Dedupe / conform

Slots

Inputs, outputs and slots.

What the Ingest (batch) template reads, what it writes and the typed slots it asks you for. The agent fills most slots from your catalog and your request.

In

Inputs

  • A folder, bucket path or extract of files such as CSV, JSON or Parquet, or a source table
  • An optional declared schema, or hints for columns that inference gets wrong
  • The record of files already loaded

Ingest (batch)

  1. Source (used)
  2. Bronze (used)
  3. Silver
  4. Gold
  5. Apps

Out

Outputs

  • A bronze table with typed columns and load metadata on every row
  • A load log: which files were loaded, when, and how many rows
  • A report of files that could not be parsed, with the reason

Template slots

6 slots · names and types may change before release

source_path
string
Where the batch lives: a storage path, a file pattern or an extract location.
source_format
enum
The file format, for example csv, json or parquet.
target_table
table
The bronze table to land in.
schema_hints
expressions
Declared types for columns that inference gets wrong, such as an id that must stay text.
duplicate_columns
columns
The columns that make two rows an exact duplicate.
on_malformed_file
enum
Report the file and continue with the rest, or stop the run.

Slot types are identifier, table, column, columns, expression, expressions, string, integer, boolean, duration and enum. What is a template slot.

Testing

Tested before it ships.

Every template comes with golden tests that run on DuckDB on your machine before anything reaches your platform.

How a run goes

The golden test for this shape is designed around the failure that hurts most in production: one bad file in an otherwise good batch.

  1. 01Fixtures go in: small tables with the hard cases for the shape.
  2. 02The template runs on DuckDB on your machine, with no cluster and no warehouse compute.
  3. 03The result is compared with the expected output, row for row.
  4. 04Green can deploy. Red sends the agent back to the template.

What the Ingest (batch) test checks

  1. Case 01

    Load three files, one of them malformed. The two good files land exactly once, and the bad one is reported with its name and the reason instead of being half-loaded.

  2. Case 02

    Run the load again on the same three files. The target table does not change.

  3. Case 03

    A numeric column arrives as text in one file. It is cast to the declared type or the row is reported, as the slots specify. It never becomes a silent null.

  4. Case 04

    Two files repeat the same rows. Each distinct row lands once.

Each template is also judged per platform: yellow when it passes on DuckDB, green when it also passes on the real platform. Nothing is released, so no template has a green mark yet.

Platforms

Written for the platform you run.

The same shape has a different correct form on each platform. This is what the Ingest (batch) template uses on each one, and the platform facts that decide it.

Databricks

In development

The Databricks template uses Auto Loader or COPY INTO, writing Delta tables governed by Unity Catalog.

  • Auto Loader records which files it has processed in its checkpoint, so a restart does not reload them. COPY INTO skips files it has already loaded into the target table.
  • Auto Loader can infer a schema, evolve it, and put values that do not fit into a rescued data column. That column is where the template's drift handling looks.
  • The source path and modification time of every file are available as hidden metadata, so each bronze row can say where it came from.
  • The target is a three-level Unity Catalog name (catalog.schema.table), so the job's principal needs permission to create and modify tables in that schema.
Emolysis for Databricks

Snowflake

Planned

The Snowflake template uses COPY INTO from a stage into a native or Iceberg table.

  • COPY INTO remembers the files it has loaded for 64 days. A file staged again after that can load a second time, so the template also keeps its own load log.
  • The ON_ERROR option decides what a bad file does: skip it, continue past bad rows or abort the statement. The template sets it from the on_malformed_file slot.
  • The file name and row number are available as metadata while loading, which is how bronze rows keep their origin.
  • Unquoted column names fold to uppercase, so the generated DDL and the later silver rules have to agree on case.
Emolysis for Snowflake

Microsoft Fabric

Planned

The Fabric template is a notebook that loads lakehouse Delta tables in OneLake.

  • A Data Pipeline copy activity moves files and tables into the lakehouse. A notebook then types and checks the rows with Spark SQL or PySpark.
  • A notebook that reads a folder does not remember which files it has read, so the template keeps a load log table of its own.
  • Delta tables in the lakehouse can be queried through the SQL analytics endpoint, so analysts can read the bronze data without a copy.
  • The template targets lakehouse tables. A warehouse target would be loaded with T-SQL or a pipeline copy, and the warehouse's T-SQL surface differs from SQL Server's.
Emolysis for Microsoft Fabric

Whatever the platform, the template is tested on DuckDB first. The platform notes describe each product in general terms, so check the details against the version you run.

Mistakes

Mistakes it prevents.

The failures that most often hit a pipeline of this shape in production. Each one is a case in the template's golden test or a rule in its design.

  1. Loading the same file twice

    A retry, a re-run or a file dropped again creates duplicate rows that only show up later, in the totals. The template records every file it has loaded and skips what it has seen.

  2. Schema drift that silently turns a column to null

    A column that arrives as text one day casts to null and nobody notices. The template casts with a declared rule or sets the row aside with the reason.

  3. A malformed file that fails the run halfway

    Half a batch lands, the rest does not, and the next run cannot tell which. The template loads each file all or nothing and reports the ones that failed.

  4. Bronze rows with no record of where they came from

    When a number looks wrong you need to know which file it arrived in. Every row carries its source file and its load time.

SQL sketch

The idea in SQL.

Land new files once, typed, with load metadata

Illustrative sketch in generic SQL. Each platform's template uses that platform's own loader (Auto Loader, COPY INTO, a pipeline copy) instead of this statement.

Illustrative sketch · SQL
BEGIN;

-- 1. Type the rows of files not loaded before, drop exact duplicates, add load metadata.
--    A value that cannot be cast keeps its row out of bronze (it goes to a rejects table, not shown).
INSERT INTO bronze.orders (order_id, amount, ordered_at, _source_file, _loaded_at)
SELECT order_id, amount, ordered_at, source_file, CURRENT_TIMESTAMP
FROM (
  SELECT
    source_file,
    TRY_CAST(raw_order_id AS BIGINT)       AS order_id,
    TRY_CAST(raw_amount AS DECIMAL(18, 2)) AS amount,
    TRY_CAST(raw_ordered_at AS TIMESTAMP)  AS ordered_at,
    ROW_NUMBER() OVER (
      PARTITION BY raw_order_id, raw_amount, raw_ordered_at
      ORDER BY source_file
    ) AS copy_number
  FROM staged.orders_files
  WHERE source_file NOT IN (SELECT source_file FROM bronze.load_log)
) AS typed
WHERE copy_number = 1
  AND order_id IS NOT NULL AND amount IS NOT NULL AND ordered_at IS NOT NULL;

-- 2. Record the files in the same transaction, so the log and the rows never disagree.
INSERT INTO bronze.load_log (source_file, loaded_at)
SELECT DISTINCT source_file, CURRENT_TIMESTAMP
FROM staged.orders_files
WHERE source_file NOT IN (SELECT source_file FROM bronze.load_log);

COMMIT;

FAQ

Questions about Ingest (batch).

What is batch ingestion to bronze?

Ingesting in batches means reading a complete batch of source data, such as a day's files or a table extract, and landing it as it arrived in a bronze table, with typed columns and load metadata. Cleaning and one-row-per-key logic happen later, in silver. Ingest (batch) is the shape Emolysis uses for this first step.

How does the template avoid loading a file twice?

It keeps a load log of every file it has landed and skips those files on the next run, and its golden test runs the load twice on the same input and checks that the table does not change. Auto Loader and COPY INTO on Databricks also track files, and COPY INTO on Snowflake keeps its own history for 64 days, which is one reason for the template's own log.

What happens to a malformed file?

It is reported with its name and the reason, and it does not stop the good files from loading. Whether a bad file is reported and skipped or stops the run is a slot. A file is never half-loaded.

Auto Loader or COPY INTO on Databricks?

Both skip files they have already loaded. Auto Loader suits folders that keep receiving files and large file counts. COPY INTO is simpler for periodic loads of a modest number of files. The Databricks template uses one of the two, depending on the source and how often files arrive.

In development · Not released

Test first, then deploy.

The Ingest (batch) template is written for Databricks, Snowflake and Microsoft Fabric, and its golden tests pass on DuckDB. Nothing is released yet. Early access is by invitation, for teams that want to help shape it.