/*-------------------------------------------------------------------------
 *
 * plancat.c--
 *     routines for accessing the system catalogs
 *
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /cvsroot/pgsql/src/backend/optimizer/util/plancat.c,v 1.6 1997/04/24 16:07:14 vadim Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <stdio.h>
#include "postgres.h"

#include "access/heapam.h"
#include "access/genam.h"
#include "access/htup.h"
#include "access/itup.h"

#include "catalog/catname.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_index.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_version.h"

#include "parser/parsetree.h"		/* for getrelid() */
#include "fmgr.h"

#include "optimizer/internal.h"
#include "optimizer/plancat.h"

#include "utils/syscache.h"
#ifndef HAVE_MEMMOVE
# include <regex/utils.h>
#else
# include <string.h>
#endif


static void IndexSelectivity(Oid indexrelid, Oid indrelid, int32 nIndexKeys,
	Oid AccessMethodOperatorClasses[], Oid operatorObjectIds[],
	int32 varAttributeNumbers[], char *constValues[], int32 constFlags[],
	float *idxPages, float *idxSelec);		     


/*
 * relation-info -
 *    Retrieves catalog information for a given relation. Given the oid of
 *    the relation, return the following information:
 *		whether the relation has secondary indices
 *		number of pages 
 *		number of tuples
 */
void
relation_info(Query *root, Index relid,
	      bool *hasindex, int *pages, int *tuples)
{
    HeapTuple		relationTuple;
    Form_pg_class	relation;
    Oid                 relationObjectId;

    relationObjectId = getrelid(relid, root->rtable);
    relationTuple = SearchSysCacheTuple(RELOID, 
					ObjectIdGetDatum(relationObjectId),
					0,0,0);
    if (HeapTupleIsValid(relationTuple)) {
	relation = (Form_pg_class)GETSTRUCT(relationTuple);

	*hasindex = (relation->relhasindex) ? TRUE : FALSE;
	*pages = relation->relpages;
	*tuples = relation->reltuples;
    } else {
	elog(WARN, "RelationCatalogInformation: Relation %d not found",
	     relationObjectId);
    }

    return;
}


/*    
 * index-info--
 *    Retrieves catalog information on an index on a given relation.
 *    
 *    The index relation is opened on the first invocation. The current
 *    retrieves the next index relation within the catalog that has not 
 *    already been retrieved by a previous call.  The index catalog 
 *    is closed when no more indices for 'relid' can be found.
 *    
 * 'first' is 1 if this is the first call 
 *    
 * Returns true if successful and false otherwise. Index info is returned
 * via the transient data structure 'info'.
 *    
 */
