/*-------------------------------------------------------------------------
 *
 * depend.c
 *	  random postgres portal and utility support code
 *
 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header$
 *
 * NOTES
 *	  Manage dependencies between varying system objects.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_language.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
#include "commands/view.h"
#include "nodes/parsenodes.h"
#include "nodes/pg_list.h"
#include "nodes/makefuncs.h"
#include "parser/parse.h"		/* For keyword RESTRICT */
#include "rewrite/rewriteRemove.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"

static char *getObjectName(const ObjectAddress *object);
static char *getObjectType(const ObjectAddress *object);


/*
 * Records a requested dependency between 2 objects via their
 * respective objectAddress.
 *
 * It makes the assumption that both objects currently (or will)
 * exist before the end of the transaction.
 *
 * Behaviour, if true tells dependDelete to ignore RESTRICT as
 * the issued behaviour at the time and cascade to the object
 * anyway.  The reason for this is sequences generated by SERIAL
 * and array types.
 */
void
dependCreate(const ObjectAddress *depender,
			 const ObjectAddress *dependee,
			 bool  behavior)
{
	Relation	dependDesc;
	TupleDesc	tupDesc;
	HeapTuple	tup;
	int			i;

	char		nulls[Natts_pg_depend];
	Datum		values[Natts_pg_depend];

	for (i = 0; i < Natts_pg_depend; ++i)
	{
		nulls[i] = ' ';
		values[i] = (Datum) 0;
	}

	/*
	 * Record the Dependency.  Assume it can be added, and
	 * doesn't previously exist.  Some items (type creation)
	 * may add duplicates.
	 */
	values[Anum_pg_depend_classid - 1]	= ObjectIdGetDatum(depender->classId);
	values[Anum_pg_depend_objid - 1]	= ObjectIdGetDatum(depender->objectId);
	values[Anum_pg_depend_objsubid - 1]	= Int32GetDatum(depender->objectSubId);

	values[Anum_pg_depend_depclassid - 1]	= ObjectIdGetDatum(dependee->classId);
	values[Anum_pg_depend_depobjid - 1]		= ObjectIdGetDatum(dependee->objectId);
	values[Anum_pg_depend_depobjsubid - 1]	= Int32GetDatum(dependee->objectSubId);
	values[Anum_pg_depend_alwayscascade -1] = BoolGetDatum(behavior);

	dependDesc = heap_openr(DependRelationName, RowExclusiveLock);
	tupDesc = dependDesc->rd_att;

	if (!HeapTupleIsValid(tup = heap_formtuple(tupDesc,
											   values,
											   nulls)))
		elog(ERROR, "DependCreate: heap_formtuple failed");

	heap_insert(dependDesc, tup);

	heap_close(dependDesc, RowExclusiveLock); /* Required? */
}


/*
 * Drops the interdependencies between the object and it's
 * children depending on the behavior specified.  RESTRICT or
 * CASCADE types supported.
 *
 * RESTRICT will abort the transaction if other objects depend
 * on this one.
 *
 * CASCADE will drop all objects which depend on the supplied
 * object address.
 */
