/*-------------------------------------------------------------------------
 *
 * pg_type.c
 *	  routines to support manipulation of the pg_type relation
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_type.c,v 1.47 2000/01/17 23:57:43 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/indexing.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "utils/builtins.h"
#include "utils/syscache.h"


static Oid TypeShellMakeWithOpenRelation(Relation pg_type_desc,
							  char *typeName);

/* ----------------------------------------------------------------
 *		TypeGetWithOpenRelation
 *
 *		preforms a scan on pg_type for a type tuple with the
 *		given type name.
 * ----------------------------------------------------------------
 *		pg_type_desc			 -- reldesc for pg_type
 *		typeName				 -- name of type to be fetched
 *		defined					 -- has the type been defined?
 */
static Oid
TypeGetWithOpenRelation(Relation pg_type_desc,
						char *typeName,
						bool *defined)
{
	HeapScanDesc scan;
	HeapTuple	tup;
	Oid			typoid;

	static ScanKeyData typeKey[1] = {
		{0, Anum_pg_type_typname, F_NAMEEQ}
	};

	/* ----------------
	 *	initialize the scan key and begin a scan of pg_type
	 * ----------------
	 */
	fmgr_info(F_NAMEEQ, &typeKey[0].sk_func);
	typeKey[0].sk_nargs = typeKey[0].sk_func.fn_nargs;
	typeKey[0].sk_argument = PointerGetDatum(typeName);

	scan = heap_beginscan(pg_type_desc,
						  0,
						  SnapshotSelf, /* cache? */
						  1,
						  typeKey);

	/* ----------------
	 *	get the type tuple, if it exists.
	 * ----------------
	 */
	tup = heap_getnext(scan, 0);

	/* ----------------
	 *	if no type tuple exists for the given type name, then
	 *	end the scan and return appropriate information.
	 * ----------------
	 */
	if (!HeapTupleIsValid(tup))
	{
		heap_endscan(scan);
		*defined = false;
		return InvalidOid;
	}

	/* ----------------
	 *	here, the type tuple does exist so we pull information from
	 *	the typisdefined field of the tuple and return the tuple's
	 *	oid, which is the oid of the type.
	 * ----------------
	 */
	*defined = (bool) ((Form_pg_type) GETSTRUCT(tup))->typisdefined;
	typoid = tup->t_data->t_oid;

	heap_endscan(scan);

	return typoid;
}

/* ----------------------------------------------------------------
 *		TypeGet
 *
 *		Finds the ObjectId of a type, even if uncommitted; "defined"
 *		is only set if the type has actually been defined, i.e., if
 *		the type tuple is not a shell.
 *
 *		Note: the meat of this function is now in the function
 *			  TypeGetWithOpenRelation().  -cim 6/15/90
 *
 *		Also called from util/remove.c
 * ----------------------------------------------------------------
 */
Oid
TypeGet(char *typeName,			/* name of type to be fetched */
		bool *defined)			/* has the type been defined? */
{
	Relation	pg_type_desc;
	Oid			typeoid;

	/* ----------------
	 *	open the pg_type relation
	 * ----------------
	 */
	pg_type_desc = heap_openr(TypeRelationName, AccessShareLock);

	/* ----------------
	 *	scan the type relation for the information we want
	 * ----------------
	 */
	typeoid = TypeGetWithOpenRelation(pg_type_desc,
									  typeName,
									  defined);

	/* ----------------
	 *	close the type relation and return the type oid.
	 * ----------------
	 */
	heap_close(pg_type_desc, AccessShareLock);

	return typeoid;
}

/* ----------------------------------------------------------------
 *		TypeShellMakeWithOpenRelation
 *
 * ----------------------------------------------------------------
 */
