/*-------------------------------------------------------------------------
 *
 * operatorcmds.c
 *
 *	  Routines for operator manipulation commands
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/operatorcmds.c
 *
 * DESCRIPTION
 *	  The "DefineFoo" routines take the parse tree and pick out the
 *	  appropriate arguments/flags, passing the results to the
 *	  corresponding "FooCreate" routines (in src/backend/catalog) that do
 *	  the actual catalog-munging.  These routines also verify permission
 *	  of the user to execute the command.
 *
 * NOTES
 *	  These things must be defined and committed in the following order:
 *		"create function":
 *				input/output, recv/send functions
 *		"create type":
 *				type
 *		"create operator":
 *				operators
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/table.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"

static Oid	ValidateRestrictionEstimator(List *restrictionName);
static Oid	ValidateJoinEstimator(List *joinName);
static Oid	ValidateOperatorReference(List *name,
									  Oid leftTypeId,
									  Oid rightTypeId);

/*
 * DefineOperator
 *		this function extracts all the information from the
 *		parameter list generated by the parser and then has
 *		OperatorCreate() do all the actual work.
 *
 * 'parameters' is a list of DefElem
 */
ObjectAddress
DefineOperator(List *names, List *parameters)
{
	char	   *oprName;
	Oid			oprNamespace;
	AclResult	aclresult;
	bool		canMerge = false;	/* operator merges */
	bool		canHash = false;	/* operator hashes */
	List	   *functionName = NIL; /* function for operator */
	TypeName   *typeName1 = NULL;	/* first type name */
	TypeName   *typeName2 = NULL;	/* second type name */
	Oid			typeId1 = InvalidOid;	/* types converted to OID */
	Oid			typeId2 = InvalidOid;
	Oid			rettype;
	List	   *commutatorName = NIL;	/* optional commutator operator name */
	List	   *negatorName = NIL;	/* optional negator operator name */
	List	   *restrictionName = NIL;	/* optional restrict. sel. function */
	List	   *joinName = NIL; /* optional join sel. function */
	Oid			functionOid;	/* functions converted to OID */
	Oid			restrictionOid;
	Oid			joinOid;
	Oid			typeId[2];		/* to hold left and right arg */
	int			nargs;
	ListCell   *pl;

	/* Convert list of names to a name and namespace */
	oprNamespace = QualifiedNameGetCreationNamespace(names, &oprName);

	/* Check we have creation rights in target namespace */
	aclresult = object_aclcheck(NamespaceRelationId, oprNamespace, GetUserId(), ACL_CREATE);
	if (aclresult != ACLCHECK_OK)
		aclcheck_error(aclresult, OBJECT_SCHEMA,
					   get_namespace_name(oprNamespace));

	/*
	 * loop over the definition list and extract the information we need.
	 */
	foreach(pl, parameters)
	{
		DefElem    *defel = (DefElem *) lfirst(pl);

		if (strcmp(defel->defname, "leftarg") == 0)
		{
			typeName1 = defGetTypeName(defel);
			if (typeName1->setof)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("SETOF type not allowed for operator argument")));
		}
		else if (strcmp(defel->defname, "rightarg") == 0)
		{
			typeName2 = defGetTypeName(defel);
			if (typeName2->setof)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("SETOF type not allowed for operator argument")));
		}
		/* "function" and "procedure" are equivalent here */
		else if (strcmp(defel->defname, "function") == 0)
			functionName = defGetQualifiedName(defel);
		else if (strcmp(defel->defname, "procedure") == 0)
			functionName = defGetQualifiedName(defel);
		else if (strcmp(defel->defname, "commutator") == 0)
			commutatorName = defGetQualifiedName(defel);
		else if (strcmp(defel->defname, "negator") == 0)
			negatorName = defGetQualifiedName(defel);
		else if (strcmp(defel->defname, "restrict") == 0)
			restrictionName = defGetQualifiedName(defel);
		else if (strcmp(defel->defname, "join") == 0)
			joinName = defGetQualifiedName(defel);
		else if (strcmp(defel->defname, "hashes") == 0)
			canHash = defGetBoolean(defel);
		else if (strcmp(defel->defname, "merges") == 0)
			canMerge = defGetBoolean(defel);
		/* These obsolete options are taken as meaning canMerge */
		else if (strcmp(defel->defname, "sort1") == 0)
			canMerge = true;
		else if (strcmp(defel->defname, "sort2") == 0)
			canMerge = true;
		else if (strcmp(defel->defname, "ltcmp") == 0)
			canMerge = true;
		else if (strcmp(defel->defname, "gtcmp") == 0)
			canMerge = true;
		else
		{
			/* WARNING, not ERROR, for historical backwards-compatibility */
			ereport(WARNING,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("operator attribute \"%s\" not recognized",
							defel->defname)));
		}
	}

	/*
	 * make sure we have our required definitions
	 */
	if (functionName == NIL)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("operator function must be specified")));

	/* Transform type names to type OIDs */
	if (typeName1)
		typeId1 = typenameTypeId(NULL, typeName1);
	if (typeName2)
		typeId2 = typenameTypeId(NULL, typeName2);

	/*
	 * If only the right argument is missing, the user is likely trying to
	 * create a postfix operator, so give them a hint about why that does not
	 * work.  But if both arguments are missing, do not mention postfix
	 * operators, as the user most likely simply neglected to mention the
	 * arguments.
	 */
	if (!OidIsValid(typeId1) && !OidIsValid(typeId2))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("operator argument types must be specified")));
	if (!OidIsValid(typeId2))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("operator right argument type must be specified"),
				 errdetail("Postfix operators are not supported.")));

	if (typeName1)
	{
		aclresult = object_aclcheck(TypeRelationId, typeId1, GetUserId(), ACL_USAGE);
		if (aclresult != ACLCHECK_OK)
			aclcheck_error_type(aclresult, typeId1);
	}

	if (typeName2)
	{
		aclresult = object_aclcheck(TypeRelationId, typeId2, GetUserId(), ACL_USAGE);
		if (aclresult != ACLCHECK_OK)
			aclcheck_error_type(aclresult, typeId2);
	}

	/*
	 * Look up the operator's underlying function.
	 */
	if (!OidIsValid(typeId1))
	{
		typeId[0] = typeId2;
		nargs = 1;
	}
	else if (!OidIsValid(typeId2))
	{
		typeId[0] = typeId1;
		nargs = 1;
	}
	else
	{
		typeId[0] = typeId1;
		typeId[1] = typeId2;
		nargs = 2;
	}
	functionOid = LookupFuncName(functionName, nargs, typeId, false);

	/*
	 * We require EXECUTE rights for the function.  This isn't strictly
	 * necessary, since EXECUTE will be checked at any attempted use of the
	 * operator, but it seems like a good idea anyway.
	 */
	aclresult = object_aclcheck(ProcedureRelationId, functionOid, GetUserId(), ACL_EXECUTE);
	if (aclresult != ACLCHECK_OK)
		aclcheck_error(aclresult, OBJECT_FUNCTION,
					   NameListToString(functionName));

	rettype = get_func_rettype(functionOid);
	aclresult = object_aclcheck(TypeRelationId, rettype, GetUserId(), ACL_USAGE);
	if (aclresult != ACLCHECK_OK)
		aclcheck_error_type(aclresult, rettype);

	/*
	 * Look up restriction and join estimators if specified
	 */
	if (restrictionName)
		restrictionOid = ValidateRestrictionEstimator(restrictionName);
	else
		restrictionOid = InvalidOid;
	if (joinName)
		joinOid = ValidateJoinEstimator(joinName);
	else
		joinOid = InvalidOid;

	/*
	 * now have OperatorCreate do all the work..
	 */
	return
		OperatorCreate(oprName, /* operator name */
					   oprNamespace,	/* namespace */
					   typeId1, /* left type id */
					   typeId2, /* right type id */
					   functionOid, /* function for operator */
					   commutatorName,	/* optional commutator operator name */
					   negatorName, /* optional negator operator name */
					   restrictionOid,	/* optional restrict. sel. function */
					   joinOid, /* optional join sel. function name */
					   canMerge,	/* operator merges */
					   canHash);	/* operator hashes */
}

