/*=======================================================================
 * FILE:
 *   prs2rel.c
 *
 * IDENTIFICATION:
 *   $Header: /private/postgres/src/rules/prs2/RCS/prs2rel.c,v 1.11 1992/07/13 06:10:28 mao Exp $
 *
 * DESCRIPTION:
 *
 *   Code for putting/removing relation level locks:
 *   Relation level locks are stored in pg_relation. All the
 *   tuples of the coresponding relation are assumed to be locked by
 *   these locks.
 *   Relation Level locks are used by both the tuple-level & the query
 *   rewrite rule systems. However the tuple-level system can also
 *   use tuple-level locks.
 *		
 *=======================================================================
 */

#include "tmp/postgres.h"
#include "nodes/primnodes.h"
#include "nodes/primnodes.a.h"
#include "nodes/pg_lisp.h"
#include "utils/log.h"
#include "utils/relcache.h"	/* RelationNameGetRelation() defined here...*/
#include "rules/prs2.h"
#include "rules/prs2stub.h"
#include "access/skey.h"	/* 'ScanKeyEntryData' defined here... */
#include "access/tqual.h"	/* 'NowTimeQual' defined here.. */
#include "access/heapam.h"	/* heap AM calls defined here */
#include "utils/lsyscache.h"	/* for get_attnum(), get_rel_name() ...*/
#include "parser/parse.h"	/* RETRIEVE, APPEND etc defined here.. */
#include "parser/parsetree.h"
#include "catalog/catname.h"	/* names of various system relations */
#include "utils/fmgr.h"	/* for F_CHAR16EQ, F_CHAR16IN etc. */
#include "access/ftup.h"	/* for FormHeapTuple() */
#include "utils/palloc.h"

#include "catalog/pg_proc.h"
#include "catalog/pg_prs2rule.h"
#include "catalog/pg_prs2plans.h"

Prs2Stub prs2FindStubsThatDependOnAttribute();

/*-----------------------------------------------------------------------
 * prs2DefRelationLevelLockRule
 *
 * Put the appropriate locks for a tuple-system rule that is implemented
 * with a relation level lock.
 *
 * We have to do two things:
 * a) put the relation level lock for this rule (easy!)
 * b) If this is a "on retrieve ... do retrieve" rule, i.e.
 * if it places a "write" lock to an attribute, then we must find all
 * the tuple-level-lock rules that reference this attribute in their
 * qualification and change them to relation-level-lock rules too.
 * (see the discussion in the comments in "prs2putlocks.c"...)
 *-----------------------------------------------------------------------
 */
