/*-------------------------------------------------------------------------
 *
 * pg_aggregate.c
 *	  routines to support manipulation of the pg_aggregate relation
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /usr/local/cvsroot/pgsql/src/backend/catalog/pg_aggregate.c,v 1.20.2.1 1999/08/02 05:56:55 scrappy Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/syscache.h"

/* ----------------
 * AggregateCreate
 *
 * aggregates overloading has been added.  Instead of the full
 * overload support we have for functions, aggregate overloading only
 * applies to exact basetype matches.  That is, we don't check the
 * the inheritance hierarchy
 *
 * OLD COMMENTS:
 *		Currently, redefining aggregates using the same name is not
 *		supported.	In such a case, a warning is printed that the
 *		aggregate already exists.  If such is not the case, a new tuple
 *		is created and inserted in the aggregate relation.	The fields
 *		of this tuple are aggregate name, owner id, 2 transition functions
 *		(called aggtransfn1 and aggtransfn2), final function (aggfinalfn),
 *		type of data on which aggtransfn1 operates (aggbasetype), return
 *		types of the two transition functions (aggtranstype1 and
 *		aggtranstype2), final return type (aggfinaltype), and initial values
 *		for the two state transition functions (agginitval1 and agginitval2).
 *		All types and functions must have been defined
 *		prior to defining the aggregate.
 *
 * ---------------
 */
