
export PGDATA=/tmp/toast_corrupt
export PGPORT=5440
export PGDATABASE=postgres

# turn checkpoint logging on
start_options="-c checkpoint_timeout=30s -c checkpoint_completion_target=0.9 -c log_checkpoints=on -c max_prepared_transactions=10"
initdb -D $PGDATA
pg_ctl -l logfile -o "$start_options" -w start
psql -c 'create table testtoast (a int, b text)'
psql -c 'create table auxtable (a int, b text)'

# Do clean shutdown and restart to ensure we start with no cached OIDs
pg_ctl -w stop
rm logfile
pg_ctl -l logfile -o "$start_options" -w start

# Generate some non-trivial work for the upcoming checkpoint.
pg_controldata $PGDATA | grep NextOID
psql -c "insert into auxtable select generate_series(1,100000), 'foo'"

# wait for the "time" checkpoint to start
echo "waiting for checkpoint start"
while ( true );
do
	tail logfile | grep "checkpoint starting"
	if [ "$?" == "0" ]; then
		echo "checkpoint is starting"
		break
	fi
	sleep 1
done

# now consume some OIDs.. insert them into the toast table. Since we started
# with zero cached OIDs, this should generate a XLOG_NEXTOID WAL record.
#
# This command must finish before the checkpoint finishes
psql -c "insert into testtoast select 1, (select string_agg('', md5(random()::text)) from generate_series(1,100)) from generate_series(1,20);"

# now wait for the checkpoint to finish.
echo "waiting for checkpoint complete"
while ( true );
do
	tail logfile | grep "checkpoint complete"
	if [ "$?" == "0" ]; then
		echo "checkpoint complete"
		break
	fi
	sleep 1
done

# the checkpoint should have the NextOID prior to last block allocation
pg_controldata $PGDATA | grep NextOID

# do some work and then crash
psql -c "insert into auxtable select generate_series(1,100), 'foo'"
pg_ctl -m immediate stop
sleep 2

# restart
pg_ctl -l logfile -o "$start_options" -w start

# the NextOID should have been advanced to include the OIDs that we consumed.
# But that doesn't happen, leading to duplicate OIDs and problems there after.
pg_controldata $PGDATA | grep NextOID

# create a prepare transaction to hold back OldestXmin
psql -c "begin; insert into auxtable select generate_series(1,100), 'foo'; prepare transaction 'p1';"

# now delete the old tuples. That will lead to deletion from the TOAST table as
# well. But since we're holding back OldestXmin, these tuples can't be cleaned
# up
psql -c "delete from testtoast"

# this should now insert duplicate OIDs while the previous RECENTLY_DEAD tuples
# are still around
psql -c "insert into testtoast select 1, (select string_agg('', md5(random()::text)) from generate_series(1,100)) from generate_series(1,20);"

### ERRROR ###
psql -c "\copy (select t.*::text from testtoast t) to /dev/null"

# prove that REINDEX won't fix the problem unless OldestXmin advances.
oid=`psql -At -c "select oid from pg_class where relname = 'testtoast'"`
psql -c "REINDEX TABLE pg_toast.pg_toast_$oid"
psql -c "\copy (select t.*::text from testtoast t) to /dev/null"

# and neither would VACUUM
psql -c "VACUUM pg_toast.pg_toast_$oid"
psql -c "\copy (select t.*::text from testtoast t) to /dev/null"
gxid=`psql -At -c "select gid from pg_prepared_xacts"`

# once the prepared transaction is released, either REINDEX or VACUUM will fix
# the problem
psql -c "COMMIT PREPARED '$gxid'"
psql -c "REINDEX TABLE pg_toast.pg_toast_$oid"
psql -c "\copy (select t.*::text from testtoast t) to /dev/null"

