Get Help

Test schemas and metadata

Before the data is right, the structure has to be: the tables the design says exist, the columns with the types the design says, the __inserted_at every bronze table must have. The design is a spreadsheet, reality is a catalog view, and both are data sources — so the structure is tested with the same five expectations as the data.

The design against reality

Most analytical solutions are built from a sheet: a list of tables and columns with types, owners, layers — the spec the developers worked from, kept in Excel or CSV in the repo. That sheet is a data source for CAT (Excel@2 / Csv@2), the catalog of the platform is another (INFORMATION_SCHEMA, sys.columns, Unity Catalog’s system.information_schema), and the test is a comparison between the two.

Name
Every designed column exists with its type
Suite
Structure
First data source
design sheet
First query
SELECT  schema_name, table_name, column_name, data_type
FROM    design.Columns
WHERE   layer = 'silver'
ORDER BY schema_name, table_name, column_name
Second data source
lakehouse
Second query
SELECT  table_schema, table_name, column_name, data_type
FROM    system.information_schema.columns
WHERE   table_catalog = 'analytics'
        AND table_schema IN ('silver_sales', 'silver_crm')
ORDER BY table_schema, table_name, column_name
Expectation
contains
Key
1, 2, 3
Maximum errors logged
50
Data sources:
- Name: design sheet
  Provider: Excel@2
  Connection string: design/Analytics-Design.xlsx     # in the repo, next to the project file
  Sheets: Columns
  Normalize column names: true
- Name: lakehouse
  Provider: Odbc@1
  Connection string: "%DATABRICKS_CONNECTION_STRING%"

Tests:
- Name: Every designed column exists with its type
  Suite: Structure
  Description: Each column of the silver layer in the design sheet exists in Unity Catalog with the designed type; extra columns in the catalog are allowed.
  First data source: lakehouse
  First query: |
    SELECT  table_schema, table_name, column_name, data_type
    FROM    system.information_schema.columns
    WHERE   table_catalog = 'analytics'
            AND table_schema IN ('silver_sales', 'silver_crm')
    ORDER BY table_schema, table_name, column_name
  Second data source: design sheet
  Second query: |
    SELECT  schema_name, table_name, column_name, data_type
    FROM    design.Columns
    WHERE   layer = 'silver'
    ORDER BY schema_name, table_name, column_name
  Expectation: contains
  Key: 1, 2, 3
  Maximum errors logged: 50

contains with the catalog as the first (super)set: every designed column must exist, extra technical columns may. Swap to sets match when nothing undesigned may exist either. The composite key — schema, table, column — makes the message name the exact column that is missing, and show the designed type next to the real one when they differ. One test per layer; the same with tables only (…information_schema.tables against the design’s table list) is the cheaper first check.

The sheet’s types and the catalog’s have to be spelled the same way (string vs STRING, decimal(18,2) vs DECIMAL(18,2)) — CAT ignores case, not spelling; a LOWER() or a mapping column in the sheet does the rest. See Differences between systems.

Mandatory columns per layer

“Every table in bronze has __inserted_at and __source_file.” One template over the list of tables, one generated test per table — and the table created next week gets its test on the next open:

Name
Bronze table %table_schema%.%table_name% has the technical columns
Suite
Structure
Metadata
bronze tables
Data source
lakehouse
Query
SELECT  expected.column_name
FROM    (SELECT '__inserted_at' AS column_name UNION ALL SELECT '__source_file') AS expected
WHERE   expected.column_name NOT IN (
          SELECT column_name FROM system.information_schema.columns
          WHERE  table_catalog = 'analytics'
                 AND table_schema = '%table_schema%' AND table_name = '%table_name%')
Expectation
set is empty
Queries:
- Name: bronze tables
  Data source: lakehouse
  Query: |
    SELECT  table_schema, table_name
    FROM    system.information_schema.tables
    WHERE   table_catalog = 'analytics' AND table_schema LIKE 'bronze%'