static Oid
TypeShellMakeWithOpenRelation(Relation pg_type_desc, char *typeName)
{
	int			i;
	HeapTuple	tup;
	Datum		values[Natts_pg_type];
	char		nulls[Natts_pg_type];
	Oid			typoid;
	NameData	name;
	TupleDesc	tupDesc;

	/* ----------------
	 *	initialize our *nulls and *values arrays
	 * ----------------
	 */
	for (i = 0; i < Natts_pg_type; ++i)
	{
		nulls[i] = ' ';
		values[i] = (Datum) NULL;		/* redundant, but safe */
	}

	/* ----------------
	 *	initialize *values with the type name and
	 * ----------------
	 */
	i = 0;
	namestrcpy(&name, typeName);
	values[i++] = NameGetDatum(&name);	/* 1 */
	values[i++] = (Datum) InvalidOid;	/* 2 */
	values[i++] = (Datum) (int16) 0;	/* 3 */
	values[i++] = (Datum) (int16) 0;	/* 4 */
	values[i++] = (Datum) (bool) 0;		/* 5 */
	values[i++] = (Datum) (bool) 0;		/* 6 */
	values[i++] = (Datum) (bool) 0;		/* 7 */
	values[i++] = (Datum) (bool) 0;		/* 8 */
	values[i++] = (Datum) InvalidOid;	/* 9 */
	values[i++] = (Datum) InvalidOid;	/* 10 */
	values[i++] = (Datum) InvalidOid;	/* 11 */
	values[i++] = (Datum) InvalidOid;	/* 12 */
	values[i++] = (Datum) InvalidOid;	/* 13 */
	values[i++] = (Datum) InvalidOid;	/* 14 */
	values[i++] = (Datum) 'i';	/* 15 */

	/*
	 * ... and fill typdefault with a bogus value
	 */
	values[i++] = (Datum) fmgr(F_TEXTIN, typeName);		/* 15 */

	/* ----------------
	 *	create a new type tuple with FormHeapTuple
	 * ----------------
	 */
	tupDesc = pg_type_desc->rd_att;

	tup = heap_formtuple(tupDesc, values, nulls);

	/* ----------------
	 *	insert the tuple in the relation and get the tuple's oid.
	 * ----------------
	 */
	heap_insert(pg_type_desc, tup);
	typoid = tup->t_data->t_oid;

	if (RelationGetForm(pg_type_desc)->relhasindex)
	{
		Relation	idescs[Num_pg_type_indices];

		CatalogOpenIndices(Num_pg_type_indices, Name_pg_type_indices, idescs);
		CatalogIndexInsert(idescs, Num_pg_type_indices, pg_type_desc, tup);
		CatalogCloseIndices(Num_pg_type_indices, idescs);
	}
	/* ----------------
	 *	free the tuple and return the type-oid
	 * ----------------
	 */
	heap_freetuple(tup);

	return typoid;
}

/* ----------------------------------------------------------------
 *		TypeShellMake
 *
 *		This procedure inserts a "shell" tuple into the type
 *		relation.  The type tuple inserted has invalid values
 *		and in particular, the "typisdefined" field is false.
 *
 *		This is used so that a tuple exists in the catalogs.
 *		The invalid fields should be fixed up sometime after
 *		this routine is called, and then the "typeisdefined"
 *		field is set to true. -cim 6/15/90
 * ----------------------------------------------------------------
 */
Oid
TypeShellMake(char *typeName)
{
	Relation	pg_type_desc;
	Oid			typoid;

	Assert(PointerIsValid(typeName));

	/* ----------------
	 *	open pg_type
	 * ----------------
	 */
	pg_type_desc = heap_openr(TypeRelationName, RowExclusiveLock);

	/* ----------------
	 *	insert the shell tuple
	 * ----------------
	 */
	typoid = TypeShellMakeWithOpenRelation(pg_type_desc, typeName);

	/* ----------------
	 *	close pg_type and return the tuple's oid.
	 * ----------------
	 */
	heap_close(pg_type_desc, RowExclusiveLock);

	return typoid;
}

/* ----------------------------------------------------------------
 *		TypeCreate
 *
 *		This does all the necessary work needed to define a new type.
 * ----------------------------------------------------------------
 */