void
AggregateCreate(char *aggName,
				char *aggtransfn1Name,
				char *aggtransfn2Name,
				char *aggfinalfnName,
				char *aggbasetypeName,
				char *aggtransfn1typeName,
				char *aggtransfn2typeName,
				char *agginitval1,
				char *agginitval2)
{
	int			i;
	Relation	aggdesc;
	HeapTuple	tup;
	char		nulls[Natts_pg_aggregate];
	Datum		values[Natts_pg_aggregate];
	Form_pg_proc proc;
	Oid			xfn1 = InvalidOid;
	Oid			xfn2 = InvalidOid;
	Oid			ffn = InvalidOid;
	Oid			xbase = InvalidOid;
	Oid			xret1 = InvalidOid;
	Oid			xret2 = InvalidOid;
	Oid			fret = InvalidOid;
	Oid			fnArgs[8];
	NameData	aname;
	TupleDesc	tupDesc;

	MemSet(fnArgs, 0, 8 * sizeof(Oid));

	/* sanity checks */
	if (!aggName)
		elog(ERROR, "AggregateCreate: no aggregate name supplied");

	if (!aggtransfn1Name && !aggtransfn2Name)
		elog(ERROR, "AggregateCreate: aggregate must have at least one transition function");

	tup = SearchSysCacheTuple(TYPNAME,
							  PointerGetDatum(aggbasetypeName),
							  0, 0, 0);
	if (!HeapTupleIsValid(tup))
		elog(ERROR, "AggregateCreate: Type '%s' undefined", aggbasetypeName);
	xbase = tup->t_data->t_oid;

	if (aggtransfn1Name)
	{
		tup = SearchSysCacheTuple(TYPNAME,
								  PointerGetDatum(aggtransfn1typeName),
								  0, 0, 0);
		if (!HeapTupleIsValid(tup))
			elog(ERROR, "AggregateCreate: Type '%s' undefined",
				 aggtransfn1typeName);
		xret1 = tup->t_data->t_oid;

		fnArgs[0] = xret1;
		fnArgs[1] = xbase;
		tup = SearchSysCacheTuple(PRONAME,
								  PointerGetDatum(aggtransfn1Name),
								  Int32GetDatum(2),
								  PointerGetDatum(fnArgs),
								  0);
		if (!HeapTupleIsValid(tup))
			elog(ERROR, "AggregateCreate: '%s('%s', '%s') does not exist",
				 aggtransfn1Name, aggtransfn1typeName, aggbasetypeName);
		if (((Form_pg_proc) GETSTRUCT(tup))->prorettype != xret1)
			elog(ERROR, "AggregateCreate: return type of '%s' is not '%s'",
				 aggtransfn1Name,
				 aggtransfn1typeName);
		xfn1 = tup->t_data->t_oid;
		if (!OidIsValid(xfn1) || !OidIsValid(xret1) ||
			!OidIsValid(xbase))
			elog(ERROR, "AggregateCreate: bogus function '%s'", aggfinalfnName);
	}

	if (aggtransfn2Name)
	{
		tup = SearchSysCacheTuple(TYPNAME,
								  PointerGetDatum(aggtransfn2typeName),
								  0, 0, 0);
		if (!HeapTupleIsValid(tup))
			elog(ERROR, "AggregateCreate: Type '%s' undefined",
				 aggtransfn2typeName);
		xret2 = tup->t_data->t_oid;

		fnArgs[0] = xret2;
		fnArgs[1] = 0;
		tup = SearchSysCacheTuple(PRONAME,
								  PointerGetDatum(aggtransfn2Name),
								  Int32GetDatum(1),
								  PointerGetDatum(fnArgs),
								  0);
		if (!HeapTupleIsValid(tup))
			elog(ERROR, "AggregateCreate: '%s'('%s') does not exist",
				 aggtransfn2Name, aggtransfn2typeName);
		if (((Form_pg_proc) GETSTRUCT(tup))->prorettype != xret2)
			elog(ERROR, "AggregateCreate: return type of '%s' is not '%s'",
				 aggtransfn2Name, aggtransfn2typeName);
		xfn2 = tup->t_data->t_oid;
		if (!OidIsValid(xfn2) || !OidIsValid(xret2))
			elog(ERROR, "AggregateCreate: bogus function '%s'", aggfinalfnName);
	}

	tup = SearchSysCacheTuple(AGGNAME,
							  PointerGetDatum(aggName),
							  ObjectIdGetDatum(xbase),
							  0, 0);
	if (HeapTupleIsValid(tup))
		elog(ERROR,
			 "AggregateCreate: aggregate '%s' with base type '%s' already exists",
			 aggName, aggbasetypeName);

	/* more sanity checks */
	if (aggtransfn1Name && aggtransfn2Name && !aggfinalfnName)
		elog(ERROR, "AggregateCreate: Aggregate must have final function with both transition functions");

	if ((!aggtransfn1Name || !aggtransfn2Name) && aggfinalfnName)
		elog(ERROR, "AggregateCreate: Aggregate cannot have final function without both transition functions");

	if (aggfinalfnName)
	{
		fnArgs[0] = xret1;
		fnArgs[1] = xret2;
		tup = SearchSysCacheTuple(PRONAME,
								  PointerGetDatum(aggfinalfnName),
								  Int32GetDatum(2),
								  PointerGetDatum(fnArgs),
								  0);
		if (!HeapTupleIsValid(tup))
			elog(ERROR, "AggregateCreate: '%s'('%s','%s') does not exist",
			   aggfinalfnName, aggtransfn1typeName, aggtransfn2typeName);
		ffn = tup->t_data->t_oid;
		proc = (Form_pg_proc) GETSTRUCT(tup);
		fret = proc->prorettype;
		if (!OidIsValid(ffn) || !OidIsValid(fret))
			elog(ERROR, "AggregateCreate: bogus function '%s'", aggfinalfnName);
	}

	/*
	 * If transition function 2 is defined, it must have an initial value,
	 * whereas transition function 1 does not, which allows man and min
	 * aggregates to return NULL if they are evaluated on empty sets.
	 */
	if (OidIsValid(xfn2) && !agginitval2)
		elog(ERROR, "AggregateCreate: transition function 2 MUST have an initial value");

	/* initialize nulls and values */
	for (i = 0; i < Natts_pg_aggregate; i++)
	{
		nulls[i] = ' ';
		values[i] = (Datum) NULL;
	}
	namestrcpy(&aname, aggName);
	values[Anum_pg_aggregate_aggname - 1] = NameGetDatum(&aname);
	values[Anum_pg_aggregate_aggowner - 1] = Int32GetDatum(GetUserId());
	values[Anum_pg_aggregate_aggtransfn1 - 1] = ObjectIdGetDatum(xfn1);
	values[Anum_pg_aggregate_aggtransfn2 - 1] = ObjectIdGetDatum(xfn2);
	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(ffn);

	values[Anum_pg_aggregate_aggbasetype - 1] = ObjectIdGetDatum(xbase);
	if (!OidIsValid(xfn1))
	{
		values[Anum_pg_aggregate_aggtranstype1 - 1] = ObjectIdGetDatum(InvalidOid);
		values[Anum_pg_aggregate_aggtranstype2 - 1] = ObjectIdGetDatum(xret2);
		values[Anum_pg_aggregate_aggfinaltype - 1] = ObjectIdGetDatum(xret2);
	}
	else if (!OidIsValid(xfn2))
	{
		values[Anum_pg_aggregate_aggtranstype1 - 1] = ObjectIdGetDatum(xret1);
		values[Anum_pg_aggregate_aggtranstype2 - 1] = ObjectIdGetDatum(InvalidOid);
		values[Anum_pg_aggregate_aggfinaltype - 1] = ObjectIdGetDatum(xret1);
	}
	else
	{
		values[Anum_pg_aggregate_aggtranstype1 - 1] = ObjectIdGetDatum(xret1);
		values[Anum_pg_aggregate_aggtranstype2 - 1] = ObjectIdGetDatum(xret2);
		values[Anum_pg_aggregate_aggfinaltype - 1] = ObjectIdGetDatum(fret);
	}

	if (agginitval1)
		values[Anum_pg_aggregate_agginitval1 - 1] = PointerGetDatum(textin(agginitval1));
	else
		nulls[Anum_pg_aggregate_agginitval1 - 1] = 'n';

	if (agginitval2)
		values[Anum_pg_aggregate_agginitval2 - 1] = PointerGetDatum(textin(agginitval2));
	else
		nulls[Anum_pg_aggregate_agginitval2 - 1] = 'n';

	if (!RelationIsValid(aggdesc = heap_openr(AggregateRelationName)))
		elog(ERROR, "AggregateCreate: could not open '%s'",
			 AggregateRelationName);

	tupDesc = aggdesc->rd_att;
	if (!HeapTupleIsValid(tup = heap_formtuple(tupDesc,
											   values,
											   nulls)))
		elog(ERROR, "AggregateCreate: heap_formtuple failed");
	if (!OidIsValid(heap_insert(aggdesc, tup)))
		elog(ERROR, "AggregateCreate: heap_insert failed");
	heap_close(aggdesc);

}

