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
logicalTypeOptions.enum property Value is one of the listed values
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
        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:

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

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']
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 not 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:

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