bool
index_info(Query *root, bool first, int relid, IdxInfoRetval *info)
{
    register		i;
    HeapTuple		indexTuple, amopTuple;
    IndexTupleForm	index;
    Relation		indexRelation;
    uint16		amstrategy;
    Oid			relam;
    Oid			indrelid;

    static Relation	relation = (Relation) NULL;
    static HeapScanDesc	scan = (HeapScanDesc) NULL;
    static ScanKeyData indexKey;


    /* find the oid of the indexed relation */
    indrelid = getrelid(relid, root->rtable);
    
    memset(info, 0, sizeof(IdxInfoRetval));

    /*
     * the maximum number of elements in each of the following arrays is
     * 8. We allocate one more for a terminating 0 to indicate the end
     * of the array.
     */
    info->indexkeys = (int *)palloc(sizeof(int)*9);
    memset(info->indexkeys, 0, sizeof(int)*9);
    info->orderOprs = (Oid *)palloc(sizeof(Oid)*9);
    memset(info->orderOprs, 0, sizeof(Oid)*9);
    info->classlist = (Oid *)palloc(sizeof(Oid)*9);
    memset(info->classlist, 0, sizeof(Oid)*9);

    /* Find an index on the given relation */
    if (first) {
	if (RelationIsValid(relation))
	    heap_close(relation);
	if (HeapScanIsValid(scan))
	    heap_endscan(scan);

	ScanKeyEntryInitialize(&indexKey, 0,
			       Anum_pg_index_indrelid,
			       F_OIDEQ,
			       ObjectIdGetDatum(indrelid));

	relation = heap_openr(IndexRelationName);
	scan = heap_beginscan(relation, 0, NowTimeQual,
			      1, &indexKey);
    }
    if (!HeapScanIsValid(scan))
	elog(WARN, "index_info: scan not started");
    indexTuple = heap_getnext(scan, 0, (Buffer *) NULL);
    if (!HeapTupleIsValid(indexTuple)) {
	heap_endscan(scan);
	heap_close(relation);
	scan = (HeapScanDesc) NULL;
	relation = (Relation) NULL;
	return(0);
    }

    /* Extract info from the index tuple */
    index = (IndexTupleForm)GETSTRUCT(indexTuple);
    info->relid = index->indexrelid; /* index relation */
    for (i = 0; i < 8; i++)
	info->indexkeys[i] = index->indkey[i];
    for (i = 0; i < 8; i++)
	info->classlist[i] = index->indclass[i];

    info->indproc = index->indproc; /* functional index ?? */

    /* partial index ?? */
    if (VARSIZE(&index->indpred) != 0) {
	/*
	 * The memory allocated here for the predicate (in lispReadString)
	 * only needs to stay around until it's used in find_index_paths,
	 * which is all within a command, so the automatic pfree at end
	 * of transaction should be ok.
	 */
	char *predString;

	predString = fmgr(F_TEXTOUT, &index->indpred);
	info->indpred = (Node*)stringToNode(predString);
	pfree(predString);
    }

    /* Extract info from the relation descriptor for the index */
    indexRelation = index_open(index->indexrelid);
#ifdef notdef
    /* XXX should iterate through strategies -- but how?  use #1 for now */
    amstrategy = indexRelation->rd_am->amstrategies;
#endif /* notdef */
    amstrategy = 1;
    relam = indexRelation->rd_rel->relam;
    info->relam = relam;
    info->pages = indexRelation->rd_rel->relpages;
    info->tuples = indexRelation->rd_rel->reltuples;
    heap_close(indexRelation);
	
    /* 
     * Find the index ordering keys 
     *
     * Must use indclass to know when to stop looking since with
     * functional indices there could be several keys (args) for
     * one opclass. -mer 27 Sept 1991
     */
    for (i = 0; i < 8 && index->indclass[i]; ++i) {
	amopTuple = SearchSysCacheTuple(AMOPSTRATEGY,
					ObjectIdGetDatum(relam),
					ObjectIdGetDatum(index->indclass[i]),
					UInt16GetDatum(amstrategy),
					0);
	if (!HeapTupleIsValid(amopTuple))
	    elog(WARN, "index_info: no amop %d %d %d",
		 relam, index->indclass[i], amstrategy);
	info->orderOprs[i] =
	    ((Form_pg_amop)GETSTRUCT(amopTuple))->amopopr;
    }
    return(TRUE);
}

/*    
 * index-selectivity--
 *    
 *    Call util/plancat.c:IndexSelectivity with the indicated arguments.
 *    
 * 'indid' is the index OID
 * 'classes' is a list of index key classes
 * 'opnos' is a list of index key operator OIDs
 * 'relid' is the OID of the relation indexed
 * 'attnos' is a list of the relation attnos which the index keys over
 * 'values' is a list of the values of the clause's constants
 * 'flags' is a list of fixnums which describe the constants
 * 'nkeys' is the number of index keys
 *    
 * Returns two floats: index pages and index selectivity in 'idxPages' and
 * 	'idxSelec'.
 *    
 */