void
prs2DefRelationLevelLockRule(r)
Prs2RuleData r;
{
    RuleLock oldLocks, newLocks;
    LispValue actionPlans;
    Name relationName;
    Prs2Stub relstubs, stubs, newstubs;
    ObjectId *ruleoids;
    int nrules;
    int i;
    AttributeNumber attributeNo;
    Prs2LockType lockType;


    /*
     * insert the appropriate info in the system catalogs.
     * (and find the rule oid)
     */
    r->ruleId = prs2InsertRuleInfoInCatalog(r);

    actionPlans = prs2GenerateActionPlans(r);
    prs2InsertRulePlanInCatalog(r->ruleId, ActionPlanNumber, actionPlans);

    /*
     * find the loc type and the attribute that must be locked
     */
    prs2FindLockTypeAndAttrNo(r, &lockType, &attributeNo);


    /*
     * Find the relation level locks of the relation
     */
    relationName = get_rel_name(r->eventRelationOid);
    oldLocks = prs2GetLocksFromRelation(relationName);

    /*
     * now claculate the new locks
     */
    newLocks = prs2CopyLocks(oldLocks);
    newLocks = prs2AddLock(newLocks,
		    r->ruleId,
		    lockType,
		    attributeNo,
		    ActionPlanNumber, /* 'ActionPlanNumber' is a constant */
		    0,			/* partial indx - UNUSED */
		    0);			/* npartial - UNUSED  */

    /*
     * if this is a "write" lock, we might have to change some other
     * rules too (from tuple-level-lock to relation-level-lock).
     */
    if (lockType == LockTypeRetrieveWrite) {
	/*
	 * find all the tuple-level-lock rules that must be changed
	 * to relation-level-lock...
	 * Look at the (relation level) stubs of this relation.
	 * and find all the stubs that "depend" on "attributeNo"
	 * (the attribute to be locked by a "write" lock).
	 */
	relstubs = prs2GetRelationStubs(r->eventRelationOid);
	stubs = prs2FindStubsThatDependOnAttribute(relstubs, attributeNo);
	/*
	 * now get the rule oids from the stubs & store them in an
	 * array.
	 */
	nrules = stubs->numOfStubs;
	if (nrules>0) {
	    ruleoids = (ObjectId *) palloc(nrules * sizeof(ObjectId));
	    if (ruleoids==NULL) {
		elog(WARN,"prs2DefRelationLevelLockRule: out of memory!");
	    }
	}
	for (i=0; i<nrules; i++) {
	    ruleoids[i] = stubs->stubRecords[i]->ruleId;
	}

#ifdef PRS2_DEBUG
	{
	    int k;

	    printf(
	    "PRS2: the following rules will change from TupLev to RelLev\n");
	    printf("PRS2:");
	    for (k=0; k<nrules; k++) {
		printf(" %ld", ruleoids[k]);
	    }
	    printf("\n");
	}
#endif PRS2_DEBUG

	/*
	 * now remove their tuple level locks and stubs..
	 */
	prs2RemoveTupleLeveLocksAndStubsOfManyRules(r->eventRelationOid,
						    ruleoids,
						    nrules);
	/*
	 * finally, these rules must be implemented with a relation
	 * level lock, so add the appropriate locks to 'newLocks'
	 */
	for (i=0; i<nrules; i++) {
	    newLocks = prs2LockUnion(newLocks, stubs->stubRecords[i]->lock);
	}
    }

    /*
     * OK, now update the relation-level-locks of the relation.
     */
    prs2SetRelationLevelLocks(r->eventRelationOid, newLocks);

}

/*--------------------------------------------------------------
 *
 * prs2UndefRelationLevelLockRule
 *
 * remove a rule that uses relation level locks...
 * delete its relation level lock & all the info from the
 * system catalogs.
 *--------------------------------------------------------------
 */
void
prs2UndefRelationLevelLockRule(ruleId, relationId)
ObjectId ruleId;
ObjectId relationId;
{

    /*
     * first remove the relation level lock
     */
    prs2RemoveRelationLevelLocksOfRule(ruleId, relationId);

    /*
     * Finally remove the system catalog info...
     */
    prs2DeleteRulePlanFromCatalog(ruleId);
    prs2DeleteRuleInfoFromCatalog(ruleId);

}

/*-----------------------------------------------------------------------
 * prs2RemoveRelationLevelLocksOfRule
 *
 * remove the relation level locks of the given rule
 *
 *-----------------------------------------------------------------------
 */
void
prs2RemoveRelationLevelLocksOfRule(ruleId, relationId)
ObjectId ruleId;
ObjectId relationId;
{
    RuleLock currentLocks;
    RuleLock newLocks;
    Name relationName;

    /*
     * first find the current locks of the relation
     */
    relationName = get_rel_name(relationId);
    if (relationName == NULL) {
	/*
	 * complain, but do not abort the transaction
	 * so that we can remove a rule, even if we have
	 * accidentally destroyed the corresponding relation...
	 */
	elog(NOTICE, "prs2RemoveRelationLevelLocksOfRule: can not find rel %d",
	    relationId);
	return;
    }

    currentLocks = prs2GetLocksFromRelation(relationName);
#ifdef PRS2_DEBUG
    printf("PRS2: Remove RelationLevelLock of rule %d from rel=%s\n",
	ruleId, relationName);
#endif PRS2_DEBUG

    /*
     * Now calculate the new locks
     */
    newLocks = prs2RemoveAllLocksOfRule(currentLocks, ruleId);

    /*
     * Now, update the locks of the relation
     */
    prs2SetRelationLevelLocks(relationId, newLocks);
}

