View
Home/Blog/Database
EngineeringFeb 2026·8 min read

Building a Database Version Control System (DBVC)

How to automatically detect schema drift, visualize SQL differences, and safely synchronize PostgreSQL databases across environments using Python and Flask.

Detecting Schema Drift and Safely Syncing Databases in Production

Every developer eventually runs into a frustrating problem:

“It works on my database, but production is different.”

Maybe a column exists locally but not in staging.

Maybe a view was modified directly in production.

Maybe a function definition changed but no one tracked it.

Over time, multiple environments begin to drift apart.

This is known as database schema drift, and it can cause deployment failures, runtime errors, and painful debugging sessions.

To solve this problem, I built Database Version Control (DBVC) — a web application that automatically compares two database schemas and safely synchronizes them.

Instead of manually digging through tables, columns, functions, and views, DBVC acts like a diff tool for databases, highlighting exactly what changed and letting you apply fixes with a single click.

Let’s explore how it works.


The Problem DBVC Solves

Imagine you have two databases:

text
Source Database (Correct Version) Target Database (Outdated Version)

Both started identical, but over time changes were applied to only one.

For example:

text
Source DB --------- users id email status
text
Target DB --------- users id email

Your application expects status, but the target database doesn’t have it.

Now deployments break.

Manually detecting these differences across hundreds of tables, functions, and views can take hours.

DBVC automates the entire process.


The Idea Behind DBVC

Think of DBVC as a database blueprint comparison tool.

It does three things:

  1. Detect differences between two database schemas
  2. Visually highlight changes in SQL definitions
  3. Safely apply patches to synchronize databases

Instead of manually writing SQL fixes, DBVC generates the SQL for you and applies it in the correct order.


High-Level Architecture

The project is built using a Flask application factory pattern, separating concerns between routing logic and service logic.

text
db-version-control-main/ │ ├── app/ │ ├── routes/ │ ├── services/ │ ├── templates/ │ ├── static/ │ └── __init__.py │ ├── tests/ │ ├── unit/ │ ├── integration/ │ ├── requirements.txt └── run.py

The architecture follows a clean separation:

LayerResponsibility
RoutesHandle HTTP requests
ServicesBusiness logic
TemplatesUI rendering
StaticCSS and frontend assets
TestsValidation of system behavior

This separation keeps the code modular and maintainable.


The Core Workflow

DBVC works in three main phases:

text
Connect → Compare → Apply

Each step is designed to make schema management safe and transparent.


Step 1: Database Connection & Demo Mode

When users open the application, they land on the home page.

They can provide two connection strings:

text
Source Database Target Database

These contain:

text
Host Port Database Name Username Password

For quick exploration, the system also includes a Demo Mode.

Instead of connecting to real databases, the application loads preconfigured schema examples and immediately jumps to the comparison interface.

This allows users to explore the UI without needing a database setup.


Step 2: Schema Introspection & Comparison

This is where the core intelligence of the system lives.

Once databases are connected, the system performs schema introspection.

It queries system catalogs like:

text
information_schema pg_catalog sys.*

These queries retrieve:

  • Tables
  • Columns
  • Data types
  • Defaults
  • Functions
  • Views
  • Triggers

The process is optimized using parallel execution.

Using Python’s ThreadPoolExecutor, DBVC fetches schema information from both databases simultaneously.

python
from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor() as executor: src_tables = executor.submit(fetch_tables, source_db) tgt_tables = executor.submit(fetch_tables, target_db)

This significantly speeds up comparisons for large databases.


Schema Normalization

Before comparing SQL definitions, DBVC normalizes them.

Why?

Because formatting differences shouldn't count as real changes.

Example:

sql
CREATE FUNCTION calculate_tax()

vs

sql
create function calculate_tax()

Both are identical.

The normalizer service removes whitespace differences, casing differences, and formatting inconsistencies before comparison.


Difference Detection

Once schemas are normalized, DBVC performs a comparison.

Example detection logic:

text
If object exists in Source but not Target → missing_in_target If object exists in both but SQL differs → modified If identical → unchanged

For tables, DBVC compares column dictionaries.

Example diff result:

text
users table Status: missing_in_target Action: generate CREATE TABLE SQL

For functions, views, and triggers, DBVC compares normalized SQL definitions.


SQL Generation

When a missing object is detected, DBVC generates SQL automatically.

For example:

