/*-------------------------------------------------------------------------
 *
 * plancat.c
 *	   routines for accessing the system catalogs
 *
 *
 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/plancat.c,v 1.68 2001/08/21 16:36:03 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <math.h>

#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_index.h"
#include "optimizer/clauses.h"
#include "optimizer/plancat.h"
#include "parser/parsetree.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/relcache.h"
#include "utils/syscache.h"
#include "catalog/catalog.h"
#include "miscadmin.h"


/*
 * get_relation_info -
 *	  Retrieves catalog information for a given relation.
 *	  Given the Oid of the relation, return the following info:
 *				whether the relation has secondary indices
 *				number of pages
 *				number of tuples
 */
void
get_relation_info(Oid relationObjectId,
				  bool *hasindex, long *pages, double *tuples)
{
	HeapTuple	relationTuple;
	Form_pg_class relation;

	relationTuple = SearchSysCache(RELOID,
								   ObjectIdGetDatum(relationObjectId),
								   0, 0, 0);
	if (!HeapTupleIsValid(relationTuple))
		elog(ERROR, "get_relation_info: Relation %u not found",
			 relationObjectId);
	relation = (Form_pg_class) GETSTRUCT(relationTuple);

	if (IsIgnoringSystemIndexes() &&
		IsSystemRelationName(NameStr(relation->relname)))
		*hasindex = false;
	else
		*hasindex = relation->relhasindex;

	*pages = relation->relpages;
	*tuples = relation->reltuples;

	ReleaseSysCache(relationTuple);
}

/*
 * find_secondary_indexes
 *	  Creates a list of IndexOptInfo nodes containing information for each
 *	  secondary index defined on the specified relation.
 *
 * 'relationObjectId' is the OID of the relation for which indices are wanted
 *
 * Returns a list of new IndexOptInfo nodes.
 */
List *
find_secondary_indexes(Oid relationObjectId)
{
	List	   *indexinfos = NIL;
	List	   *indexoidlist,
			   *indexoidscan;
	Relation	relation;

	/*
	 * We used to scan pg_index directly, but now the relcache offers a
	 * cached list of OID indexes for each relation.  So, get that list
	 * and then use the syscache to obtain pg_index entries.
	 */
	relation = heap_open(relationObjectId, AccessShareLock);
	indexoidlist = RelationGetIndexList(relation);

	foreach(indexoidscan, indexoidlist)
	{
		Oid			indexoid = lfirsti(indexoidscan);
		HeapTuple	indexTuple;
		Form_pg_index index;
		IndexOptInfo *info;
		int			i;
		Relation	indexRelation;
		int16		amorderstrategy;

		indexTuple = SearchSysCache(INDEXRELID,
									ObjectIdGetDatum(indexoid),
									0, 0, 0);
		if (!HeapTupleIsValid(indexTuple))
			elog(ERROR, "find_secondary_indexes: index %u not found",
				 indexoid);
		index = (Form_pg_index) GETSTRUCT(indexTuple);
		info = makeNode(IndexOptInfo);

		/*
		 * Need to make these arrays large enough to be sure there is
		 * room for a terminating 0 at the end of each one.
		 */
		info->classlist = (Oid *) palloc(sizeof(Oid) * (INDEX_MAX_KEYS + 1));
		info->indexkeys = (int *) palloc(sizeof(int) * (INDEX_MAX_KEYS + 1));
		info->ordering = (Oid *) palloc(sizeof(Oid) * (INDEX_MAX_KEYS + 1));

		/* Extract info from the pg_index tuple */
		info->indexoid = index->indexrelid;
		info->indproc = index->indproc; /* functional index ?? */
		if (VARSIZE(&index->indpred) > VARHDRSZ) /* partial index ?? */
		{
			char	   *predString;

			predString = DatumGetCString(DirectFunctionCall1(textout,
									  PointerGetDatum(&index->indpred)));
			info->indpred = (List *) stringToNode(predString);
			pfree(predString);
		}
		else
			info->indpred = NIL;
		info->unique = index->indisunique;

		for (i = 0; i < INDEX_MAX_KEYS; i++)
		{
			if (index->indclass[i] == (Oid) 0)
				break;
			info->classlist[i] = index->indclass[i];
		}
		info->classlist[i] = (Oid) 0;
		info->ncolumns = i;

		for (i = 0; i < INDEX_MAX_KEYS; i++)
		{
			if (index->indkey[i] == 0)
				break;
			info->indexkeys[i] = index->indkey[i];
		}
		info->indexkeys[i] = 0;
		info->nkeys = i;

		/* Extract info from the relation descriptor for the index */
		indexRelation = index_open(index->indexrelid);
		info->relam = indexRelation->rd_rel->relam;
		info->pages = indexRelation->rd_rel->relpages;
		info->tuples = indexRelation->rd_rel->reltuples;
		info->amcostestimate = index_cost_estimator(indexRelation);
		amorderstrategy = indexRelation->rd_am->amorderstrategy;
		index_close(indexRelation);

		/*
		 * Fetch the ordering operators associated with the index, if any.
		 */
		MemSet(info->ordering, 0, sizeof(Oid) * (INDEX_MAX_KEYS + 1));
		if (amorderstrategy != 0)
		{
			for (i = 0; i < info->ncolumns; i++)
			{
				HeapTuple	amopTuple;
				Form_pg_amop amop;

				amopTuple =
					SearchSysCache(AMOPSTRATEGY,
								   ObjectIdGetDatum(index->indclass[i]),
								   Int16GetDatum(amorderstrategy),
								   0, 0);
				if (!HeapTupleIsValid(amopTuple))
					elog(ERROR, "find_secondary_indexes: no amop %u %d",
						 index->indclass[i], (int) amorderstrategy);
				amop = (Form_pg_amop) GETSTRUCT(amopTuple);
				info->ordering[i] = amop->amopopr;
				ReleaseSysCache(amopTuple);
			}
		}

		ReleaseSysCache(indexTuple);

		indexinfos = lcons(info, indexinfos);
	}

	freeList(indexoidlist);

	/* XXX keep the lock here? */
	heap_close(relation, AccessShareLock);

	return indexinfos;
}