/*-----------------------------------------------------------------------
 * prs2SetRelationLevelLocks
 *
 * Set the relation level locks for a relation to the given ones.
 * These locks are stored in the appropriate tuple of pg_relation.
 * All the tuples of the target relation are assumed to be locked
 *
 * Relation level locks are used by both the tuple-level system and
 * the query rewrite system.
 *
 *-----------------------------------------------------------------------
 */

void
prs2SetRelationLevelLocks(relationId, newLocks)
ObjectId relationId;
RuleLock newLocks;
{

    Relation relationRelation;
    HeapScanDesc scanDesc;
    ScanKeyData scanKey;
    HeapTuple tuple;
    Buffer buffer;
    HeapTuple newTuple;

    /*
     * Lock a relation given its ObjectId.
     * Go to the RelationRelation (i.e. pg_relation), find the
     * appropriate tuple, and add the specified lock to it.
     */
    relationRelation = RelationNameOpenHeapRelation(RelationRelationName);

	ScanKeyEntryInitialize(&scanKey.data[0], 0, ObjectIdAttributeNumber,
						   ObjectIdEqualRegProcedure, 
						   ObjectIdGetDatum(relationId));

    scanDesc = RelationBeginHeapScan(relationRelation,
					false, NowTimeQual,
					1, &scanKey);
    
    tuple = HeapScanGetNextTuple(scanDesc, false, &buffer);
    if (!HeapTupleIsValid(tuple)) {
	elog(WARN, "prs2SetRelationLevelLocks: Invalid rel OID %ld",
		relationId);
    }

#ifdef PRS2_DEBUG
    {
	RuleLock l;

	l = prs2GetLocksFromTuple(tuple, buffer);
	printf(
	    "PRS2:prs2SetRelationLevelLocks: Updating locks of relation %d\n",
	    relationId);
	printf("PRS2: Old RelLev Locks = ");
	prs2PrintLocks(l);
	printf("\n");
	printf("PRS2: New RelLev Locks = ");
	prs2PrintLocks(newLocks);
	printf("\n");
    }
#endif PRS2_DEBUG

    /*
     * Create a new tuple (i.e. a copy of the old tuple
     * with its rule lock field changed and replace the old
     * tuple in the RelationRelation
     * NOTE: XXX ??? do we really need to make that copy ????
     */
    newTuple = palloctup(tuple, buffer, relationRelation);
    prs2PutLocksInTuple(newTuple, InvalidBuffer, relationRelation, newLocks);

    RelationReplaceHeapTuple(relationRelation, &(tuple->t_ctid),
			    newTuple, (double *)NULL);
    
    RelationCloseHeapRelation(relationRelation);
    HeapScanEnd(scanDesc);

}


/*-------------------------------------------------------------------
 * prs2AddRelationLevelLock
 *
 * create & add a new relation level lock to the given relation.
 *
 *-------------------------------------------------------------------
 */
void
prs2AddRelationLevelLock(ruleId, lockType, relationId, attrNo)
ObjectId ruleId;
Prs2LockType lockType;
ObjectId relationId;
AttributeNumber attrNo;
{
    RuleLock currentLocks;
    RuleLock newLocks;
    Name relationName;

    /*
     * first find the current locks of the relation
     */
    relationName = get_rel_name(relationId);
    currentLocks = prs2GetLocksFromRelation(relationName);

    /*
     * Now calculate the new locks
     */
    newLocks = prs2AddLock(currentLocks,
			ruleId,
			lockType,
			attrNo,
			ActionPlanNumber, /* ActionPlanNumber is a constant.*/
			0,		  /* partialindx - UNUSED */
			0);		  /* npartial - UNUSED */

    /*
     * Now, update the locks of the relation
     */
    prs2SetRelationLevelLocks(relationId, newLocks);

}

