/* ----------------------------------------------------------------
 *   FILE
 *	istrat.c
 *	
 *   DESCRIPTION
 *	index scan strategy manipulation code and 
 *	index strategy manipulation operator code.
 *
 *   INTERFACE ROUTINES
 *	StrategyMapGetScanKeyEntry
 *	IndexStrategyGetStrategyMap
 *	AttributeNumberGetIndexStrategySize
 *	StrategyOperatorIsValid
 *	StrategyTermIsValid
 *	StrategyExpressionIsValid
 *	StrategyEvaluationIsValid
 *	RelationGetStrategy
 *	RelationInvokeStrategy
 *	IndexSupportInitialize
 *	
 *   IDENTIFICATION
 *	$Header: /private/postgres/src/access/index/RCS/istrat.c,v 1.8 1992/03/04 22:13:52 mer Exp $
 * ----------------------------------------------------------------
 */

#include "tmp/postgres.h"

RcsId("$Header: /private/postgres/src/access/index/RCS/istrat.c,v 1.8 1992/03/04 22:13:52 mer Exp $");

#include "access/att.h"
#include "access/attnum.h"
#include "access/heapam.h"
#include "access/istrat.h"
#include "access/isop.h"
#include "access/itup.h"	/* for MaxIndexAttributeNumber */
#include "access/skey.h"
#include "access/tqual.h"	/* for NowTimeQual */

#include "utils/log.h"
#include "utils/rel.h"

#include "catalog/catname.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_index.h"
#include "catalog/pg_proc.h"

/*
 * OperatorRelationFillScanKeyEntry --
 *	Fills the scan key entry for an OPERATOR object.
 */
extern
void
OperatorRelationFillScanKeyEntry ARGS((
	Relation	operatorRelation,
	ObjectId	operatorObjectId,
	ScanKeyEntry	entry
));

/* ----------------------------------------------------------------
 *	           misc strategy support routines
 * ----------------------------------------------------------------
 */

/* 
 *	StrategyNumberIsValid
 *	StrategyNumberIsInBounds
 *	StrategyMapIsValid
 *	StrategyTransformMapIsValid
 *	IndexStrategyIsValid
 *
 *		... are now macros in istrat.h -cim 4/27/91
 */

/* ----------------
 *	StrategyMapGetScanKeyEntry
 * ----------------
 */
ScanKeyEntry
StrategyMapGetScanKeyEntry(map, strategyNumber)
    StrategyMap		map;
    StrategyNumber	strategyNumber;
{
    Assert(StrategyMapIsValid(map));
    Assert(StrategyNumberIsValid(strategyNumber));
    return (&map->entry[strategyNumber - 1]);
}

/* ----------------
 *	IndexStrategyGetStrategyMap
 * ----------------
 */
StrategyMap
IndexStrategyGetStrategyMap(indexStrategy, maxStrategyNum, attrNum)
    IndexStrategy	indexStrategy;
    StrategyNumber	maxStrategyNum;
    AttributeNumber	attrNum;
{
    Assert(IndexStrategyIsValid(indexStrategy));
    Assert(StrategyNumberIsValid(maxStrategyNum));
    Assert(AttributeNumberIsValid(attrNum));

    maxStrategyNum = AMStrategies(maxStrategyNum);	/* XXX */
    return
	&indexStrategy->strategyMapData[maxStrategyNum * (attrNum - 1)];
}

/* ----------------
 *	AttributeNumberGetIndexStrategySize
 * ----------------
 */
Size
AttributeNumberGetIndexStrategySize(maxAttributeNumber, maxStrategyNumber)
    AttributeNumber	maxAttributeNumber;
    StrategyNumber	maxStrategyNumber;
{
    maxStrategyNumber = AMStrategies(maxStrategyNumber);	/* XXX */
    return
	maxAttributeNumber * maxStrategyNumber * sizeof (ScanKeyData);
}

/* 
 * StrategyTransformMapIsValid is now a macro in istrat.h -cim 4/27/91
 */

/* ----------------
 *	StrategyOperatorIsValid
 * ----------------
 */
bool
StrategyOperatorIsValid(operator, maxStrategy)
    StrategyOperator	operator;
    StrategyNumber	maxStrategy;
{
    return (bool)
	(PointerIsValid(operator) &&
	 StrategyNumberIsInBounds(operator->strategy, maxStrategy) &&
	 !(operator->flags & ~(NegateResult | CommuteArguments)));
}

