Skip to main content

Define your Schema

The schema block describes the structure of the data: the schemas (tables, views, topics, files) and their properties (columns, fields). Many of its attributes are automatically verified by datacontract test — you declare them once and they become executable checks on every run, on every backend.

This page covers only those attributes. Everything else in the schema block is documentation, carried into exports but never asserted against the data.

datacontract test --checks schema datacontract.yaml

What generates a check

AttributeLevelCheck
(any property)propertyThe column exists in the data source
physicalTypepropertyThe column's native type matches (catalog backends only)
logicalTypepropertyThe column's type matches, normalized to an ODCS category
requiredpropertyNo missing values
uniquepropertyNo duplicate values
primaryKeypropertyNo missing values and no duplicates
logicalTypeOptions.minLength / maxLengthpropertyValue length within bounds
logicalTypeOptions.minimum / maximumpropertyValue within bounds (inclusive)
logicalTypeOptions.exclusiveMinimum / exclusiveMaximumpropertyValue within bounds (exclusive)
logicalTypeOptions.patternpropertyValue matches the regular expression
logicalTypeOptions.enumpropertyValue is one of the listed values
qualityschema, propertySee Define your Quality Rules

A contract that uses all of them:

schema:
- name: orders
physicalName: orders_v2
properties:
- name: order_id
logicalType: string
physicalType: varchar(36)
primaryKey: true
- name: order_status
logicalType: string
required: true
logicalTypeOptions:
enum: ['pending', 'shipped', 'delivered']
- name: order_total
logicalType: integer
physicalType: integer
logicalTypeOptions:
minimum: 0
maximum: 1000000
- name: customer_email
logicalType: string
logicalTypeOptions:
maxLength: 255
pattern: '^[^@]+@[^@]+$'

Presence and naming

Every property produces a presence check — the column must exist in the data source. This is the one check you always get, even for a property that declares nothing but a name.

physicalName selects the real object in the data source; name is the logical name used in the contract. When physicalName is set, the checks run against it:

schema:
- name: orders # logical name
physicalName: orders_v2 # the real table
properties:
- name: total
physicalName: order_total # the real column

Types

A property can declare a portable logicalType, a native physicalType, or both. Which one is checked depends on the backend:

  • physicalType is compared against the column's real declared type read from the platform catalog. This applies on the nine backends with catalog introspection: Snowflake, BigQuery, Databricks, Postgres, Redshift, SQL Server, Oracle, Trino, and Athena. It takes precedence over logicalType.
  • logicalType is used everywhere else, and as the fallback when the native type cannot be read. Both the declared and the actual type are normalized to an ODCS category before comparison, so integer and number are mutually compatible.

Properties of logicalType: object or array that declare properties or items also get a nested type check covering the full declared structure.

note

For file servers with format: csv, json, or avro no type check is generated at all — the file is read as the contract's types, so a mismatch surfaces as a read error instead. format: json is additionally validated against a JSON Schema derived from the contract. See Data Source Reference for the full type-mapping rules.

Required, unique, and primary keys

properties:
- name: order_id
primaryKey: true
- name: external_ref
unique: true
- name: order_status
required: true
  • required: true → no missing values.
  • unique: true → no duplicate values.
  • primaryKey: true → both of the above. Declaring required or unique alongside it does not duplicate the check.

A composite primary key — several properties with primaryKey: true — is treated as a key over the tuple, not column by column. Each member gets its own not-null check, and the combination gets a single uniqueness check. Use primaryKeyPosition to order the members:

properties:
- name: order_id
primaryKey: true
primaryKeyPosition: 1
- name: line_number
primaryKey: true
primaryKeyPosition: 2

Produces: order_id not null, line_number not null, and (order_id, line_number) unique.

Value constraints

logicalTypeOptions turns into value checks that behave identically on every backend — the CLI compiles each into dialect-specific SQL.

properties:
- name: order_total
logicalType: integer
logicalTypeOptions:
minimum: 0
maximum: 1000000
- name: country_code
logicalType: string
logicalTypeOptions:
minLength: 2
maxLength: 2
pattern: '^[A-Z]{2}$'
- name: order_status
logicalType: string
logicalTypeOptions:
enum: ['pending', 'shipped', 'delivered']
OptionFails when a value is…
minLength / maxLengthshorter / longer than the bound
minimum / maximumbelow / above the bound (the bound itself passes)
exclusiveMinimum / exclusiveMaximumbelow / above or equal to the bound
patternnot matching the regular expression
enumnot one of the listed values

exclusiveMinimum and exclusiveMaximum each produce two checks — a bound check and an inequality check — so a violation of either is reported separately.

What is not checked

These are common sources of confusion. They are valid ODCS and appear in exports, but they generate no check:

  • isNullable — the CLI reads required, not isNullable. Write required: true to assert that a column has no nulls.
  • logicalTypeOptions.format (email, uuid, uri, …) — use pattern for an enforceable equivalent.
  • Descriptive attributesdescription, businessName, examples, tags, classification, criticalDataElement, transformSourceObjects, and customProperties. authoritativeDefinitions generates no check either, but it is resolved and inlined before the checks are built — see Link your Semantics.
  • Schema-level attributes other than name, physicalName, properties, and quality.

Anything beyond this list that you want verified belongs in a quality rule — a type: library metric for the common cases, or type: sql for arbitrary expressions.

Next steps