#include "postgres.h"
#include "c.h"
#include "fmgr.h"
#include "postgres_ext.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/htup.h"
#include "access/tupdesc.h"
#include "funcapi.h"
#include "storage/lock.h"
#include "utils/elog.h"
#include "utils/errcodes.h"
#include "utils/rel.h"
#include "utils/relcache.h"

extern Datum exercise_tuple_memory_leak(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(exercise_tuple_memory_leak);

/*
 * exercise_tuple_memory_leak calls BuildTupleFromCStrings in a loop to
 * demonstrate that memory leaks arise even when its return value is freed.
 */
Datum
exercise_tuple_memory_leak(PG_FUNCTION_ARGS)
{
	Oid relationId = PG_GETARG_OID(0);
	char *value = PG_GETARG_CSTRING(1);
	char **values = &value;

	Relation relation = heap_open(relationId, AccessShareLock);
	TupleDesc tupleDescriptor = RelationGetDescr(relation);
	AttInMetadata *attInputMetadata = TupleDescGetAttInMetadata(tupleDescriptor);

	if (tupleDescriptor->natts != 1)
	{
		ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						errmsg("provided relation must have exactly one column")));
	}

	while (true)
	{
		HeapTuple tuple = BuildTupleFromCStrings(attInputMetadata, values);
		heap_freetuple(tuple);
	}

	heap_close(relation, RowExclusiveLock);

	PG_RETURN_VOID();
}
