#!/bin/bash
# shellcheck disable=SC2129,SC2230
# SC2129: Consider using { cmd1; cmd2; } >> file instead of individual redirects.
# SC2230: which is non-standard. Use builtin 'command -v' instead.

# Run smoke tests to test that updating between versions work.
#
# Usage:
#      bash test_update_smoke.sh postgres://...
#
# This is based on our update tests but doing some tweaks to ensure we
# can run it on Forge (or any other PostgreSQL server that only permit
# a single user and single database).
#
# In particular, we cannot create new roles and we cannot create new
# databases.
#
# Info on the parameters:
# - current_version is the version to update from
#
# - next_version is the version to update to 
#
# - connection_string is the URL to use for the connection
#
# - dbname if not defined use "tsdb" as database name
#

if [ "$#" -ne 3 ]; then
    echo "${0} <current_version> <next_version> <connection_string>"
    exit 2
fi

CURRENT_VERSION=$1
NEXT_VERSION=$2
CONNECTION_STRING=$3

echo "testing upgrade path from v${CURRENT_VERSION} to ${NEXT_VERSION} .."

SCRIPT_DIR=$(dirname $0)
BASE_DIR=${PWD}/${SCRIPT_DIR}/..
SCRATCHDIR=$(mktemp -d -t "smoketest-${CURRENT_VERSION}-${NEXT_VERSION}-XXXX")
LOGFILE="$SCRATCHDIR/update-test.log"
DUMPFILE="$SCRATCHDIR/smoke.dump"
UPGRADE_OUT="$SCRATCHDIR/upgrade.out"
CLEAN_OUT="$SCRATCHDIR/clean.out"
RESTORE_OUT="$SCRATCHDIR/restore.out"
TEST_VERSION=${TEST_VERSION:-v7}

# We do not have superuser privileges when running smoke tests.
WITH_SUPERUSER=false
WITH_ROLES=false

shift $((OPTIND-1))

echo "**** pg_dump at   " "$(which pg_dump)"
echo "**** pg_restore at" "$(which pg_restore)"

# Extra options to pass to psql
PGOPTS="-v TEST_VERSION=${TEST_VERSION} -v WITH_SUPERUSER=${WITH_SUPERUSER} -v WITH_ROLES=${WITH_ROLES} -v WITH_CHUNK=false"
PSQL="psql -a -qX $PGOPTS"

