/*-------------------------------------------------------------------------
 *
 * defind.c
 *	  POSTGRES define, extend and remove index code.
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.12 1999/10/02 21:33:24 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/pg_index.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "parser/parsetree.h"
#include "utils/builtins.h"
#include "utils/syscache.h"

#define IsFuncIndex(ATTR_LIST) (((IndexElem*)lfirst(ATTR_LIST))->args!=NULL)

/* non-export function prototypes */
static void CheckPredicate(List *predList, List *rangeTable, Oid baseRelOid);
static void CheckPredExpr(Node *predicate, List *rangeTable,
			  Oid baseRelOid);
static void
			CheckPredClause(Expr *predicate, List *rangeTable, Oid baseRelOid);
static void FuncIndexArgs(IndexElem *funcIndex, AttrNumber *attNumP,
			  Oid *argTypes, Oid *opOidP, Oid relId);
static void NormIndexAttrs(List *attList, AttrNumber *attNumP,
			   Oid *opOidP, Oid relId);
static char *GetDefaultOpClass(Oid atttypid);

/*
 * DefineIndex
 *		Creates a new index.
 *
 * 'attributeList' is a list of IndexElem specifying either a functional
 *		index or a list of attributes to index on.
 * 'parameterList' is a list of DefElem specified in the with clause.
 * 'predicate' is the qual specified in the where clause.
 * 'rangetable' is for the predicate
 *
 * Exceptions:
 *		XXX
 */
void
DefineIndex(char *heapRelationName,
			char *indexRelationName,
			char *accessMethodName,
			List *attributeList,
			List *parameterList,
			bool unique,
			bool primary,
			Expr *predicate,
			List *rangetable)
{
	Oid		   *classObjectId;
	Oid			accessMethodId;
	Oid			relationId;
	int			numberOfAttributes;
	AttrNumber *attributeNumberA;
	HeapTuple	tuple;
	uint16		parameterCount = 0;
	Datum	   *parameterA = NULL;
	FuncIndexInfo fInfo;
	List	   *cnfPred = NULL;
	bool		lossy = FALSE;
	List	   *pl;

	/*
	 * Handle attributes
	 */
	numberOfAttributes = length(attributeList);
	if (numberOfAttributes <= 0)
		elog(ERROR, "DefineIndex: must specify at least one attribute");

	/*
	 * compute heap relation id
	 */
	if ((relationId = RelnameFindRelid(heapRelationName)) == InvalidOid)
	{
		elog(ERROR, "DefineIndex: %s relation not found",
			 heapRelationName);
	}

	if (unique && strcmp(accessMethodName, "btree") != 0)
		elog(ERROR, "DefineIndex: unique indices are only available with the btree access method");

	if (numberOfAttributes > 1 && strcmp(accessMethodName, "btree") != 0)
		elog(ERROR, "DefineIndex: multi-column indices are only available with the btree access method");

	/*
	 * compute access method id
	 */
	tuple = SearchSysCacheTuple(AMNAME,
								PointerGetDatum(accessMethodName),
								0, 0, 0);
	if (!HeapTupleIsValid(tuple))
	{
		elog(ERROR, "DefineIndex: %s access method not found",
			 accessMethodName);
	}
	accessMethodId = tuple->t_data->t_oid;

	/*
	 * WITH clause reinstated to handle lossy indices. -- JMH, 7/22/96
	 */
	foreach(pl, parameterList)
	{
		DefElem *param = (DefElem *) lfirst(pl);

		if (!strcasecmp(param->defname, "islossy"))
			lossy = TRUE;
		else
			elog(NOTICE, "Unrecognized index attribute '%s' ignored",
				 param->defname);
	}

	/*
	 * Convert the partial-index predicate from parsetree form to plan
	 * form, so it can be readily evaluated during index creation. Note:
	 * "predicate" comes in as a list containing (1) the predicate itself
	 * (a where_clause), and (2) a corresponding range table.
	 *
	 * [(1) is 'predicate' and (2) is 'rangetable' now. - ay 10/94]
	 */
	if (predicate != NULL && rangetable != NIL)
	{
		cnfPred = cnfify((Expr *) copyObject(predicate), true);
		fix_opids((Node *) cnfPred);
		CheckPredicate(cnfPred, rangetable, relationId);
	}

	if (IsFuncIndex(attributeList))
	{
		IndexElem  *funcIndex = lfirst(attributeList);
		int			nargs;

		nargs = length(funcIndex->args);
		if (nargs > INDEX_MAX_KEYS)
		{
			elog(ERROR,
			   "Too many args to function, limit of %d", INDEX_MAX_KEYS);
		}

		FIsetnArgs(&fInfo, nargs);

		strcpy(FIgetname(&fInfo), funcIndex->name);

		attributeNumberA = (AttrNumber *) palloc(nargs * sizeof attributeNumberA[0]);

		classObjectId = (Oid *) palloc(sizeof classObjectId[0]);


		FuncIndexArgs(funcIndex, attributeNumberA,
					  &(FIgetArg(&fInfo, 0)),
					  classObjectId, relationId);

		index_create(heapRelationName,
					 indexRelationName,
					 &fInfo, NULL, accessMethodId,
					 numberOfAttributes, attributeNumberA,
			 classObjectId, parameterCount, parameterA, (Node *) cnfPred,
					 lossy, unique, primary);
	}
	else
	{
		attributeNumberA = (AttrNumber *) palloc(numberOfAttributes *
											 sizeof attributeNumberA[0]);

		classObjectId = (Oid *) palloc(numberOfAttributes * sizeof classObjectId[0]);

		NormIndexAttrs(attributeList, attributeNumberA,
					   classObjectId, relationId);

		index_create(heapRelationName, indexRelationName, NULL,
					 attributeList,
					 accessMethodId, numberOfAttributes, attributeNumberA,
			 classObjectId, parameterCount, parameterA, (Node *) cnfPred,
					 lossy, unique, primary);
	}
}


