Where to define one
- Web. Open a resource on the canvas. The Transformations section of the panel builds steps from the same function catalog and shows issues as you edit.
- API. A
PipelineEdgecarries the definition in itstransformfield as a JSON object. An edge for one resource may only define that resource. The Transformations reference covers the catalog and validation RPCs. - Go.
pipeline.Config.Transformtakes a parsedtransform.Definitionwhen Filament is embedded.
The definition
version is always 1. resources names each resource to transform and
lists its steps. A resource the definition does not name passes through
unchanged. JSON with the same shape is accepted anywhere YAML is, and the API
carries it that way.
A step is exactly one of rename, drop, or compute. where belongs to
compute alone. Steps run in order, and each step sees the columns as the
previous step left them.
Rename
Every current name must exist and every new name must be free. A primary key column cannot be renamed, so an existing destination stays keyed the same way. A name starting with_filament_ is reserved. Entries in one rename step are
independent, so a name that another entry in the same step frees is not
available to it. Use two steps for that.
Drop
Every name must exist. A primary key column cannot be dropped.Compute
Every entry in a compute step is evaluated against the columns as they stood before the step, then all entries are assigned together. Entries in one step cannot refer to each other. Put a dependent computation in a later step. An entry on an existing column replaces it and may change its type. An entry on a new column appends it. New columns from one step land after the existing ones in name order. A computed column is always nullable. The name cannot be a primary key column or start with_filament_.
Where
where limits a compute step to the rows its condition matches. The condition
must produce a bool. A row where it is null is not matched. On an unmatched
row an existing column keeps its value and a new column is null. Because
unmatched rows keep their old values, a compute under where on an existing
column must keep its type.
Expressions
An expression is one of three things.
A call with one argument may pass it directly. A call with several passes a
list. Calls nest to any depth. The function names are a closed set, listed
below. Anything else fails the grammar check.
A YAML integer is an
int64 literal, a number with a fraction is a float64,
a quoted value is a string, and true or false is a bool. There is no
date or null literal.
Where a function needs its arguments to share a type, a literal widens to the
column it sits beside when the value fits. {eq: [{col: quantity}, 1]}
compares an int16 column with an int16 literal. A literal that does not
fit, such as 70000 beside an int16 column, is an error. A decimal column
takes an integer or fractional literal when the literal needs no more scale or
precision than the column has. A decimal column with precision above 38 is
carried as text, and no literal can stand beside it.
Functions
Logical type names are the onesGetResourceColumns reports, such as string,
int64, float64, decimal, bool, date, timestamp, and timestamptz.
A column argument may itself be a call that produces the right type. A null
input gives a null output unless noted. “Column” marks an argument that must be
a column or a call, never a literal. “Literal” marks a constant parameter.
“Input type” as a return means the result takes the shared type of the value
arguments.
Strings
Patterns use Go regular expression syntax, which is RE2. An invalid pattern is
reported when the definition is compiled.
Digests
Digests take one or more columns of type
string, uuid, int16, int32,
or int64 and return the lowercase hex digest of their text joined with -.
A string is hashed as its bytes and an integer as its decimal text, which
matches a warehouse that casts before hashing. Other types are not accepted
because no two systems agree on their text form.
A null column contributes nothing between the separators, so a key built from
several columns survives a missing value. A row where every column is null
stays null, so a masked column keeps its nulls.
Numbers
Arithmetic takes
int16, int32, int64, float32, or float64. Both
sides share one type, with a literal widening to the column’s type, and the
result has that type. Division by zero and integer overflow fail the batch.
floor, ceil, and round take float32 or float64.
Comparisons
Both sides share one type, at least one is a column, and a literal widens to
the column’s type. Orderable types are the integer and float types,
decimal,
date, time, timestamp, and timestamptz. A null on either side compares
to null.
Logic
and and or use three-valued logic as SQL does. False and null is false,
true or null is true, and the rest is null.
Nulls
Dates
A
timestamptz is read in its own zone.
Validation
Compiling a definition reports every issue at once, each with the path in the definition where it sits.resources["users"].steps[2].compute["email"].lower[0]
is the first argument of the lower call that computes email in the third
step. The web builder places issues by those paths, and the API returns them
on the edge or from ValidateTransform.
The checks run in this order.
- The document matches the grammar.
versionis 1, every step has exactly one of its three keys,wheresits only on a compute step, and every function name is in the catalog. The grammar is a JSON schema thatListTransformFunctionsreturns, so a client can check shape before sending. - Every column a step names exists in the layout the previous steps left, and the primary key and reserved name rules above hold.
- Every call has the right number of arguments, each argument is a column or
a literal as the function requires, the types match, and literals fit the
column they widen to. Function rules such as a valid pattern or a positive
substringstart run here too. - A
wherecondition is abool, and a compute under it keeps an existing column’s type.
ValidateTransform reports the resulting columns and the type of every
expression that compiled alongside the issues.
At run time
A plan is compiled once per resource when the source opens its first batch for that resource, against the schema the source supplies. It then applies to every batch as a fixed list of whole-column operations over Arrow arrays. Columns no step touches are shared with the source batch, not copied. The transform runs after the source’s batch is built and before the integrity checksum, so the checksum and thebatch.written fact describe the transformed
batch. Filament’s own _filament_ lineage columns are appended after the
transform and are never visible to it. Each transformed batch publishes
batch.transformed with its record count, bytes, and the time the plan took.
A batch the plan cannot transform fails the run. That covers a to_date value
that does not parse, division by zero, and integer overflow.
Limits
- Steps never remove rows. A
wherecondition picks rows to rewrite, not rows to keep. - A transformation sees one resource at a time. There are no joins, lookups, or aggregations.
- The catalog is closed.
to_dateis the only type conversion. - Entries in one compute step cannot see each other.
- Literals are strings, integers, floats, and booleans only.