Oid
TypeCreate(char *typeName,
		   Oid relationOid,		/* only for 'c'atalog typeTypes */
		   int16 internalSize,
		   int16 externalSize,
		   char typeType,
		   char typDelim,
		   char *inputProcedure,
		   char *outputProcedure,
		   char *receiveProcedure,
		   char *sendProcedure,
		   char *elementTypeName,
		   char *defaultTypeValue,		/* internal rep */
		   bool passedByValue,
		   char alignment)
{
	int			i,
				j;
	Relation	pg_type_desc;
	HeapScanDesc pg_type_scan;

	Oid			typeObjectId;
	Oid			elementObjectId = InvalidOid;

	HeapTuple	tup;
	char		nulls[Natts_pg_type];
	char		replaces[Natts_pg_type];
	Datum		values[Natts_pg_type];

	char	   *procname;
	char	   *procs[4];
	bool		defined;
	NameData	name;
	TupleDesc	tupDesc;
	Oid			argList[FUNC_MAX_ARGS];

	static ScanKeyData typeKey[1] = {
		{0, Anum_pg_type_typname, F_NAMEEQ}
	};

	fmgr_info(F_NAMEEQ, &typeKey[0].sk_func);
	typeKey[0].sk_nargs = typeKey[0].sk_func.fn_nargs;

	/* ----------------
	 *	check that the type is not already defined.
	 * ----------------
	 */
	typeObjectId = TypeGet(typeName, &defined);
	if (OidIsValid(typeObjectId) && defined)
		elog(ERROR, "TypeCreate: type %s already defined", typeName);

	/* ----------------
	 *	if this type has an associated elementType, then we check that
	 *	it is defined.
	 * ----------------
	 */
	if (elementTypeName)
	{
		elementObjectId = TypeGet(elementTypeName, &defined);
		if (!defined)
			elog(ERROR, "TypeCreate: type %s is not defined", elementTypeName);
	}

	/* ----------------
	 *	XXX comment me
	 * ----------------
	 */
	if (externalSize == 0)
		externalSize = -1;		/* variable length */

	/* ----------------
	 *	initialize arrays needed by FormHeapTuple
	 * ----------------
	 */
	for (i = 0; i < Natts_pg_type; ++i)
	{
		nulls[i] = ' ';
		replaces[i] = 'r';
		values[i] = (Datum) NULL;		/* redundant, but nice */
	}

	/*
	 * XXX
	 *
	 * Do this so that user-defined types have size -1 instead of zero if
	 * they are variable-length - this is so that everything else in the
	 * backend works.
	 */

	if (internalSize == 0)
		internalSize = -1;

	/* ----------------
	 *	initialize the *values information
	 * ----------------
	 */
	i = 0;
	namestrcpy(&name, typeName);
	values[i++] = NameGetDatum(&name);	/* 1 */
	values[i++] = (Datum) GetUserId();	/* 2 */
	values[i++] = (Datum) internalSize; /* 3 */
	values[i++] = (Datum) externalSize; /* 4 */
	values[i++] = (Datum) passedByValue;/* 5 */
	values[i++] = (Datum) typeType;		/* 6 */
	values[i++] = (Datum) (bool) 1;		/* 7 */
	values[i++] = (Datum) typDelim;		/* 8 */
	values[i++] = (Datum) (typeType == 'c' ? relationOid : InvalidOid); /* 9 */
	values[i++] = (Datum) elementObjectId;		/* 10 */

	procs[0] = inputProcedure;
	procs[1] = outputProcedure;
	procs[2] = (receiveProcedure) ? receiveProcedure : inputProcedure;
	procs[3] = (sendProcedure) ? sendProcedure : outputProcedure;

	for (j = 0; j < 4; ++j)
	{
		procname = procs[j];

		/*
		 * First look for a 1-argument func with all argtypes 0. This is
		 * valid for all four kinds of procedure.
		 */
		MemSet(argList, 0, FUNC_MAX_ARGS * sizeof(Oid));

		tup = SearchSysCacheTuple(PROCNAME,
								  PointerGetDatum(procname),
								  Int32GetDatum(1),
								  PointerGetDatum(argList),
								  0);

		if (!HeapTupleIsValid(tup))
		{

			/*
			 * For array types, the input procedures may take 3 args (data
			 * value, element OID, atttypmod); the pg_proc argtype
			 * signature is 0,0,INT4OID.  The output procedures may take 2
			 * args (data value, element OID).
			 */
			if (OidIsValid(elementObjectId))
			{
				int			nargs;

				if (j % 2)
				{
					/* output proc */
					nargs = 2;
				}
				else
				{
					/* input proc */
					nargs = 3;
					argList[2] = INT4OID;
				}
				tup = SearchSysCacheTuple(PROCNAME,
										  PointerGetDatum(procname),
										  Int32GetDatum(nargs),
										  PointerGetDatum(argList),
										  0);
			}
			if (!HeapTupleIsValid(tup))
				func_error("TypeCreate", procname, 1, argList, NULL);
		}

		values[i++] = (Datum) tup->t_data->t_oid;		/* 11 - 14 */
	}

	/* ----------------
	 * set default alignment
	 * ----------------
	 */
	values[i++] = (Datum) alignment;	/* 15 */

	/* ----------------
	 *	initialize the default value for this type.
	 * ----------------
	 */
	values[i] = (Datum) fmgr(F_TEXTIN,	/* 16 */
							 PointerIsValid(defaultTypeValue)
							 ? defaultTypeValue : "-"); /* XXX default
														 * typdefault */

	/* ----------------
	 *	open pg_type and begin a scan for the type name.
	 * ----------------
	 */
	pg_type_desc = heap_openr(TypeRelationName, RowExclusiveLock);

	typeKey[0].sk_argument = PointerGetDatum(typeName);
	pg_type_scan = heap_beginscan(pg_type_desc,
								  0,
								  SnapshotSelf, /* cache? */
								  1,
								  typeKey);

	/* ----------------
	 *	define the type either by adding a tuple to the type
	 *	relation, or by updating the fields of the "shell" tuple
	 *	already there.
	 * ----------------
	 */
	tup = heap_getnext(pg_type_scan, 0);
	if (HeapTupleIsValid(tup))
	{
		tup = heap_modifytuple(tup,
							   pg_type_desc,
							   values,
							   nulls,
							   replaces);

		heap_update(pg_type_desc, &tup->t_self, tup, NULL);

		typeObjectId = tup->t_data->t_oid;
	}
	else
	{
		tupDesc = pg_type_desc->rd_att;

		tup = heap_formtuple(tupDesc,
							 values,
							 nulls);

		heap_insert(pg_type_desc, tup);

		typeObjectId = tup->t_data->t_oid;
	}

	/* ----------------
	 *	finish up
	 * ----------------
	 */
	heap_endscan(pg_type_scan);

	if (RelationGetForm(pg_type_desc)->relhasindex)
	{
		Relation	idescs[Num_pg_type_indices];

		CatalogOpenIndices(Num_pg_type_indices, Name_pg_type_indices, idescs);
		CatalogIndexInsert(idescs, Num_pg_type_indices, pg_type_desc, tup);
		CatalogCloseIndices(Num_pg_type_indices, idescs);
	}

	heap_close(pg_type_desc, RowExclusiveLock);

	return typeObjectId;
}