/* ----------------
 *	StrategyTermIsValid
 * ----------------
 */
bool
StrategyTermIsValid(term, maxStrategy)
    StrategyTerm	term;
    StrategyNumber	maxStrategy;
{
    Index	index;

    if (! PointerIsValid(term) || term->degree == 0)
	return false;

    for (index = 0; index < term->degree; index += 1) {
	if (! StrategyOperatorIsValid(&term->operatorData[index],
				      maxStrategy)) {

	    return false;
	}
    }

    return true;
}

/* ----------------
 *	StrategyExpressionIsValid
 * ----------------
 */
bool
StrategyExpressionIsValid(expression, maxStrategy)
    StrategyExpression	expression;
    StrategyNumber		maxStrategy;
{
    StrategyTerm	*termP;

    if (!PointerIsValid(expression))
	return true;

    if (!StrategyTermIsValid(expression->term[0], maxStrategy))
	return false;

    termP = &expression->term[1];
    while (StrategyTermIsValid(*termP, maxStrategy))
	termP += 1;

    return (bool)
	(! PointerIsValid(*termP));
}

/* ----------------
 *	StrategyEvaluationIsValid
 * ----------------
 */
bool
StrategyEvaluationIsValid(evaluation)
    StrategyEvaluation	evaluation;
{
    Index	index;

    if (! PointerIsValid(evaluation) ||
	! StrategyNumberIsValid(evaluation->maxStrategy) ||
	! StrategyTransformMapIsValid(evaluation->negateTransform) ||
	! StrategyTransformMapIsValid(evaluation->commuteTransform) ||
	! StrategyTransformMapIsValid(evaluation->negateCommuteTransform)) {

	return false;
    }

    for (index = 0; index < evaluation->maxStrategy; index += 1) {
	if (! StrategyExpressionIsValid(evaluation->expression[index],
					evaluation->maxStrategy)) {

	    return false;
	}
    }
    return true;
}

/* ----------------
 *	StrategyTermEvaluate
 * ----------------
 */
static
bool
StrategyTermEvaluate(term, map, left, right)
    StrategyTerm	term;
    StrategyMap		map;
    Datum		left;
    Datum		right;
{
    Index		index;
    bool		result;
    StrategyOperator	operator;
    ScanKeyEntry	entry;

    for (index = 0, operator = &term->operatorData[0];
	 index < term->degree; index += 1, operator += 1) {

	entry = &map->entry[operator->strategy - 1];

	Assert(RegProcedureIsValid(entry->procedure));

	switch (operator->flags ^ entry->flags) {
	case 0x0:
	    result = (bool) (*(entry->func)) (left, right);
	    break;
	    
	case NegateResult:
	    result = (bool) !(*(entry->func)) (left, right);
	    break;
	    
	case CommuteArguments:
	    result = (bool) (*(entry->func)) (right, left);
	    break;
	    
	case NegateResult | CommuteArguments:
	    result = (bool) !(*(entry->func)) (right, left);
	    break;

	default:
	    elog(FATAL, "StrategyTermEvaluate: impossible case %d",
		 operator->flags ^ entry->flags);
	}

	if (!result)
	    return result;
    }
    
    return result;
}


/* ----------------
 *	RelationGetStrategy
 * ----------------
 */
StrategyNumber
RelationGetStrategy(relation, attributeNumber, evaluation, procedure)
    Relation		relation;
    AttributeNumber	attributeNumber;
    StrategyEvaluation	evaluation;
    RegProcedure	procedure;
{
    StrategyNumber	strategy;
    StrategyMap		strategyMap;
    ScanKeyEntry	entry;
    Index		index;
    AttributeNumber 	numattrs;

    Assert(RelationIsValid(relation));
    numattrs = RelationGetNumberOfAttributes(relation);

    Assert(relation->rd_rel->relkind == 'i');	/* XXX use accessor */
    Assert(AttributeNumberIsValid(attributeNumber));
    Assert(OffsetIsInBounds(attributeNumber, 1, 1 + numattrs) );

    Assert(StrategyEvaluationIsValid(evaluation));
    Assert(RegProcedureIsValid(procedure));

    strategyMap =
	IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation),
				    evaluation->maxStrategy,
				    attributeNumber);

    /* get a strategy number for the procedure ignoring flags for now */
    for (index = 0; index < evaluation->maxStrategy; index += 1) {
	if (strategyMap->entry[index].procedure == procedure) {
	    break;
	}
    }

    if (index == evaluation->maxStrategy)
	return InvalidStrategy;

    strategy = 1 + index;
    entry = StrategyMapGetScanKeyEntry(strategyMap, strategy);

    Assert(!(entry->flags & ~(NegateResult | CommuteArguments)));

    switch (entry->flags & (NegateResult | CommuteArguments)) {
    case 0x0:
	return strategy;

    case NegateResult:
	strategy = evaluation->negateTransform->strategy[strategy - 1];
	break;

    case CommuteArguments:
	strategy = evaluation->commuteTransform->strategy[strategy - 1];
	break;

    case NegateResult | CommuteArguments:
	strategy = evaluation->negateCommuteTransform->strategy[strategy - 1];
	break;

    default:
	elog(FATAL, "RelationGetStrategy: impossible case %d", entry->flags);
    }


    if (! StrategyNumberIsInBounds(strategy, evaluation->maxStrategy)) {
	if (! StrategyNumberIsValid(strategy)) {
	    elog(WARN, "RelationGetStrategy: corrupted evaluation");
	}
    }

    return strategy;
}