/*
 * ExtendIndex
 *		Extends a partial index.
 *
 * Exceptions:
 *		XXX
 */
void
ExtendIndex(char *indexRelationName, Expr *predicate, List *rangetable)
{
	Oid		   *classObjectId;
	Oid			accessMethodId;
	Oid			indexId,
				relationId;
	Oid			indproc;
	int			numberOfAttributes;
	AttrNumber *attributeNumberA;
	HeapTuple	tuple;
	FuncIndexInfo fInfo;
	FuncIndexInfo *funcInfo = NULL;
	Form_pg_index index;
	Node	   *oldPred = NULL;
	List	   *cnfPred = NULL;
	PredInfo   *predInfo;
	Relation	heapRelation;
	Relation	indexRelation;
	int			i;

	/*
	 * compute index relation id and access method id
	 */
	tuple = SearchSysCacheTuple(RELNAME,
								PointerGetDatum(indexRelationName),
								0, 0, 0);
	if (!HeapTupleIsValid(tuple))
	{
		elog(ERROR, "ExtendIndex: %s index not found",
			 indexRelationName);
	}
	indexId = tuple->t_data->t_oid;
	accessMethodId = ((Form_pg_class) GETSTRUCT(tuple))->relam;

	/*
	 * find pg_index tuple
	 */
	tuple = SearchSysCacheTuple(INDEXRELID,
								ObjectIdGetDatum(indexId),
								0, 0, 0);
	if (!HeapTupleIsValid(tuple))
	{
		elog(ERROR, "ExtendIndex: %s is not an index",
			 indexRelationName);
	}

	/*
	 * Extract info from the pg_index tuple
	 */
	index = (Form_pg_index) GETSTRUCT(tuple);
	Assert(index->indexrelid == indexId);
	relationId = index->indrelid;
	indproc = index->indproc;

	for (i = 0; i < INDEX_MAX_KEYS; i++)
		if (index->indkey[i] == InvalidAttrNumber)
			break;
	numberOfAttributes = i;

	if (VARSIZE(&index->indpred) != 0)
	{
		char	   *predString;

		predString = fmgr(F_TEXTOUT, &index->indpred);
		oldPred = stringToNode(predString);
		pfree(predString);
	}
	if (oldPred == NULL)
		elog(ERROR, "ExtendIndex: %s is not a partial index",
			 indexRelationName);

	/*
	 * Convert the extension predicate from parsetree form to plan form,
	 * so it can be readily evaluated during index creation. Note:
	 * "predicate" comes in as a list containing (1) the predicate itself
	 * (a where_clause), and (2) a corresponding range table.
	 */
	if (rangetable != NIL)
	{
		cnfPred = cnfify((Expr *) copyObject(predicate), true);
		fix_opids((Node *) cnfPred);
		CheckPredicate(cnfPred, rangetable, relationId);
	}

	/* make predInfo list to pass to index_build */
	predInfo = (PredInfo *) palloc(sizeof(PredInfo));
	predInfo->pred = (Node *) cnfPred;
	predInfo->oldPred = oldPred;

	attributeNumberA = (AttrNumber *) palloc(numberOfAttributes *
											 sizeof attributeNumberA[0]);
	classObjectId = (Oid *) palloc(numberOfAttributes * sizeof classObjectId[0]);


	for (i = 0; i < numberOfAttributes; i++)
	{
		attributeNumberA[i] = index->indkey[i];
		classObjectId[i] = index->indclass[i];
	}

	if (indproc != InvalidOid)
	{
		funcInfo = &fInfo;
/*		FIgetnArgs(funcInfo) = numberOfAttributes; */
		FIsetnArgs(funcInfo, numberOfAttributes);

		tuple = SearchSysCacheTuple(PROOID,
									ObjectIdGetDatum(indproc),
									0, 0, 0);
		if (!HeapTupleIsValid(tuple))
			elog(ERROR, "ExtendIndex: index procedure not found");

		namecpy(&(funcInfo->funcName),
				&(((Form_pg_proc) GETSTRUCT(tuple))->proname));

		FIsetProcOid(funcInfo, tuple->t_data->t_oid);
	}

	heapRelation = heap_open(relationId, ShareLock);
	indexRelation = index_open(indexId);

	InitIndexStrategy(numberOfAttributes, indexRelation, accessMethodId);

	index_build(heapRelation, indexRelation, numberOfAttributes,
				attributeNumberA, 0, NULL, funcInfo, predInfo);

	/* heap and index rels are closed as a side-effect of index_build */
}