/*
 * Look up a restriction estimator function by name, and verify that it has
 * the correct signature and we have the permissions to attach it to an
 * operator.
 */
static Oid
ValidateRestrictionEstimator(List *restrictionName)
{
	Oid			typeId[4];
	Oid			restrictionOid;

	typeId[0] = INTERNALOID;	/* PlannerInfo */
	typeId[1] = OIDOID;			/* operator OID */
	typeId[2] = INTERNALOID;	/* args list */
	typeId[3] = INT4OID;		/* varRelid */

	restrictionOid = LookupFuncName(restrictionName, 4, typeId, false);

	/* estimators must return float8 */
	if (get_func_rettype(restrictionOid) != FLOAT8OID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("restriction estimator function %s must return type %s",
						NameListToString(restrictionName), "float8")));

	/*
	 * If the estimator is not a built-in function, require superuser
	 * privilege to install it.  This protects against using something that is
	 * not a restriction estimator or has hard-wired assumptions about what
	 * data types it is working with.  (Built-in estimators are required to
	 * defend themselves adequately against unexpected data type choices, but
	 * it seems impractical to expect that of extensions' estimators.)
	 *
	 * If it is built-in, only require EXECUTE rights.
	 */
	if (restrictionOid >= FirstGenbkiObjectId)
	{
		if (!superuser())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("must be superuser to specify a non-built-in restriction estimator function")));
	}
	else
	{
		AclResult	aclresult;

		aclresult = object_aclcheck(ProcedureRelationId, restrictionOid,
									GetUserId(), ACL_EXECUTE);
		if (aclresult != ACLCHECK_OK)
			aclcheck_error(aclresult, OBJECT_FUNCTION,
						   NameListToString(restrictionName));
	}

	return restrictionOid;
}

