Skip to main content

Validate Database Schema Integrity

When teams first run atlas schema validate, they often discover issues that have been silently lurking in their database for years. These are objects that were created in the past and never updated, or were created with database-specific settings that allowed invalid definitions (e.g., check_function_bodies = off in PostgreSQL). These problems surface when you try to recreate the schema from scratch or migrate to a new environment.

Atlas treats your database schema like application code: version controlled in git, reviewed in pull requests, validated for semantic correctness, and reproducible across environments. This guide focuses on the validation part - catching issues that traditional migration tools miss.

The Problem: "It Works in Production"

Database schemas, like any software, accumulate technical debt over time. But unlike application code, databases are forgiving - they let you create objects that reference other objects, then let you modify those dependencies without re-validating the entire schema graph. This hidden debt stays invisible until something breaks - either when you try to recreate the schema from scratch, or when you interact with those objects at runtime, like calling a function or querying a view.

Atlas catches these issues because it validates your schema semantically - not just syntactically. When you inspect a database and try to apply that schema to a fresh database, invalid objects fail loudly.

Why Databases Allow Invalid Objects

Most databases defer validation to runtime. This is by design - it allows flexibility, circular references, and dynamic SQL. But it also means invalid objects accumulate silently:

DatabaseBehavior
SQL ServerDeferred name resolution - procedures validated at execution, not creation
MySQLValidates syntax only, not object references
PostgreSQLValidates function bodies at creation, but often disabled via check_function_bodies = off

The result: schemas that "work" in production only because no one has called that broken function or queried that stale view recently.

Running Validation

# Export your schema
atlas schema inspect -u "$DB_URL" --format '{{ sql . }}' > schema.sql

# Validate against a clean database
atlas schema validate \
--url file://schema.sql \
--dev-url "docker://postgres/16/dev"

The --dev-url spins up a clean database to test recreation. See Dev Database for connection strings for MySQL, SQL Server, and others.

What Atlas Catches

Stale Views

Views using SELECT * don't update when underlying tables change:

CREATE VIEW active_users AS SELECT * FROM users WHERE active = true;

-- Later:
ALTER TABLE users ADD COLUMN email VARCHAR(255);

The view still returns the old column set. Queries expecting email fail or return wrong data. SQL Server caches view metadata at creation - the view literally doesn't know about new columns.

Fix: Explicit column lists. Always.

CREATE VIEW active_users AS
SELECT id, name, email, active FROM users WHERE active = true;

Invalid Functions

Functions that reference non-existent tables, columns, or other functions fail validation. If a function body refers to an object that doesn't exist, Atlas catches it:

CREATE FUNCTION get_active_users() RETURNS SETOF users AS $$
SELECT * FROM users WHERE status = 'active';
$$ LANGUAGE sql;

-- Later:
ALTER TABLE users RENAME COLUMN status TO user_status;

The function body still references status, which no longer exists. PostgreSQL won't catch this if check_function_bodies is off (common in migration scripts). Atlas validates the function can actually be created - if the referenced column doesn't exist, validation fails.

Orphaned Foreign Keys

Foreign keys pointing to renamed columns:

ALTER TABLE orders ADD CONSTRAINT fk_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);

-- Later:
ALTER TABLE customers RENAME COLUMN id TO customer_id;

Depending on the database, this silently breaks the constraint or blocks the rename. Either way, your schema state is inconsistent with what you think it is.

After Validation

Once atlas schema validate passes, apply the cleaned schema:

Continuous Validation

Running validation once finds existing debt. Running Atlas continuously prevents new debt from accumulating. Every schema apply or migrate diff validates the entire schema graph - broken objects can't be introduced because Atlas won't generate migrations for invalid states.

This is the difference between periodic audits and continuous enforcement. The issues described above simply don't happen when Atlas manages your schema.

Have questions? Find us on Discord.