/*-------------------------------------------------------------------------
 *
 * pg_aggregate.c
 *	  routines to support manipulation of the pg_aggregate relation
 *
 * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_aggregate.c,v 1.35 2000/07/17 03:04:43 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/indexing.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "parser/parse_coerce.h"
#include "parser/parse_func.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
 * 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.
 *		All types and functions must have been defined
 *		prior to defining the aggregate.
 *
 * ---------------
 */
void
AggregateCreate(char *aggName,
				char *aggtransfnName,
				char *aggfinalfnName,
				char *aggbasetypeName,
				char *aggtranstypeName,
				char *agginitval)
{
	Relation	aggdesc;
	HeapTuple	tup;
	char		nulls[Natts_pg_aggregate];
	Datum		values[Natts_pg_aggregate];
	Form_pg_proc proc;
	Oid			transfn;
	Oid			finalfn = InvalidOid; /* can be omitted */
	Oid			basetype;
	Oid			transtype;
	Oid			finaltype;
	Oid			fnArgs[FUNC_MAX_ARGS];
	int			nargs;
	NameData	aname;
	TupleDesc	tupDesc;
	int			i;

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

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

	if (!aggtransfnName)
		elog(ERROR, "AggregateCreate: aggregate must have a transition function");

	/*
	 * Handle the aggregate's base type (input data type).  This can be
	 * specified as 'ANY' for a data-independent transition function,
	 * such as COUNT(*).
	 */
	tup = SearchSysCacheTuple(TYPENAME,
							  PointerGetDatum(aggbasetypeName),
							  0, 0, 0);
	if (HeapTupleIsValid(tup))
	{
		basetype = tup->t_data->t_oid;
		Assert(OidIsValid(basetype));
	}
	else
	{
		if (strcasecmp(aggbasetypeName, "ANY") != 0)
			elog(ERROR, "AggregateCreate: Type '%s' undefined",
				 aggbasetypeName);
		basetype = InvalidOid;
	}

	/* make sure there is no existing agg of same name and base type */
	tup = SearchSysCacheTuple(AGGNAME,
							  PointerGetDatum(aggName),
							  ObjectIdGetDatum(basetype),
							  0, 0);
	if (HeapTupleIsValid(tup))
		elog(ERROR,
			 "AggregateCreate: aggregate '%s' with base type '%s' already exists",
			 aggName, aggbasetypeName);

	/* handle transtype */
	tup = SearchSysCacheTuple(TYPENAME,
							  PointerGetDatum(aggtranstypeName),
							  0, 0, 0);
	if (!HeapTupleIsValid(tup))
		elog(ERROR, "AggregateCreate: Type '%s' undefined",
			 aggtranstypeName);
	transtype = tup->t_data->t_oid;
	Assert(OidIsValid(transtype));

	/* handle transfn */
	fnArgs[0] = transtype;
	if (OidIsValid(basetype))
	{
		fnArgs[1] = basetype;
		nargs = 2;
	}
	else
	{
		nargs = 1;
	}
	tup = SearchSysCacheTuple(PROCNAME,
							  PointerGetDatum(aggtransfnName),
							  Int32GetDatum(nargs),
							  PointerGetDatum(fnArgs),
							  0);
	if (!HeapTupleIsValid(tup))
		func_error("AggregateCreate", aggtransfnName, nargs, fnArgs, NULL);
	transfn = tup->t_data->t_oid;
	proc = (Form_pg_proc) GETSTRUCT(tup);
	if (proc->prorettype != transtype)
		elog(ERROR, "AggregateCreate: return type of '%s' is not '%s'",
			 aggtransfnName, aggtranstypeName);
	Assert(OidIsValid(transfn));
	/*
	 * If the transfn is strict and the initval is NULL, make sure
	 * input type and transtype are the same (or at least binary-
	 * compatible), so that it's OK to use the first input value
	 * as the initial transValue.
	 */
	if (((Form_pg_proc) GETSTRUCT(tup))->proisstrict && agginitval == NULL)
	{
		if (basetype != transtype &&
			! IS_BINARY_COMPATIBLE(basetype, transtype))
			elog(ERROR, "AggregateCreate: must not omit initval when transfn is strict and transtype is not compatible with input type");
	}

	/* handle finalfn, if supplied */
	if (aggfinalfnName)
	{
		fnArgs[0] = transtype;
		fnArgs[1] = 0;
		tup = SearchSysCacheTuple(PROCNAME,
								  PointerGetDatum(aggfinalfnName),
								  Int32GetDatum(1),
								  PointerGetDatum(fnArgs),
								  0);
		if (!HeapTupleIsValid(tup))
			func_error("AggregateCreate", aggfinalfnName, 1, fnArgs, NULL);
		finalfn = tup->t_data->t_oid;
		proc = (Form_pg_proc) GETSTRUCT(tup);
		finaltype = proc->prorettype;
		Assert(OidIsValid(finalfn));
	}
	else
	{
		/*
		 * If no finalfn, aggregate result type is type of the state value
		 */
		finaltype = transtype;
	}
	Assert(OidIsValid(finaltype));

	/* 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_aggtransfn - 1] = ObjectIdGetDatum(transfn);
	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
	values[Anum_pg_aggregate_aggbasetype - 1] = ObjectIdGetDatum(basetype);
	values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(transtype);
	values[Anum_pg_aggregate_aggfinaltype - 1] = ObjectIdGetDatum(finaltype);

	if (agginitval)
		values[Anum_pg_aggregate_agginitval - 1] =
			DirectFunctionCall1(textin, CStringGetDatum(agginitval));
	else
		nulls[Anum_pg_aggregate_agginitval - 1] = 'n';

	aggdesc = heap_openr(AggregateRelationName, RowExclusiveLock);
	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");

	if (RelationGetForm(aggdesc)->relhasindex)
	{
		Relation	idescs[Num_pg_aggregate_indices];

		CatalogOpenIndices(Num_pg_aggregate_indices, Name_pg_aggregate_indices, idescs);
		CatalogIndexInsert(idescs, Num_pg_aggregate_indices, aggdesc, tup);
		CatalogCloseIndices(Num_pg_aggregate_indices, idescs);
	}

	heap_close(aggdesc, RowExclusiveLock);
}

Datum
AggNameGetInitVal(char *aggName, Oid basetype, bool *isNull)
{
	HeapTuple	tup;
	Oid			transtype,
				typinput,
				typelem;
	Datum		textInitVal;
	char	   *strInitVal;
	Datum		initVal;

	Assert(PointerIsValid(aggName));
	Assert(PointerIsValid(isNull));

	tup = SearchSysCacheTuple(AGGNAME,
							  PointerGetDatum(aggName),
							  ObjectIdGetDatum(basetype),
							  0, 0);
	if (!HeapTupleIsValid(tup))
		elog(ERROR, "AggNameGetInitVal: cache lookup failed for aggregate '%s'",
			 aggName);
	transtype = ((Form_pg_aggregate) GETSTRUCT(tup))->aggtranstype;

	/*
	 * initval is potentially null, so don't try to access it as a struct
	 * field. Must do it the hard way with SysCacheGetAttr.
	 */
	textInitVal = SysCacheGetAttr(AGGNAME, tup,
								  Anum_pg_aggregate_agginitval,
								  isNull);
	if (*isNull)
		return (Datum) 0;

	strInitVal = DatumGetCString(DirectFunctionCall1(textout, textInitVal));

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

	initVal = OidFunctionCall3(typinput,
							   CStringGetDatum(strInitVal),
							   ObjectIdGetDatum(typelem),
							   Int32GetDatum(-1));

	pfree(strInitVal);
	return initVal;
}