void
dependDelete(ObjectAddress *object, int behavior)
{

	Relation		rel;
	ScanKeyData 	dropkey[3];
	HeapTuple		tup;
	int				dropnkeys = 0;
	HeapScanDesc	scan;

	bool			deprem = true;
	ObjectAddress	objectCopy;

	if (behavior != RESTRICT && behavior != CASCADE)
		elog(ERROR, "Unknown Behavior Id %d", behavior);

	/*
	 * A copy of the object passed in needs to be taken, else we risk
	 * it being wiped from memory mid way through the drops.
	 */
	objectCopy.classId = object->classId;
	objectCopy.objectId = object->objectId;
	objectCopy.objectSubId  = object->objectSubId;	

	/*
	 * Test whether object being dropped is a dependee
	 * or not.
	 */
	rel = heap_openr(DependRelationName, RowExclusiveLock);


	while (deprem)
	{
		ScanKeyData key[3];

		ObjectAddress	foundObject;
		char 		   *findName;
		HeapTuple		findTup;
		Form_pg_depend	foundTup;
		int				nkeys = 0;


		/* Class Oid */
		Assert(objectCopy.classId != InvalidOid);
		ScanKeyEntryInitialize(&key[nkeys++],
							   0, Anum_pg_depend_depclassid, F_OIDEQ,
							   ObjectIdGetDatum(objectCopy.classId));

		/* Object Oid */
		Assert(objectCopy.objectId != InvalidOid);
		ScanKeyEntryInitialize(&key[nkeys++],
							   0, Anum_pg_depend_depobjid, F_OIDEQ,
							   ObjectIdGetDatum(objectCopy.objectId));

		/* SubObject Id */
		ScanKeyEntryInitialize(&key[nkeys++],
							   0, Anum_pg_depend_depobjsubid, F_INT4EQ,
							   Int32GetDatum(objectCopy.objectSubId));

		scan = heap_beginscan(rel, 0, SnapshotNow, nkeys, key);

		/*
		 * If no type tuple exists for the given type name, then end the scan
		 * and return appropriate information.
		 */
		tup = heap_getnext(scan, 0);
		if (!HeapTupleIsValid(tup))
		{
			deprem = false;
			continue;
		}

		foundTup = (Form_pg_depend) GETSTRUCT(tup);

		/*
		 * Lets load up and test the object which depends
		 * on the one we want to drop.
		 */
		foundObject.classId = foundTup->classid;
		foundObject.objectId = foundTup->objid;
		foundObject.objectSubId = foundTup->objsubid;

		heap_endscan(scan);

		/*
		 * If there are dependencies and behaviour is RESTRICT
		 * then drop them all.
		 */
		if (behavior == RESTRICT &&
			!foundTup->alwayscascade) {

			elog(ERROR, "Drop Restricted as %s %s Depends on %s %s",
				 getObjectType(&foundObject),
				 getObjectName(&foundObject),
				 getObjectType(&objectCopy),
				 getObjectName(&objectCopy));
		}

		/*
		 * Find out what type of object this is
		 * and call it's respective drop function.
		 *
		 * Assume we can find it.
		 */
		findTup = SearchSysCache(RELOID,
								 ObjectIdGetDatum(foundObject.classId),
								 0, 0, 0);
		if (!HeapTupleIsValid(findTup)) {
			elog(ERROR, "dependDelete:  Relation OID %d does not exist %d, %d",
				 foundObject.classId, objectCopy.classId, objectCopy.objectId);
		}

		findName = NameStr(((Form_pg_class) GETSTRUCT(findTup))->relname);

		ReleaseSysCache(findTup);

		/* Tell the user */
		if (foundTup->alwayscascade)
			elog(DEBUG1, "Implicit drop of %s %s",
				 getObjectType(&foundObject),
				 getObjectName(&foundObject));
		else
			elog(NOTICE, "Cascading drop to %s %s",
				 getObjectType(&foundObject),
				 getObjectName(&foundObject));

		/*
		 * The below functions are expected to cascade back here by calling
		 * dependDelete().  If they don't, a partial cascade can occur leaving
		 * poor relations in place.
		 */
		if (strcmp(findName, TypeRelationName) == 0)
		{
			TypeName   *typename;

			/* Make a TypeName so we can use standard type lookup machinery */
			typename = makeNode(TypeName);
			typename->names = NIL;
			typename->typeid = foundObject.objectId;
			typename->typmod = -1;
			typename->arrayBounds = NIL;

			/* Drop the type */
			RemoveTypeByTypeName(typename);

		}
		else if (strcmp(findName, RelationRelationName) == 0)
		{
			HeapTuple	relTup;
			char		relKind;
			char	   *relName;
			char	   *schemaName;

			relTup = SearchSysCache(RELOID,
									ObjectIdGetDatum(foundObject.objectId),
									0, 0, 0);
			if (!HeapTupleIsValid(relTup)) {
				elog(ERROR, "dependDelete: Relation %d does not exist",
					 foundObject.objectId);
			}

			relKind = ((Form_pg_class) GETSTRUCT(relTup))->relkind;
			relName = NameStr(((Form_pg_class) GETSTRUCT(relTup))->relname);
			schemaName = namespaceIdGetNspname(((Form_pg_class) GETSTRUCT(relTup))->relnamespace);

			ReleaseSysCache(relTup);

			if (relKind == RELKIND_INDEX)
			{
				/* Drop INDEX
				 *
				 * Future use will use below once indexes drops are
				 * corrected to be selfcontained.  Messages of tuple
				 * updates occur if more than a single index is removed
				 * during a table drop.
				 */
				index_drop(foundObject.objectId);
			}
			else if (relKind == RELKIND_VIEW)
				RemoveView(makeRangeVar(schemaName, relName));

	/*		else if (relKind == RELKIND_TOASTVALUE)	*/
				/* Drop Toast Table */


	/*		else if (relKind == RELKIND_SPECIAL)	*/
				/* Drop Special*/
			else if (relKind == RELKIND_SEQUENCE
				|| relKind == RELKIND_RELATION)
			{
				/* Drop Sequence or Table */
				RemoveRelation(makeRangeVar(schemaName, relName));
			}
			else
				elog(ERROR, "dependDelete: Unknown relkind %c", relKind);
		}
		else if (strcmp(findName, TriggerRelationName) == 0)
			/* Drop Trigger */
			DropTriggerById(foundObject.objectId);

		else if (strcmp(findName, RewriteRelationName) == 0)
			/* Drop Rule */
			RemoveRewriteRuleById(foundObject.objectId);

		else
			elog(ERROR, "dependDelete: Unknown object class %s", findName);

		/*
		 * We need to assume that cascaded items could potentially
		 * remove dependencies we want to as well.  The simplest
		 * way to ovoid double deletions (and warnings about tuples
		 * being modified twice) is to rescan our list after
		 * bumping the command counter.
		 */
		CommandCounterIncrement();
	}

	/*
	 * Now go through the whole thing again looking for our object
	 * as the depender so we can drop those dependencies.
	 */

	/* Class Oid */
	Assert(objectCopy.classId != InvalidOid);
	ScanKeyEntryInitialize(&dropkey[dropnkeys++],
						   0, Anum_pg_depend_classid, F_OIDEQ,
						   ObjectIdGetDatum(objectCopy.classId));
	/* Object Oid */
	Assert(objectCopy.objectId != InvalidOid);
	ScanKeyEntryInitialize(&dropkey[dropnkeys++],
						   0, Anum_pg_depend_objid, F_OIDEQ,
						   ObjectIdGetDatum(objectCopy.objectId));
	/* SubObject Id */
	ScanKeyEntryInitialize(&dropkey[dropnkeys++],
						   0, Anum_pg_depend_objsubid, F_INT4EQ,
						   Int32GetDatum(objectCopy.objectSubId));

	scan = heap_beginscan(rel, 0, SnapshotNow, dropnkeys, dropkey);

	/* Drop dependencies found */
	while (HeapTupleIsValid(tup = heap_getnext(scan, 0))) {
		simple_heap_delete(rel, &tup->t_self);
	}

	heap_endscan(scan);

	/* Cleanup and get out */
	heap_close(rel, RowExclusiveLock);
}