/*
 * CheckPredicate
 *		Checks that the given list of partial-index predicates refer
 *		(via the given range table) only to the given base relation oid,
 *		and that they're in a form the planner can handle, i.e.,
 *		boolean combinations of "ATTR OP CONST" (yes, for now, the ATTR
 *		has to be on the left).
 */

static void
CheckPredicate(List *predList, List *rangeTable, Oid baseRelOid)
{
	List	   *item;

	foreach(item, predList)
		CheckPredExpr(lfirst(item), rangeTable, baseRelOid);
}

static void
CheckPredExpr(Node *predicate, List *rangeTable, Oid baseRelOid)
{
	List	   *clauses = NIL,
			   *clause;

	if (is_opclause(predicate))
	{
		CheckPredClause((Expr *) predicate, rangeTable, baseRelOid);
		return;
	}
	else if (or_clause(predicate) || and_clause(predicate))
		clauses = ((Expr *) predicate)->args;
	else
		elog(ERROR, "Unsupported partial-index predicate expression type");

	foreach(clause, clauses)
		CheckPredExpr(lfirst(clause), rangeTable, baseRelOid);
}

static void
CheckPredClause(Expr *predicate, List *rangeTable, Oid baseRelOid)
{
	Var		   *pred_var;
	Const	   *pred_const;

	pred_var = (Var *) get_leftop(predicate);
	pred_const = (Const *) get_rightop(predicate);

	if (!IsA(predicate->oper, Oper) ||
		!IsA(pred_var, Var) ||
		!IsA(pred_const, Const))
		elog(ERROR, "Unsupported partial-index predicate clause type");

	if (getrelid(pred_var->varno, rangeTable) != baseRelOid)
		elog(ERROR,
		 "Partial-index predicates may refer only to the base relation");
}


static void
FuncIndexArgs(IndexElem *funcIndex,
			  AttrNumber *attNumP,
			  Oid *argTypes,
			  Oid *opOidP,
			  Oid relId)
{
	List	   *rest;
	HeapTuple	tuple;
	Form_pg_attribute att;

	tuple = SearchSysCacheTuple(CLANAME,
								PointerGetDatum(funcIndex->class),
								0, 0, 0);

	if (!HeapTupleIsValid(tuple))
	{
		elog(ERROR, "DefineIndex: %s class not found",
			 funcIndex->class);
	}
	*opOidP = tuple->t_data->t_oid;

	MemSet(argTypes, 0, 8 * sizeof(Oid));

	/*
	 * process the function arguments
	 */
	for (rest = funcIndex->args; rest != NIL; rest = lnext(rest))
	{
		char	   *arg;

		arg = strVal(lfirst(rest));

		tuple = SearchSysCacheTuple(ATTNAME,
									ObjectIdGetDatum(relId),
									PointerGetDatum(arg), 0, 0);

		if (!HeapTupleIsValid(tuple))
		{
			elog(ERROR,
				 "DefineIndex: attribute \"%s\" not found",
				 arg);
		}
		att = (Form_pg_attribute) GETSTRUCT(tuple);
		*attNumP++ = att->attnum;
		*argTypes++ = att->atttypid;
	}
}

