#!/usr/bin/env bash # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 # # This script can aid in comparing the database schema between two commits. # It will: # # 1. Create a docker postgres database. # 2. Apply the schema migrations for the current commit. # 3. Create a dump of the database using pg_dump # 4. Destroy the database container # 5. Extract the definitions of specific database objects from the dump. # These are extracted to place each definition into a separeate .sql file, # which aids in the performing a diff that can detect renames. # 6. Switch to the provided base branch (defaults to main) # 7. Repeate the dump and extract process. # 8. Create diffs for each kind of database object using `git diff` to compare # directories which helps with detecting renames. # # The dumps and extracted .sql files are left in place so they can be examined # and compared using other tools. For example an alternate diff tool like delta # could be used to view the diff to the functions: # # delta .schema-diff/funcs_$(git rev-parse main) .schema-diff/funcs_$(git rev-parse HEAD) # # These files get removed if the script is run again, or can be manually removed with: # # rm -r .schema-diff # # Limitations: # # Since this script is extracting specifc parts of the schema from # a database dump, there may be some differences that are not extracted and therefore # not reported. Some notable limitations follow: # # - domain definitions: These are not easily extracted from a pg_dump. Therefore # changes to domain types or new domain definitions will not be present in the diff. die() { echo "$@" exit 255 } which pg_dump &> /dev/null || die "pg_dump must be installed" which pg_restore &> /dev/null || die "pg_restore must be installed" which pg_isready &> /dev/null || die "pg_isready must be installed" which awk &> /dev/null || die "awk must be installed" which git &> /dev/null || die "git must be installed" which make &> /dev/null || die "make must be installed" set -e SQL_TEST_DB_PORT=${SQL_TEST_DB_PORT:=5432} export SQL_TEST_DB_PORT DB_HOST=${DB_HOST:=127.0.0.1} base_branch=${1} if [[ -z "${base_branch}" ]]; then base_branch="main" fi base_commit=$(git rev-parse "${base_branch}") new_branch=$(git rev-parse --abbrev-ref HEAD) new_commit=$(git rev-parse HEAD) tmp_dir=".schema-diff" extract() { local suffix=$1 local dump="${tmp_dir}/${suffix}.dump" mkdir -p \ "${tmp_dir}/funcs_${suffix}" \ "${tmp_dir}/tables_${suffix}" \ "${tmp_dir}/views_${suffix}" \ "${tmp_dir}/triggers_${suffix}" \ "${tmp_dir}/indexes_${suffix}" \ "${tmp_dir}/constraints_${suffix}" \ "${tmp_dir}/fk_constraints_${suffix}" echo "extracting function definitions from ${dump}" while read -r f; do fname="${f%(*}" pg_restore -s -O -P "${f}" -f - "${dump}" | tr '[:upper:]' '[:lower:]' > "${tmp_dir}/funcs_${suffix}/${fname}.sql" done < <(pg_restore -l "${dump}" -f - | awk '$4 == "FUNCTION" {for(i=6;i "${tmp_dir}/tables_${suffix}/${tname}.sql" done < <(pg_restore -l "${dump}" -f - | awk '$4 == "TABLE" {for(i=6;i "${tmp_dir}/views_${suffix}/${vname}.sql" done < <(pg_restore -l "${dump}" -f - | awk '$4 == "VIEW" {for(i=6;i "${tmp_dir}/triggers_${suffix}/${tname}.sql" done < <(pg_restore -l "${dump}" -f - | awk '$4 == "TRIGGER" {for(i=6;i "${tmp_dir}/indexes_${suffix}/${dname}.sql" done < <(pg_restore -l "${dump}" -f - | awk '$4 == "INDEX" {for(i=6;i "${tmp_dir}/constraints_${suffix}/${cName}.sql" done < <(pg_restore -l "${dump}" -f - | awk '$4 == "CONSTRAINT" {for(i=6;i "${tmp_dir}/fk_constraints_${suffix}/${cName}.sql" done < <(pg_restore -l "${dump}" -f - | awk '$4 == "FK" && $5 == "CONSTRAINT" {for(i=7;i