# If we are providing a URI for the connection, we parse it here and
# set the PG??? variables since that is the only reliable way to
# provide connection information to psql, pg_dump, and pg_restore.
#
# To work with Forge, we need to only set PGPASSWORD when the password
# is available and leave it unset otherwise. If the user has either
# set PGPASSWORD or has the password in a .pgpass file, it will be
# picked up and used for the connection.
# shellcheck disable=SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting).
parts=($(echo $CONNECTION_STRING | perl -mURI::Split=uri_split -ne '@F = uri_split($_); print join(" ", split(qr/[:@]/, $F[1]), substr($F[2], 1))'))
export PGUSER=${parts[0]}
if [[ ${#parts[@]} -eq 5 ]]; then
    # Cloud has 5 fields
    export PGPASSWORD=${parts[1]}
    export PGHOST=${parts[2]}
    export PGPORT=${parts[3]}
    export PGDATABASE=${parts[4]}
elif [[ ${#parts[@]} -eq 4 ]]; then
    # Forge has 4 fields
    export PGHOST=${parts[1]}
    export PGPORT=${parts[2]}
    export PGDATABASE=${parts[3]}
else
    echo "Malformed URL '$CONNECTION_STRING'" 1>&2
    exit 2
fi

err_trap() {
    exit 3
}

exit_trap() {
    exit_code=$?
    if [ "$exit_code" != "0" ]; then
        echo "!!!! FAILED !!!!"
    else
        echo "**** passed ****"
    fi
    echo "**** logs can be found in $SCRATCHDIR"
}

set -e
set -o pipefail
trap exit_trap EXIT
trap err_trap ERR

missing_versions() {
        $PSQL -v ECHO=none -t <<-EOF
	SELECT * FROM (VALUES ('$1'), ('$2')) AS foo
	EXCEPT
	SELECT version FROM pg_available_extension_versions
	WHERE name = 'timescaledb' AND version IN ('$1', '$2');
	EOF
}

echo "**** Scratch directory: ${SCRATCHDIR}"
echo "**** Update files in directory ${BASE_DIR}/test/sql/updates"
cd ${BASE_DIR}/test/sql/updates

$PSQL -c '\conninfo'
$PSQL -c "ALTER DATABASE ${PGDATABASE} SET timescaledb.enable_compression_ratio_warnings = 'off'";

# shellcheck disable=SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting).
missing=($(missing_versions $CURRENT_VERSION $NEXT_VERSION))
if [[ ${#missing[@]} -gt 0 ]]; then
    echo "ERROR: Missing version(s) ${missing[*]} of 'timescaledb'"
    echo "Available versions: " "$($PSQL -tc "SELECT version FROM pg_available_extension_versions WHERE name = 'timescaledb'")"
    exit 1
fi

# For the comments below, we assume the upgrade is from 1.7.5 to 2.0.2
# (this is just an example, the real value is given by variables
# above).

# Create a 1.7.5 version Upgrade
echo "---- Connecting to ${FORGE_CONNINFO} and running setup ----"
$PSQL -f cleanup.${TEST_VERSION}.sql >>$LOGFILE 2>&1
$PSQL -c "DROP EXTENSION IF EXISTS timescaledb CASCADE" >>$LOGFILE 2>&1
$PSQL -f pre.cleanup.sql >>$LOGFILE 2>&1
$PSQL -c "CREATE EXTENSION timescaledb VERSION '${CURRENT_VERSION}'" >>$LOGFILE 2>&1
$PSQL -c "\dx"

# Run setup on Upgrade
$PSQL -f pre.smoke.sql >>$LOGFILE 2>&1
$PSQL -f setup.${TEST_VERSION}.sql >>$LOGFILE 2>&1
# Run update on Upgrade. You now have a 2.0.2 version in Upgrade.
$PSQL -c "ALTER EXTENSION timescaledb UPDATE TO '${NEXT_VERSION}'" >>$LOGFILE 2>&1

echo -n "Dumping the contents of Upgrade..."
pg_dump -Fc -f $DUMPFILE >>$LOGFILE 2>&1
echo "done"

# Run the post scripts on Upgrade to get UpgradeOut to compare
# with.  We can now discard Upgrade database.
echo -n "Collecting post-update status..."
$PSQL -f post.${TEST_VERSION}.sql >$UPGRADE_OUT
echo "done"

$PSQL -f cleanup.${TEST_VERSION}.sql >>$LOGFILE 2>&1

echo "---- Create a ${NEXT_VERSION} version Clean ----"
$PSQL -c "DROP EXTENSION IF EXISTS timescaledb CASCADE" >>$LOGFILE 2>&1
$PSQL -f pre.cleanup.sql >>$LOGFILE 2>&1
$PSQL -c "CREATE EXTENSION timescaledb VERSION '${NEXT_VERSION}'" >>$LOGFILE 2>&1
$PSQL -c "\dx"

echo "---- Run the setup scripts on Clean, with post-update actions ----"
$PSQL -f pre.smoke.sql >>$LOGFILE 2>&1
$PSQL -f setup.${TEST_VERSION}.sql >>$LOGFILE 2>&1

echo "---- Run the post scripts on Clean to get output CleanOut ----"
$PSQL -f post.${TEST_VERSION}.sql >$CLEAN_OUT

$PSQL -f cleanup.${TEST_VERSION}.sql >>$LOGFILE 2>&1

echo "---- Create a ${NEXT_VERSION} version Restore ----"
$PSQL -c "DROP EXTENSION IF EXISTS timescaledb CASCADE" >>$LOGFILE 2>&1
$PSQL -f pre.cleanup.sql >>$LOGFILE 2>&1
$PSQL -c "CREATE EXTENSION timescaledb VERSION '${NEXT_VERSION}'" >>$LOGFILE 2>&1
$PSQL -c "\dx"

echo "---- Restore the UpgradeDump into Restore ----"
echo -n "Restoring dump..."
$PSQL -c "SELECT timescaledb_pre_restore()" >>$LOGFILE 2>&1
pg_restore -d $PGDATABASE $DUMPFILE >>$LOGFILE 2>&1 || true
$PSQL -c "SELECT timescaledb_post_restore()" >>$LOGFILE 2>&1
echo "done"

echo "---- Run the post scripts on Restore to get a RestoreOut ----"
$PSQL -f post.${TEST_VERSION}.sql >$RESTORE_OUT

echo "---- Compare UpgradeOut with CleanOut and make sure they are identical ----"
diff -u $UPGRADE_OUT $CLEAN_OUT | tee $SCRATCHDIR/upgrade-clean.diff

echo "---- Compare RestoreOut with CleanOut and make sure they are identical ----"
diff -u $RESTORE_OUT $CLEAN_OUT | tee $SCRATCHDIR/restore-clean.diff

$PSQL -f cleanup.${TEST_VERSION}.sql >>$LOGFILE 2>&1
