Skip to main content

Custom Quality Checks

A custom quality check is a SQL check defined once, outside any contract, and used by its ID from as many contracts as need it. The contract names the check and supplies its arguments; the check holds the query.

schema:
- name: orders
properties:
- name: amount
logicalType: number
quality:
- type: custom
engine: datacontract-cli
implementation:
check: between
arguments:
min: 0
max: 10000

Point datacontract test at the folder the checks live in:

datacontract test datacontract.yaml --custom-quality-checks ./custom-quality-checks

or set DATACONTRACT_CUSTOM_QUALITY_CHECKS. See the test command reference.

Defining a check​

The folder holds one YAML file per check. The file name is the check's ID (between.yaml defines between). IDs are unique and must not contain whitespace or any of / \ % ; # ?.

# custom-quality-checks/between.yaml
name: Value range
description: All ${column} values are between ${arguments.min} and ${arguments.max}.
owner: data-platform
dimension: conformity
arguments:
min:
max:
queries:
default: |
SELECT COUNT(*) FROM ${table}
WHERE ${column} < ${arguments.min} OR ${column} > ${arguments.max}
mustBe: 0
KeyMeaning
nameA short display name (optional, only used when exporting to Entropy Data).
descriptionWhat the check verifies. Becomes the check's name in the results, with the placeholders and arguments filled in, unless the rule has a description of its own.
ownerWho maintains the check. Documentation only.
dimensionThe quality dimension the check measures. A dimension on the rule takes precedence.
argumentsThe arguments the check takes, each with an optional type and default. See Arguments.
queriesAt least one query, keyed by SQL dialect or default.
mustBe, mustBeGreaterThan, …The default expected result, one of the SQL rule comparators.

Arguments​

A check declares its arguments under arguments, and a rule passes their values under implementation.arguments. ${arguments.<argumentName>} in the check definition gets replaced by the respective value:

# custom-quality-checks/recent_rows.yaml
arguments:
timestamp_column: {type: identifier}
days: {type: number, default: 1}
queries:
default: |
SELECT COUNT(*) FROM ${table}
WHERE ${arguments.timestamp_column} >= CURRENT_DATE - ${arguments.days} * INTERVAL '1' DAY
mustBeGreaterThan: 0
# datacontract.yaml
implementation:
check: recent_rows
arguments:
timestamp_column: created_at
days: 7

The type in the check definition specifies how arguments are parsed:

TypeBecomesExample
any (default)An SQL literal; a list becomes comma-separated literals, as in IN (...)EUR → 'EUR', [A, B] → 'A', 'B'
numberA number literal"7" → 7
identifierA column or table name, quoted where needed like the placeholdersorder date → "order date"

Values are never inserted as raw SQL. Inside a single-quoted string, an any or number argument is inserted as escaped text, as in LIKE '%${arguments.text}%'.

Placeholders​

The queries use the same placeholders as SQL rules — ${table}, ${column}, ${schema}, and so on. A check whose queries use ${column} (or ${field}, ${property}) can only be declared on a property. As in placeholders, the $ in an argument reference is optional: {arguments.min} works the same as ${arguments.min}.

Dialects​

A run uses the query for the SQL dialect of the server, and otherwise the default query (if it exists):

queries:
default: |
SELECT COUNT(*) FROM ${table}
WHERE ${arguments.timestamp_column} >= CURRENT_DATE - ${arguments.days} * INTERVAL '1' DAY
tsql: |
SELECT COUNT(*) FROM ${table}
WHERE ${arguments.timestamp_column} >= DATEADD(day, -${arguments.days}, CAST(GETDATE() AS date))

The keys are the dialect names in the SQL dialect table, so a check for a mysql server needs a duckdb query. Write the default query in ANSI SQL. Note that on SAP HANA, only the default query runs.

Overriding the expected result​

The check's expected result is a default. A comparator on the rule replaces it:

quality:
- type: custom
engine: datacontract-cli
implementation:
check: recent_rows
arguments:
timestamp_column: created_at
mustBeGreaterThan: 10000