/*-------------------------------------------------------------------------
 *
 * table.c
 *	  Generic routines for table related code.
 *
 * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/table/table.c
 *
 *
 * NOTES
 *	  This file contains table_ routines that implement access to tables (in
 *	  contrast to other relation types like indexes) that are independent of
 *	  individual table access methods.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/relation.h"
#include "access/table.h"
#include "storage/lmgr.h"

#include "catalog/namespace.h"
#include "cdb/cdbvars.h"
#include "utils/faultinjector.h"
#include "utils/guc.h"


/* ----------------
 *		table_open - open a table relation by relation OID
 *
 *		This is essentially relation_open plus check that the relation
 *		is not an index nor a composite type.  (The caller should also
 *		check that it's not a view or foreign table before assuming it has
 *		storage.)
 * ----------------
 */
Relation
table_open(Oid relationId, LOCKMODE lockmode)
{
	Relation	r;

	r = relation_open(relationId, lockmode);

	if (r->rd_rel->relkind == RELKIND_INDEX ||
		r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is an index",
						RelationGetRelationName(r))));
	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is a composite type",
						RelationGetRelationName(r))));

	return r;
}


/* ----------------
 *		try_table_open - open a table relation by relation OID
 *
 *		Same as table_open, except return NULL instead of failing
 *		if the relation does not exist.
 * ----------------
 */
Relation
try_table_open(Oid relationId, LOCKMODE lockmode, bool noWait)
{
	Relation	r;

	r = try_relation_open(relationId, lockmode, noWait);

	/* leave if table does not exist */
	if (!r)
		return NULL;

	if (r->rd_rel->relkind == RELKIND_INDEX ||
		r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is an index",
						RelationGetRelationName(r))));
	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is a composite type",
						RelationGetRelationName(r))));

	return r;
}

/* ----------------
 *		table_openrv - open a table relation specified
 *		by a RangeVar node
 *
 *		As above, but relation is specified by a RangeVar.
 * ----------------
 */
Relation
table_openrv(const RangeVar *relation, LOCKMODE lockmode)
{
	Relation	r;

	r = relation_openrv(relation, lockmode);

	if (r->rd_rel->relkind == RELKIND_INDEX ||
		r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is an index",
						RelationGetRelationName(r))));
	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is a composite type",
						RelationGetRelationName(r))));

	return r;
}

/* ----------------
 *		table_openrv_extended - open a table relation specified
 *		by a RangeVar node
 *
 *		As above, but optionally return NULL instead of failing for
 *		relation-not-found.
 * ----------------
 */
Relation
table_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
					  bool missing_ok)
{
	Relation	r;

	r = relation_openrv_extended(relation, lockmode, missing_ok);

	if (r)
	{
		if (r->rd_rel->relkind == RELKIND_INDEX ||
			r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is an index",
							RelationGetRelationName(r))));
		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a composite type",
							RelationGetRelationName(r))));
	}

	return r;
}

/* ----------------
 *		table_close - close a table
 *
 *		If lockmode is not "NoLock", we then release the specified lock.
 *
 *		Note that it is often sensible to hold a lock beyond relation_close;
 *		in that case, the lock is released automatically at xact end.
 *		----------------
 */
void
table_close(Relation relation, LOCKMODE lockmode)
{
	relation_close(relation, lockmode);
}



/*
 * CdbTryOpenTable -- Opens a table with a specified lock mode.
 *
 * CDB: Like try_table_open, except that it will upgrade the lock when needed
 * for distributed tables.
 *
 * Note1: Postgres will always hold RowExclusiveLock for DMLs
 * Note2: INSERT statement will not call this function.
 * Note3: This function may return NULL (eg. when just before we open the table,
 *        other transaction drop the table), caller should check it.
 *
 * Cloudberry only upgrade lock level for UPDATE and DELETE statement under some
 * condition:
 *   1. always upgrade when gp_enable_global_deadlock_detector is not set
 *   2. when gp_enable_global_deadlock_detector is set:
 *     a. if target table is AO|AOCO table, upgrade the lock level
 *     b. if target table is heap table, just like Postgres, do not upgrade
 */
Relation
CdbTryOpenTable(Oid relid, LOCKMODE reqmode, bool *lockUpgraded)
{
	LOCKMODE    lockmode;

	Relation    rel;

	/*
	 * This if-else statement will try to open the relation and
	 * save the lockmode it uses to open the relation.
	 *
	 * If we are doing expclicit UPDATE|DELETE on catalogs (this can
	 * only be possible when GUC allow_system_table_mods is set), the
	 * update or delete does not hold locks on catalog on segments, so
	 * we do not need to consider lock-upgrade for DML on catalogs.
	 *
	 * In singlenode mode, since local deadlock detection can already
	 * detect and solve deadlocks, we act as if the global deadlock
	 * detector is enabled.
	 */
	if (reqmode == RowExclusiveLock &&
		(Gp_role == GP_ROLE_DISPATCH || IS_SINGLENODE()) &&
		relid >= FirstNormalObjectId)
	{
		if (!gp_enable_global_deadlock_detector && !IS_SINGLENODE())
		{
			/*
			 * Without GDD, to avoid global deadlock, always
			 * upgrade locklevel to ExclusiveLock
			 */
			lockmode = ExclusiveLock;
			rel = try_table_open(relid, lockmode, false);
		}
		else
		{
			lockmode = RowExclusiveLock;
			rel = try_table_open(relid, lockmode, false);

			if (RelationIsValid(rel) &&
				RelationIsNonblockRelation(rel))
			{
				/*
				 * AO|AOCO table does not support concurrently
				 * update or delete on segments, so we first close
				 * the relation and reopen it using upgraded lockmode.
				 * NOTE: during this time window, there is a race that
				 * the table with relid is dropped, and will lead to
				 * returning NULL. This will not cause any problem
				 * because it is caller's duty to check NULL pointer.
				 */
				table_close(rel, RowExclusiveLock);
				lockmode = ExclusiveLock;
				rel = try_table_open(relid, lockmode, false);
			}
		}

	}
	else
	{
		lockmode = reqmode;
		rel = try_table_open(relid, lockmode, false);
	}

	if (lockUpgraded != NULL && lockmode > reqmode)
		*lockUpgraded = true;

	/* inject fault after holding the lock */
	SIMPLE_FAULT_INJECTOR("upgrade_row_lock");

	return rel;
}                                       /* CdbOpenTable */

/*
 * CdbOpenTable -- Opens a table with a specified lock mode.
 *
 * CDB: Like CdbTryOpenTable, except that it guarantees either
 * an error or a valid opened table returned.
 */
Relation
CdbOpenTable(Oid relid, LOCKMODE reqmode, bool *lockUpgraded)
{
	Relation rel;

	rel = CdbTryOpenTable(relid, reqmode, lockUpgraded);

	if (!RelationIsValid(rel))
	{
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_TABLE),
				 errmsg("table not found (OID %u)", relid),
				 errdetail("This can be validly caused by a concurrent delete operation on this object.")));
	}

	return rel;

}                                       /* CdbOpenTable */