/*
 * Look up a join estimator function by name, and verify that it has the
 * correct signature and we have the permissions to attach it to an
 * operator.
 */
static Oid
ValidateJoinEstimator(List *joinName)
{
	Oid			typeId[5];
	Oid			joinOid;
	Oid			joinOid2;

	typeId[0] = INTERNALOID;	/* PlannerInfo */
	typeId[1] = OIDOID;			/* operator OID */
	typeId[2] = INTERNALOID;	/* args list */
	typeId[3] = INT2OID;		/* jointype */
	typeId[4] = INTERNALOID;	/* SpecialJoinInfo */

	/*
	 * As of Postgres 8.4, the preferred signature for join estimators has 5
	 * arguments, but we still allow the old 4-argument form.  Whine about
	 * ambiguity if both forms exist.
	 */
	joinOid = LookupFuncName(joinName, 5, typeId, true);
	joinOid2 = LookupFuncName(joinName, 4, typeId, true);
	if (OidIsValid(joinOid))
	{
		if (OidIsValid(joinOid2))
			ereport(ERROR,
					(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
					 errmsg("join estimator function %s has multiple matches",
							NameListToString(joinName))));
	}
	else
	{
		joinOid = joinOid2;
		/* If not found, reference the 5-argument signature in error msg */
		if (!OidIsValid(joinOid))
			joinOid = LookupFuncName(joinName, 5, typeId, false);
	}

	/* estimators must return float8 */
	if (get_func_rettype(joinOid) != FLOAT8OID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("join estimator function %s must return type %s",
						NameListToString(joinName), "float8")));

	/* privilege checks are the same as in ValidateRestrictionEstimator */
	if (joinOid >= FirstGenbkiObjectId)
	{
		if (!superuser())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("must be superuser to specify a non-built-in join estimator function")));
	}
	else
	{
		AclResult	aclresult;

		aclresult = object_aclcheck(ProcedureRelationId, joinOid,
									GetUserId(), ACL_EXECUTE);
		if (aclresult != ACLCHECK_OK)
			aclcheck_error(aclresult, OBJECT_FUNCTION,
						   NameListToString(joinName));
	}

	return joinOid;
}

/*
 * Look up and return the OID of an operator,
 * given a possibly-qualified name and left and right type IDs.
 *
 * Verifies that the operator is defined (not a shell) and owned by
 * the current user, so that we have permission to associate it with
 * the operator being altered.  Rejecting shell operators is a policy
 * choice to help catch mistakes, rather than something essential.
 */
static Oid
ValidateOperatorReference(List *name,
						  Oid leftTypeId,
						  Oid rightTypeId)
{
	Oid			oid;
	bool		defined;

	oid = OperatorLookup(name,
						 leftTypeId,
						 rightTypeId,
						 &defined);

	/* These message strings are chosen to match parse_oper.c */
	if (!OidIsValid(oid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("operator does not exist: %s",
						op_signature_string(name,
											leftTypeId,
											rightTypeId))));

	if (!defined)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("operator is only a shell: %s",
						op_signature_string(name,
											leftTypeId,
											rightTypeId))));

	if (!object_ownercheck(OperatorRelationId, oid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
					   NameListToString(name));

	return oid;
}


/*
 * Guts of operator deletion.
 */