/* Delete function to save time */
void
dependDeleteTuple(const HeapTuple tup, const Relation relation, int behavior)
{
	ObjectAddress	myself;

	/* Collect the information and call the real delete function */
	myself.classId = RelationGetRelid(relation);
	myself.objectId = tup->t_data->t_oid;
	myself.objectSubId = 0;

	dependDelete(&myself, behavior);
}


/* Fetch the Object Name for display */
static char *
getObjectName(const ObjectAddress *object)
{
	HeapTuple	tup;
	char	   *name = "Unknown"; /* Unknown to Keep compiler quiet */
	char       *relName;

	tup = SearchSysCache(RELOID,
						 ObjectIdGetDatum(object->classId),
						 0, 0, 0);

	/* Object Addresses should always be valid */
	if (!HeapTupleIsValid(tup)) {
		elog(ERROR, "getObjectName: Relation %d does not exist", object->classId);
	}

	relName = NameStr(((Form_pg_class) GETSTRUCT(tup))->relname);

	ReleaseSysCache(tup);


	if (strcmp(relName, AggregateRelationName) == 0) {
		/* AGGREGATE */
		name = "Unknown";
	} else if (strcmp(relName, AttributeRelationName) == 0) {
		/* ATTRIBUTE */
		name = "Unknown";
	} else if (strcmp(relName, LanguageRelationName) == 0) {
		/* LANGUAGE */
		HeapTuple		langTup;

		langTup = SearchSysCache(LANGOID,
								 ObjectIdGetDatum(object->objectId),
								 0, 0, 0);
		name = NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname);

		ReleaseSysCache(langTup);

	} else if (strcmp(relName, OperatorRelationName) == 0) {
		/* OPERATOR */
		HeapTuple		operTup;

		operTup = SearchSysCache(OPEROID,
								 ObjectIdGetDatum(object->objectId),
								 0, 0, 0);
		name = NameStr(((Form_pg_operator) GETSTRUCT(operTup))->oprname);

		ReleaseSysCache(operTup);

	} else if (strcmp(relName, ProcedureRelationName) == 0) {
		/* FUNCTION */
		HeapTuple		procTup;

		procTup = SearchSysCache(PROCOID,
								 ObjectIdGetDatum(object->objectId),
								 0, 0, 0);
		name = NameStr(((Form_pg_proc) GETSTRUCT(procTup))->proname);

		ReleaseSysCache(procTup);

	} else if (strcmp(relName, RelationRelationName) == 0) {
		/* RELATION */
		HeapTuple		relTup;

		relTup = SearchSysCache(RELOID,
								ObjectIdGetDatum(object->objectId),
								0, 0, 0);
		name = NameStr(((Form_pg_class) GETSTRUCT(relTup))->relname);

		ReleaseSysCache(relTup);

	} else if (strcmp(relName, RewriteRelationName) == 0) {
		/* RULE */

		name = "Unknown";
	} else if (strcmp(relName, TypeRelationName) == 0) {
		/* TYPE */
		HeapTuple		typeTup;

		typeTup = SearchSysCache(TYPEOID,
								 ObjectIdGetDatum(object->objectId),
								 0, 0, 0);
		name = NameStr(((Form_pg_type) GETSTRUCT(typeTup))->typname);

		ReleaseSysCache(typeTup);

	} else if (strcmp(relName, TriggerRelationName) == 0) {
		/* TRIGGER */
		name = "Unknown";
	} else {
		elog(ERROR, "getObjectName: Unknown object class %s", relName);
	}

	return name;
}