void
index_selectivity(Oid indid,
		  Oid *classes,
		  List *opnos,
		  Oid relid,
		  List *attnos,
		  List *values,
		  List *flags,
		  int32 nkeys,
		  float *idxPages,
		  float *idxSelec)
{
    Oid *opno_array;
    int *attno_array, *flag_array;
    char **value_array;
    int i = 0;
    List *xopno, *xattno, *value, *flag;

    if (length(opnos)!=nkeys || length(attnos)!=nkeys ||
	length(values)!=nkeys || length(flags)!=nkeys) {

	*idxPages = 0.0;
	*idxSelec = 1.0;
	return;
    }
	
    opno_array = (Oid *)palloc(nkeys*sizeof(Oid));
    attno_array = (int *)palloc(nkeys*sizeof(int32));
    value_array = (char **)palloc(nkeys*sizeof(char *));
    flag_array = (int *)palloc(nkeys*sizeof(int32));
    
    i = 0;
    foreach(xopno, opnos) {
	opno_array[i++] = lfirsti(xopno);
    }

    i = 0;
    foreach(xattno,attnos) {
	attno_array[i++] = lfirsti(xattno);
    }

    i = 0;
    foreach(value, values) {
	value_array[i++] = (char *)lfirst(value);
    }

    i = 0;
    foreach(flag,flags) {
	flag_array[i++] = lfirsti(flag);
    }
    
    IndexSelectivity(indid,
		     relid,
		     nkeys,
		     classes,	/* not used */
		     opno_array,
		     attno_array,
		     value_array,
		     flag_array,
		     idxPages,
		     idxSelec);
    return;
}

/*
 * restriction_selectivity in lisp system.--
 *
 *    NOTE: The routine is now merged with RestrictionClauseSelectivity
 *    as defined in plancat.c
 *
 * Returns the selectivity of a specified operator.
 * This code executes registered procedures stored in the
 * operator relation, by calling the function manager.
 *
 * XXX The assumption in the selectivity procedures is that if the
 *	relation OIDs or attribute numbers are -1, then the clause
 *	isn't of the form (op var const).
 */
Cost
restriction_selectivity(Oid functionObjectId,
			Oid operatorObjectId,
			Oid relationObjectId,
			AttrNumber attributeNumber,
			char *constValue,
			int32 constFlag)
{
    float64 result;

    result = (float64) fmgr(functionObjectId,
			    (char *) operatorObjectId,
			    (char *) relationObjectId, 
			    (char *) (int)attributeNumber,
			    (char *) constValue,
			    (char *) constFlag,
			    NULL);
    if (!PointerIsValid(result))
	elog(WARN, "RestrictionClauseSelectivity: bad pointer");

    if (*result < 0.0 || *result > 1.0)
	elog(WARN, "RestrictionClauseSelectivity: bad value %lf",
	     *result);

    return ((Cost)*result);
}

/*
 * join_selectivity--
 *    Similarly, this routine is merged with JoinClauseSelectivity in
 *    plancat.c
 *
 *    Returns the selectivity of an operator, given the join clause
 *    information.
 *
 * XXX The assumption in the selectivity procedures is that if the
 *	relation OIDs or attribute numbers are -1, then the clause
 *	isn't of the form (op var var).
 */
Cost
join_selectivity (Oid functionObjectId,
		  Oid operatorObjectId,
		  Oid relationObjectId1,
		  AttrNumber attributeNumber1,
		  Oid relationObjectId2,
		  AttrNumber attributeNumber2)
{
    float64 result;
    
    result = (float64) fmgr(functionObjectId,
			    (char *) operatorObjectId,
			    (char *) relationObjectId1,
			    (char *) (int)attributeNumber1,
			    (char *) relationObjectId2,
			    (char *) (int)attributeNumber2,
			    NULL);
    if (!PointerIsValid(result))
	elog(WARN, "JoinClauseSelectivity: bad pointer");

    if (*result < 0.0 || *result > 1.0)
	elog(WARN, "JoinClauseSelectivity: bad value %lf",
	     *result);

    return((Cost)*result);
}

/*
 * find_all_inheritors--
 *
 * Returns a LISP list containing the OIDs of all relations which
 * inherits from the relation with OID 'inhparent'.
 */