/* ----------------
 *	RelationInvokeStrategy
 * ----------------
 */
bool		/* XXX someday, this may return Datum */
RelationInvokeStrategy(relation, evaluation, attributeNumber,
		       strategy, left, right)

    Relation		relation;
    StrategyEvaluation	evaluation;
    AttributeNumber	attributeNumber;
    StrategyNumber	strategy;
    Datum		left;
    Datum		right;
{
    StrategyNumber	newStrategy;
    StrategyMap		strategyMap;
    ScanKeyEntry	entry;
    StrategyTermData	termData;
    AttributeNumber     numattrs;

    Assert(RelationIsValid(relation));
    Assert(relation->rd_rel->relkind == 'i');	/* XXX use accessor */
    numattrs = RelationGetNumberOfAttributes(relation);
	
    Assert(StrategyEvaluationIsValid(evaluation));
    Assert(AttributeNumberIsValid(attributeNumber));
    Assert(OffsetIsInBounds(attributeNumber, 1, 1 + numattrs));
    Assert(StrategyNumberIsInBounds(strategy, evaluation->maxStrategy));

    termData.degree = 1;

    strategyMap =
	IndexStrategyGetStrategyMap(RelationGetIndexStrategy(relation),
				    evaluation->maxStrategy,
				    attributeNumber);

    entry = StrategyMapGetScanKeyEntry(strategyMap, strategy);

    if (RegProcedureIsValid(entry->procedure)) {
	termData.operatorData[0].strategy = strategy;
	termData.operatorData[0].flags = 0x0;

	return
	    StrategyTermEvaluate(&termData, strategyMap, left, right);
    }


    newStrategy = evaluation->negateTransform->strategy[strategy - 1];
    if (newStrategy != strategy && StrategyNumberIsValid(newStrategy)) {
	
	entry = StrategyMapGetScanKeyEntry(strategyMap, newStrategy);

	if (RegProcedureIsValid(entry->procedure)) {
	    termData.operatorData[0].strategy = newStrategy;
	    termData.operatorData[0].flags = NegateResult;

	    return
		StrategyTermEvaluate(&termData, strategyMap, left, right);
	}
    }

    newStrategy = evaluation->commuteTransform->strategy[strategy - 1];
    if (newStrategy != strategy && StrategyNumberIsValid(newStrategy)) {
	
	entry = StrategyMapGetScanKeyEntry(strategyMap, newStrategy);

	if (RegProcedureIsValid(entry->procedure)) {
	    termData.operatorData[0].strategy = newStrategy;
	    termData.operatorData[0].flags = CommuteArguments;

	    return
		StrategyTermEvaluate(&termData, strategyMap, left, right);
	}
    }

    newStrategy = evaluation->negateCommuteTransform->strategy[strategy - 1];
    if (newStrategy != strategy && StrategyNumberIsValid(newStrategy)) {
	
	entry = StrategyMapGetScanKeyEntry(strategyMap, newStrategy);

	if (RegProcedureIsValid(entry->procedure)) {
	    termData.operatorData[0].strategy = newStrategy;
	    termData.operatorData[0].flags = NegateResult | CommuteArguments;

	    return
		StrategyTermEvaluate(&termData, strategyMap, left, right);
	}
    }

    if (PointerIsValid(evaluation->expression[strategy - 1])) {
	StrategyTerm		*termP;

	termP = &evaluation->expression[strategy - 1]->term[0];
	while (PointerIsValid(*termP)) {
	    Index	index;

	    for (index = 0; index < (*termP)->degree; index += 1) {
		entry = StrategyMapGetScanKeyEntry(strategyMap,
				   (*termP)->operatorData[index].strategy);

		if (! RegProcedureIsValid(entry->procedure)) {
		    break;
		}
	    }

	    if (index == (*termP)->degree) {
		return
		    StrategyTermEvaluate(*termP, strategyMap, left, right);
	    }

	    termP += 1;
	}
    }

    elog(WARN, "RelationInvokeStrategy: cannot evaluate strategy %d",
	 strategy);
    /* NOTREACHED */
}