/* Fetch the Object Type for display */
static char *
getObjectType(const ObjectAddress *object)
{
	char	   *relName;
	char	   *name = "Unknown"; /* Unknown to keep compiler quiet */
	HeapTuple	tup;

	tup = SearchSysCache(RELOID,
						 ObjectIdGetDatum(object->classId),
						 0, 0, 0);

	/* Object Addresses should always be valid */
	if (!HeapTupleIsValid(tup)) {
		elog(ERROR, "getObjectType:  Relation %d does not exist", object->classId);
	}

	relName = NameStr(((Form_pg_class) GETSTRUCT(tup))->relname);

	ReleaseSysCache(tup);

	if (strcmp(relName, AggregateRelationName) == 0) {
		name = "Aggregate";
	} else if (strcmp(relName, AttributeRelationName) == 0) {
		name = "Table Attribute";
	} else if (strcmp(relName, LanguageRelationName) == 0) {
		name = "PL Hander";
	} else if (strcmp(relName, OperatorRelationName) == 0) {
		name = "Operator";
	} else if (strcmp(relName, ProcedureRelationName) == 0) {
		name = "Function";
	} else if (strcmp(relName, RelationRelationName) == 0) {
		HeapTuple	relTup;
		char		relKind;

		relTup = SearchSysCache(RELOID,
								ObjectIdGetDatum(object->objectId),
								0, 0, 0);
		if (!HeapTupleIsValid(relTup)) {
			elog(ERROR, "getObjectType: Relation %d does not exist",
				 object->objectId);
		}

		relKind = ((Form_pg_class) GETSTRUCT(relTup))->relkind;
		ReleaseSysCache(relTup);

		if (relKind == RELKIND_INDEX)
			name = "Index";
		else if (relKind == RELKIND_VIEW)
			name = "View";
		else if (relKind == RELKIND_RELATION)
			name = "Table";
		else if (relKind == RELKIND_TOASTVALUE)
			name = "Toast Table";
		else if (relKind == RELKIND_SEQUENCE)
			name = "Sequence";
		else if (relKind == RELKIND_SPECIAL)
			name = "Special";
		else
			elog(ERROR, "dependDelete: Unknown relkind %c", relKind);

		/* Should this be broken down to INDEX, View, Table, etc? */
	} else if (strcmp(relName, RewriteRelationName) == 0) {
		name = "Rule";
	} else if (strcmp(relName, TypeRelationName) == 0) {
		name = "Type";
	} else if (strcmp(relName, TriggerRelationName) == 0) {
		name = "Trigger";
	} else {
		elog(ERROR, "getObjectType: Unknown object class %s", relName);
	}

	return name;
}

