Schema Comparison Guide
This guide explains how to use sqlmeta for schema comparison and drift detection.
Basic Comparison
Compare two table definitions:
from sqlmeta import Table, SqlColumn
from sqlmeta.comparison.comparator import ObjectComparator
source = Table("users", columns=[
SqlColumn("id", "INTEGER", is_primary_key=True),
SqlColumn("name", "VARCHAR(100)"),
])
target = Table("users", columns=[
SqlColumn("id", "INTEGER", is_primary_key=True),
SqlColumn("name", "VARCHAR(100)"),
SqlColumn("email", "VARCHAR(255)", is_nullable=False),
])
comparator = ObjectComparator(dialect="postgresql")
diff = comparator.compare_tables(source, target)
Understanding Diff Results
The comparison returns a TableDiff object with detailed information:
if diff.has_diffs:
print(f"Severity: {diff.severity.value}")
# Missing columns (in target but not in source)
for col_name in diff.missing_columns:
print(f"Add column: {col_name}")
# Extra columns (in source but not in target)
for col_name in diff.extra_columns:
print(f"Drop column: {col_name}")
# Modified columns
for col_diff in diff.modified_columns:
print(f"Modify column: {col_diff.column_name}")
if col_diff.type_mismatch:
print(f" Type: {col_diff.source_type} -> {col_diff.target_type}")
if col_diff.nullable_mismatch:
print(f" Nullable: {col_diff.source_nullable} -> {col_diff.target_nullable}")
Severity Levels
Differences are categorized by severity:
ERROR - Breaking changes (column removed, incompatible type change)
WARNING - Non-breaking but important (nullable changed, constraint modified)
INFO - Cosmetic differences (comments, formatting)
from sqlmeta.comparison.diff_models import DiffSeverity
if diff.severity == DiffSeverity.ERROR:
print("Breaking changes detected!")
elif diff.severity == DiffSeverity.WARNING:
print("Non-breaking changes detected")
else:
print("Only cosmetic changes")
Comparing Schemas
Compare entire schemas with multiple tables:
from sqlmeta.comparison.diff_models import SchemaDiff
source_tables = [table1, table2, table3]
target_tables = [table1_modified, table2, table4]
comparator = ObjectComparator(dialect="postgresql")
schema_diff = comparator.compare_schemas(source_tables, target_tables)
# Iterate through table diffs
for table_diff in schema_diff.table_diffs:
if table_diff.has_diffs:
print(f"Table {table_diff.table_name}: {table_diff.severity.value}")
Type Normalization
The comparator automatically normalizes data types for accurate comparison:
from sqlmeta.comparison.type_normalizer import DataTypeNormalizer
normalizer = DataTypeNormalizer(dialect="postgresql")
# These are considered equivalent
assert normalizer.normalize("VARCHAR(255)") == normalizer.normalize("CHARACTER VARYING(255)")
assert normalizer.normalize("INT") == normalizer.normalize("INTEGER")
assert normalizer.normalize("BOOL") == normalizer.normalize("BOOLEAN")
System-Generated Names
The comparator automatically handles system-generated constraint names:
# Oracle: SYS_C0013220
# SQL Server: PK__users__3213E83F
# These are matched by constraint type and columns, not name
source = Table("users", columns=[
SqlColumn("id", "INTEGER", is_primary_key=True),
], constraints=[
SqlConstraint(
constraint_type=ConstraintType.PRIMARY_KEY,
name="SYS_C0013220", # System-generated
column_names=["id"]
)
])
target = Table("users", columns=[
SqlColumn("id", "INTEGER", is_primary_key=True),
], constraints=[
SqlConstraint(
constraint_type=ConstraintType.PRIMARY_KEY,
name="pk_users", # User-defined
column_names=["id"]
)
])
diff = comparator.compare_tables(source, target)
# No constraint difference detected (matched by structure)
Generating Reports
Generate human-readable comparison reports:
def generate_report(diff):
"""Generate a comparison report."""
if not diff.has_diffs:
return "No differences found"
lines = [f"Table: {diff.table_name}", f"Severity: {diff.severity.value}", ""]
if diff.missing_columns:
lines.append("Columns to add:")
for col in diff.missing_columns:
lines.append(f" + {col}")
lines.append("")
if diff.extra_columns:
lines.append("Columns to remove:")
for col in diff.extra_columns:
lines.append(f" - {col}")
lines.append("")
if diff.modified_columns:
lines.append("Columns to modify:")
for col_diff in diff.modified_columns:
lines.append(f" ~ {col_diff.column_name}")
if col_diff.type_mismatch:
lines.append(f" Type: {col_diff.source_type} -> {col_diff.target_type}")
if col_diff.nullable_mismatch:
lines.append(f" Nullable: {col_diff.source_nullable} -> {col_diff.target_nullable}")
lines.append("")
return "\n".join(lines)
print(generate_report(diff))