ory-am/hydra

View on GitHub
scripts/db-diff.sh

Summary

Maintainability
Test Coverage
#!/bin/bash

set -o nounset
set -o errexit
set -o pipefail

# This script is used to generate and compare the Hydra DDL at different
# versions. This is useful when reviewing changes and troubleshooting
# migrations.
#
# Side effects:
# - Creates a directory at ./output/sql and stores the generated SQL
#
# Arguments:
# $1 - database type (e.g. postgres, mysql, sqlite)
# $2 - commit hash or branch name of the earlier version
# $3 - commit hash or branch name of the later version
#
# Example output: (the script prints the command instead of executing it)
# 'git diff --no-index ./output/sql/f5864b39.sqlite.dump.sql ./output/sql/master.sqlite.dump.sql'
#

# Usage
# ./scripts/db-diff.sh sqlite master 649f56cc
# ./scripts/db-diff.sh postgres HEAD~1 HEAD
if [ "$#" -ne 3 ]; then
    echo "Usage: $0 <sqlite|postgres|cockroach|mysql> <commit-ish> <commit-ish>"
    exit 1
fi

# Exports:
# - DB_DIALECT
# - DB_USER
# - DB_PASSWORD
# - DB_HOST
# - DB_PORT
# - DB_NAME
function hydra::util::parse-connection-url {
    local -r url=$1
    if [[ "${url}" =~ ^(.*)://([^:]*):?(.*)@\(?(.*):([0-9]*)\)?/([^?]*) ]]; then
        export DB_DIALECT="${BASH_REMATCH[1]}"
        export DB_USER="${BASH_REMATCH[2]}"
        export DB_PASSWORD="${BASH_REMATCH[3]}"
        export DB_HOST="${BASH_REMATCH[4]}"
        export DB_PORT="${BASH_REMATCH[5]}"
        export DB_DB="${BASH_REMATCH[6]}"
    else
        echo "Failed to parse URL"
        exit 1
    fi
}

function hydra::util::ensure-sqlite {
    if ! sqlite3 --version > /dev/null 2>&1; then
        echo 'Error: sqlite3 is not installed' >&2
        exit 1
    fi
}

function hydra::util::ensure-pg_dump {
    if ! pg_dump --version > /dev/null 2>&1; then
        echo 'Error: pg_dump is not installed' >&2
        exit 1
    fi
}

function hydra::util::ensure-mysqldump {
    if ! mysqldump --version > /dev/null 2>&1; then
        echo 'Error: mysqldump is not installed' >&2
        exit 1
    fi
}

function dump_pg {
    if test -z $TEST_DATABASE_POSTGRESQL; then
        echo 'Error: TEST_DATABASE_POSTGRESQL is not set; try running "source scripts/test-env.sh"' >&2
        exit 1
    fi

    hydra::util::ensure-pg_dump

    make test-resetdb >/dev/null 2>&1
    sleep 4
  go run . migrate sql "$TEST_DATABASE_POSTGRESQL" --yes >&2 || true
    sleep 1
    pg_dump -s "$TEST_DATABASE_POSTGRESQL" | sed '/^--/d'
}

function dump_cockroach {
    if test -z $TEST_DATABASE_COCKROACHDB; then
        echo 'Error: TEST_DATABASE_COCKROACHDB is not set; try running "source scripts/test-env.sh"' >&2
        exit 1
    fi

    make test-resetdb >/dev/null 2>&1
    sleep 4
    go run . migrate sql "$TEST_DATABASE_COCKROACHDB" --yes > /dev/null || true
    hydra::util::parse-connection-url "${TEST_DATABASE_COCKROACHDB}"
    docker run --rm --net=host -it cockroachdb/cockroach:latest-v24.1 dump --dump-all --dump-mode=schema --insecure --user="${DB_USER}" --host="${DB_HOST}" --port="${DB_PORT}"
}

function dump_sqlite {
    if test -z $SQLITE_PATH; then
        SQLITE_PATH="$(mktemp -d)/temp.sqlite"
    fi

    hydra::util::ensure-sqlite

    rm "$SQLITE_PATH" > /dev/null 2>&1 || true
    go run -tags sqlite,sqlite_omit_load_extension . migrate sql "sqlite://$SQLITE_PATH?_fk=true" --yes > /dev/null 2>&1 || true
    echo '.dump' | sqlite3 "$SQLITE_PATH"
}

function dump_mysql {
    if test -z $TEST_DATABASE_MYSQL; then
        echo 'Error: TEST_DATABASE_MYSQL is not set; try running "source scripts/test-env.sh"' >&2
        exit 1
    fi

    hydra::util::ensure-mysqldump
    make test-resetdb >/dev/null 2>&1
    sleep 10
    go run . migrate sql "$TEST_DATABASE_MYSQL" --yes > /dev/null || true
    hydra::util::parse-connection-url "${TEST_DATABASE_MYSQL}"
    mysqldump --user="$DB_USER" --password="$DB_PASSWORD" --host="$DB_HOST" --port="$DB_PORT" "$DB_DB" --no-data
}

if ! git diff-index --quiet HEAD --; then
    echo 'Error: working tree is dirty' >&2
    exit 1
fi

case $1 in
    postgres)
        DUMP_CMD=dump_pg
        ;;
    cockroach)
        DUMP_CMD=dump_cockroach
        ;;
    sqlite)
        DUMP_CMD=dump_sqlite
        ;;
    mysql)
        DUMP_CMD=dump_mysql
        ;;
    *)
        echo 'Error: unknown database type' >&2
        exit 1
        ;;
esac

DIALECT=$1
COMMIT_FROM=$(git rev-parse "$2")
COMMIT_TO=$(git rev-parse "$3")
DDL_FROM="./output/sql/$COMMIT_FROM.$DIALECT.dump.sql"
DDL_TO="./output/sql/$COMMIT_TO.$DIALECT.dump.sql"

mkdir -p ./output/sql/

set -x
# shellcheck disable=SC2064

if git symbolic-ref --quiet HEAD; then
    trap "git checkout -q $(git symbolic-ref HEAD); git symbolic-ref HEAD $(git symbolic-ref HEAD)" EXIT
else
    trap "git checkout $(git rev-parse HEAD)" EXIT
fi

git checkout "$COMMIT_FROM" >/dev/null 2>&1
$DUMP_CMD > "$DDL_FROM"

git checkout "$COMMIT_TO" >/dev/null 2>&1
$DUMP_CMD > "$DDL_TO"

set +x
echo '+--------------------------'
echo '|'
echo '| Use the following command to print the diff:'
echo '| git diff --no-index '"$DDL_FROM"' '"$DDL_TO"
echo '|'
echo '+--------------------------'
set -x