/* ----------------
 *	OperatorRelationFillScanKeyEntry
 * ----------------
 */
static
void
OperatorRelationFillScanKeyEntry(operatorRelation, operatorObjectId, entry)
    Relation		operatorRelation;
    ObjectId		operatorObjectId;
    ScanKeyEntry	entry;
{
    HeapScanDesc	scan;
    ScanKeyData		scanKeyData;
    HeapTuple		tuple;

    ScanKeyEntryInitialize(&scanKeyData.data[0], 0, 
			   ObjectIdAttributeNumber,
			   ObjectIdEqualRegProcedure,
			   ObjectIdGetDatum(operatorObjectId));

    scan = heap_beginscan(operatorRelation, false, NowTimeQual,
			  1, &scanKeyData);

    tuple = heap_getnext(scan, false, (Buffer *)NULL);
    if (! HeapTupleIsValid(tuple)) {
	elog(WARN, "OperatorObjectIdFillScanKeyEntry: unknown operator %lu",
	     (uint32) operatorObjectId);
    }

    entry->flags = 0;
    entry->procedure = ((OperatorTupleForm) HeapTupleGetForm(tuple))->oprcode;
	fmgr_info(entry->procedure, &entry->func, &entry->nargs);

    if (! RegProcedureIsValid(entry->procedure)) {
	elog(WARN,
	     "OperatorObjectIdFillScanKeyEntry: no procedure for operator %lu",
	     (uint32) operatorObjectId);
    }

    heap_endscan(scan);
}


/* ----------------
 *	IndexSupportInitialize
 * ----------------
 */
