| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * operatorcmds.c |
| 4 | * |
| 5 | * Routines for operator manipulation commands |
| 6 | * |
| 7 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 8 | * Portions Copyright (c) 1994, Regents of the University of California |
| 9 | * |
| 10 | * |
| 11 | * IDENTIFICATION |
| 12 | * src/backend/commands/operatorcmds.c |
| 13 | * |
| 14 | * DESCRIPTION |
| 15 | * The "DefineFoo" routines take the parse tree and pick out the |
| 16 | * appropriate arguments/flags, passing the results to the |
| 17 | * corresponding "FooDefine" routines (in src/catalog) that do |
| 18 | * the actual catalog-munging. These routines also verify permission |
| 19 | * of the user to execute the command. |
| 20 | * |
| 21 | * NOTES |
| 22 | * These things must be defined and committed in the following order: |
| 23 | * "create function": |
| 24 | * input/output, recv/send functions |
| 25 | * "create type": |
| 26 | * type |
| 27 | * "create operator": |
| 28 | * operators |
| 29 | * |
| 30 | * Most of the parse-tree manipulation routines are defined in |
| 31 | * commands/manip.c. |
| 32 | * |
| 33 | *------------------------------------------------------------------------- |
| 34 | */ |
| 35 | #include "postgres.h" |
| 36 | |
| 37 | #include "access/htup_details.h" |
| 38 | #include "access/table.h" |
| 39 | #include "catalog/dependency.h" |
| 40 | #include "catalog/indexing.h" |
| 41 | #include "catalog/objectaccess.h" |
| 42 | #include "catalog/pg_operator.h" |
| 43 | #include "catalog/pg_type.h" |
| 44 | #include "commands/alter.h" |
| 45 | #include "commands/defrem.h" |
| 46 | #include "miscadmin.h" |
| 47 | #include "parser/parse_func.h" |
| 48 | #include "parser/parse_oper.h" |
| 49 | #include "parser/parse_type.h" |
| 50 | #include "utils/builtins.h" |
| 51 | #include "utils/lsyscache.h" |
| 52 | #include "utils/rel.h" |
| 53 | #include "utils/syscache.h" |
| 54 | |
| 55 | static Oid ValidateRestrictionEstimator(List *restrictionName); |
| 56 | static Oid ValidateJoinEstimator(List *joinName); |
| 57 | |
| 58 | /* |
| 59 | * DefineOperator |
| 60 | * this function extracts all the information from the |
| 61 | * parameter list generated by the parser and then has |
| 62 | * OperatorCreate() do all the actual work. |
| 63 | * |
| 64 | * 'parameters' is a list of DefElem |
| 65 | */ |
| 66 | ObjectAddress |
| 67 | DefineOperator(List *names, List *parameters) |
| 68 | { |
| 69 | char *oprName; |
| 70 | Oid oprNamespace; |
| 71 | AclResult aclresult; |
| 72 | bool canMerge = false; /* operator merges */ |
| 73 | bool canHash = false; /* operator hashes */ |
| 74 | List *functionName = NIL; /* function for operator */ |
| 75 | TypeName *typeName1 = NULL; /* first type name */ |
| 76 | TypeName *typeName2 = NULL; /* second type name */ |
| 77 | Oid typeId1 = InvalidOid; /* types converted to OID */ |
| 78 | Oid typeId2 = InvalidOid; |
| 79 | Oid rettype; |
| 80 | List *commutatorName = NIL; /* optional commutator operator name */ |
| 81 | List *negatorName = NIL; /* optional negator operator name */ |
| 82 | List *restrictionName = NIL; /* optional restrict. sel. function */ |
| 83 | List *joinName = NIL; /* optional join sel. function */ |
| 84 | Oid functionOid; /* functions converted to OID */ |
| 85 | Oid restrictionOid; |
| 86 | Oid joinOid; |
| 87 | Oid typeId[2]; /* to hold left and right arg */ |
| 88 | int nargs; |
| 89 | ListCell *pl; |
| 90 | |
| 91 | /* Convert list of names to a name and namespace */ |
| 92 | oprNamespace = QualifiedNameGetCreationNamespace(names, &oprName); |
| 93 | |
| 94 | /* Check we have creation rights in target namespace */ |
| 95 | aclresult = pg_namespace_aclcheck(oprNamespace, GetUserId(), ACL_CREATE); |
| 96 | if (aclresult != ACLCHECK_OK) |
| 97 | aclcheck_error(aclresult, OBJECT_SCHEMA, |
| 98 | get_namespace_name(oprNamespace)); |
| 99 | |
| 100 | /* |
| 101 | * loop over the definition list and extract the information we need. |
| 102 | */ |
| 103 | foreach(pl, parameters) |
| 104 | { |
| 105 | DefElem *defel = (DefElem *) lfirst(pl); |
| 106 | |
| 107 | if (strcmp(defel->defname, "leftarg" ) == 0) |
| 108 | { |
| 109 | typeName1 = defGetTypeName(defel); |
| 110 | if (typeName1->setof) |
| 111 | ereport(ERROR, |
| 112 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| 113 | errmsg("SETOF type not allowed for operator argument" ))); |
| 114 | } |
| 115 | else if (strcmp(defel->defname, "rightarg" ) == 0) |
| 116 | { |
| 117 | typeName2 = defGetTypeName(defel); |
| 118 | if (typeName2->setof) |
| 119 | ereport(ERROR, |
| 120 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| 121 | errmsg("SETOF type not allowed for operator argument" ))); |
| 122 | } |
| 123 | /* "function" and "procedure" are equivalent here */ |
| 124 | else if (strcmp(defel->defname, "function" ) == 0) |
| 125 | functionName = defGetQualifiedName(defel); |
| 126 | else if (strcmp(defel->defname, "procedure" ) == 0) |
| 127 | functionName = defGetQualifiedName(defel); |
| 128 | else if (strcmp(defel->defname, "commutator" ) == 0) |
| 129 | commutatorName = defGetQualifiedName(defel); |
| 130 | else if (strcmp(defel->defname, "negator" ) == 0) |
| 131 | negatorName = defGetQualifiedName(defel); |
| 132 | else if (strcmp(defel->defname, "restrict" ) == 0) |
| 133 | restrictionName = defGetQualifiedName(defel); |
| 134 | else if (strcmp(defel->defname, "join" ) == 0) |
| 135 | joinName = defGetQualifiedName(defel); |
| 136 | else if (strcmp(defel->defname, "hashes" ) == 0) |
| 137 | canHash = defGetBoolean(defel); |
| 138 | else if (strcmp(defel->defname, "merges" ) == 0) |
| 139 | canMerge = defGetBoolean(defel); |
| 140 | /* These obsolete options are taken as meaning canMerge */ |
| 141 | else if (strcmp(defel->defname, "sort1" ) == 0) |
| 142 | canMerge = true; |
| 143 | else if (strcmp(defel->defname, "sort2" ) == 0) |
| 144 | canMerge = true; |
| 145 | else if (strcmp(defel->defname, "ltcmp" ) == 0) |
| 146 | canMerge = true; |
| 147 | else if (strcmp(defel->defname, "gtcmp" ) == 0) |
| 148 | canMerge = true; |
| 149 | else |
| 150 | { |
| 151 | /* WARNING, not ERROR, for historical backwards-compatibility */ |
| 152 | ereport(WARNING, |
| 153 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 154 | errmsg("operator attribute \"%s\" not recognized" , |
| 155 | defel->defname))); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /* |
| 160 | * make sure we have our required definitions |
| 161 | */ |
| 162 | if (functionName == NIL) |
| 163 | ereport(ERROR, |
| 164 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| 165 | errmsg("operator function must be specified" ))); |
| 166 | |
| 167 | /* Transform type names to type OIDs */ |
| 168 | if (typeName1) |
| 169 | typeId1 = typenameTypeId(NULL, typeName1); |
| 170 | if (typeName2) |
| 171 | typeId2 = typenameTypeId(NULL, typeName2); |
| 172 | |
| 173 | if (!OidIsValid(typeId1) && !OidIsValid(typeId2)) |
| 174 | ereport(ERROR, |
| 175 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| 176 | errmsg("at least one of leftarg or rightarg must be specified" ))); |
| 177 | |
| 178 | if (typeName1) |
| 179 | { |
| 180 | aclresult = pg_type_aclcheck(typeId1, GetUserId(), ACL_USAGE); |
| 181 | if (aclresult != ACLCHECK_OK) |
| 182 | aclcheck_error_type(aclresult, typeId1); |
| 183 | } |
| 184 | |
| 185 | if (typeName2) |
| 186 | { |
| 187 | aclresult = pg_type_aclcheck(typeId2, GetUserId(), ACL_USAGE); |
| 188 | if (aclresult != ACLCHECK_OK) |
| 189 | aclcheck_error_type(aclresult, typeId2); |
| 190 | } |
| 191 | |
| 192 | /* |
| 193 | * Look up the operator's underlying function. |
| 194 | */ |
| 195 | if (!OidIsValid(typeId1)) |
| 196 | { |
| 197 | typeId[0] = typeId2; |
| 198 | nargs = 1; |
| 199 | } |
| 200 | else if (!OidIsValid(typeId2)) |
| 201 | { |
| 202 | typeId[0] = typeId1; |
| 203 | nargs = 1; |
| 204 | } |
| 205 | else |
| 206 | { |
| 207 | typeId[0] = typeId1; |
| 208 | typeId[1] = typeId2; |
| 209 | nargs = 2; |
| 210 | } |
| 211 | functionOid = LookupFuncName(functionName, nargs, typeId, false); |
| 212 | |
| 213 | /* |
| 214 | * We require EXECUTE rights for the function. This isn't strictly |
| 215 | * necessary, since EXECUTE will be checked at any attempted use of the |
| 216 | * operator, but it seems like a good idea anyway. |
| 217 | */ |
| 218 | aclresult = pg_proc_aclcheck(functionOid, GetUserId(), ACL_EXECUTE); |
| 219 | if (aclresult != ACLCHECK_OK) |
| 220 | aclcheck_error(aclresult, OBJECT_FUNCTION, |
| 221 | NameListToString(functionName)); |
| 222 | |
| 223 | rettype = get_func_rettype(functionOid); |
| 224 | aclresult = pg_type_aclcheck(rettype, GetUserId(), ACL_USAGE); |
| 225 | if (aclresult != ACLCHECK_OK) |
| 226 | aclcheck_error_type(aclresult, rettype); |
| 227 | |
| 228 | /* |
| 229 | * Look up restriction and join estimators if specified |
| 230 | */ |
| 231 | if (restrictionName) |
| 232 | restrictionOid = ValidateRestrictionEstimator(restrictionName); |
| 233 | else |
| 234 | restrictionOid = InvalidOid; |
| 235 | if (joinName) |
| 236 | joinOid = ValidateJoinEstimator(joinName); |
| 237 | else |
| 238 | joinOid = InvalidOid; |
| 239 | |
| 240 | /* |
| 241 | * now have OperatorCreate do all the work.. |
| 242 | */ |
| 243 | return |
| 244 | OperatorCreate(oprName, /* operator name */ |
| 245 | oprNamespace, /* namespace */ |
| 246 | typeId1, /* left type id */ |
| 247 | typeId2, /* right type id */ |
| 248 | functionOid, /* function for operator */ |
| 249 | commutatorName, /* optional commutator operator name */ |
| 250 | negatorName, /* optional negator operator name */ |
| 251 | restrictionOid, /* optional restrict. sel. function */ |
| 252 | joinOid, /* optional join sel. function name */ |
| 253 | canMerge, /* operator merges */ |
| 254 | canHash); /* operator hashes */ |
| 255 | } |
| 256 | |
| 257 | /* |
| 258 | * Look up a restriction estimator function ny name, and verify that it has |
| 259 | * the correct signature and we have the permissions to attach it to an |
| 260 | * operator. |
| 261 | */ |
| 262 | static Oid |
| 263 | ValidateRestrictionEstimator(List *restrictionName) |
| 264 | { |
| 265 | Oid typeId[4]; |
| 266 | Oid restrictionOid; |
| 267 | AclResult aclresult; |
| 268 | |
| 269 | typeId[0] = INTERNALOID; /* PlannerInfo */ |
| 270 | typeId[1] = OIDOID; /* operator OID */ |
| 271 | typeId[2] = INTERNALOID; /* args list */ |
| 272 | typeId[3] = INT4OID; /* varRelid */ |
| 273 | |
| 274 | restrictionOid = LookupFuncName(restrictionName, 4, typeId, false); |
| 275 | |
| 276 | /* estimators must return float8 */ |
| 277 | if (get_func_rettype(restrictionOid) != FLOAT8OID) |
| 278 | ereport(ERROR, |
| 279 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 280 | errmsg("restriction estimator function %s must return type %s" , |
| 281 | NameListToString(restrictionName), "float8" ))); |
| 282 | |
| 283 | /* Require EXECUTE rights for the estimator */ |
| 284 | aclresult = pg_proc_aclcheck(restrictionOid, GetUserId(), ACL_EXECUTE); |
| 285 | if (aclresult != ACLCHECK_OK) |
| 286 | aclcheck_error(aclresult, OBJECT_FUNCTION, |
| 287 | NameListToString(restrictionName)); |
| 288 | |
| 289 | return restrictionOid; |
| 290 | } |
| 291 | |
| 292 | /* |
| 293 | * Look up a join estimator function ny name, and verify that it has the |
| 294 | * correct signature and we have the permissions to attach it to an |
| 295 | * operator. |
| 296 | */ |
| 297 | static Oid |
| 298 | ValidateJoinEstimator(List *joinName) |
| 299 | { |
| 300 | Oid typeId[5]; |
| 301 | Oid joinOid; |
| 302 | AclResult aclresult; |
| 303 | |
| 304 | typeId[0] = INTERNALOID; /* PlannerInfo */ |
| 305 | typeId[1] = OIDOID; /* operator OID */ |
| 306 | typeId[2] = INTERNALOID; /* args list */ |
| 307 | typeId[3] = INT2OID; /* jointype */ |
| 308 | typeId[4] = INTERNALOID; /* SpecialJoinInfo */ |
| 309 | |
| 310 | /* |
| 311 | * As of Postgres 8.4, the preferred signature for join estimators has 5 |
| 312 | * arguments, but we still allow the old 4-argument form. Try the |
| 313 | * preferred form first. |
| 314 | */ |
| 315 | joinOid = LookupFuncName(joinName, 5, typeId, true); |
| 316 | if (!OidIsValid(joinOid)) |
| 317 | joinOid = LookupFuncName(joinName, 4, typeId, true); |
| 318 | /* If not found, reference the 5-argument signature in error msg */ |
| 319 | if (!OidIsValid(joinOid)) |
| 320 | joinOid = LookupFuncName(joinName, 5, typeId, false); |
| 321 | |
| 322 | /* estimators must return float8 */ |
| 323 | if (get_func_rettype(joinOid) != FLOAT8OID) |
| 324 | ereport(ERROR, |
| 325 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 326 | errmsg("join estimator function %s must return type %s" , |
| 327 | NameListToString(joinName), "float8" ))); |
| 328 | |
| 329 | /* Require EXECUTE rights for the estimator */ |
| 330 | aclresult = pg_proc_aclcheck(joinOid, GetUserId(), ACL_EXECUTE); |
| 331 | if (aclresult != ACLCHECK_OK) |
| 332 | aclcheck_error(aclresult, OBJECT_FUNCTION, |
| 333 | NameListToString(joinName)); |
| 334 | |
| 335 | return joinOid; |
| 336 | } |
| 337 | |
| 338 | /* |
| 339 | * Guts of operator deletion. |
| 340 | */ |
| 341 | void |
| 342 | RemoveOperatorById(Oid operOid) |
| 343 | { |
| 344 | Relation relation; |
| 345 | HeapTuple tup; |
| 346 | Form_pg_operator op; |
| 347 | |
| 348 | relation = table_open(OperatorRelationId, RowExclusiveLock); |
| 349 | |
| 350 | tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); |
| 351 | if (!HeapTupleIsValid(tup)) /* should not happen */ |
| 352 | elog(ERROR, "cache lookup failed for operator %u" , operOid); |
| 353 | op = (Form_pg_operator) GETSTRUCT(tup); |
| 354 | |
| 355 | /* |
| 356 | * Reset links from commutator and negator, if any. In case of a |
| 357 | * self-commutator or self-negator, this means we have to re-fetch the |
| 358 | * updated tuple. (We could optimize away updates on the tuple we're |
| 359 | * about to drop, but it doesn't seem worth convoluting the logic for.) |
| 360 | */ |
| 361 | if (OidIsValid(op->oprcom) || OidIsValid(op->oprnegate)) |
| 362 | { |
| 363 | OperatorUpd(operOid, op->oprcom, op->oprnegate, true); |
| 364 | if (operOid == op->oprcom || operOid == op->oprnegate) |
| 365 | { |
| 366 | ReleaseSysCache(tup); |
| 367 | tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid)); |
| 368 | if (!HeapTupleIsValid(tup)) /* should not happen */ |
| 369 | elog(ERROR, "cache lookup failed for operator %u" , operOid); |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | CatalogTupleDelete(relation, &tup->t_self); |
| 374 | |
| 375 | ReleaseSysCache(tup); |
| 376 | |
| 377 | table_close(relation, RowExclusiveLock); |
| 378 | } |
| 379 | |
| 380 | /* |
| 381 | * AlterOperator |
| 382 | * routine implementing ALTER OPERATOR <operator> SET (option = ...). |
| 383 | * |
| 384 | * Currently, only RESTRICT and JOIN estimator functions can be changed. |
| 385 | */ |
| 386 | ObjectAddress |
| 387 | AlterOperator(AlterOperatorStmt *stmt) |
| 388 | { |
| 389 | ObjectAddress address; |
| 390 | Oid oprId; |
| 391 | Relation catalog; |
| 392 | HeapTuple tup; |
| 393 | Form_pg_operator oprForm; |
| 394 | int i; |
| 395 | ListCell *pl; |
| 396 | Datum values[Natts_pg_operator]; |
| 397 | bool nulls[Natts_pg_operator]; |
| 398 | bool replaces[Natts_pg_operator]; |
| 399 | List *restrictionName = NIL; /* optional restrict. sel. function */ |
| 400 | bool updateRestriction = false; |
| 401 | Oid restrictionOid; |
| 402 | List *joinName = NIL; /* optional join sel. function */ |
| 403 | bool updateJoin = false; |
| 404 | Oid joinOid; |
| 405 | |
| 406 | /* Look up the operator */ |
| 407 | oprId = LookupOperWithArgs(stmt->opername, false); |
| 408 | catalog = table_open(OperatorRelationId, RowExclusiveLock); |
| 409 | tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(oprId)); |
| 410 | if (!HeapTupleIsValid(tup)) |
| 411 | elog(ERROR, "cache lookup failed for operator %u" , oprId); |
| 412 | oprForm = (Form_pg_operator) GETSTRUCT(tup); |
| 413 | |
| 414 | /* Process options */ |
| 415 | foreach(pl, stmt->options) |
| 416 | { |
| 417 | DefElem *defel = (DefElem *) lfirst(pl); |
| 418 | List *param; |
| 419 | |
| 420 | if (defel->arg == NULL) |
| 421 | param = NIL; /* NONE, removes the function */ |
| 422 | else |
| 423 | param = defGetQualifiedName(defel); |
| 424 | |
| 425 | if (strcmp(defel->defname, "restrict" ) == 0) |
| 426 | { |
| 427 | restrictionName = param; |
| 428 | updateRestriction = true; |
| 429 | } |
| 430 | else if (strcmp(defel->defname, "join" ) == 0) |
| 431 | { |
| 432 | joinName = param; |
| 433 | updateJoin = true; |
| 434 | } |
| 435 | |
| 436 | /* |
| 437 | * The rest of the options that CREATE accepts cannot be changed. |
| 438 | * Check for them so that we can give a meaningful error message. |
| 439 | */ |
| 440 | else if (strcmp(defel->defname, "leftarg" ) == 0 || |
| 441 | strcmp(defel->defname, "rightarg" ) == 0 || |
| 442 | strcmp(defel->defname, "function" ) == 0 || |
| 443 | strcmp(defel->defname, "procedure" ) == 0 || |
| 444 | strcmp(defel->defname, "commutator" ) == 0 || |
| 445 | strcmp(defel->defname, "negator" ) == 0 || |
| 446 | strcmp(defel->defname, "hashes" ) == 0 || |
| 447 | strcmp(defel->defname, "merges" ) == 0) |
| 448 | { |
| 449 | ereport(ERROR, |
| 450 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 451 | errmsg("operator attribute \"%s\" cannot be changed" , |
| 452 | defel->defname))); |
| 453 | } |
| 454 | else |
| 455 | ereport(ERROR, |
| 456 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 457 | errmsg("operator attribute \"%s\" not recognized" , |
| 458 | defel->defname))); |
| 459 | } |
| 460 | |
| 461 | /* Check permissions. Must be owner. */ |
| 462 | if (!pg_oper_ownercheck(oprId, GetUserId())) |
| 463 | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR, |
| 464 | NameStr(oprForm->oprname)); |
| 465 | |
| 466 | /* |
| 467 | * Look up restriction and join estimators if specified |
| 468 | */ |
| 469 | if (restrictionName) |
| 470 | restrictionOid = ValidateRestrictionEstimator(restrictionName); |
| 471 | else |
| 472 | restrictionOid = InvalidOid; |
| 473 | if (joinName) |
| 474 | joinOid = ValidateJoinEstimator(joinName); |
| 475 | else |
| 476 | joinOid = InvalidOid; |
| 477 | |
| 478 | /* Perform additional checks, like OperatorCreate does */ |
| 479 | if (!(OidIsValid(oprForm->oprleft) && OidIsValid(oprForm->oprright))) |
| 480 | { |
| 481 | /* If it's not a binary op, these things mustn't be set: */ |
| 482 | if (OidIsValid(joinOid)) |
| 483 | ereport(ERROR, |
| 484 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| 485 | errmsg("only binary operators can have join selectivity" ))); |
| 486 | } |
| 487 | |
| 488 | if (oprForm->oprresult != BOOLOID) |
| 489 | { |
| 490 | if (OidIsValid(restrictionOid)) |
| 491 | ereport(ERROR, |
| 492 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| 493 | errmsg("only boolean operators can have restriction selectivity" ))); |
| 494 | if (OidIsValid(joinOid)) |
| 495 | ereport(ERROR, |
| 496 | (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), |
| 497 | errmsg("only boolean operators can have join selectivity" ))); |
| 498 | } |
| 499 | |
| 500 | /* Update the tuple */ |
| 501 | for (i = 0; i < Natts_pg_operator; ++i) |
| 502 | { |
| 503 | values[i] = (Datum) 0; |
| 504 | replaces[i] = false; |
| 505 | nulls[i] = false; |
| 506 | } |
| 507 | if (updateRestriction) |
| 508 | { |
| 509 | replaces[Anum_pg_operator_oprrest - 1] = true; |
| 510 | values[Anum_pg_operator_oprrest - 1] = restrictionOid; |
| 511 | } |
| 512 | if (updateJoin) |
| 513 | { |
| 514 | replaces[Anum_pg_operator_oprjoin - 1] = true; |
| 515 | values[Anum_pg_operator_oprjoin - 1] = joinOid; |
| 516 | } |
| 517 | |
| 518 | tup = heap_modify_tuple(tup, RelationGetDescr(catalog), |
| 519 | values, nulls, replaces); |
| 520 | |
| 521 | CatalogTupleUpdate(catalog, &tup->t_self, tup); |
| 522 | |
| 523 | address = makeOperatorDependencies(tup, true); |
| 524 | |
| 525 | InvokeObjectPostAlterHook(OperatorRelationId, oprId, 0); |
| 526 | |
| 527 | table_close(catalog, NoLock); |
| 528 | |
| 529 | return address; |
| 530 | } |
| 531 | |