#!/bin/sh
# Incremental backup misses VM bits cleared by VACUUM truncation.
#
# usage: sh incr-vm-truncate.sh BINDIR
#   BINDIR is the bin directory of a PostgreSQL 17 or later install, for example
#   sh incr-vm-truncate.sh /usr/local/pgsql/bin
# It runs two throwaway servers on ports 54871 and 54872 in a temp directory.
export LC_ALL=C
BIN=$1
DIR=$(mktemp -d /tmp/ivt.XXXX)
PORT=54871
trap '"$BIN/pg_ctl" -D $DIR/primary stop >/dev/null; "$BIN/pg_ctl" -D $DIR/restored stop >/dev/null; rm -rf $DIR' EXIT
sql() { "$BIN/psql" -X -q -At -h $DIR -p $1 -d postgres -c "$2"; }

# 18 and later enable data checksums by default, so turn them off there
"$BIN/initdb" -D $DIR/primary -A trust $("$BIN/initdb" --help | grep -o -- --no-data-checksums) >/dev/null
printf "port=$PORT\nlisten_addresses=''\nunix_socket_directories='$DIR'\nsummarize_wal=on\nautovacuum=off\n" >> $DIR/primary/postgresql.conf
"$BIN/pg_ctl" -D $DIR/primary -l $DIR/primary.log -w start >/dev/null

# Empty the second half of the table, VACUUM it without truncating, full backup
sql $PORT "create table t (id int primary key)"
sql $PORT "insert into t select generate_series(1, 200000)"
sql $PORT "delete from t where id > 100000"
sql $PORT "vacuum (truncate false) t"
"$BIN/pg_basebackup" -h $DIR -p $PORT -D $DIR/full -c fast

# Now VACUUM truncates the table, then take an incremental backup and restore it
sql $PORT "vacuum t"
"$BIN/pg_basebackup" -h $DIR -p $PORT -D $DIR/incr -c fast --incremental=$DIR/full/backup_manifest
"$BIN/pg_combinebackup" -o $DIR/restored $DIR/full $DIR/incr
echo "port=$((PORT + 1))" >> $DIR/restored/postgresql.conf
"$BIN/pg_ctl" -D $DIR/restored -l $DIR/restored.log -w start >/dev/null

# On both servers, insert rows and roll back, then count them with an index-only scan
for server in "primary $PORT" "restored $((PORT + 1))"; do
	set -- $server
	sql $2 "begin; insert into t select generate_series(300001, 310000); rollback"
	sql $2 "set enable_seqscan = off; set enable_bitmapscan = off;
	        select '$1: rolled back rows seen by index-only scan (expect 0): ' || count(*) from t where id > 300000"
done
