| /*------------------------------------------------------------------------- |
| * |
| * pg_operator.c |
| * routines to support manipulation of the pg_operator relation |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/catalog/pg_operator.c |
| * |
| * NOTES |
| * these routines moved here from commands/define.c and somewhat cleaned up. |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/htup_details.h" |
| #include "access/table.h" |
| #include "access/xact.h" |
| #include "catalog/catalog.h" |
| #include "catalog/dependency.h" |
| #include "catalog/indexing.h" |
| #include "catalog/namespace.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 "miscadmin.h" |
| #include "parser/parse_oper.h" |
| #include "utils/acl.h" |
| #include "utils/builtins.h" |
| #include "utils/lsyscache.h" |
| #include "utils/rel.h" |
| #include "utils/syscache.h" |
| |
| #include "catalog/oid_dispatch.h" |
| |
| |
| static Oid OperatorGet(const char *operatorName, |
| Oid operatorNamespace, |
| Oid leftObjectId, |
| Oid rightObjectId, |
| bool *defined); |
| |
| static Oid OperatorLookup(List *operatorName, |
| Oid leftObjectId, |
| Oid rightObjectId, |
| bool *defined); |
| |
| static Oid OperatorShellMake(const char *operatorName, |
| Oid operatorNamespace, |
| Oid leftTypeId, |
| Oid rightTypeId); |
| |
| static Oid get_other_operator(List *otherOp, |
| Oid otherLeftTypeId, Oid otherRightTypeId, |
| const char *operatorName, Oid operatorNamespace, |
| Oid leftTypeId, Oid rightTypeId, |
| bool isCommutator); |
| |
| |
| /* |
| * Check whether a proposed operator name is legal |
| * |
| * This had better match the behavior of parser/scan.l! |
| * |
| * We need this because the parser is not smart enough to check that |
| * the arguments of CREATE OPERATOR's COMMUTATOR, NEGATOR, etc clauses |
| * are operator names rather than some other lexical entity. |
| */ |
| static bool |
| validOperatorName(const char *name) |
| { |
| size_t len = strlen(name); |
| |
| /* Can't be empty or too long */ |
| if (len == 0 || len >= NAMEDATALEN) |
| return false; |
| |
| /* Can't contain any invalid characters */ |
| /* Test string here should match op_chars in scan.l */ |
| if (strspn(name, "~!@#^&|`?+-*/%<>=") != len) |
| return false; |
| |
| /* Can't contain slash-star or dash-dash (comment starts) */ |
| if (strstr(name, "/*") || strstr(name, "--")) |
| return false; |
| |
| /* |
| * For SQL standard compatibility, '+' and '-' cannot be the last char of |
| * a multi-char operator unless the operator contains chars that are not |
| * in SQL operators. The idea is to lex '=-' as two operators, but not to |
| * forbid operator names like '?-' that could not be sequences of standard |
| * SQL operators. |
| */ |
| if (len > 1 && |
| (name[len - 1] == '+' || |
| name[len - 1] == '-')) |
| { |
| int ic; |
| |
| for (ic = len - 2; ic >= 0; ic--) |
| { |
| if (strchr("~!@#^&|`?%", name[ic])) |
| break; |
| } |
| if (ic < 0) |
| return false; /* nope, not valid */ |
| } |
| |
| /* != isn't valid either, because parser will convert it to <> */ |
| if (strcmp(name, "!=") == 0) |
| return false; |
| |
| return true; |
| } |
| |
| |
| /* |
| * OperatorGet |
| * |
| * finds an operator given an exact specification (name, namespace, |
| * left and right type IDs). |
| * |
| * *defined is set true if defined (not a shell) |
| */ |
| static Oid |
| OperatorGet(const char *operatorName, |
| Oid operatorNamespace, |
| Oid leftObjectId, |
| Oid rightObjectId, |
| bool *defined) |
| { |
| HeapTuple tup; |
| Oid operatorObjectId; |
| |
| tup = SearchSysCache4(OPERNAMENSP, |
| PointerGetDatum(operatorName), |
| ObjectIdGetDatum(leftObjectId), |
| ObjectIdGetDatum(rightObjectId), |
| ObjectIdGetDatum(operatorNamespace)); |
| if (HeapTupleIsValid(tup)) |
| { |
| Form_pg_operator oprform = (Form_pg_operator) GETSTRUCT(tup); |
| |
| operatorObjectId = oprform->oid; |
| *defined = RegProcedureIsValid(oprform->oprcode); |
| ReleaseSysCache(tup); |
| } |
| else |
| { |
| operatorObjectId = InvalidOid; |
| *defined = false; |
| } |
| |
| return operatorObjectId; |
| } |
| |
| /* |
| * OperatorLookup |
| * |
| * looks up an operator given a possibly-qualified name and |
| * left and right type IDs. |
| * |
| * *defined is set true if defined (not a shell) |
| */ |
| static Oid |
| OperatorLookup(List *operatorName, |
| Oid leftObjectId, |
| Oid rightObjectId, |
| bool *defined) |
| { |
| Oid operatorObjectId; |
| RegProcedure oprcode; |
| |
| operatorObjectId = LookupOperName(NULL, operatorName, |
| leftObjectId, rightObjectId, |
| true, -1); |
| if (!OidIsValid(operatorObjectId)) |
| { |
| *defined = false; |
| return InvalidOid; |
| } |
| |
| oprcode = get_opcode(operatorObjectId); |
| *defined = RegProcedureIsValid(oprcode); |
| |
| return operatorObjectId; |
| } |
| |
| |
| /* |
| * OperatorShellMake |
| * Make a "shell" entry for a not-yet-existing operator. |
| */ |
| static Oid |
| OperatorShellMake(const char *operatorName, |
| Oid operatorNamespace, |
| Oid leftTypeId, |
| Oid rightTypeId) |
| { |
| Relation pg_operator_desc; |
| Oid operatorObjectId; |
| int i; |
| HeapTuple tup; |
| Datum values[Natts_pg_operator]; |
| bool nulls[Natts_pg_operator]; |
| NameData oname; |
| TupleDesc tupDesc; |
| |
| /* |
| * validate operator name |
| */ |
| if (!validOperatorName(operatorName)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_NAME), |
| errmsg("\"%s\" is not a valid operator name", |
| operatorName))); |
| |
| /* |
| * open pg_operator |
| */ |
| pg_operator_desc = table_open(OperatorRelationId, RowExclusiveLock); |
| tupDesc = pg_operator_desc->rd_att; |
| |
| /* |
| * initialize our *nulls and *values arrays |
| */ |
| for (i = 0; i < Natts_pg_operator; ++i) |
| { |
| nulls[i] = false; |
| values[i] = (Datum) NULL; /* redundant, but safe */ |
| } |
| |
| /* |
| * initialize values[] with the operator name and input data types. Note |
| * that oprcode is set to InvalidOid, indicating it's a shell. |
| */ |
| operatorObjectId = GetNewOidForOperator(pg_operator_desc, OperatorOidIndexId, |
| Anum_pg_operator_oid, |
| unconstify(char *, operatorName), |
| leftTypeId, rightTypeId, |
| operatorNamespace); |
| values[Anum_pg_operator_oid - 1] = ObjectIdGetDatum(operatorObjectId); |
| namestrcpy(&oname, operatorName); |
| values[Anum_pg_operator_oprname - 1] = NameGetDatum(&oname); |
| values[Anum_pg_operator_oprnamespace - 1] = ObjectIdGetDatum(operatorNamespace); |
| values[Anum_pg_operator_oprowner - 1] = ObjectIdGetDatum(GetUserId()); |
| values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? 'b' : 'l'); |
| values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(false); |
| values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(false); |
| values[Anum_pg_operator_oprleft - 1] = ObjectIdGetDatum(leftTypeId); |
| values[Anum_pg_operator_oprright - 1] = ObjectIdGetDatum(rightTypeId); |
| values[Anum_pg_operator_oprresult - 1] = ObjectIdGetDatum(InvalidOid); |
| values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(InvalidOid); |
| values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(InvalidOid); |
| values[Anum_pg_operator_oprcode - 1] = ObjectIdGetDatum(InvalidOid); |
| values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(InvalidOid); |
| values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(InvalidOid); |
| |
| /* |
| * create a new operator tuple |
| */ |
| tup = heap_form_tuple(tupDesc, values, nulls); |
| |
| /* |
| * insert our "shell" operator tuple |
| */ |
| CatalogTupleInsert(pg_operator_desc, tup); |
| |
| /* Add dependencies for the entry */ |
| makeOperatorDependencies(tup, true, false); |
| |
| heap_freetuple(tup); |
| |
| /* Post creation hook for new shell operator */ |
| InvokeObjectPostCreateHook(OperatorRelationId, operatorObjectId, 0); |
| |
| /* |
| * Make sure the tuple is visible for subsequent lookups/updates. |
| */ |
| CommandCounterIncrement(); |
| |
| /* |
| * close the operator relation and return the oid. |
| */ |
| table_close(pg_operator_desc, RowExclusiveLock); |
| |
| return operatorObjectId; |
| } |
| |
| /* |
| * OperatorCreate |
| * |
| * "X" indicates an optional argument (i.e. one that can be NULL or 0) |
| * operatorName name for new operator |
| * operatorNamespace namespace for new operator |
| * leftTypeId X left type ID |
| * rightTypeId X right type ID |
| * procedureId procedure ID for operator |
| * commutatorName X commutator operator |
| * negatorName X negator operator |
| * restrictionId X restriction selectivity procedure ID |
| * joinId X join selectivity procedure ID |
| * canMerge merge join can be used with this operator |
| * canHash hash join can be used with this operator |
| * |
| * The caller should have validated properties and permissions for the |
| * objects passed as OID references. We must handle the commutator and |
| * negator operator references specially, however, since those need not |
| * exist beforehand. |
| * |
| * This routine gets complicated because it allows the user to |
| * specify operators that do not exist. For example, if operator |
| * "op" is being defined, the negator operator "negop" and the |
| * commutator "commop" can also be defined without specifying |
| * any information other than their names. Since in order to |
| * add "op" to the PG_OPERATOR catalog, all the Oid's for these |
| * operators must be placed in the fields of "op", a forward |
| * declaration is done on the commutator and negator operators. |
| * This is called creating a shell, and its main effect is to |
| * create a tuple in the PG_OPERATOR catalog with minimal |
| * information about the operator (just its name and types). |
| * Forward declaration is used only for this purpose, it is |
| * not available to the user as it is for type definition. |
| */ |
| ObjectAddress |
| OperatorCreate(const char *operatorName, |
| Oid operatorNamespace, |
| Oid leftTypeId, |
| Oid rightTypeId, |
| Oid procedureId, |
| List *commutatorName, |
| List *negatorName, |
| Oid restrictionId, |
| Oid joinId, |
| bool canMerge, |
| bool canHash) |
| { |
| Relation pg_operator_desc; |
| HeapTuple tup; |
| bool isUpdate; |
| bool nulls[Natts_pg_operator]; |
| bool replaces[Natts_pg_operator]; |
| Datum values[Natts_pg_operator]; |
| Oid operatorObjectId; |
| bool operatorAlreadyDefined; |
| Oid operResultType; |
| Oid commutatorId, |
| negatorId; |
| bool selfCommutator = false; |
| NameData oname; |
| int i; |
| ObjectAddress address; |
| |
| /* |
| * Sanity checks |
| */ |
| if (!validOperatorName(operatorName)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_NAME), |
| errmsg("\"%s\" is not a valid operator name", |
| operatorName))); |
| |
| if (!(OidIsValid(leftTypeId) && OidIsValid(rightTypeId))) |
| { |
| /* If it's not a binary op, these things mustn't be set: */ |
| if (commutatorName) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only binary operators can have commutators"))); |
| if (OidIsValid(joinId)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only binary operators can have join selectivity"))); |
| if (canMerge) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only binary operators can merge join"))); |
| if (canHash) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only binary operators can hash"))); |
| } |
| |
| operResultType = get_func_rettype(procedureId); |
| |
| if (operResultType != BOOLOID) |
| { |
| /* If it's not a boolean op, these things mustn't be set: */ |
| if (negatorName) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only boolean operators can have negators"))); |
| if (OidIsValid(restrictionId)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only boolean operators can have restriction selectivity"))); |
| if (OidIsValid(joinId)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only boolean operators can have join selectivity"))); |
| if (canMerge) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only boolean operators can merge join"))); |
| if (canHash) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("only boolean operators can hash"))); |
| } |
| |
| operatorObjectId = OperatorGet(operatorName, |
| operatorNamespace, |
| leftTypeId, |
| rightTypeId, |
| &operatorAlreadyDefined); |
| |
| if (operatorAlreadyDefined) |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_FUNCTION), |
| errmsg("operator %s already exists", |
| operatorName))); |
| |
| /* |
| * At this point, if operatorObjectId is not InvalidOid then we are |
| * filling in a previously-created shell. Insist that the user own any |
| * such shell. |
| */ |
| if (OidIsValid(operatorObjectId) && |
| !pg_oper_ownercheck(operatorObjectId, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR, |
| operatorName); |
| |
| /* |
| * Set up the other operators. If they do not currently exist, create |
| * shells in order to get ObjectId's. |
| */ |
| |
| if (commutatorName) |
| { |
| /* commutator has reversed arg types */ |
| commutatorId = get_other_operator(commutatorName, |
| rightTypeId, leftTypeId, |
| operatorName, operatorNamespace, |
| leftTypeId, rightTypeId, |
| true); |
| |
| /* Permission check: must own other operator */ |
| if (OidIsValid(commutatorId) && |
| !pg_oper_ownercheck(commutatorId, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR, |
| NameListToString(commutatorName)); |
| |
| /* |
| * self-linkage to this operator; will fix below. Note that only |
| * self-linkage for commutation makes sense. |
| */ |
| if (!OidIsValid(commutatorId)) |
| selfCommutator = true; |
| } |
| else |
| commutatorId = InvalidOid; |
| |
| if (negatorName) |
| { |
| /* negator has same arg types */ |
| negatorId = get_other_operator(negatorName, |
| leftTypeId, rightTypeId, |
| operatorName, operatorNamespace, |
| leftTypeId, rightTypeId, |
| false); |
| |
| /* Permission check: must own other operator */ |
| if (OidIsValid(negatorId) && |
| !pg_oper_ownercheck(negatorId, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR, |
| NameListToString(negatorName)); |
| } |
| else |
| negatorId = InvalidOid; |
| |
| /* |
| * set up values in the operator tuple |
| */ |
| |
| for (i = 0; i < Natts_pg_operator; ++i) |
| { |
| values[i] = (Datum) NULL; |
| replaces[i] = true; |
| nulls[i] = false; |
| } |
| |
| namestrcpy(&oname, operatorName); |
| values[Anum_pg_operator_oprname - 1] = NameGetDatum(&oname); |
| values[Anum_pg_operator_oprnamespace - 1] = ObjectIdGetDatum(operatorNamespace); |
| values[Anum_pg_operator_oprowner - 1] = ObjectIdGetDatum(GetUserId()); |
| values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? 'b' : 'l'); |
| values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(canMerge); |
| values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(canHash); |
| values[Anum_pg_operator_oprleft - 1] = ObjectIdGetDatum(leftTypeId); |
| values[Anum_pg_operator_oprright - 1] = ObjectIdGetDatum(rightTypeId); |
| values[Anum_pg_operator_oprresult - 1] = ObjectIdGetDatum(operResultType); |
| values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(commutatorId); |
| values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(negatorId); |
| values[Anum_pg_operator_oprcode - 1] = ObjectIdGetDatum(procedureId); |
| values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(restrictionId); |
| values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(joinId); |
| |
| pg_operator_desc = table_open(OperatorRelationId, RowExclusiveLock); |
| |
| /* |
| * If we are replacing an operator shell, update; else insert |
| */ |
| if (operatorObjectId) |
| { |
| isUpdate = true; |
| |
| tup = SearchSysCacheCopy1(OPEROID, |
| ObjectIdGetDatum(operatorObjectId)); |
| if (!HeapTupleIsValid(tup)) |
| elog(ERROR, "cache lookup failed for operator %u", |
| operatorObjectId); |
| |
| replaces[Anum_pg_operator_oid - 1] = false; |
| tup = heap_modify_tuple(tup, |
| RelationGetDescr(pg_operator_desc), |
| values, |
| nulls, |
| replaces); |
| |
| CatalogTupleUpdate(pg_operator_desc, &tup->t_self, tup); |
| } |
| else |
| { |
| isUpdate = false; |
| |
| operatorObjectId = GetNewOidForOperator(pg_operator_desc, |
| OperatorOidIndexId, |
| Anum_pg_operator_oid, |
| NameStr(oname), leftTypeId, |
| rightTypeId, operatorNamespace); |
| values[Anum_pg_operator_oid - 1] = ObjectIdGetDatum(operatorObjectId); |
| |
| tup = heap_form_tuple(RelationGetDescr(pg_operator_desc), |
| values, nulls); |
| |
| CatalogTupleInsert(pg_operator_desc, tup); |
| } |
| |
| /* Add dependencies for the entry */ |
| address = makeOperatorDependencies(tup, true, isUpdate); |
| |
| /* Post creation hook for new operator */ |
| InvokeObjectPostCreateHook(OperatorRelationId, operatorObjectId, 0); |
| |
| table_close(pg_operator_desc, RowExclusiveLock); |
| |
| /* |
| * If a commutator and/or negator link is provided, update the other |
| * operator(s) to point at this one, if they don't already have a link. |
| * This supports an alternative style of operator definition wherein the |
| * user first defines one operator without giving negator or commutator, |
| * then defines the other operator of the pair with the proper commutator |
| * or negator attribute. That style doesn't require creation of a shell, |
| * and it's the only style that worked right before Postgres version 6.5. |
| * This code also takes care of the situation where the new operator is |
| * its own commutator. |
| */ |
| if (selfCommutator) |
| commutatorId = operatorObjectId; |
| |
| if (OidIsValid(commutatorId) || OidIsValid(negatorId)) |
| OperatorUpd(operatorObjectId, commutatorId, negatorId, false); |
| |
| return address; |
| } |
| |
| /* |
| * Try to lookup another operator (commutator, etc) |
| * |
| * If not found, check to see if it is exactly the operator we are trying |
| * to define; if so, return InvalidOid. (Note that this case is only |
| * sensible for a commutator, so we error out otherwise.) If it is not |
| * the same operator, create a shell operator. |
| */ |
| static Oid |
| get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId, |
| const char *operatorName, Oid operatorNamespace, |
| Oid leftTypeId, Oid rightTypeId, bool isCommutator) |
| { |
| Oid other_oid; |
| bool otherDefined; |
| char *otherName; |
| Oid otherNamespace; |
| AclResult aclresult; |
| |
| other_oid = OperatorLookup(otherOp, |
| otherLeftTypeId, |
| otherRightTypeId, |
| &otherDefined); |
| |
| if (OidIsValid(other_oid)) |
| { |
| /* other op already in catalogs */ |
| return other_oid; |
| } |
| |
| otherNamespace = QualifiedNameGetCreationNamespace(otherOp, |
| &otherName); |
| |
| if (strcmp(otherName, operatorName) == 0 && |
| otherNamespace == operatorNamespace && |
| otherLeftTypeId == leftTypeId && |
| otherRightTypeId == rightTypeId) |
| { |
| /* |
| * self-linkage to this operator; caller will fix later. Note that |
| * only self-linkage for commutation makes sense. |
| */ |
| if (!isCommutator) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| errmsg("operator cannot be its own negator or sort operator"))); |
| return InvalidOid; |
| } |
| |
| /* not in catalogs, different from operator, so make shell */ |
| |
| aclresult = pg_namespace_aclcheck(otherNamespace, GetUserId(), |
| ACL_CREATE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_SCHEMA, |
| get_namespace_name(otherNamespace)); |
| |
| other_oid = OperatorShellMake(otherName, |
| otherNamespace, |
| otherLeftTypeId, |
| otherRightTypeId); |
| return other_oid; |
| } |
| |
| /* |
| * OperatorUpd |
| * |
| * For a given operator, look up its negator and commutator operators. |
| * When isDelete is false, update their negator and commutator fields to |
| * point back to the given operator; when isDelete is true, update those |
| * fields to no longer point back to the given operator. |
| * |
| * The !isDelete case solves a problem for users who need to insert two new |
| * operators that are the negator or commutator of each other, while the |
| * isDelete case is needed so as not to leave dangling OID links behind |
| * after dropping an operator. |
| */ |
| void |
| OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete) |
| { |
| Relation pg_operator_desc; |
| HeapTuple tup; |
| |
| /* |
| * If we're making an operator into its own commutator, then we need a |
| * command-counter increment here, since we've just inserted the tuple |
| * we're about to update. But when we're dropping an operator, we can |
| * skip this because we're at the beginning of the command. |
| */ |
| if (!isDelete) |
| CommandCounterIncrement(); |
| |
| /* Open the relation. */ |
| pg_operator_desc = table_open(OperatorRelationId, RowExclusiveLock); |
| |
| /* Get a writable copy of the commutator's tuple. */ |
| if (OidIsValid(commId)) |
| tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(commId)); |
| else |
| tup = NULL; |
| |
| /* Update the commutator's tuple if need be. */ |
| if (HeapTupleIsValid(tup)) |
| { |
| Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup); |
| bool update_commutator = false; |
| |
| /* |
| * Out of due caution, we only change the commutator's oprcom field if |
| * it has the exact value we expected: InvalidOid when creating an |
| * operator, or baseId when dropping one. |
| */ |
| if (isDelete && t->oprcom == baseId) |
| { |
| t->oprcom = InvalidOid; |
| update_commutator = true; |
| } |
| else if (!isDelete && !OidIsValid(t->oprcom)) |
| { |
| t->oprcom = baseId; |
| update_commutator = true; |
| } |
| |
| /* If any columns were found to need modification, update tuple. */ |
| if (update_commutator) |
| { |
| CatalogTupleUpdate(pg_operator_desc, &tup->t_self, tup); |
| |
| /* |
| * Do CCI to make the updated tuple visible. We must do this in |
| * case the commutator is also the negator. (Which would be a |
| * logic error on the operator definer's part, but that's not a |
| * good reason to fail here.) We would need a CCI anyway in the |
| * deletion case for a self-commutator with no negator. |
| */ |
| CommandCounterIncrement(); |
| } |
| } |
| |
| /* |
| * Similarly find and update the negator, if any. |
| */ |
| if (OidIsValid(negId)) |
| tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(negId)); |
| else |
| tup = NULL; |
| |
| if (HeapTupleIsValid(tup)) |
| { |
| Form_pg_operator t = (Form_pg_operator) GETSTRUCT(tup); |
| bool update_negator = false; |
| |
| /* |
| * Out of due caution, we only change the negator's oprnegate field if |
| * it has the exact value we expected: InvalidOid when creating an |
| * operator, or baseId when dropping one. |
| */ |
| if (isDelete && t->oprnegate == baseId) |
| { |
| t->oprnegate = InvalidOid; |
| update_negator = true; |
| } |
| else if (!isDelete && !OidIsValid(t->oprnegate)) |
| { |
| t->oprnegate = baseId; |
| update_negator = true; |
| } |
| |
| /* If any columns were found to need modification, update tuple. */ |
| if (update_negator) |
| { |
| CatalogTupleUpdate(pg_operator_desc, &tup->t_self, tup); |
| |
| /* |
| * In the deletion case, do CCI to make the updated tuple visible. |
| * We must do this in case the operator is its own negator. (Which |
| * would be a logic error on the operator definer's part, but |
| * that's not a good reason to fail here.) |
| */ |
| if (isDelete) |
| CommandCounterIncrement(); |
| } |
| } |
| |
| /* Close relation and release catalog lock. */ |
| table_close(pg_operator_desc, RowExclusiveLock); |
| } |
| |
| /* |
| * Create dependencies for an operator (either a freshly inserted |
| * complete operator, a new shell operator, a just-updated shell, |
| * or an operator that's being modified by ALTER OPERATOR). |
| * |
| * makeExtensionDep should be true when making a new operator or |
| * replacing a shell, false for ALTER OPERATOR. Passing false |
| * will prevent any change in the operator's extension membership. |
| * |
| * NB: the OidIsValid tests in this routine are necessary, in case |
| * the given operator is a shell. |
| */ |
| ObjectAddress |
| makeOperatorDependencies(HeapTuple tuple, |
| bool makeExtensionDep, |
| bool isUpdate) |
| { |
| Form_pg_operator oper = (Form_pg_operator) GETSTRUCT(tuple); |
| ObjectAddress myself, |
| referenced; |
| ObjectAddresses *addrs; |
| |
| ObjectAddressSet(myself, OperatorRelationId, oper->oid); |
| |
| /* |
| * If we are updating the operator, delete any existing entries, except |
| * for extension membership which should remain the same. |
| */ |
| if (isUpdate) |
| { |
| deleteDependencyRecordsFor(myself.classId, myself.objectId, true); |
| deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0); |
| } |
| |
| addrs = new_object_addresses(); |
| |
| /* Dependency on namespace */ |
| if (OidIsValid(oper->oprnamespace)) |
| { |
| ObjectAddressSet(referenced, NamespaceRelationId, oper->oprnamespace); |
| add_exact_object_address(&referenced, addrs); |
| } |
| |
| /* Dependency on left type */ |
| if (OidIsValid(oper->oprleft)) |
| { |
| ObjectAddressSet(referenced, TypeRelationId, oper->oprleft); |
| add_exact_object_address(&referenced, addrs); |
| } |
| |
| /* Dependency on right type */ |
| if (OidIsValid(oper->oprright)) |
| { |
| ObjectAddressSet(referenced, TypeRelationId, oper->oprright); |
| add_exact_object_address(&referenced, addrs); |
| } |
| |
| /* Dependency on result type */ |
| if (OidIsValid(oper->oprresult)) |
| { |
| ObjectAddressSet(referenced, TypeRelationId, oper->oprresult); |
| add_exact_object_address(&referenced, addrs); |
| } |
| |
| /* |
| * NOTE: we do not consider the operator to depend on the associated |
| * operators oprcom and oprnegate. We would not want to delete this |
| * operator if those go away, but only reset the link fields; which is not |
| * a function that the dependency code can presently handle. (Something |
| * could perhaps be done with objectSubId though.) For now, it's okay to |
| * let those links dangle if a referenced operator is removed. |
| */ |
| |
| /* Dependency on implementation function */ |
| if (OidIsValid(oper->oprcode)) |
| { |
| ObjectAddressSet(referenced, ProcedureRelationId, oper->oprcode); |
| add_exact_object_address(&referenced, addrs); |
| } |
| |
| /* Dependency on restriction selectivity function */ |
| if (OidIsValid(oper->oprrest)) |
| { |
| ObjectAddressSet(referenced, ProcedureRelationId, oper->oprrest); |
| add_exact_object_address(&referenced, addrs); |
| } |
| |
| /* Dependency on join selectivity function */ |
| if (OidIsValid(oper->oprjoin)) |
| { |
| ObjectAddressSet(referenced, ProcedureRelationId, oper->oprjoin); |
| add_exact_object_address(&referenced, addrs); |
| } |
| |
| record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); |
| free_object_addresses(addrs); |
| |
| /* Dependency on owner */ |
| recordDependencyOnOwner(OperatorRelationId, oper->oid, |
| oper->oprowner); |
| |
| /* Dependency on extension */ |
| if (makeExtensionDep) |
| recordDependencyOnCurrentExtension(&myself, isUpdate); |
| |
| return myself; |
| } |