mirror of
https://github.com/postgres/postgres.git
synced 2025-06-14 18:42:34 +03:00
Allow the COMMUTATOR, NEGATOR, MERGES, and HASHES attributes to be set by ALTER OPERATOR. However, we don't allow COMMUTATOR/NEGATOR to be changed once set, nor allow the MERGES/HASHES flags to be unset once set. Changes like that might invalidate plans already made, and dealing with the consequences seems like more trouble than it's worth. The main use-case we foresee for this is to allow addition of missed properties in extension update scripts, such as extending an existing operator to support hashing. So only transitions from not-set to set states seem very useful. This patch also causes us to reject some incorrect cases that formerly resulted in inconsistent catalog state, such as trying to set the commutator of an operator to be some other operator that already has a (different) commutator. While at it, move the InvokeObjectPostCreateHook call for CREATE OPERATOR to not occur until after we've fixed up commutator or negator links as needed. The previous ordering could only be justified by thinking of the OperatorUpd call as a kind of ALTER OPERATOR step; but we don't call InvokeObjectPostAlterHook therein. It seems better to let the hook see the final state of the operator object. In the documentation, move the discussion of how to establish commutator pairs from xoper.sgml to the CREATE OPERATOR ref page. Tommy Pavlicek, reviewed and editorialized a bit by me Discussion: https://postgr.es/m/CAEhP-W-vGVzf4udhR5M8Bdv88UYnPrhoSkj3ieR3QNrsGQoqdg@mail.gmail.com
705 lines
21 KiB
C
705 lines
21 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* operatorcmds.c
|
|
*
|
|
* Routines for operator manipulation commands
|
|
*
|
|
* Portions Copyright (c) 1996-2023, 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 "FooDefine" routines (in src/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/dependency.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/alter.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/builtins.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;
|
|
AclResult aclresult;
|
|
|
|
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")));
|
|
|
|
/* Require EXECUTE rights for the estimator */
|
|
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;
|
|
AclResult aclresult;
|
|
|
|
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")));
|
|
|
|
/* Require EXECUTE rights for the estimator */
|
|
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;
|
|
}
|