List *
find_inheritance_children(Oid inhparent)
{
    static ScanKeyData	key[1] = {
	{ 0, Anum_pg_inherits_inhparent, F_OIDEQ }
    };

    HeapTuple	inheritsTuple;
    Relation	relation;
    HeapScanDesc scan;
    List  	*list = NIL;
    Oid		inhrelid;

    fmgr_info(F_OIDEQ, &key[0].sk_func, &key[0].sk_nargs);

    key[0].sk_argument = ObjectIdGetDatum((Oid)inhparent);
    relation = heap_openr(InheritsRelationName);
    scan = heap_beginscan(relation, 0, NowTimeQual, 1, key);
    while (HeapTupleIsValid(inheritsTuple =
			    heap_getnext(scan, 0,
					 (Buffer *) NULL))) {
	inhrelid = ((InheritsTupleForm)GETSTRUCT(inheritsTuple))->inhrel;
	list = lappendi(list, inhrelid);
    }
    heap_endscan(scan);
    heap_close(relation);
    return(list);
}

/*
 * VersionGetParents--
 *
 * Returns a LISP list containing the OIDs of all relations which are
 * base relations of the relation with OID 'verrelid'.
 */
List *
VersionGetParents(Oid verrelid)
{
    static ScanKeyData	key[1] = {
	{ 0, Anum_pg_version_verrelid, F_OIDEQ }
    };

    HeapTuple		versionTuple;
    Relation		relation;
    HeapScanDesc	scan;
    Oid			verbaseid;
    List 		*list= NIL;

    fmgr_info(F_OIDEQ, &key[0].sk_func, &key[0].sk_nargs);
    relation = heap_openr(VersionRelationName);
    key[0].sk_argument = ObjectIdGetDatum(verrelid);
    scan = heap_beginscan(relation, 0, NowTimeQual, 1, key);
    for (;;) {
	versionTuple = heap_getnext(scan, 0,
				    (Buffer *) NULL);
	if (!HeapTupleIsValid(versionTuple))
	    break;
	verbaseid = ((VersionTupleForm)
		     GETSTRUCT(versionTuple))->verbaseid;

	list = lconsi(verbaseid, list);

	key[0].sk_argument = ObjectIdGetDatum(verbaseid);
	heap_rescan(scan, 0, key);
    }
    heap_endscan(scan);
    heap_close(relation);
    return(list);
}

/*****************************************************************************
 *
 *****************************************************************************/

/*
 * IdexSelectivity--
 *
 *    Retrieves the 'amopnpages' and 'amopselect' parameters for each
 *    AM operator when a given index (specified by 'indexrelid') is used.
 *    These two parameters are returned by copying them to into an array of
 *    floats.
 *
 *    Assumption: the attribute numbers and operator ObjectIds are in order
 *    WRT to each other (otherwise, you have no way of knowing which
 *    AM operator class or attribute number corresponds to which operator.
 *
 * 'varAttributeNumbers' contains attribute numbers for variables
 * 'constValues' contains the constant values
 * 'constFlags' describes how to treat the constants in each clause
 * 'nIndexKeys' describes how many keys the index actually has
 *
 * Returns 'selectivityInfo' filled with the sum of all pages touched
 * and the product of each clause's selectivity.
 *
 */
