Data contracts
A data contract is a fact you declare in a feature definition about data the compiler never reads. The generated query assumes that fact. When the fact is false, the query still compiles and the rows are wrong.
Compilation reads the definition only. The table, the view, and the text of a SQL() source stay on the warehouse until that generated query runs. That is what lets one definition target several backends, and what keeps your rows out of compilation. The cost is the list below: each line is trusted, and none of it is checked against the data.
validate() prints the list for the query under Unverified assumptions. Each bullet is the fact and a check you can run on the warehouse. EXPLAIN (FIELDS (DATA_CONTRACTS)) returns the same list. Neither one rejects the query.
Datatype, entity, unit, and additivity checks are a different layer. Those follow from the declarations themselves. See Semantic contracts .
What a definition assumes
| You write | Assumed true | When it is false |
|---|---|---|
EXTERNAL_COLUMNS … FROM TABLE | Each declared column exists and is read as its declared type, including # entity. The BIND TO columns are unique together. An ON filter does not remove a column from this list. A DETERMINED BY on a column is one value per key. | A missing column, or a cast the backend cannot perform, fails when the query runs. A duplicate key returns one output row per source row. A false DETERMINED BY lets a rollup count one business fact more than once. |
EXTERNAL_COLUMNS … FROM SQL | The same column names, types, BIND TO uniqueness, and DETERMINED BY. The SQL text is not read to prove the projection. | Same as a table. |
EXTERNAL_COLUMNS … FROM VIEW | The same unread schema as a table, including DETERMINED BY. | Same as a table. |
BIND_COLUMNS … FROM TABLE or FROM SQL | Each listed source column exists and is read as the type of the INPUT it binds. | A wrong type is cast. There is no uniqueness claim: this clause supplies the population. |
INLINE_COLUMNS … FROM CSV | The BIND TO columns are unique. | There is no warehouse “column is read as” claim, because the payload is the query. Repeated keys in that CSV are a compile error. |
INDEX_UNIQUE(array BY key) | The key is unique. | The query still returns one row for that key. |
INDEX_MULTI(array BY key) | Nothing about uniqueness. | Several rows for one key are the meaning of the function. |
A registered datakey cache adds one more line of the same kind: the cache column is read as the feature's type.
A column is read as the type you declare
EXTERNAL_COLUMNS names every column the mapping will touch, and the type it will be read as. status VARCHAR means the generated SQL casts that column to VARCHAR. id BIGINT#ACCOUNTS means the same cast, and the value is an account key inside FeatureQL. The entity annotation has no extra warehouse cost. It is still part of the type the column is read as.
An ON filter keeps every declared column in the contract. Filtering ON SELF[status] = 'active' still assumes status exists and is read as the type you wrote.
FROM SQL(...) carries the same contract. Column names and types come from the EXTERNAL_COLUMNS list. A projection that omits a column you declared still compiles.
FROM VIEW(...) is the same unread schema as FROM TABLE(...), against a configured JDBC source.
BIND_COLUMNS is narrower. It states each source column you listed, read as the bound INPUT type. It does not state that those columns are unique.
The ratings table below stores 1.5. The mapping declares that column as BIGINT.
DEFINE
accounts := ENTITY(),
account_id := INPUT(BIGINT#accounts),
ratings := EXTERNAL_COLUMNS(
account_id BIGINT#accounts BIND TO account_id,
rating BIGINT
FROM TABLE(docs_contracts.ratings)
)
RETURN
account_id,
rating := ratings[rating]
FOR
account_id := BIND_VALUE(1)
;| ACCOUNT_ID BIGINT#ACCOUNTS | RATING BIGINT |
|---|---|
| 1 | 2 |
The stored 1.5 comes back as 2. A cast that fails, and a column that is not on the relation, fail when the backend runs the query.
A BIND TO key is one row
BIND TO declares that the source has at most one row for each value of those columns. Several BIND TO columns are one composite key: the combination is what must be unique.
The accounts table has two rows for account 1. The query binds that one key.
DEFINE
accounts := ENTITY(),
account_id := INPUT(BIGINT#accounts),
accounts_src := EXTERNAL_COLUMNS(
account_id BIGINT#accounts BIND TO account_id,
status VARCHAR
FROM TABLE(docs_contracts.accounts)
)
RETURN
account_id,
status := accounts_src[status]
FOR
account_id := BIND_VALUE(1)
;| ACCOUNT_ID BIGINT#ACCOUNTS | STATUS VARCHAR |
|---|---|
| 1 | gold |
| 1 | silver |
Both statuses come back. Nothing in compilation reports the duplicate.
The check validate() names for this assumption is COUNT() against COUNT(DISTINCT ACCOUNT_ID) on the mapping. For a composite key it is COUNT(DISTINCT order_id, line_id). Run that on the warehouse. In the enterprise is where teams put the test.
Rows that are already in the query
An inline CSV is the payload, so validate() does not claim that a warehouse column is read as a type. It still states that the BIND TO columns are unique. When the CSV itself repeats a key, that repetition is in the text, and compilation fails:
DEFINE
accounts := ENTITY(),
account_id := INPUT(BIGINT#accounts),
accounts_src := INLINE_COLUMNS(
account_id BIGINT#accounts BIND TO account_id,
status VARCHAR
FROM CSV(
account_id,status
1,gold
1,silver
)
)
RETURN
account_id,
status := accounts_src[status]
FOR
account_id := BIND_VALUE(1)
;DETERMINED BY is one value per key
amount DECIMAL(10, 2) DETERMINED BY (order_id, line_id) declares that each distinct (order_id, line_id) has one amount. The compiler uses that when a later SUM would otherwise count a repeated value. It does not read the source to see whether the declaration holds.
The check is COUNT(DISTINCT order_id, line_id) against COUNT(DISTINCT order_id, line_id, amount) on that source. When the second count is larger, at least one key has two amounts, and a rollup that trusts the declaration can count the same fact more than once. See Semantic contracts .
When the rows are inline and they contradict the declaration, the compiler warns SUMMARIZABILITY-DETERMINED-BY-CONTRADICTED. A table, a view, or a SQL source produces no warning.
INDEX_UNIQUE assumes a unique key
INDEX_UNIQUE(orders BY order_id) declares that order_id occurs once in that array. validate() states it, with a COUNT() against COUNT(DISTINCT order_id) check on the array. A duplicate key does not fail the query: one row is still returned for that key. See Lookup with indexes .
INDEX_MULTI maps a key to every matching row. validate() states no uniqueness contract for it.
Next
Write the mapping in Mapping to data sources . The checks the compiler does apply, from these declarations, are Semantic contracts .