/*===================================================================
 *
 * FILE:
 *   prs2replace.c
 *
 * IDENTIFICATION:
 *   $Header: /private/postgres/src/rules/prs2/RCS/prs2replace.c,v 1.16 1991/11/18 22:21:22 mer Exp $
 *
 * DESCRIPTION:
 *
 *
 *===================================================================
 */

#include "tmp/c.h"
#include "utils/log.h"
#include "rules/prs2.h"
#include "rules/prs2stub.h"
#include "nodes/execnodes.h"	/* which includes access/rulescan.h */
#include "parser/parse.h"	/* for the APPEND/DELETE */

/*-------------------------------------------------------------------
 *
 * prs2Replace
 */
Prs2Status
prs2Replace(prs2EStateInfo, relationRuleInfo, explainRelation,
	    oldTuple, oldBuffer,
	    newTuple, newBuffer, rawTuple, rawBuffer,
	    attributeArray, numberOfAttributes, relation,
	    returnedTupleP, returnedBufferP)
Prs2EStateInfo prs2EStateInfo;
RelationRuleInfo relationRuleInfo;
Relation explainRelation;
HeapTuple oldTuple;
Buffer oldBuffer;
HeapTuple newTuple;
Buffer newBuffer;
HeapTuple rawTuple;
Buffer rawBuffer;
AttributeNumberPtr attributeArray;
int numberOfAttributes;
Relation relation;
HeapTuple *returnedTupleP;
Buffer *returnedBufferP;
{
    RuleLock oldLocks, oldTupleLocks, explocks;
    RuleLock newLocks;
    RuleLock relLocks;
    RuleLock finalLocks;
    Prs2Stub stubs;
    Prs2OneLock oneLock;
    int i,j;
    AttributeValues oldAttrValues, newAttrValues, rawAttrValues;
    AttributeNumber attr, maxattrs, attrno;
    int newTupleMade;
    Name relName;
    bool insteadRuleFound;
    bool insteadRuleFoundThisTime;
    TupleDescriptor tupDesc;

    /*
     * Find the locks of the tuple.
     *
     * To find the locks of the RAW(old) tuple, we have to check both
     * for relation level locks and for tuple level locks.
     * 
     * NOTE: 'old' tuple has no lock on it, because locks
     * are not propagated from node to node inside the executor.
     *
     * NOTE: 'on replace' rules have either a relation level
     * lock, or a tuple-level-lock on the old tuple.
     *
     * The rule stubs need only be checked at the end, and only
     * to find out what locks the "new" tuple should get.
     *
     */
    relName = & ((RelationGetRelationTupleForm(relation))->relname);
    tupDesc = RelationGetTupleDescriptor(relation);

    relLocks = relationRuleInfo->relationLocks;
    stubs = relationRuleInfo->relationStubs;
    if (relationRuleInfo->ignoreTupleLocks)
	oldTupleLocks = prs2MakeLocks();
    else
	oldTupleLocks = prs2GetLocksFromTuple(rawTuple, rawBuffer);
    oldLocks = prs2LockUnion(oldTupleLocks, relLocks);

    /*
     * now extract from the tuple the array of the attribute values.
     */
    newAttrValues = attributeValuesCreate(newTuple, newBuffer, relation);
    oldAttrValues = attributeValuesCreate(oldTuple, oldBuffer, relation);
    rawAttrValues = attributeValuesCreate(rawTuple, rawBuffer, relation);

    /*
     * First activate all the 'late evaluation' rules, i.e.
     * all the rules that try to update the fields of the 'new' tuple
     *
     * NOTE: WE use as locks of the new tuple the locks of the old
     * tuple! But in reality we are only interested in the 
     * 'LockTypeReplaceWrite' locks...
     */
    maxattrs = RelationGetNumberOfAttributes(relation);
    for(attr=1; attr <= maxattrs; attr++) {
	prs2ActivateBackwardChainingRules(
		    prs2EStateInfo,
		    explainRelation,
		    relation,
		    attr,
		    PRS2_NEW_TUPLE,
		    oldTuple->t_oid,
		    oldAttrValues,
		    oldLocks,
		    LockTypeRetrieveWrite,
		    newTuple->t_oid,
		    newAttrValues,
		    oldLocks,
		    LockTypeReplaceWrite,
		    attributeArray,
		    numberOfAttributes);
    }

    /*
     * Then, activate all the 'forward chaining rules'
     * NOTE: Both old & new locks have the same "LockTypeReplaceAction"
     * locks
     */
    insteadRuleFound = false;
    for(i=0; i<numberOfAttributes; i++) {
	prs2ActivateForwardChainingRules(
		    prs2EStateInfo,
		    explainRelation,
		    relation,
		    attributeArray[i],
		    LockTypeReplaceAction,
		    PRS2_OLD_TUPLE,
		    oldTuple->t_oid,
		    oldAttrValues,
		    oldLocks,
		    LockTypeRetrieveWrite,
		    newTuple->t_oid,
		    newAttrValues,
		    InvalidRuleLock,
		    LockTypeInvalid,
		    &insteadRuleFoundThisTime,
		    attributeArray,
		    numberOfAttributes);
	if (insteadRuleFoundThisTime) {
	    insteadRuleFound = true;
	}
    }
    /*
     * Look also for 'on replace' rules with
     * an 'InvalidAttributeNumber' locked attrno.
     * (I.e. rules like: 'on replace to EMP where... do...'
     */
    prs2ActivateForwardChainingRules(
		prs2EStateInfo,
		explainRelation,
		relation,
		InvalidAttributeNumber,
		LockTypeReplaceAction,
		PRS2_OLD_TUPLE,
		oldTuple->t_oid,
		oldAttrValues,
		oldLocks,
		LockTypeRetrieveWrite,
		newTuple->t_oid,
		newAttrValues,
		InvalidRuleLock,
		LockTypeInvalid,
		&insteadRuleFoundThisTime,
		attributeArray,
		numberOfAttributes);
    if (insteadRuleFoundThisTime) {
	insteadRuleFound = true;
    }

    /*
     * Now all the correct values 
     * Create a new tuple combining the 'raw' tuple, i.e. the
     * tuple without any rules activated and the 'new' tuple.
     */
    *returnedBufferP = InvalidBuffer;
    *returnedTupleP = attributeValuesCombineNewAndOldTuple(
				rawAttrValues,
				newAttrValues,
				relation,
				attributeArray,
				numberOfAttributes);

    /*
     * OK, now find out the rule locks that the new tuple
     * must get.
     * NOTE: these locks will NOT include "export" locks
     * yet. These, will be calculated in a while...
     */
    newLocks = prs2FindLocksForNewTupleFromStubs(
		    *returnedTupleP,
		    *returnedBufferP,
		    stubs,
		    relation);

    /*
     * are there any export locks "broken" ???
     * That includes
     * a) new export locks that didn't exist before
     * b) removal of old export locks
     * c) value of an attribute locked by an export lock has changed.
     */
    explocks = prs2FindNewExportLocksFromLocks(newLocks);
    for (i=0; i<prs2GetNumberOfLocks(explocks); i++) {
	oneLock = prs2GetOneLockFromLocks(explocks, i);
	if (!prs2OneLockIsMemberOfLocks(oneLock, oldLocks)) {
	    /*
	     * a brand new export lock !
	     */
	    attrno = prs2OneLockGetAttributeNumber(oneLock);
	    if (!newAttrValues[attrno-1].isNull)
		prs2ActivateExportLockRulePlan(oneLock,
				newAttrValues[attrno-1].value,
				tupDesc->data[attrno-1]->atttypid,
				APPEND);

	}
    }
    for (i=0; i<prs2GetNumberOfLocks(oldLocks); i++) {
	oneLock = prs2GetOneLockFromLocks(oldLocks, i);
	if (prs2OneLockGetLockType(oneLock) != LockTypeExport)
	    continue;
	if (!prs2OneLockIsMemberOfLocks(oneLock, oldLocks)) {
	    /*
	     * an old lock that has been deleted...
	     */
	    attrno = prs2OneLockGetAttributeNumber(oneLock);
	    if (!oldAttrValues[attrno-1].isNull)
		prs2ActivateExportLockRulePlan(oneLock,
				oldAttrValues[attrno-1].value,
				tupDesc->data[attrno-1]->atttypid,
				DELETE);
	}
    }
    for (i=0; i<prs2GetNumberOfLocks(explocks); i++) {
	oneLock = prs2GetOneLockFromLocks(explocks, i);
	if (prs2OneLockIsMemberOfLocks(oneLock, oldLocks)) {
	    /*
	     * this lock existed before & still exists now...
	     * has the locked atribute changed in any way ??
	     * (either by the user or by the rule ?)
	     */
	    attrno = prs2OneLockGetAttributeNumber(oneLock);
	    for (j=0; j<numberOfAttributes; j++) {
		    if (attributeArray[j] == attrno)
			break;
	    }
	    if (newAttrValues[attrno-1].isChanged || j <numberOfAttributes) {
		/*
		 * yes, this attribute values has been changed...
		 */
		if (!oldAttrValues[attrno-1].isNull)
		    prs2ActivateExportLockRulePlan(oneLock,
				    oldAttrValues[attrno-1].value,
				    tupDesc->data[attrno-1]->atttypid,
				    DELETE);
		if (!newAttrValues[attrno-1].isNull)
		    prs2ActivateExportLockRulePlan(oneLock,
				    newAttrValues[attrno-1].value,
				    tupDesc->data[attrno-1]->atttypid,
				    APPEND);
	    }
	}
    }
	    
    /*
     * Calculate the new locks for this tuple.
     * This is the union of 'newLocks' and the 'explocks'
     *
     * NOTE: THING TO DO:
     * REMOVE ALL IMPORT LOCKS (same thing in prs2append.c)
     * THEY DON"T BREAK ANYTHING BUT THEY CHEW UP SPACE...
     */
    finalLocks = prs2LockUnion(newLocks, explocks);
    HeapTupleSetRuleLock(*returnedTupleP, InvalidBuffer, finalLocks);
    
    /*
     * clean up, sweep the floor and do the laundry....
     */
    prs2FreeLocks(oldLocks);
    prs2FreeLocks(oldTupleLocks);
    attributeValuesFree(newAttrValues, relation);
    attributeValuesFree(oldAttrValues, relation);
    prs2FreeLocks(newLocks);
    prs2FreeLocks(explocks);

    if (insteadRuleFound) {
	return(PRS2_STATUS_INSTEAD);
    } else {
	return(PRS2_STATUS_TUPLE_CHANGED);
    }

}