static void
IndexSelectivity(Oid indexrelid,
		 Oid indrelid,
		 int32 nIndexKeys,
		 Oid AccessMethodOperatorClasses[], /* XXX not used? */
		 Oid operatorObjectIds[],
		 int32 varAttributeNumbers[],
		 char *constValues[],
		 int32 constFlags[],
		 float *idxPages,
		 float *idxSelec)
{
    register	i, n;
    HeapTuple	indexTuple, amopTuple, indRel;
    IndexTupleForm	index;
    Form_pg_amop	amop;
    Oid		indclass;
    float64data	npages, select;
    float64	amopnpages, amopselect;
    Oid relam;
    bool nphack = false;
    float64data	fattr_select = 1.0;

    indRel = SearchSysCacheTuple(RELOID,
				 ObjectIdGetDatum(indexrelid),
				 0,0,0);
    if (!HeapTupleIsValid(indRel))
	elog(WARN, "IndexSelectivity: index %d not found",
	     indexrelid);
    relam = ((Form_pg_class)GETSTRUCT(indRel))->relam;

    indexTuple = SearchSysCacheTuple(INDEXRELID,
				     ObjectIdGetDatum(indexrelid),
				     0,0,0);
    if (!HeapTupleIsValid(indexTuple))
	elog(WARN, "IndexSelectivity: index %d not found",
	     indexrelid);
    index = (IndexTupleForm)GETSTRUCT(indexTuple);
    
    /*
     * Hack for non-functional btree npages estimation:
     * npages = index_pages * selectivity_of_1st_attr_clause(s)
     *		- vadim 04/24/97
     */
    if ( relam == BTREE_AM_OID && 
    		varAttributeNumbers[0] != InvalidAttrNumber )
    	nphack = true;

    npages = 0.0;
    select = 1.0;
    for (n = 0; n < nIndexKeys; ++n) {
	/* 
	 * Find the AM class for this key. 
	 *
	 * If the first attribute number is invalid then we have a
	 * functional index, and AM class is the first one defined
	 * since functional indices have exactly one key.
	 */
	indclass = (varAttributeNumbers[0] == InvalidAttrNumber) ?
	    index->indclass[0] : InvalidOid;
	i = 0;
	while ((i < nIndexKeys) && (indclass == InvalidOid)) {
	    if (varAttributeNumbers[n] == index->indkey[i]) {
		indclass = index->indclass[i];
		break;
	    }
	    i++;
	}
	if (!OidIsValid(indclass)) {
	    /*
	     * Presumably this means that we are using a functional
	     * index clause and so had no variable to match to
	     * the index key ... if not we are in trouble.
	     */
	    elog(NOTICE, "IndexSelectivity: no key %d in index %d",
		 varAttributeNumbers[n], indexrelid);
	    continue;
	}

	amopTuple = SearchSysCacheTuple(AMOPOPID,
					ObjectIdGetDatum(indclass),
					ObjectIdGetDatum(operatorObjectIds[n]),
					ObjectIdGetDatum(relam),
					0);
	if (!HeapTupleIsValid(amopTuple))
	    elog(WARN, "IndexSelectivity: no amop %d %d",
		 indclass, operatorObjectIds[n]);
	amop = (Form_pg_amop)GETSTRUCT(amopTuple);
	
	if ( !nphack )
	{
	    amopnpages = (float64) fmgr(amop->amopnpages,
				    (char *) operatorObjectIds[n],
				    (char *) indrelid,
				    (char *) varAttributeNumbers[n],
				    (char *) constValues[n],
				    (char *) constFlags[n],
				    (char *) nIndexKeys, 
				    (char *) indexrelid);
#if 0 
/* 
 * So cool guys! Npages for x > 10 and x < 20 is twice as
 * npages for x > 10!	- vadim 04/09/97
 */
	npages += PointerIsValid(amopnpages) ? *amopnpages : 0.0;
	if ((i = npages) < npages) /* ceil(npages)? */
	    npages += 1.0;
#endif
	    npages += PointerIsValid(amopnpages) ? *amopnpages : 0.0;
	}
	
	amopselect = (float64) fmgr(amop->amopselect,
				    (char *) operatorObjectIds[n],
				    (char *) indrelid,
				    (char *) varAttributeNumbers[n],
				    (char *) constValues[n],
				    (char *) constFlags[n],
				    (char *) nIndexKeys,
				    (char *) indexrelid);

	if ( nphack && varAttributeNumbers[n] == index->indkey[0] )
	    fattr_select *= PointerIsValid(amopselect) ? *amopselect : 1.0;

	select *= PointerIsValid(amopselect) ? *amopselect : 1.0;
    }
    /*
     * Estimation of npages below is hack of course, but it's
     * better than it was before.	- vadim 04/09/97
     */
    if ( nphack )
    {
	npages = fattr_select * ((Form_pg_class)GETSTRUCT(indRel))->relpages;
    	*idxPages = ceil ((double)npages);
    }
    else
    {
    	if ( nIndexKeys > 1 )
    	    npages = npages / (1.0 + nIndexKeys);
    	*idxPages = ceil ((double)(npages/nIndexKeys));
    }
    *idxSelec = select;
}