Tests:
- Name: Bronze table %table_schema%.%table_name% has the technical columns
  Suite: Structure
  Metadata: bronze tables
  Data source: lakehouse
  Query: |
    SELECT  expected.column_name
    FROM    (SELECT '__inserted_at' AS column_name UNION ALL SELECT '__source_file') AS expected
    WHERE   expected.column_name NOT IN (
              SELECT column_name FROM system.information_schema.columns
              WHERE  table_catalog = 'analytics'
                     AND table_schema = '%table_schema%' AND table_name = '%table_name%')
  Expectation: set is empty

The violation query returns the names of the mandatory columns a table lacks — so the message says which. The list of mandatory columns can itself come from the design sheet instead of the UNION ALL, and the catalog query is the same on SQL Server (INFORMATION_SCHEMA.COLUMNS), PostgreSQL, Snowflake, Fabric. How templates expand and what happens when the metadata query returns nothing: Generate tests from metadata.

Rules about the structure itself

The catalog is a table like any other; rules about it are violation queries:

Name
No column name with a space or a reserved word
Suite
Structure
Data source
DWH
Query
SELECT  TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
FROM    INFORMATION_SCHEMA.COLUMNS
WHERE   TABLE_SCHEMA IN ('dim', 'fact')
        AND (COLUMN_NAME LIKE '% %' OR COLUMN_NAME IN ('order', 'user', 'date', 'key'))
Expectation
set is empty
Name
Every fact table has a clustered columnstore index
Suite
Structure
Data source
DWH
Query
SELECT  s.name, t.name
FROM    sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id
WHERE   s.name = 'fact'
        AND NOT EXISTS (SELECT 1 FROM sys.indexes i WHERE i.object_id = t.object_id AND i.type = 5)
Expectation
set is empty
Name
Money columns are DECIMAL(19,4) everywhere
Suite
Structure
Data source
DWH
Query
SELECT  TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE
FROM    INFORMATION_SCHEMA.COLUMNS
WHERE   COLUMN_NAME LIKE '%Amount%'
        AND NOT (DATA_TYPE = 'decimal' AND NUMERIC_PRECISION = 19 AND NUMERIC_SCALE = 4)
Expectation
set is empty
- Name: No column name with a space or a reserved word
  Suite: Structure
  Data source: DWH
  Query: |
    SELECT  TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
    FROM    INFORMATION_SCHEMA.COLUMNS
    WHERE   TABLE_SCHEMA IN ('dim', 'fact')
            AND (COLUMN_NAME LIKE '% %' OR COLUMN_NAME IN ('order', 'user', 'date', 'key'))
  Expectation: set is empty

- Name: Every fact table has a clustered columnstore index
  Suite: Structure
  Data source: DWH
  Query: |
    SELECT  s.name, t.name
    FROM    sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id
    WHERE   s.name = 'fact'
            AND NOT EXISTS (SELECT 1 FROM sys.indexes i WHERE i.object_id = t.object_id AND i.type = 5)
  Expectation: set is empty

- Name: Money columns are DECIMAL(19,4) everywhere
  Suite: Structure
  Data source: DWH
  Query: |
    SELECT  TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE
    FROM    INFORMATION_SCHEMA.COLUMNS
    WHERE   COLUMN_NAME LIKE '%Amount%'
            AND NOT (DATA_TYPE = 'decimal' AND NUMERIC_PRECISION = 19 AND NUMERIC_SCALE = 4)
  Expectation: set is empty

Primary keys present, no nullable key columns, every table documented (a description in the design sheet for every table in the catalog — contains the other way round), every dimension with an IsCurrent column — the same shape each time.

Why this is worth a suite of its own

Structure breaks before data does: a column renamed in a migration, a type narrowed by a tool, a new bronze table without the technical columns — and the loads run green until something downstream falls over. A structure suite is cheap (catalog views are small), runs in seconds, and can run on every deployment. And because the design sheet is a first-class data source, the sheet stays the single source of truth: the test is the diff between what was agreed and what was built.