char *
AggNameGetInitVal(char *aggName, Oid basetype, int xfuncno, bool *isNull)
{
	HeapTuple	tup;
	Relation	aggRel;
	int			initValAttno;
	Oid			transtype;
	text	   *textInitVal;
	char	   *strInitVal,
			   *initVal;

	Assert(PointerIsValid(aggName));
	Assert(PointerIsValid(isNull));
	Assert(xfuncno == 1 || xfuncno == 2);

	tup = SearchSysCacheTuple(AGGNAME,
							  PointerGetDatum(aggName),
							  ObjectIdGetDatum(basetype),
							  0, 0);
	if (!HeapTupleIsValid(tup))
		elog(ERROR, "AggNameGetInitVal: cache lookup failed for aggregate '%s'",
			 aggName);
	if (xfuncno == 1)
	{
		transtype = ((Form_pg_aggregate) GETSTRUCT(tup))->aggtranstype1;
		initValAttno = Anum_pg_aggregate_agginitval1;
	}
	else
		/* can only be 1 or 2 */
	{
		transtype = ((Form_pg_aggregate) GETSTRUCT(tup))->aggtranstype2;
		initValAttno = Anum_pg_aggregate_agginitval2;
	}

	aggRel = heap_openr(AggregateRelationName);
	if (!RelationIsValid(aggRel))
		elog(ERROR, "AggNameGetInitVal: could not open \"%-.*s\"",
			 AggregateRelationName);

	/*
	 * must use fastgetattr in case one or other of the init values is
	 * NULL
	 */
	textInitVal = (text *) fastgetattr(tup, initValAttno,
									   RelationGetDescr(aggRel),
									   isNull);
	if (!PointerIsValid(textInitVal))
		*isNull = true;
	if (*isNull)
	{
		heap_close(aggRel);
		return (char *) NULL;
	}
	strInitVal = textout(textInitVal);
	heap_close(aggRel);

	tup = SearchSysCacheTuple(TYPOID,
							  ObjectIdGetDatum(transtype),
							  0, 0, 0);
	if (!HeapTupleIsValid(tup))
	{
		pfree(strInitVal);
		elog(ERROR, "AggNameGetInitVal: cache lookup failed on aggregate transition function return type");
	}
	initVal = fmgr(((Form_pg_type) GETSTRUCT(tup))->typinput, strInitVal, -1);
	pfree(strInitVal);
	return initVal;
}