void
RemoveOperatorById(Oid operOid)
{
	Relation	relation;
	HeapTuple	tup;
	Form_pg_operator op;

	relation = table_open(OperatorRelationId, RowExclusiveLock);

	tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid));
	if (!HeapTupleIsValid(tup)) /* should not happen */
		elog(ERROR, "cache lookup failed for operator %u", operOid);
	op = (Form_pg_operator) GETSTRUCT(tup);

	/*
	 * Reset links from commutator and negator, if any.  In case of a
	 * self-commutator or self-negator, this means we have to re-fetch the
	 * updated tuple.  (We could optimize away updates on the tuple we're
	 * about to drop, but it doesn't seem worth convoluting the logic for.)
	 */
	if (OidIsValid(op->oprcom) || OidIsValid(op->oprnegate))
	{
		OperatorUpd(operOid, op->oprcom, op->oprnegate, true);
		if (operOid == op->oprcom || operOid == op->oprnegate)
		{
			ReleaseSysCache(tup);
			tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid));
			if (!HeapTupleIsValid(tup)) /* should not happen */
				elog(ERROR, "cache lookup failed for operator %u", operOid);
		}
	}

	CatalogTupleDelete(relation, &tup->t_self);

	ReleaseSysCache(tup);

	table_close(relation, RowExclusiveLock);
}

/*
 * AlterOperator
 *		routine implementing ALTER OPERATOR <operator> SET (option = ...).
 *
 * Currently, only RESTRICT and JOIN estimator functions can be changed.
 * COMMUTATOR, NEGATOR, MERGES, and HASHES attributes can be set if they
 * have not been set previously.  (Changing or removing one of these
 * attributes could invalidate existing plans, which seems more trouble
 * than it's worth.)
 */