/* ----------------------------------------------------------------
 *		TypeRename
 *
 *		This renames a type
 * ----------------------------------------------------------------
 */
void
TypeRename(char *oldTypeName, char *newTypeName)
{
	Relation	pg_type_desc;
	Relation	idescs[Num_pg_type_indices];
	HeapTuple	oldtup,
				newtup;

	pg_type_desc = heap_openr(TypeRelationName, RowExclusiveLock);

	oldtup = SearchSysCacheTupleCopy(TYPENAME,
									 PointerGetDatum(oldTypeName),
									 0, 0, 0);

	if (!HeapTupleIsValid(oldtup))
	{
		heap_close(pg_type_desc, RowExclusiveLock);
		elog(ERROR, "TypeRename: type %s not defined", oldTypeName);
	}

	newtup = SearchSysCacheTuple(TYPENAME,
								 PointerGetDatum(newTypeName),
								 0, 0, 0);
	if (HeapTupleIsValid(newtup))
	{
		heap_freetuple(oldtup);
		heap_close(pg_type_desc, RowExclusiveLock);
		elog(ERROR, "TypeRename: type %s already defined", newTypeName);
	}

	namestrcpy(&(((Form_pg_type) GETSTRUCT(oldtup))->typname), newTypeName);

	heap_update(pg_type_desc, &oldtup->t_self, oldtup, NULL);

	/* update the system catalog indices */
	CatalogOpenIndices(Num_pg_type_indices, Name_pg_type_indices, idescs);
	CatalogIndexInsert(idescs, Num_pg_type_indices, pg_type_desc, oldtup);
	CatalogCloseIndices(Num_pg_type_indices, idescs);

	heap_freetuple(oldtup);
	heap_close(pg_type_desc, RowExclusiveLock);
}

/*
 * makeArrayTypeName(typeName);
 *	  - given a base type name, make an array of type name out of it
 *
 * the CALLER is responsible for pfreeing the
 */

char *
makeArrayTypeName(char *typeName)
{
	char	   *arr;

	if (!typeName)
		return NULL;
	arr = palloc(strlen(typeName) + 2);
	arr[0] = '_';
	strcpy(arr + 1, typeName);

	return arr;

}