void
IndexSupportInitialize(indexStrategy, indexSupport,
			indexObjectId, accessMethodObjectId,
			maxStrategyNumber, maxSupportNumber,
			maxAttributeNumber)

    IndexStrategy	indexStrategy;
    RegProcedure	*indexSupport;
    ObjectId		indexObjectId;
    ObjectId		accessMethodObjectId;
    StrategyNumber	maxStrategyNumber;
    StrategyNumber	maxSupportNumber;
    AttributeNumber	maxAttributeNumber;
{
    Relation		relation;
    Relation		operatorRelation;
    HeapScanDesc	scan;
    HeapTuple		tuple;
    ScanKeyEntryData	entry[2];
    StrategyMap		map;
    AttributeNumber	attributeNumber;
    AttributeOffset	attributeIndex;
    ObjectId		operatorClassObjectId[ MaxIndexAttributeNumber ];

    maxStrategyNumber = AMStrategies(maxStrategyNumber);

    ScanKeyEntryInitialize(&entry[0], 0, IndexRelationIdAttributeNumber,
						   ObjectIdEqualRegProcedure, 
						   ObjectIdGetDatum(indexObjectId));

    relation = heap_openr(IndexRelationName);
    scan = heap_beginscan(relation, false, NowTimeQual, 1, (ScanKey)entry);
    tuple = heap_getnext(scan, false, (Buffer *)NULL);
    if (! HeapTupleIsValid(tuple))
	elog(WARN, "IndexSupportInitialize: corrupted catalogs");

    /*
     * XXX note that the following assumes the INDEX tuple is well formed and
     * that the key[] and class[] are 0 terminated.
     */
    for (attributeIndex=0; attributeIndex<maxAttributeNumber; attributeIndex++)
    {
	IndexTupleForm	iform;

	iform = (IndexTupleForm) HeapTupleGetForm(tuple);

	if (!ObjectIdIsValid(iform->indkey[attributeIndex])) {
	    if (attributeIndex == 0) {
		elog(WARN, "IndexSupportInitialize: no pg_index tuple");
	    }
	    break;
	}

	operatorClassObjectId[attributeIndex]
		= iform->indclass[attributeIndex];
    }

    heap_endscan(scan);
    heap_close(relation);

    /* if support routines exist for this access method, load them */
    if (maxSupportNumber > 0) {

	ScanKeyEntryInitialize(&entry[0], 0, Anum_pg_amproc_amid,
						   ObjectIdEqualRegProcedure,
						   ObjectIdGetDatum(accessMethodObjectId));

	ScanKeyEntryInitialize(&entry[1], 0, Anum_pg_amproc_amopclaid,
						   ObjectIdEqualRegProcedure, 0);

	relation = heap_openr(Name_pg_amproc);

	for (attributeNumber = maxAttributeNumber; attributeNumber > 0;
	     attributeNumber--) {

	    int16		support;
	    Form_pg_amproc	form;
	    RegProcedure	*loc;

	    loc = &indexSupport[((attributeNumber - 1) * maxSupportNumber)];

	    for (support = maxSupportNumber; --support >= 0; ) {
		loc[support] = InvalidObjectId;
	    }

	    entry[1].argument =
		ObjectIdGetDatum(operatorClassObjectId[attributeNumber - 1]);

	    scan = heap_beginscan(relation, false, NowTimeQual, 2,
				  (ScanKey) entry);

	    while (tuple = heap_getnext(scan, false, (Buffer *)NULL),
		   HeapTupleIsValid(tuple)) {

		form = (Form_pg_amproc) HeapTupleGetForm(tuple);
		loc[(form->amprocnum - 1)] = form->amproc;
	    }

	    heap_endscan(scan);
	}
	heap_close(relation);
    }

    ScanKeyEntryInitialize(&entry[0], 0, 
			   AccessMethodOperatorAccessMethodIdAttributeNumber,
                           ObjectIdEqualRegProcedure,
                           ObjectIdGetDatum(accessMethodObjectId));

    ScanKeyEntryInitialize(&entry[1], 0, 
			   AccessMethodOperatorOperatorClassIdAttributeNumber,
                           ObjectIdEqualRegProcedure, 0);

    relation = heap_openr(AccessMethodOperatorRelationName);
    operatorRelation = heap_openr(OperatorRelationName);

    for (attributeNumber = maxAttributeNumber; attributeNumber > 0;
	 attributeNumber--) {

	StrategyNumber	strategy;

	entry[1].argument =
	    ObjectIdGetDatum(operatorClassObjectId[attributeNumber - 1]);

	map = IndexStrategyGetStrategyMap(indexStrategy,
					  maxStrategyNumber,
					  attributeNumber);

	for (strategy = 1; strategy <= maxStrategyNumber; strategy++)
	    ScanKeyEntrySetIllegal(StrategyMapGetScanKeyEntry(map, strategy));

	scan = heap_beginscan(relation, false, NowTimeQual, 2, (ScanKey)entry);

	while (tuple = heap_getnext(scan, false, (Buffer *)NULL),
		HeapTupleIsValid(tuple)) {
	    AccessMethodOperatorTupleForm form;

	    form = (AccessMethodOperatorTupleForm) HeapTupleGetForm(tuple);

	    OperatorRelationFillScanKeyEntry(operatorRelation,
					     form->amopoprid,
		          StrategyMapGetScanKeyEntry(map, form->amopstrategy));
	}

	heap_endscan(scan);
    }

    heap_close(operatorRelation);
    heap_close(relation);
}

/* ----------------
 *	IndexStrategyDisplay
 * ----------------
 */
#ifdef	ISTRATDEBUG
int
IndexStrategyDisplay(indexStrategy, numberOfStrategies, numberOfAttributes)
    IndexStrategy	indexStrategy;
    StrategyNumber	numberOfStrategies;
    AttributeNumber	numberOfAttributes;
{
    StrategyMap	strategyMap;
    AttributeNumber	attributeNumber;
    StrategyNumber	strategyNumber;

    for (attributeNumber = 1; attributeNumber <= numberOfAttributes;
	 attributeNumber += 1) {

	strategyMap = IndexStrategyGetStrategyMap(indexStrategy,
						  numberOfStrategies,
						  attributeNumber);

	for (strategyNumber = 1;
	     strategyNumber <= AMStrategies(numberOfStrategies);
	     strategyNumber += 1) {

	    printf(":att %d\t:str %d\t:opr 0x%x(%d)\n",
		   attributeNumber, strategyNumber,
		   strategyMap->entry[strategyNumber - 1].procedure,
		   strategyMap->entry[strategyNumber - 1].procedure);
	}
    }
}
#endif	/* defined(ISTRATDEBUG) */