sql
CREATE TABLE users ( id SERIAL PRIMARY KEY, email TEXT NOT NULL, status VARCHAR(20) DEFAULT 'active' );

The SQL generator reconstructs PostgreSQL DDL using the introspected metadata.

It also intelligently converts sequence defaults like:

sql
nextval(...)

back into PostgreSQL's cleaner SERIAL syntax.


Visual Diff Interface

One of the most powerful features of DBVC is the visual SQL diff interface.

The application integrates the Monaco Editor, the same editor used in VS Code.

Users see a side-by-side comparison:

text
SOURCE SQL TARGET SQL ----------------------------------------- CREATE FUNCTION... CREATE FUNCTION...

Differences are highlighted just like Git diffs.

This allows developers to review exactly what will change before applying anything.


Step 3: Safe Schema Synchronization

Once differences are identified, users can apply fixes.

Two options exist:

Apply Single Change

Users click Apply next to a specific object.

The system sends a POST request containing the SQL patch.


Apply All Changes

DBVC can apply all missing objects automatically.

But database dependencies matter.

For example:

text
View → depends on table Trigger → depends on function

So DBVC applies changes in a strict order:

text
Tables → Functions → Views → Triggers

This prevents dependency failures.


Safe SQL Execution

SQL execution is handled by the executor service.

Before running SQL, it performs safety checks.

Forbidden commands include:

text
DROP DATABASE DROP SCHEMA

Execution runs inside a transaction:

python
with engine.begin() as conn: conn.execute(sql)

If an error occurs, the transaction rolls back.

DBVC also captures common PostgreSQL errors like:

text
relation does not exist

and converts them into user-friendly messages.


Services Layer Deep Dive

The services layer contains the core logic of the system.

introspection.py

Responsible for reading database metadata from system catalogs.

Optimized for PostgreSQL with partial MSSQL support.


sql_generator.py

Reconstructs CREATE TABLE statements dynamically using schema metadata.


normalizer.py

Removes formatting inconsistencies before comparisons.


differ.py

Performs the actual comparison between schema objects.


executor.py

Handles SQL execution with safety checks and error translation.


Frontend Design

The frontend focuses on clarity and usability.

Features include:

  • Dark theme UI
  • Glassmorphism design
  • Sticky headers
  • Responsive layout
  • SQL diff visualization

The Monaco Editor provides IDE-level highlighting directly in the browser.

Flash messages display execution results as toast notifications.


Testing Strategy

The project includes a comprehensive pytest testing suite.

Two testing levels exist.


Unit Tests

Located in:

text
tests/unit/

These test individual functions such as:

  • SQL generation
  • schema normalization
  • validation logic

Integration Tests

Located in:

text
tests/integration/

These test complete application flows including:

  • route endpoints
  • demo mode
  • schema comparison pipeline
  • execution workflow

This ensures the system works correctly from request to response.


Security Considerations

DBVC was designed with safety in mind.

Key protections include:

Restricted SQL Execution

Dangerous commands are filtered before execution.


Controlled Credentials

The system only executes SQL using provided database credentials.


Ephemeral Session State

Connection details and comparison results are stored temporarily in session memory.

Example:

python
session["compare"]

This ensures state persists only during the user session.


Why This Project Matters

Database schema changes are one of the most fragile parts of software systems.

Without proper tooling:

  • environments drift apart
  • deployments break
  • debugging becomes painful

DBVC provides a safer workflow:

  • ✔ Automatic schema comparison
  • ✔ Visual SQL diffing
  • ✔ Safe dependency-aware execution
  • ✔ Parallelized introspection
  • ✔ Clean architecture with strong testing

Instead of manually fixing schemas, developers can see exactly what changed and apply fixes with confidence.


Final Thoughts

Modern software engineering treats databases as first-class citizens in the development lifecycle.

Just like we use Git for code versioning, tools like DBVC help bring version control and visibility to database schemas.

By combining schema introspection, SQL diffing, and safe execution pipelines, DBVC provides a powerful interface for managing database evolution across environments.

And most importantly, it transforms one of the most frustrating deployment problems into a simple, controlled workflow.

Because when it comes to production databases, visibility and safety are everything. 🚀

LP

Written by Lakshya Purohit

Published on Feb 2026 · Originally authored & owned by Lakshya Purohit

© 2026 Lakshya Purohit. All rights reserved.

Back to All Posts