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
| Attribute | Level | Check |
|---|---|---|
| (any property) | property | The column exists in the data source |
physicalType | property | The column's native type matches (catalog backends only) |
logicalType | property | The column's type matches, normalized to an ODCS category |
required | property | No missing values |
unique | property | No duplicate values |
primaryKey | property | No missing values and no duplicates |
logicalTypeOptions.minLength / maxLength | property | Value length within bounds |
logicalTypeOptions.minimum / maximum | property | Value within bounds (inclusive) |
logicalTypeOptions.exclusiveMinimum / exclusiveMaximum | property | Value within bounds (exclusive) |
logicalTypeOptions.pattern | property | Value matches the regular expression |
enum | property | Value is one of the listed values (ODCS v3.2.0; logicalTypeOptions.enum is still accepted) |
logicalTypeOptions.minItems / maxItems | array property | Number of elements within bounds |
logicalTypeOptions.uniqueItems | array property | Elements of the array are distinct |
quality | schema, property | See 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
enum:
- value: pending
- value: shipped
label: Shipped
- value: delivered
label: Delivered
description: Handed over to the customer
- name: order_total
logicalType: integer
physicalType: integer
logicalTypeOptions:
minimum: 0
maximum: 1000000
- name: customer_email
logicalType: string
logicalTypeOptions:
maxLength: 255
pattern: '^[^@]+@[^@]+$'
- name: tags
logicalType: array
items:
logicalType: string
logicalTypeOptions:
minItems: 1
maxItems: 10
uniqueItems: true
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:
physicalTypeis 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 overlogicalType.logicalTypeis 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, sointegerandnumberare mutually compatible.
Properties of logicalType: object, array or map that declare properties, items or a map block (key and value, ODCS v3.2.0) also get a nested type check covering the full declared structure.
ODCS v3.2.0 logicalType: vector requires logicalTypeOptions.dimensions to be a positive integer. Lint rejects a missing options block as well as missing or invalid dimensions, including in nested definitions.
Property-level enum entries must have distinct values, even if their labels, IDs, or descriptions differ. Lint checks this throughout properties, array items, and map keys and values. JSON numbers compare by value (1 equals 1.0); strings and booleans remain distinct from numbers.
Vector type checks compare dimensions and element types when the data source reports them. logicalTypeOptions.elementType defaults to float32; a column reported as float64 or int8 does not satisfy that declaration. Catalogs that expose only a numeric array without its element width cannot confirm an element-type mismatch.
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. Declaringrequiredoruniquealongside 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_idnot null,line_numbernot 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
enum:
- value: pending
- value: shipped
- value: delivered
- name: tags
logicalType: array
items:
logicalType: string
logicalTypeOptions:
minItems: 1
maxItems: 10
uniqueItems: true
| Option | Fails when a value is… |
|---|---|
minLength / maxLength | shorter / longer than the bound |
minimum / maximum | below / above the bound (the bound itself passes) |
exclusiveMinimum / exclusiveMaximum | below / above or equal to the bound |
pattern | not matching the regular expression |
enum (property level, or logicalTypeOptions.enum) | not one of the listed values |
minItems / maxItems | an array with fewer / more elements than the bound |
uniqueItems | an array that repeats an element |
exclusiveMinimum and exclusiveMaximum each produce two checks — a bound check and an inequality check — so a violation of either is reported separately.
Descriptive metadata
ODCS v3.2.0 adds fields that describe a schema object or property for people and tools without generating a check: semanticType (column, measure, dimension), synonyms, deprecated, and a context block with instructions, verifiedStatements and constraints for AI agents and semantic layers. The HTML and Markdown exports render them, changelog reports changes to them by their natural key (a synonym's synonym or id, an enum entry's value or id), and export odcs keeps them. The DCS export drops them, as the Data Contract Specification has no equivalent.
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 readsrequired, notisNullable. Writerequired: trueto assert that a column has no nulls.logicalTypeOptions.format— never enforced, on any property. On astringproperty (email,uuid,uri, …) usepatternfor an enforceable equivalent. On adate,timestamportimepropertyformatholds a date pattern such asyyyy-MM-dd, andpatternis not an equivalent there: by the time a check runs, the column has already been parsed as a date, so there is no string left to match. Such a column is validated as a date, in whatever format the server stores it.- Descriptive attributes —
description,businessName,examples,tags,classification,criticalDataElement,transformSourceObjects, andcustomProperties.authoritativeDefinitionsgenerates 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, andquality.
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
- Add rules that go beyond structure: Define your Quality Rules.
- Declare freshness and retention promises: Define your Service Levels.
- Link a property to a shared business term instead of repeating it: Link your Semantics.
- Generate a schema block from an existing table instead of writing it by hand: Imports.