#!/usr/bin/env bash
# Caída y recuperación en un cluster de prueba. Crea un cluster nuevo en
# /tmp (puerto 5415, no el del curso), hace cambios, lo detiene en modo
# immediate (como un corte de luz), lo vuelve a arrancar y muestra lo que
# PostgreSQL escribió en su log durante la recuperación. Al final lo borra.
# El servidor del curso no se toca.
#
#   bash crash.sh        (dentro del contenedor, como root o como postgres;
#                         make crash hace lo mismo)
# Con PORT=5515 bash crash.sh usa otro puerto (las pruebas lo hacen así).
set -eu

PORT="${PORT:-5415}"
DIR="/tmp/w15-crash-$PORT"
LOG="$DIR.log"

# initdb y pg_ctl no aceptan correr como root
if [ "$(id -u)" = 0 ]; then AS="gosu postgres"; else AS=""; fi
sql() { $AS psql -X -q -At -h /tmp -p "$PORT" -d postgres -v ON_ERROR_STOP=1 "$@"; }
cleanup() {
    $AS pg_ctl -D "$DIR" -m immediate stop >/dev/null 2>&1 || true
    rm -rf "$DIR" "$LOG"
}
if pg_isready -q -h /tmp -p "$PORT"; then
    echo "Ya hay un servidor en el puerto $PORT; usen otro: PORT=5499 bash crash.sh" >&2
    exit 1
fi
trap cleanup EXIT

cleanup
$AS initdb -D "$DIR" -U postgres -A trust --no-instructions >/dev/null
$AS pg_ctl -D "$DIR" -l "$LOG" -w -o "-p $PORT -k /tmp -c listen_addresses=''" start >/dev/null

echo "== Antes de la caída"
sql -c "CREATE TABLE payment (payment_id int PRIMARY KEY, amount numeric(8,2) NOT NULL)" \
    -c "CHECKPOINT"
redo=$(sql -c 'SELECT redo_lsn FROM pg_control_checkpoint()')
echo "redo_lsn del último checkpoint: $redo"
sql -c "INSERT INTO payment SELECT i, 10 FROM generate_series(1, 1000) AS i"
echo "commit de 1000 filas; WAL insertado hasta $(sql -c 'SELECT pg_current_wal_insert_lsn()')"

# Una transacción que queda abierta: cambia todas las filas y no hace commit
( printf '%s\n' "BEGIN;" "UPDATE payment SET amount = 0;" "SELECT pg_sleep(60);" \
    | $AS psql -X -q -h /tmp -p "$PORT" -d postgres >/dev/null 2>&1 ) &
for _ in $(seq 1 50); do
    n=$(sql -c "SELECT count(*) FROM pg_stat_activity WHERE query LIKE 'SELECT pg_sleep%'")
    [ "$n" = 1 ] && break
    sleep 0.1
done
xid=$(sql -c "SELECT backend_xid FROM pg_stat_activity WHERE query LIKE 'SELECT pg_sleep%'")
echo "transacción $xid abierta: UPDATE payment SET amount = 0 (sin commit)"
echo "WAL insertado hasta $(sql -c 'SELECT pg_current_wal_insert_lsn()')"

echo "== Caída: pg_ctl -m immediate stop"
$AS pg_ctl -D "$DIR" -m immediate stop >/dev/null
wait || true

echo "== Arranque (líneas nuevas del log del servidor)"
lines=$(wc -l < "$LOG")
$AS pg_ctl -D "$DIR" -l "$LOG" -w -o "-p $PORT -k /tmp -c listen_addresses=''" start >/dev/null
tail -n +"$((lines + 1))" "$LOG" | sed 's/^[0-9-]* [0-9:.]* [A-Z0-9+-]* \[[0-9]*\] //'

echo "== Después de la recuperación"
echo "filas y total: $(sql -F ' ' -c 'SELECT count(*), sum(amount) FROM payment')"
echo "filas con xmax = $xid: $(sql -c "SELECT count(*) FROM payment WHERE xmax = '$xid'")"
echo "estado de la transacción $xid: $(sql -c "SELECT pg_xact_status('$xid')")"
sql -c "CREATE EXTENSION pg_walinspect"
sql -F ' ' -c "SELECT count(*), count(*) FILTER (WHERE record_type IN ('COMMIT', 'ABORT'))
               FROM pg_get_wal_records_info('$redo', pg_current_wal_lsn())
               WHERE xid = '$xid'" | {
    read -r total fin
    echo "registros del WAL con xid $xid: $total; de ellos COMMIT o ABORT: $fin"
}