ObjectAddress
AlterOperator(AlterOperatorStmt *stmt)
{
	ObjectAddress address;
	Oid			oprId;
	Relation	catalog;
	HeapTuple	tup;
	Form_pg_operator oprForm;
	int			i;
	ListCell   *pl;
	Datum		values[Natts_pg_operator];
	bool		nulls[Natts_pg_operator];
	bool		replaces[Natts_pg_operator];
	List	   *restrictionName = NIL;	/* optional restrict. sel. function */
	bool		updateRestriction = false;
	Oid			restrictionOid;
	List	   *joinName = NIL; /* optional join sel. function */
	bool		updateJoin = false;
	Oid			joinOid;
	List	   *commutatorName = NIL;	/* optional commutator operator name */
	Oid			commutatorOid;
	List	   *negatorName = NIL;	/* optional negator operator name */
	Oid			negatorOid;
	bool		canMerge = false;
	bool		updateMerges = false;
	bool		canHash = false;
	bool		updateHashes = false;

	/* Look up the operator */
	oprId = LookupOperWithArgs(stmt->opername, false);
	catalog = table_open(OperatorRelationId, RowExclusiveLock);
	tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(oprId));
	if (!HeapTupleIsValid(tup))
		elog(ERROR, "cache lookup failed for operator %u", oprId);
	oprForm = (Form_pg_operator) GETSTRUCT(tup);

	/* Process options */
	foreach(pl, stmt->options)
	{
		DefElem    *defel = (DefElem *) lfirst(pl);
		List	   *param;

		if (defel->arg == NULL)
			param = NIL;		/* NONE, removes the function */
		else
			param = defGetQualifiedName(defel);

		if (strcmp(defel->defname, "restrict") == 0)
		{
			restrictionName = param;
			updateRestriction = true;
		}
		else if (strcmp(defel->defname, "join") == 0)
		{
			joinName = param;
			updateJoin = true;
		}
		else if (strcmp(defel->defname, "commutator") == 0)
		{
			commutatorName = defGetQualifiedName(defel);
		}
		else if (strcmp(defel->defname, "negator") == 0)
		{
			negatorName = defGetQualifiedName(defel);
		}
		else if (strcmp(defel->defname, "merges") == 0)
		{
			canMerge = defGetBoolean(defel);
			updateMerges = true;
		}
		else if (strcmp(defel->defname, "hashes") == 0)
		{
			canHash = defGetBoolean(defel);
			updateHashes = true;
		}

		/*
		 * The rest of the options that CREATE accepts cannot be changed.
		 * Check for them so that we can give a meaningful error message.
		 */
		else if (strcmp(defel->defname, "leftarg") == 0 ||
				 strcmp(defel->defname, "rightarg") == 0 ||
				 strcmp(defel->defname, "function") == 0 ||
				 strcmp(defel->defname, "procedure") == 0)
		{
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("operator attribute \"%s\" cannot be changed",
							defel->defname)));
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("operator attribute \"%s\" not recognized",
							defel->defname)));
	}

	/* Check permissions. Must be owner. */
	if (!object_ownercheck(OperatorRelationId, oprId, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
					   NameStr(oprForm->oprname));

	/*
	 * Look up OIDs for any parameters specified
	 */
	if (restrictionName)
		restrictionOid = ValidateRestrictionEstimator(restrictionName);
	else
		restrictionOid = InvalidOid;
	if (joinName)
		joinOid = ValidateJoinEstimator(joinName);
	else
		joinOid = InvalidOid;

	if (commutatorName)
	{
		/* commutator has reversed arg types */
		commutatorOid = ValidateOperatorReference(commutatorName,
												  oprForm->oprright,
												  oprForm->oprleft);

		/*
		 * We don't need to do anything extra for a self commutator as in
		 * OperatorCreate, since the operator surely exists already.
		 */
	}
	else
		commutatorOid = InvalidOid;

	if (negatorName)
	{
		negatorOid = ValidateOperatorReference(negatorName,
											   oprForm->oprleft,
											   oprForm->oprright);

		/* Must reject self-negation */
		if (negatorOid == oprForm->oid)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
					 errmsg("operator cannot be its own negator")));
	}
	else
	{
		negatorOid = InvalidOid;
	}

	/*
	 * Check that we're not changing any attributes that might be depended on
	 * by plans, while allowing no-op updates.
	 */
	if (OidIsValid(commutatorOid) && OidIsValid(oprForm->oprcom) &&
		commutatorOid != oprForm->oprcom)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("operator attribute \"%s\" cannot be changed if it has already been set",
						"commutator")));

	if (OidIsValid(negatorOid) && OidIsValid(oprForm->oprnegate) &&
		negatorOid != oprForm->oprnegate)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("operator attribute \"%s\" cannot be changed if it has already been set",
						"negator")));

	if (updateMerges && oprForm->oprcanmerge && !canMerge)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("operator attribute \"%s\" cannot be changed if it has already been set",
						"merges")));

	if (updateHashes && oprForm->oprcanhash && !canHash)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("operator attribute \"%s\" cannot be changed if it has already been set",
						"hashes")));

	/* Perform additional checks, like OperatorCreate does */
	OperatorValidateParams(oprForm->oprleft,
						   oprForm->oprright,
						   oprForm->oprresult,
						   OidIsValid(commutatorOid),
						   OidIsValid(negatorOid),
						   OidIsValid(restrictionOid),
						   OidIsValid(joinOid),
						   canMerge,
						   canHash);

	/* Update the tuple */
	for (i = 0; i < Natts_pg_operator; ++i)
	{
		values[i] = (Datum) 0;
		replaces[i] = false;
		nulls[i] = false;
	}
	if (updateRestriction)
	{
		replaces[Anum_pg_operator_oprrest - 1] = true;
		values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(restrictionOid);
	}
	if (updateJoin)
	{
		replaces[Anum_pg_operator_oprjoin - 1] = true;
		values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(joinOid);
	}
	if (OidIsValid(commutatorOid))
	{
		replaces[Anum_pg_operator_oprcom - 1] = true;
		values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(commutatorOid);
	}
	if (OidIsValid(negatorOid))
	{
		replaces[Anum_pg_operator_oprnegate - 1] = true;
		values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(negatorOid);
	}
	if (updateMerges)
	{
		replaces[Anum_pg_operator_oprcanmerge - 1] = true;
		values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(canMerge);
	}
	if (updateHashes)
	{
		replaces[Anum_pg_operator_oprcanhash - 1] = true;
		values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(canHash);
	}

	tup = heap_modify_tuple(tup, RelationGetDescr(catalog),
							values, nulls, replaces);

	CatalogTupleUpdate(catalog, &tup->t_self, tup);

	address = makeOperatorDependencies(tup, false, true);

	if (OidIsValid(commutatorOid) || OidIsValid(negatorOid))
		OperatorUpd(oprId, commutatorOid, negatorOid, false);

	InvokeObjectPostAlterHook(OperatorRelationId, oprId, 0);

	table_close(catalog, NoLock);

	return address;
}
