Skip to content

Sources & schema — name your columns

The question for this lesson: before Clinker can transform your data, it has to read it and know what each column is: text, a number, a date. Where does that come from?

  • Declare a schema on a source: name and type each column.
  • Choose the right type for a column, and know which types exist.
  • Decide what happens to columns that aren’t in your schema.

This is the most important thing to know about sources: you declare the columns and their types; runtime ingestion enforces them. A CSV header only names columns; it never sets types.

A source’s schema: is a list of { name, type } entries:

- type: source
name: customers
config:
type: csv
path: ./data/customers.csv
options: { has_header: true }
schema:
- { name: customer_id, type: string }
- { name: status, type: string }
- { name: amount, type: int }
- { name: signed_up, type: date }

The types you can use in an inline schema:

TypeFor
stringtext
intwhole numbers
floatapproximate binary floating-point numbers
decimalexact base-10 numbers; declare precision and scale for money
booltrue / false
date, date_timea date, or a date + time
array, mapa list, or a set of key/values
🌱 New here? — picking a type

Match the type to how you’ll use the column, not just what it looks like. A zip code looks like a number but you never do math on it, so make it string and the leading zero (“01970”) survives. An integer count should be int; for money, use decimal with an explicit precision and scale.

The date-and-time spelling is date_time. Exact decimal columns are supported:

schema:
- { name: amount, type: decimal, precision: 12, scale: 2 }
- { name: signed_up, type: date, format: "%Y-%m-%d" }

A CSV decoder first produces text. Source admission then parses that text against these declarations before downstream nodes see it. Declare amount as int and CXL receives an integer without .to_int(). Declare it as string and it stays text until you explicitly convert it. Invalid input rejects the row under the run’s error policy; it never silently falls back to a string.

clinker guess is a separate authoring tool for explicitly marked numeric columns. Its default preview is read-only; it proposes int or float. Resolve those placeholders before running a pipeline. It does not guess an entire schema or change runtime types behind your back.

“Declared, not inferred” cuts both ways: the type you write has to be one Clinker knows. A made-up type like dollars is not silently accepted or guessed. It’s rejected before any data is read. If you change amount’s type to dollars and validate:

schema:
- { name: customer_id, type: string }
- { name: amount, type: dollars } # not a real type

clinker run pipeline.yaml --dry-run rejects the unknown type before reading data. Use one of the supported types above. numeric is an inference-only placeholder, not an executable source type; nullable wraps a supported concrete type.

// quick check

A column holds US zip codes like 01970. What type should it be?

What about columns in the file that you didn’t list in the schema? The on_unmapped setting decides:

  • auto_widen (default): retain undeclared CSV cells in the engine sidecar. A Sink with include_unmapped: true can include them in its output; they do not become declared CXL columns.
  • reject: fail if the file has a column you didn’t declare.
  • drop: silently ignore them.
config:
type: csv
path: ./data/customers.csv
options: { has_header: true }
on_unmapped: reject # be strict: an unexpected column is an error
schema:
- { name: status, type: string }

Predict, then run: this source declares amount as int. Trace what the output type of amount is, versus customer_id (declared string).

pipeline.yaml // editable

A --dry-run validates the config and the declared schema without reading any data, so the summary line reports the shape of the pipeline (here, one source, one output, and no transforms) and exits cleanly. It does not print a column-by-column type table; the place the types live is the schema: you wrote.