/*
 * restriction_selectivity
 *
 * Returns the selectivity of a specified restriction operator clause.
 * This code executes registered procedures stored in the
 * operator relation, by calling the function manager.
 *
 * varRelid is either 0 or a rangetable index.  See clause_selectivity()
 * for details about its meaning.
 */
Selectivity
restriction_selectivity(Query *root,
						Oid operator,
						List *args,
						int varRelid)
{
	RegProcedure oprrest = get_oprrest(operator);
	float8		result;

	/*
	 * if the oprrest procedure is missing for whatever reason,
	 * use a selectivity of 0.5
	 */
	if (!oprrest)
		return (Selectivity) 0.5;

	result = DatumGetFloat8(OidFunctionCall4(oprrest,
											 PointerGetDatum(root),
											 ObjectIdGetDatum(operator),
											 PointerGetDatum(args),
											 Int32GetDatum(varRelid)));

	if (result < 0.0 || result > 1.0)
		elog(ERROR, "restriction_selectivity: bad value %f", result);

	return (Selectivity) result;
}

/*
 * join_selectivity
 *
 * Returns the selectivity of a specified join operator clause.
 * This code executes registered procedures stored in the
 * operator relation, by calling the function manager.
 */
Selectivity
join_selectivity(Query *root,
				 Oid operator,
				 List *args)
{
	RegProcedure oprjoin = get_oprjoin(operator);
	float8		result;

	/*
	 * if the oprjoin procedure is missing for whatever reason,
	 * use a selectivity of 0.5
	 */
	if (!oprjoin)
		return (Selectivity) 0.5;

	result = DatumGetFloat8(OidFunctionCall3(oprjoin,
											 PointerGetDatum(root),
											 ObjectIdGetDatum(operator),
											 PointerGetDatum(args)));

	if (result < 0.0 || result > 1.0)
		elog(ERROR, "join_selectivity: bad value %f", result);

	return (Selectivity) result;
}

/*
 * find_inheritance_children
 *
 * Returns an integer list containing the OIDs of all relations which
 * inherit *directly* from the relation with OID 'inhparent'.
 *
 * XXX might be a good idea to create an index on pg_inherits' inhparent
 * field, so that we can use an indexscan instead of sequential scan here.
 * However, in typical databases pg_inherits won't have enough entries to
 * justify an indexscan...
 */
List *
find_inheritance_children(Oid inhparent)
{
	List	   *list = NIL;
	Relation	relation;
	HeapScanDesc scan;
	HeapTuple	inheritsTuple;
	Oid			inhrelid;
	ScanKeyData key[1];

	/*
	 * Can skip the scan if pg_class shows the relation has never had a
	 * subclass.
	 */
	if (!has_subclass(inhparent))
		return NIL;

	ScanKeyEntryInitialize(&key[0],
						   (bits16) 0x0,
						   (AttrNumber) Anum_pg_inherits_inhparent,
						   (RegProcedure) F_OIDEQ,
						   ObjectIdGetDatum(inhparent));
	relation = heap_openr(InheritsRelationName, AccessShareLock);
	scan = heap_beginscan(relation, 0, SnapshotNow, 1, key);
	while (HeapTupleIsValid(inheritsTuple = heap_getnext(scan, 0)))
	{
		inhrelid = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhrelid;
		list = lappendi(list, inhrelid);
	}
	heap_endscan(scan);
	heap_close(relation, AccessShareLock);
	return list;
}

/*
 * has_subclass
 *
 * In the current implementation, has_subclass returns whether a
 * particular class *might* have a subclass. It will not return the
 * correct result if a class had a subclass which was later dropped.
 * This is because relhassubclass in pg_class is not updated when a
 * subclass is dropped, primarily because of concurrency concerns.
 *
 * Currently has_subclass is only used as an efficiency hack to skip
 * unnecessary inheritance searches, so this is OK.
 */
bool
has_subclass(Oid relationId)
{
	HeapTuple	tuple;
	bool		result;

	tuple = SearchSysCache(RELOID,
						   ObjectIdGetDatum(relationId),
						   0, 0, 0);
	if (!HeapTupleIsValid(tuple))
		elog(ERROR, "has_subclass: Relation %u not found", relationId);

	result = ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass;
	ReleaseSysCache(tuple);
	return result;
}

/*
 * has_unique_index
 *
 * Detect whether there is a unique index on the specified attribute
 * of the specified relation, thus allowing us to conclude that all
 * the (non-null) values of the attribute are distinct.
 */
bool
has_unique_index(RelOptInfo *rel, AttrNumber attno)
{
	List	   *ilist;

	foreach(ilist, rel->indexlist)
	{
		IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);

		/*
		 * Note: ignore functional and partial indexes, since they
		 * don't allow us to conclude that all attr values are distinct.
		 * Also, a multicolumn unique index doesn't allow us to conclude
		 * that just the specified attr is unique.
		 */
		if (index->unique &&
			index->nkeys == 1 &&
			index->indexkeys[0] == attno &&
			index->indproc == InvalidOid &&
			index->indpred == NIL)
			return true;
	}
	return false;
}