static void
NormIndexAttrs(List *attList,	/* list of IndexElem's */
			   AttrNumber *attNumP,
			   Oid *classOidP,
			   Oid relId)
{
	List	   *rest;
	HeapTuple	atttuple,
				tuple;

	/*
	 * process attributeList
	 */

	for (rest = attList; rest != NIL; rest = lnext(rest))
	{
		IndexElem  *attribute;
		Form_pg_attribute attform;

		attribute = lfirst(rest);

		if (attribute->name == NULL)
			elog(ERROR, "missing attribute for define index");

		atttuple = SearchSysCacheTupleCopy(ATTNAME,
										   ObjectIdGetDatum(relId),
										PointerGetDatum(attribute->name),
										   0, 0);
		if (!HeapTupleIsValid(atttuple))
		{
			elog(ERROR,
				 "DefineIndex: attribute \"%s\" not found",
				 attribute->name);
		}

		attform = (Form_pg_attribute) GETSTRUCT(atttuple);
		*attNumP++ = attform->attnum;

		/* we want the type so we can set the proper alignment, etc. */
		if (attribute->typename == NULL)
		{
			tuple = SearchSysCacheTuple(TYPOID,
									 ObjectIdGetDatum(attform->atttypid),
										0, 0, 0);
			if (!HeapTupleIsValid(tuple))
				elog(ERROR, "create index: type for attribute '%s' undefined",
					 attribute->name);
			/* we just set the type name because that is all we need */
			attribute->typename = makeNode(TypeName);
			attribute->typename->name = nameout(&((Form_pg_type) GETSTRUCT(tuple))->typname);

			/* we all need the typmod for the char and varchar types. */
			attribute->typename->typmod = attform->atttypmod;
		}

		if (attribute->class == NULL)
		{
			/* no operator class specified, so find the default */
			attribute->class = GetDefaultOpClass(attform->atttypid);
			if (attribute->class == NULL)
			{
				elog(ERROR,
					 "Can't find a default operator class for type %u.",
					 attform->atttypid);
			}
		}

		tuple = SearchSysCacheTuple(CLANAME,
									PointerGetDatum(attribute->class),
									0, 0, 0);

		if (!HeapTupleIsValid(tuple))
		{
			elog(ERROR, "DefineIndex: %s class not found",
				 attribute->class);
		}
		*classOidP++ = tuple->t_data->t_oid;
		pfree(atttuple);
	}
}

static char *
GetDefaultOpClass(Oid atttypid)
{
	HeapTuple	tuple;

	tuple = SearchSysCacheTuple(CLADEFTYPE,
								ObjectIdGetDatum(atttypid),
								0, 0, 0);
	if (!HeapTupleIsValid(tuple))
		return 0;

	return nameout(&(((Form_pg_opclass) GETSTRUCT(tuple))->opcname));
}

/*
 * RemoveIndex
 *		Deletes an index.
 *
 * Exceptions:
 *		BadArg if name is invalid.
 *		"WARN" if index nonexistent.
 *		...
 */
void
RemoveIndex(char *name)
{
	HeapTuple	tuple;

	tuple = SearchSysCacheTuple(RELNAME,
								PointerGetDatum(name),
								0, 0, 0);

	if (!HeapTupleIsValid(tuple))
		elog(ERROR, "index \"%s\" nonexistent", name);

	if (((Form_pg_class) GETSTRUCT(tuple))->relkind != RELKIND_INDEX)
	{
		elog(ERROR, "relation \"%s\" is of type \"%c\"",
			 name,
			 ((Form_pg_class) GETSTRUCT(tuple))->relkind);
	}

	index_destroy(tuple->t_data->t_oid);
}
