Data Contract Quality Rules Generation
DQX enables the generation of data quality rules directly from data contracts that follow the Open Data Contract Standard (ODCS). This feature allows teams to implement federated governance strategies by defining data contracts for their Data Products and automatically deriving comprehensive quality checks from those contracts.
Overview
The data contract-based quality rules generation is available via the DQGenerator class and supports generation of the following rule types:
- Schema Validation: One dataset-level
has_valid_schemarule per ODCS schema whengenerate_schema_validation=True(default), derived from the contract's schema definition. Usestrict_schema_validation=True(default) for exact column set, order, and types; set toFalsefor permissive validation (expected columns with compatible types, extra columns allowed). - Predefined Rules: Automatically derived from schema properties and constraints (required, unique, pattern, min/max, enum, format).
- Explicit Rules: Custom DQX rules embedded directly in the contract's quality section using DQX native format.
- Text-Based Rules: Natural language quality expectations that are processed using AI/LLM to generate appropriate checks (requires
[llm]extra).
This approach is beneficial when you want to:
- Implement standardised data contracts across your organisation
- Enable data product owners to define quality expectations in a familiar format
- Maintain version-controlled quality rules alongside schema definitions
- Trace quality checks back to contract specifications for compliance and lineage
Data contract-based generation is ideal for:
- Federated Data Governance: Enable data product teams to define and own their quality contracts
- Standardisation: Use industry-standard ODCS format for data contracts
- Version Control: Track data contracts and derived quality rules together
- Compliance: Maintain auditable links between contracts and quality checks
- Data Mesh: Support domain-oriented data ownership with clear contracts
For automated discovery of data patterns without contracts, consider using the Data Profiling approach instead. For generating rules from natural language without contracts, see AI-Assisted Generation.
ODCS v3.x Native Support
DQX natively supports Open Data Contract Standard (ODCS) v3.x contracts. ODCS is part of the Linux Foundation and provides a comprehensive format for defining data product contracts.
Key Features:
- Native Processing: DQX processes ODCS v3.x contracts directly without conversion
- Schema Validation: Generates one
has_valid_schemarule per schema when enabled (default strict; optional permissive) - Full Support: Extracts constraints from
logicalTypeOptions(pattern, minimum, maximum, minLength, maxLength) - Quality Sections: Handles explicit DQX rules via
implementationblocks in quality sections - Version Compatibility: Supports ODCS v3.0.x and above (no backward compatibility with older versions)
DQX leverages the datacontract-cli library to parse and validate contracts, ensuring full compatibility with the ODCS ecosystem.
Prerequisites
To use the data contract-based quality rules generation feature, you need to install DQX with the datacontract extra dependencies:
pip install 'databricks-labs-dqx[datacontract]'
This will install the required packages including datacontract-cli for ODCS contract parsing and validation.
If your contracts include text-based quality expectations (natural language descriptions), you'll also need to install the LLM dependencies:
pip install 'databricks-labs-dqx[datacontract,llm]'
Without the LLM dependencies, text-based expectations will be skipped with a warning, but predefined and explicit rules will still be generated.
Basic Usage
Loading from File Path
Here's a simple example of generating quality rules from a data contract file:
- Python
from databricks.labs.dqx.profiler.generator import DQGenerator
from databricks.sdk import WorkspaceClient
# Initialize the generator
ws = WorkspaceClient()
generator = DQGenerator(workspace_client=ws, spark=spark)
# Generate rules from contract file
rules = generator.generate_rules_from_contract(
contract_file="/Workspace/Shared/contracts/customers.yaml"
)
print(f"Generated {len(rules)} quality rules")
for rule in rules[:3]:
print(f"- {rule['name']}: {rule['check']['function']}")
Loading from DataContract Object
You can also pass a pre-loaded DataContract object from datacontract-cli:
- Python
from databricks.labs.dqx.profiler.generator import DQGenerator
from databricks.sdk import WorkspaceClient
from datacontract.data_contract import DataContract
ws = WorkspaceClient()
generator = DQGenerator(workspace_client=ws, spark=spark)
# Load contract using datacontract-cli
contract = DataContract(data_contract_file="/path/to/contract.yaml")
# Generate rules from contract object
rules = generator.generate_rules_from_contract(contract=contract)
print(f"Generated {len(rules)} quality rules")
Schema validation
When the contract defines a schema and generate_schema_validation=True (default), DQX generates one dataset-level rule per ODCS schema that calls has_valid_schema with the expected schema derived from the contract. Set generate_schema_validation=False to skip these rules. The strictness of schema validation is controlled by strict_schema_validation: default True (exact columns, order, and types); set to False for permissive validation (expected columns must exist with compatible types; extra columns are allowed).
Schema validation requires every property in the contract to have a physicalType field set to a valid Unity Catalog data type. Properties with only logicalType will cause an InvalidPhysicalTypeError.
# Default: schema validation rules generated, strict mode
rules = generator.generate_rules_from_contract(contract_file="/path/to/contract.yaml")
# Skip schema validation rules
rules = generator.generate_rules_from_contract(
contract_file="/path/to/contract.yaml",
generate_schema_validation=False,
)
# Permissive schema validation (allow extra columns)
rules = generator.generate_rules_from_contract(
contract_file="/path/to/contract.yaml",
strict_schema_validation=False,
)
- Recursive validation: Inner types of
ARRAY<T>,MAP<K,V>, andSTRUCT<...>are validated at every nesting level. Invalid inner types (e.g.ARRAY<NOT_A_TYPE>) raiseInvalidPhysicalTypeErrorat contract load time. - Nesting depth limit: Type nesting is limited to 50 levels to avoid stack overflow on malformed or excessively deep types. Exceeding this limit raises
InvalidPhysicalTypeError.
Generated schema validation rules include user_metadata.rule_type: "schema_validation" and the expected DDL string built from the contract's physicalType for each property.
Predefined Rule Generation
Predefined rules are automatically derived from schema field constraints defined in your data contract. DQX analyzes field properties and generates appropriate quality checks without requiring explicit rule definitions.
Supported ODCS v3.x Property Constraints
| Constraint | Location | Generated DQX Rule | Example |
|---|---|---|---|
required: true | Direct property attribute | is_not_null | All mandatory properties |
unique: true | Direct property attribute | is_unique | Primary keys, identifiers |
pattern | logicalTypeOptions | regex_match | Email, phone, ID formats |
minimum + maximum | logicalTypeOptions | is_in_range or sql_expression (floats) | Age, amount limits |
minimum only | logicalTypeOptions | is_aggr_not_less_than | Positive amounts |
maximum only | logicalTypeOptions | is_aggr_not_greater_than | Upper bounds |
minLength only | logicalTypeOptions | sql_expression (LENGTH >= N) | Minimum code length |
maxLength only | logicalTypeOptions | sql_expression (LENGTH <= N) | Maximum field size |
minLength + maxLength | logicalTypeOptions | sql_expression (both checks) | Fixed-length codes |
format | logicalTypeOptions | Logged (not yet implemented) | Date/timestamp formats |
Note: In ODCS v3.x, enum values are typically handled via pattern constraints or quality rules with type: library.
Example Contract with Predefined Rules
- Contract (YAML)
- Generated Rules (Python)
kind: DataContract
apiVersion: v3.0.2
id: urn:datacontract:ecommerce:orders
name: E-Commerce Orders
version: 1.0.0
status: active
domain: ecommerce
dataProduct: orders_data_product
schema:
- name: orders
physicalType: table
properties:
- name: order_id
logicalType: string
physicalType: string
required: true # → is_not_null
unique: true # → is_unique
logicalTypeOptions:
pattern: '^ORD-[0-9]{8}$' # → regex_match
- name: customer_email
logicalType: string
physicalType: string
required: true # → is_not_null
logicalTypeOptions:
pattern: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' # → regex_match
- name: order_total
logicalType: number
physicalType: decimal(10,2)
required: true # → is_not_null
logicalTypeOptions:
minimum: 0.01 # → sql_expression (floats)
maximum: 100000.00 # → sql_expression (floats)
- name: order_status
logicalType: string
physicalType: string
required: true # → is_not_null
logicalTypeOptions:
pattern: '^(pending|confirmed|shipped|delivered|cancelled)$' # → regex_match
from databricks.labs.dqx.profiler.generator import DQGenerator
from databricks.sdk import WorkspaceClient
ws = WorkspaceClient()
generator = DQGenerator(workspace_client=ws, spark=spark)
rules = generator.generate_rules_from_contract(
contract_file="orders_contract.yaml"
)
# Generated rules will include:
# - has_valid_schema (dataset-level, derived from the contract schema)
# - is_not_null for order_id, customer_email, order_total, order_status
# - is_unique for order_id
# - regex_match for order_id (pattern validation)
# - regex_match for customer_email (email validation)
# - sql_expression for order_total (float range: 0.01 to 100000.00)
# - regex_match for order_status (pattern validation)
Controlling Predefined Rule Generation
You can control whether predefined rules are generated:
- Python
# Generate only explicit rules, skip predefined ones
rules = generator.generate_rules_from_contract(
contract_file="contract.yaml",
generate_predefined_rules=False
)
# Generate all rules (default behavior)
rules = generator.generate_rules_from_contract(
contract_file="contract.yaml",
generate_predefined_rules=True
)
Setting Default Criticality
All predefined rules inherit a default criticality level:
- Python
# Generate predefined rules with 'warn' criticality (default is 'error')
rules = generator.generate_rules_from_contract(
contract_file="contract.yaml",
default_criticality="warn" # or "error"
)
# All predefined rules will have criticality: "warn"
# Explicit rules can still define their own criticality
Explicit Rule Generation
Explicit rules allow you to embed custom DQX quality checks directly within your ODCS v3.x data contract. This is useful for business logic rules that cannot be expressed as simple property constraints.
Defining Explicit Rules in ODCS v3.x Contracts
Explicit rules are defined in the quality section of a property or schema using type: custom with engine: dqx and an implementation block:
- Contract (YAML)
kind: DataContract
apiVersion: v3.0.2
id: urn:datacontract:finance:transactions
name: Financial Transactions
version: 1.0.0
status: active
domain: finance
dataProduct: transactions_data_product
schema:
- name: transactions
physicalType: table
properties:
- name: transaction_id
logicalType: string
physicalType: string
required: true
unique: true
- name: account_id
logicalType: string
physicalType: string
required: true
quality:
# Property-level explicit check
- type: custom
engine: dqx
description: Account ID must not be empty
implementation:
name: account_id_format
criticality: error
check:
function: is_not_null_and_not_empty
arguments:
column: account_id
trim_strings: true
- name: amount
logicalType: number
physicalType: decimal(10,2)
required: true
logicalTypeOptions:
minimum: 0
quality:
# Business logic rule
- type: custom
engine: dqx
description: Flag suspicious high-value transactions
implementation:
name: suspicious_transaction_flag
criticality: warn
check:
function: sql_expression
arguments:
expression: amount < 10000 OR verified = true
columns:
- amount
- verified
# Schema-level (dataset-level) quality checks
quality:
# Check data freshness
- type: custom
engine: dqx
description: Ensure transaction data is recent
implementation:
name: transaction_data_freshness
criticality: error
check:
function: is_data_fresh_per_time_window
arguments:
column: transaction_timestamp
window_minutes: 60
min_records_per_window: 1
lookback_windows: 2
# Check minimum row count
- type: custom
engine: dqx
description: Ensure dataset contains transactions
implementation:
name: minimum_transactions
criticality: warn
check:
function: is_aggr_not_less_than
arguments:
column: transaction_id
limit: 1
aggr_type: count
Explicit Rule Structure for ODCS v3.x
Each explicit DQX rule must follow this structure:
- type: custom # Required: marks as custom quality check
engine: dqx # Required: specifies DQX engine
description: ... # Optional: human-readable description
implementation: # Required: contains the DQX rule (ODCS v3.x)
name: rule_name # Required: unique rule name
criticality: error # Required: "error" or "warn"
check: # Required: the actual check definition
function: check_function_name
arguments:
# function-specific arguments
Unlike predefined rules (which use default_criticality), explicit rules must define their own criticality within the implementation block. This allows fine-grained control over different business rules.
Text-Based Rule Generation
Text-based rules allow you to define quality expectations in natural language, which are then processed by an LLM to generate appropriate DQX checks. This combines the power of data contracts with AI-assisted rule generation.
Prerequisites for Text-Based Rules
Text-based rule generation requires both the datacontract and llm extras:
pip install 'databricks-labs-dqx[datacontract,llm]'
Defining Text-Based Expectations
Text-based expectations are defined using type: text in the quality section:
- Contract (YAML)
kind: DataContract
apiVersion: v3.0.2
id: urn:datacontract:compliance:user_data
name: User Data (GDPR Compliant)
version: 1.0.0
status: active
schema:
- name: users
physicalType: table
# Dataset-level text expectation
quality:
- type: text
description: |
The dataset should not contain duplicate user records based on
email address. Users with the same email are considered duplicates
and should be flagged for review.
properties:
- name: email
logicalType: string
physicalType: string
required: true
quality:
# Field-level text expectation
- type: text
description: |
Email addresses must be valid and from approved corporate
domains only (@company.com or @partner.com). Personal email
domains like gmail.com or yahoo.com should be rejected.
- name: consent_date
logicalType: date
physicalType: date
required: true
quality:
# Another text expectation
- type: text
description: |
Consent date must not be in the future and should be within
the last 7 years to comply with GDPR retention policies.
Generating Rules with Text Expectations
- Python
from databricks.labs.dqx.profiler.generator import DQGenerator
from databricks.sdk import WorkspaceClient
ws = WorkspaceClient()
generator = DQGenerator(workspace_client=ws, spark=spark)
# Generate rules including text-based expectations (requires LLM)
rules = generator.generate_rules_from_contract(
contract_file="user_data_contract.yaml",
generate_predefined_rules=True, # Include schema-derived rules
process_text_rules=True # Process text expectations with LLM
)
# The generated rules will include:
# - Predefined rules from schema constraints
# - Explicit DQX rules (if any)
# - Rules generated from text expectations via LLM
Disabling Text Rule Processing
If you don't have LLM dependencies installed or want to skip text processing:
- Python
# Skip text-based rules, generate only predefined and explicit rules
rules = generator.generate_rules_from_contract(
contract_file="contract.yaml",
process_text_rules=False # Skip LLM processing
)
# Text expectations will be logged as warnings and skipped
Metric Rule Generation
ODCS quality entries with type: library (or, per the spec, no type at all as long as metric is set) reference a named, standard data quality metric instead of a natural-language description or a hand-written DQX check. DQX recognizes five of these metrics — rowCount, nullValues, missingValues, invalidValues, and duplicateValues — and maps each one onto the DQX check(s) that best express it.
Library metrics are for teams who are already ODCS-native, or who have existing ODCS contracts and want DQX enforcing them straight away. Point DQX at a contract you already have and you get checks immediately, with no rewrite — and the same five-metric vocabulary is understood by any other ODCS-compliant tool.
Treat that as a starting point. The five metrics cover the common cases, but not the rest of what DQX can check: no outlier detection, freshness, foreign keys, schema validation, or dataset comparison. Longer term you will get more out of defining checks natively, either as explicit rules with type: custom and engine: dqx or as text-based expectations — both give you the full DQX check surface and far more flexibility than a fixed metric vocabulary.
This five-metric surface is intentionally closed: DQX does not plan to grow this mapping into a general ODCS interpreter for arbitrary quality vocabularies.
Every type: library entry is processed whenever generate_rules_from_contract runs, unless disabled with process_library_rules=False (default True), matching the generate_predefined_rules/process_text_rules opt-out pattern used elsewhere. An entry that DQX cannot map is skipped with a logged warning instead of failing the whole contract (see Malformed and Unrecognized Entries).
Several of the mapping decisions below are not settled by the ODCS spec text. Where that's the case, DQX follows the interpretation already established by other ODCS-compliant tooling rather than inventing its own reading, so a contract produces the same verdict regardless of which tool evaluates it. Each judgment call is called out explicitly where it applies: duplicateValues's counting unit and invalidValues's combined-condition handling of validValues + pattern. Bound inclusivity for mustBeBetween/mustNotBeBetween is a different case — the spec is inconsistent rather than silent — and is covered under Threshold Fields and the SQL Fallback.
Defining Metric Rules in ODCS v3.x Contracts
A library quality entry sets a metric name (and optionally type: library) in the quality section of a property (for nullValues, missingValues, invalidValues, and the single-column form of duplicateValues) or a schema (for rowCount and the composite-key form of duplicateValues):
quality:
- type: library
metric: nullValues
mustBe: 0
dimension: completeness # optional; defaults per metric, see below
severity: critical # optional; recorded verbatim, DQX defines no vocabulary for it
Contracts written against ODCS 3.0.x may use the deprecated rule key instead of metric; DQX accepts both.
Every metric shares the same threshold fields (one of which must be set) and an optional unit — see Threshold Fields and the SQL Fallback and Rows vs. Percent below. dimension and severity are recorded in user_metadata for every metric rule; dimension falls back to a per-metric ODCS default when the contract omits it, and severity is only present when the contract sets it.
Supported Metrics
| Metric | Level | Default dimension | What it validates |
|---|---|---|---|
rowCount | Schema | completeness | The total row count of the dataset. |
nullValues | Property | completeness | The count or percentage of NULL values in a column. |
missingValues | Property | completeness | The count or percentage of NULLs plus contract-supplied sentinel values (e.g. "", "N/A") in a column. |
invalidValues | Property | conformity | The count or percentage of values in a column that fail an allowlist and/or a regex pattern. |
duplicateValues | Property (single column) or Schema (composite key) | uniqueness | The count or percentage of distinct values (or key combinations) that occur more than once. |
nullValues, missingValues, and invalidValues are property-level only; a schema-level entry for one of these is skipped with a warning. rowCount is schema-level only. duplicateValues supports both: a property-level entry with no arguments checks that single column, while a schema-level entry reads a composite key from arguments.properties.
Threshold Fields and the SQL Fallback
Every library metric is evaluated against the eight ODCS threshold fields: mustBe, mustNotBe, mustBeGreaterOrEqualTo, mustBeLessOrEqualTo, mustBeGreaterThan, mustBeLessThan, mustBeBetween, mustNotBeBetween. Each field set on an entry produces its own rule, so a range written as two fields on one entry (e.g. mustBeGreaterOrEqualTo: 10 and mustBeLessOrEqualTo: 1000) enforces both bounds.
mustBe: 0is special-cased fornullValues,missingValues,invalidValues, andduplicateValues: it maps onto a cheap row-level check (is_not_null,is_not_in_list,is_in_list/regex_match, oris_unique) that pinpoints the offending rows, rather than a dataset-level count.mustBe,mustNotBe,mustBeGreaterOrEqualTo,mustBeLessOrEqualTo(includingmustBewith a non-zero value) map onto exact-fit dataset-level aggregate checks —is_aggr_equal,is_aggr_not_equal,is_aggr_not_less_than,is_aggr_not_greater_than— over the metric's count or percentage, forrowCount,nullValues,missingValues, andinvalidValues.mustBeGreaterThan,mustBeLessThan,mustBeBetween,mustNotBeBetweenhave no strict-inequality or range equivalent among DQX's aggregate checks, so they fall back to a dataset-levelsql_querycheck. FormustBeBetween/mustNotBeBetween, both bounds are inclusive — a value exactly equal to either bound counts as being "between" them. The ODCS spec is inconsistent here: its operator table writesmustBeBetween: [0, 100]as∈(a closed interval), while a note calls it equivalent tomustBeGreaterThan+mustBeLessThan. DQX follows SQLBETWEEN, somustBeBetween: [0, 5]passes a column with no nulls.duplicateValuesis the one exception: every non-mustBe: 0threshold falls back tosql_query, not just the four above. See duplicateValues below for exactly what it counts.
An entry with none of the eight fields set is skipped with a warning naming all eight, so a contract author can spot a typo (e.g. mustbe instead of mustBe) without reading DQX source. Threshold values must be numbers (a numeric string such as "0" is accepted), and mustBeBetween/mustNotBeBetween must be two numbers, smallest first; anything else is skipped with a warning.
Rows vs. Percent
The optional unit field controls whether a non-mustBe: 0 threshold is evaluated as a raw row count (unit: rows, the default) or a percentage of all rows (unit: percent). This applies to nullValues, missingValues, invalidValues, and duplicateValues. rowCount is inherently a count and accepts only unit: rows. Any other unit value causes the entry's thresholds to be skipped with a warning, except mustBe: 0, which ignores the unit, since zero rows and zero percent are the same fact. On an empty dataset a percentage is treated as 0.
rowCount
- Contract (YAML)
schema:
- name: orders
physicalType: table
quality:
- type: library
metric: rowCount
mustBeGreaterOrEqualTo: 1 # → is_aggr_not_less_than(column="*", aggr_type="count")
A mustBeGreaterThan, mustBeLessThan, mustBeBetween, or mustNotBeBetween threshold on rowCount falls back to a sql_query check counting COUNT(*) over the whole dataset.
nullValues
- Contract (YAML)
properties:
- name: customer_email
logicalType: string
physicalType: string
quality:
- type: library
metric: nullValues
mustBe: 0 # → row-level is_not_null(column="customer_email")
- name: middle_name
logicalType: string
physicalType: string
quality:
# `type: library` can be omitted when `metric` is set, which is how the ODCS
# spec's own examples are written -- DQX recognizes both forms.
- metric: nullValues
mustBeLessOrEqualTo: 5
unit: percent # → dataset-level check on the null percentage
missingValues
missingValues combines real NULLs with a contract-supplied sentinel list, so it needs arguments.missingValues — a list that may include a literal null alongside sentinel strings:
- Contract (YAML)
properties:
- name: country_code
logicalType: string
physicalType: string
quality:
- type: library
metric: missingValues
mustBe: 0
arguments:
missingValues: [null, "", "N/A", "UNKNOWN"]
# → row-level is_not_null (for the null entry)
# + row-level is_not_in_list(forbidden=["", "N/A", "UNKNOWN"])
NULL is always counted as missing, whether or not a literal null appears in arguments.missingValues — listing it is redundant, not required. With mustBe: 0, a row-level is_not_null rule is always generated, plus a row-level is_not_in_list rule when non-null sentinels are present; both rules share the same user_metadata and are OR'd together via DQX's own per-row error/warning union, since is_not_in_list's forbidden list can never itself match a real SQL NULL. Any other threshold routes through a dataset-level count/percentage of rows matching column IS NULL OR column IN (...) — the same unconditional-NULL condition, so the metric means the same thing at every threshold.
invalidValues
invalidValues accepts an allowlist (arguments.validValues), a regex (arguments.pattern), or both — a row fails if it matches neither. When both are present, DQX combines them into a single "invalid" condition (NOT (in the allowlist) OR NOT (matches the pattern)) rather than treating them as two independent thresholds, so mustBeLessOrEqualTo: 5 means at most 5 rows failing either criterion, not up to 5 failing each independently. This combination is a DQX judgment call: other ODCS tooling commonly honors only validValues and ignores arguments.pattern for invalidValues, so pattern support here goes beyond what most implementations cover, even though the spec documents the field:
- Contract (YAML)
properties:
- name: order_status
logicalType: string
physicalType: string
quality:
- type: library
metric: invalidValues
mustBe: 0
arguments:
validValues: ["pending", "confirmed", "shipped", "delivered", "cancelled"]
# → row-level is_in_list(allowed=[...], case_sensitive=true)
- name: postal_code
logicalType: string
physicalType: string
quality:
- type: library
metric: invalidValues
mustBeLessOrEqualTo: 0.5
unit: percent
arguments:
pattern: '^[0-9]{5}$'
# → dataset-level check on the invalid percentage
arguments.pattern is a Java regular expression (Spark evaluates it with Java's regex engine) and is checked at generation time. A pattern that fails a check is treated as absent — its own warning is logged, and the entry still proceeds using validValues alone if present. A pattern is rejected when it:
- Could cause catastrophic backtracking (ReDoS): it is longer than 200 characters, or it repeats (
+,*,{m,},{m,n}) a group that contains a variable quantifier or an alternation, at any nesting depth — e.g.(a+)+,((a+))+,(\w+\s?)+or(a|ab)*. Optional groups ((a+)?) and exact counts ((\d{3})+) are allowed. - Uses Python-only syntax that Java rejects:
(?P<name>...)(write(?<name>...)),(?#...)comments, conditionals or{,n}. - Is not a valid regular expression, e.g. an unclosed group or class.
Java syntax such as Unicode classes (\p{L}), possessive quantifiers (\d++), atomic groups and inline flags is accepted.
duplicateValues
The single-column, argument-less form is a property-level entry; the composite-key form is a schema-level entry with arguments.properties.
For a non-mustBe: 0 threshold, duplicateValues counts the number of distinct values (or key combinations) that recur — for a column holding [A, A, A, B, B, C] that's 2 (A and B each recur), not 5 (the rows sitting in those two groups). unit: percent divides that count by the total row count. This matches how other ODCS-compliant tooling counts duplicates; the ODCS spec text ("Counts duplicate values in a column") doesn't settle it either way, so DQX follows the established interpretation rather than counting affected rows. mustBe: 0 is unaffected by this choice, since both readings agree at zero.
- Single column (property-level)
- Composite key (schema-level)
properties:
- name: order_id
logicalType: string
physicalType: string
quality:
- type: library
metric: duplicateValues
mustBe: 0 # → row-level is_unique(columns=["order_id"])
schema:
- name: order_lines
physicalType: table
quality:
- type: library
metric: duplicateValues
mustBeLessOrEqualTo: 1
unit: percent
arguments:
properties: [order_id, line_number]
# → dataset-level check on the percentage of (order_id, line_number)
# combinations that recur
Rule Names and Row Filters
Each library entry produces rules named <property>_<metric>_<thresholdField> (or <schema>_<metric>_<thresholdField> for schema-level entries), e.g. email_nullValues_mustBeLessOrEqualTo. Where mustBe: 0 produces more than one row-level rule, each carries a suffix naming its part (_null/_sentinel for missingValues, _allowed/_pattern for invalidValues). Two entries that would still share a name, such as two mustBeLessOrEqualTo entries on the same property, get _2, _3, ... suffixes, so a range expressed as two entries never collides. Suffixes also skip names already used by other rules generated from the contract (for example an explicit rule), so every generated rule name is unique.
A row_filter (or rowFilter) custom property on a library entry is honoured the same way as on an explicit rule: it is emitted as the rule's filter, so the metric is computed over, and reported on, the matching rows only.
quality:
- metric: nullValues
mustBeLessOrEqualTo: 5
customProperties:
- property: row_filter
value: "country = 'UK'"
Malformed and Unrecognized Entries
DQX warns and skips an individual type: library entry — logging the reason and continuing to process the rest of the contract — rather than failing generation, whenever it encounters:
- A missing or unrecognized
metricname (the warning names all five supported metrics). - No recognized threshold field set (none of the eight
mustBe.../mustNotBe...fields). A threshold field whose value is not a number is skipped on its own; the entry's other valid threshold fields still produce rules. - An unrecognized
unit(anything other thanrowsorpercent, or anything other thanrowsforrowCount). Only the entry's thresholds other thanmustBe: 0are skipped, sincemustBe: 0ignores the unit; the warning is still logged, and a keptmustBe: 0rule recordsunit: rows— see Rows vs. Percent. - A schema-level entry for a property-only metric (
nullValues,missingValues,invalidValues), or a property-levelrowCountentry. - Malformed
arguments— not a dict, a missing or wrong-typed key (e.g.arguments.missingValuesnot a list of scalar values, or containingNaN/infinity), or an empty list where a non-empty one is required. - For
invalidValues, an entry with neither a usablearguments.validValueslist nor a usablearguments.patternstring (after the pattern safety check, described above).
This per-entry warn-and-skip behavior is independent of process_library_rules: even with process_library_rules=True (the default), an individual malformed entry is skipped rather than failing the whole contract. Set process_library_rules=False to skip all type: library entries instead — e.g. if a contract's library entries need to be ignored altogether.
DQX does not deduplicate library metric rules against predefined rules generated from schema constraints. A property marked required: true (which generates a predefined is_not_null rule) that also carries a nullValues/mustBe: 0 entry produces two separate is_not_null rules under different names. Both are harmless (they agree on every row), but if you want only one, disable one of the two sources — e.g. generate_predefined_rules=False or omit the redundant nullValues entry.
Disabling Library Metric Processing
If a contract's type: library entries misbehave, or you'd rather express quality entirely through explicit rules:
- Python
# Skip type: library processing, generate only predefined/explicit/text rules
rules = generator.generate_rules_from_contract(
contract_file="contract.yaml",
process_library_rules=False # Skip library metric mapping
)
Complete Usage Example
Here's a complete example showing contract-based rule generation with every rule type. Each generated rule records how it was produced in user_metadata.rule_type:
- Predefined (
rule_type: "predefined"): derived from the schema, e.g.required,uniqueandlogicalTypeOptionssuch aspattern,minimumandmaximum. - Explicit (
rule_type: "explicit"): DQX-native checks declared astype: customwithengine: dqx. - Text (
rule_type: "text_llm"):type: textexpectations translated into checks by the LLM (requiresprocess_text_rules=True). - Metric (
rule_type: "metric"): ODCStype: librarymetrics such asrowCountornullValues, see Metric Rule Generation. - Schema validation (
rule_type: "schema_validation"): onehas_valid_schemarule per schema (disable withgenerate_schema_validation=False).
- Contract (YAML)
- Python Code
kind: DataContract
apiVersion: v3.0.2
id: urn:datacontract:iot:sensor_readings
name: IoT Sensor Readings
version: 2.1.0
status: active
description:
purpose: Quality contract for IoT sensor data
team:
- username: iot-platform-team
role: owner
schema:
- name: sensor_readings
physicalType: table
description: Real-time sensor readings from IoT devices
quality:
# Explicit (rule_type: explicit): DQX-native dataset-level check
- type: custom
engine: dqx
description: Ensure sensor data is fresh and recent
implementation:
name: sensor_data_freshness
criticality: error
check:
function: is_data_fresh_per_time_window
arguments:
column: reading_timestamp
window_minutes: 60
min_records_per_window: 1
lookback_windows: 24
# Text (rule_type: text_llm): expectation translated into a check by the LLM
- type: text
description: |
The dataset should not contain duplicate sensor readings for the
same sensor_id and reading_timestamp combination.
# Metric (rule_type: metric): ODCS library metric, the table must not be empty
- type: library
metric: rowCount
mustBeGreaterOrEqualTo: 1
# Predefined (rule_type: predefined): derived from the property definitions marked below
properties:
- name: sensor_id
logicalType: string
physicalType: string
required: true # Predefined: is_not_null
unique: true # Predefined: is_unique
logicalTypeOptions:
pattern: '^SENSOR-[A-Z]{2}-[0-9]{4}$' # Predefined: regex_match
- name: temperature
logicalType: decimal
physicalType: decimal(5,2)
required: true # Predefined: is_not_null
logicalTypeOptions:
minimum: -40.0 # Predefined: sql_expression (range check)
maximum: 125.0 # Predefined: sql_expression (range check)
quality:
# Text (rule_type: text_llm): property-level expectation
- type: text
description: |
Temperature readings outside normal operating range (-10°C to 50°C)
should be flagged as warnings for manual review.
- name: status
logicalType: string
physicalType: string
required: true # Predefined: is_not_null
logicalTypeOptions:
pattern: '^(active|maintenance|offline)$' # Predefined: regex_match
- name: battery_level
logicalType: integer
physicalType: int
quality:
# Metric (rule_type: metric): at most 5% of readings may lack a battery level
- type: library
metric: nullValues
mustBeLessOrEqualTo: 5
unit: percent
from databricks.labs.dqx.profiler.generator import DQGenerator
from databricks.labs.dqx.engine import DQEngine
from databricks.sdk import WorkspaceClient
# Initialize
ws = WorkspaceClient()
generator = DQGenerator(workspace_client=ws, spark=spark)
# Generate every rule type from the contract (explicit rules are always generated)
rules = generator.generate_rules_from_contract(
contract_file="/Workspace/Shared/contracts/sensor_readings.yaml",
generate_predefined_rules=True, # Predefined (rule_type: predefined): schema-derived rules
process_text_rules=True, # Text (rule_type: text_llm): LLM-processed expectations
process_library_rules=True, # Metric (rule_type: metric): ODCS library metrics
generate_schema_validation=True, # Schema validation (rule_type: schema_validation): has_valid_schema
default_criticality="error" # Default for predefined rules
)
print(f"Generated {len(rules)} quality rules:")
# Group rules by rule_type
predefined_rules = [r for r in rules if r['user_metadata']['rule_type'] == 'predefined']
explicit_rules = [r for r in rules if r['user_metadata']['rule_type'] == 'explicit']
text_rules = [r for r in rules if r['user_metadata']['rule_type'] == 'text_llm']
metric_rules = [r for r in rules if r['user_metadata']['rule_type'] == 'metric']
schema_rules = [r for r in rules if r['user_metadata']['rule_type'] == 'schema_validation']
print(f" - {len(predefined_rules)} predefined (from schema properties)")
print(f" - {len(explicit_rules)} explicit (DQX native)")
print(f" - {len(text_rules)} text_llm (LLM-generated)")
print(f" - {len(metric_rules)} metric (ODCS library metrics)")
print(f" - {len(schema_rules)} schema_validation (has_valid_schema)")
# Apply rules to data
engine = DQEngine(ws)
df = spark.read.table("iot.production.sensor_readings")
result_df = engine.apply_checks_by_metadata(df, rules)
# Save generated rules
from databricks.labs.dqx.config import WorkspaceFileChecksStorageConfig
engine.save_checks(
checks=rules,
config=WorkspaceFileChecksStorageConfig(
location="/Shared/DataQuality/sensor_readings_checks.yml"
)
)
Contract Metadata and Lineage
All generated rules include rich metadata that traces them back to the source contract for lineage and governance:
{
"name": "sensor_id_not_null",
"criticality": "error",
"check": {
"function": "is_not_null",
"arguments": {"column": "sensor_id"}
},
"user_metadata": {
"contract_id": "urn:datacontract:iot:sensor_readings",
"contract_version": "2.1.0",
"odcs_version": "v3.0.2",
"schema": "sensor_readings",
"field": "sensor_id",
"rule_type": "predefined" # or "explicit", "text_llm", "metric" or "schema_validation"
}
}
Metadata Fields
| Field | Description | Example |
|---|---|---|
contract_id | Unique identifier from contract | urn:datacontract:iot:sensor_readings |
contract_version | Contract version | 2.1.0 |
odcs_version | ODCS API version (standard version) | v3.0.2 |
schema | Schema/table name (ODCS v3.x) | sensor_readings |
field | Property/column name (single-field property-level rule) | sensor_id |
fields | Composite key columns as a JSON-encoded list, since user_metadata is a string map (schema-level duplicateValues metric rule only) | '["order_id", "line_number"]' |
rule_type | How rule was generated | predefined, explicit, text_llm, metric (see Metric Rule Generation), or schema_validation |
text_expectation | Original text (for text-based rules only) | Natural language description |
metric | ODCS library metric name (rule_type: "metric" only) | rowCount, nullValues, missingValues, invalidValues, or duplicateValues |
threshold_field | ODCS threshold field that produced the check (rule_type: "metric" only) | mustBe, mustBeGreaterOrEqualTo, mustBeBetween, etc. |
unit | Threshold unit for the metric (rule_type: "metric" only) | rows (default) or percent |
dimension | Data quality dimension — the contract's own dimension (whitespace trimmed) if set and not blank, else the metric's ODCS default | completeness, uniqueness, or conformity |
severity | Contract's own severity value, recorded verbatim for audit only (never used to derive criticality); present only when the contract sets it | Any contract-defined string |
This metadata enables:
- Traceability: Link quality check results back to contract specifications
- Impact Analysis: Understand which rules are affected by contract changes
- Governance: Demonstrate compliance with data contracts
- Debugging: Identify the source of quality rules during troubleshooting
Combining with Other Rule Sources
Data contract-based rules can be combined with rules from other sources for comprehensive coverage:
- Python
from databricks.labs.dqx.profiler.profiler import DQProfiler
from databricks.labs.dqx.profiler.generator import DQGenerator
from databricks.sdk import WorkspaceClient
ws = WorkspaceClient()
# Step 1: Profile the data to discover statistical patterns
profiler = DQProfiler(workspace_client=ws, spark=spark)
df = spark.read.table("production.sales_data")
summary_stats, profiles = profiler.profile(df)
generator = DQGenerator(workspace_client=ws, spark=spark)
# Step 2: Generate profiler-based rules (technical/statistical)
profiler_rules = generator.generate_dq_rules(profiles)
# Step 3: Generate contract-based rules (business/compliance)
contract_rules = generator.generate_rules_from_contract(
contract_file="/Shared/contracts/sales_data_contract.yaml"
)
# Step 4: Combine all rules
all_rules = profiler_rules + contract_rules
print(f"Total rules: {len(all_rules)}")
print(f" - Profiler (discovered): {len(profiler_rules)}")
print(f" - Contract (specified): {len(contract_rules)}")
# Apply combined rules
from databricks.labs.dqx.engine import DQEngine
engine = DQEngine(ws)
result_df = engine.apply_checks_by_metadata(df, all_rules)
Best Practices
-
Version Your Contracts: Store data contracts in version control (Git) alongside your code and track changes over time.
-
Validate Contracts Externally: Use
datacontract-clito validate contracts before using them with DQX:pip install datacontract-clidatacontract lint /path/to/contract.yaml -
Start with Predefined Rules: Begin with schema-based predefined rules, then add explicit rules for business logic that cannot be expressed as constraints.
-
Use Text Expectations Judiciously: Text-based rules are powerful but require LLM processing. Use them for complex business logic that's easier to express in natural language.
-
Review Generated Rules: Always review the generated rules before applying to production data, especially text-based rules generated by LLM.
-
Set Appropriate Criticality: Use
errorfor critical violations that should block pipelines andwarnfor informational checks. -
Combine Approaches: Use profiling for discovering technical patterns and contracts for codifying business requirements.
-
Document in Contracts: Use the
descriptionfields in contracts to explain business context and rationale for constraints. -
Track Lineage: Leverage the contract metadata in generated rules to maintain traceability from checks back to contracts.
-
Iterate on Contracts: Treat contracts as living documents that evolve with your data and business requirements.
Example Use Cases
Use Case 1: Federated Data Governance
Enable domain teams to define and own quality contracts:
# Data product team defines their contract
contract_yaml = """
kind: DataContract
apiVersion: v3.0.2
id: urn:datacontract:marketing:customer_360
name: Customer 360 View
version: 1.2.0
status: active
team:
- username: marketing-analytics-team
role: owner
schema:
- name: customer_360
physicalType: table
properties:
- name: customer_id
logicalType: string
physicalType: string
required: true
unique: true
- name: email
logicalType: string
physicalType: string
required: true
logicalTypeOptions:
pattern: '^[a-zA-Z0-9._%+-]+@.+$'
- name: lifetime_value
logicalType: decimal
physicalType: decimal(18,2)
logicalTypeOptions:
minimum: 0
"""
# Central platform team generates and applies rules
generator = DQGenerator(workspace_client=ws, spark=spark)
rules = generator.generate_rules_from_contract(contract_file="customer_360_contract.yaml")
# Rules are automatically tracked back to the owning team's contract
Use Case 2: GDPR Compliance
Define compliance requirements in contracts:
contract_yaml = """
kind: DataContract
apiVersion: v3.0.2
id: urn:datacontract:gdpr:personal_data
name: GDPR Personal Data
version: 1.0.0
status: active
schema:
- name: user_data
physicalType: table
properties:
- name: email
logicalType: string
physicalType: string
required: true
- name: consent_date
logicalType: date
physicalType: date
required: true
quality:
- type: text
description: |
Consent date must be within the last 7 years to comply with
GDPR data retention policies. Records older than 7 years should
be flagged for deletion.
"""
# Generate compliance-focused rules
rules = generator.generate_rules_from_contract(
contract_file="gdpr_contract.yaml",
process_text_rules=True
)
# Apply rules and track violations
engine = DQEngine(ws)
result_df = engine.apply_checks_by_metadata(df, rules)
Use Case 3: Data Mesh with Contracts
Implement data mesh principles with contracts as interfaces:
# Producer team defines data product contract
producer_contract = "/Volumes/catalog/schemas/contracts/sales_transactions_v2.yaml"
# Consumer team generates rules from producer's contract
generator = DQGenerator(workspace_client=ws, spark=spark)
rules = generator.generate_rules_from_contract(contract_file=producer_contract)
# Apply rules on consumer side to validate producer data
df = spark.read.table("sales_domain.published.transactions")
result_df, bad_df = engine.apply_checks_by_metadata_and_split(df, rules)
# Alert if producer violates contract
if bad_df.count() > 0:
print(f"Contract violation detected! {bad_df.count()} rows failed quality checks")
# Trigger alert to producer team
Troubleshooting
Error: datacontract-cli not installed
Problem: You receive an error saying "Data contract functionality requires datacontract-cli".
Solution: Install the datacontract dependencies:
pip install 'databricks-labs-dqx[datacontract]'
Contract Validation Errors
Problem: Contract fails validation or generates unexpected rules.
Solution:
- Validate your contract externally using
datacontract-cli:datacontract lint /path/to/contract.yaml - Ensure your contract follows ODCS v3.x specification (
kind: DataContract,apiVersion: v3.0.x) - Check that constraints are in
logicalTypeOptionsfor ODCS v3.x - Review the ODCS specification
Text Rules Not Generated
Problem: Text-based expectations are skipped with warnings.
Solution:
- Install LLM dependencies:
pip install 'databricks-labs-dqx[datacontract,llm]' - Ensure
process_text_rules=Trueis set (default) - Check LLM model access and credentials
- See AI-Assisted Generation Troubleshooting
Missing Rules for Some Fields
Problem: Expected predefined rules are not generated for certain fields.
Solution:
- Verify field constraints are properly defined in the contract
- Check that
generate_predefined_rules=True(default) - Review supported constraint types in the Predefined Rule Generation section
- Some constraints may require specific formats (e.g.,
format: '%Y-%m-%d'for dates)
Explicit Rules Not Recognized
Problem: Explicit DQX rules defined in contract are not generated.
Solution:
- Ensure you're using
type: customandengine: dqx - Verify the rule structure matches the required format (see Explicit Rule Generation)
- Check that the rule is wrapped in an
implementationblock (ODCS v3.x) - Validate YAML syntax and indentation
Contract Metadata Not Preserved
Problem: Generated rules don't include contract metadata.
Solution:
- Ensure your contract includes
idandinfo.versionfields - Verify you're examining the
user_metadatafield of generated rules - Check that you're using a recent version of DQX (≥0.8.0)
Limitations
- The data contract feature requires the
datacontract-clilibrary which increases package size. - Text-based rule generation requires LLM dependencies and network access to the model endpoint.
- Only ODCS v3.0.x format is currently supported (earlier versions are not supported).
- Dataset-level checks beyond the
rowCountandduplicateValueslibrary metrics must be defined as explicit DQX rules (custom quality checks). - Generated rules quality depends on the completeness and correctness of the contract definition.
- Complex business logic (text based rules) may still require manual rule definition or refinement.
type: librarycovers five metrics only (see Metric Rule Generation) and is not intended to grow into a general ODCS interpreter; usetype: customwithengine: dqxfor anything beyond simple row-count/null/duplicate/allowlist checks.