| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * catalog.c |
| 4 | * routines concerned with catalog naming conventions and other |
| 5 | * bits of hard-wired knowledge |
| 6 | * |
| 7 | * |
| 8 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 9 | * Portions Copyright (c) 1994, Regents of the University of California |
| 10 | * |
| 11 | * |
| 12 | * IDENTIFICATION |
| 13 | * src/backend/catalog/catalog.c |
| 14 | * |
| 15 | *------------------------------------------------------------------------- |
| 16 | */ |
| 17 | |
| 18 | #include "postgres.h" |
| 19 | |
| 20 | #include <fcntl.h> |
| 21 | #include <unistd.h> |
| 22 | |
| 23 | #include "access/genam.h" |
| 24 | #include "access/htup_details.h" |
| 25 | #include "access/sysattr.h" |
| 26 | #include "access/table.h" |
| 27 | #include "access/transam.h" |
| 28 | #include "catalog/catalog.h" |
| 29 | #include "catalog/indexing.h" |
| 30 | #include "catalog/namespace.h" |
| 31 | #include "catalog/pg_auth_members.h" |
| 32 | #include "catalog/pg_authid.h" |
| 33 | #include "catalog/pg_database.h" |
| 34 | #include "catalog/pg_namespace.h" |
| 35 | #include "catalog/pg_pltemplate.h" |
| 36 | #include "catalog/pg_db_role_setting.h" |
| 37 | #include "catalog/pg_replication_origin.h" |
| 38 | #include "catalog/pg_shdepend.h" |
| 39 | #include "catalog/pg_shdescription.h" |
| 40 | #include "catalog/pg_shseclabel.h" |
| 41 | #include "catalog/pg_subscription.h" |
| 42 | #include "catalog/pg_tablespace.h" |
| 43 | #include "catalog/pg_type.h" |
| 44 | #include "catalog/toasting.h" |
| 45 | #include "miscadmin.h" |
| 46 | #include "storage/fd.h" |
| 47 | #include "utils/fmgroids.h" |
| 48 | #include "utils/fmgrprotos.h" |
| 49 | #include "utils/rel.h" |
| 50 | #include "utils/snapmgr.h" |
| 51 | #include "utils/syscache.h" |
| 52 | |
| 53 | |
| 54 | /* |
| 55 | * IsSystemRelation |
| 56 | * True iff the relation is either a system catalog or a toast table. |
| 57 | * See IsCatalogRelation for the exact definition of a system catalog. |
| 58 | * |
| 59 | * We treat toast tables of user relations as "system relations" for |
| 60 | * protection purposes, e.g. you can't change their schemas without |
| 61 | * special permissions. Therefore, most uses of this function are |
| 62 | * checking whether allow_system_table_mods restrictions apply. |
| 63 | * For other purposes, consider whether you shouldn't be using |
| 64 | * IsCatalogRelation instead. |
| 65 | * |
| 66 | * This function does not perform any catalog accesses. |
| 67 | * Some callers rely on that! |
| 68 | */ |
| 69 | bool |
| 70 | IsSystemRelation(Relation relation) |
| 71 | { |
| 72 | return IsSystemClass(RelationGetRelid(relation), relation->rd_rel); |
| 73 | } |
| 74 | |
| 75 | /* |
| 76 | * IsSystemClass |
| 77 | * Like the above, but takes a Form_pg_class as argument. |
| 78 | * Used when we do not want to open the relation and have to |
| 79 | * search pg_class directly. |
| 80 | */ |
| 81 | bool |
| 82 | IsSystemClass(Oid relid, Form_pg_class reltuple) |
| 83 | { |
| 84 | /* IsCatalogRelationOid is a bit faster, so test that first */ |
| 85 | return (IsCatalogRelationOid(relid) || IsToastClass(reltuple)); |
| 86 | } |
| 87 | |
| 88 | /* |
| 89 | * IsCatalogRelation |
| 90 | * True iff the relation is a system catalog. |
| 91 | * |
| 92 | * By a system catalog, we mean one that is created during the bootstrap |
| 93 | * phase of initdb. That includes not just the catalogs per se, but |
| 94 | * also their indexes, and TOAST tables and indexes if any. |
| 95 | * |
| 96 | * This function does not perform any catalog accesses. |
| 97 | * Some callers rely on that! |
| 98 | */ |
| 99 | bool |
| 100 | IsCatalogRelation(Relation relation) |
| 101 | { |
| 102 | return IsCatalogRelationOid(RelationGetRelid(relation)); |
| 103 | } |
| 104 | |
| 105 | /* |
| 106 | * IsCatalogRelationOid |
| 107 | * True iff the relation identified by this OID is a system catalog. |
| 108 | * |
| 109 | * By a system catalog, we mean one that is created during the bootstrap |
| 110 | * phase of initdb. That includes not just the catalogs per se, but |
| 111 | * also their indexes, and TOAST tables and indexes if any. |
| 112 | * |
| 113 | * This function does not perform any catalog accesses. |
| 114 | * Some callers rely on that! |
| 115 | */ |
| 116 | bool |
| 117 | IsCatalogRelationOid(Oid relid) |
| 118 | { |
| 119 | /* |
| 120 | * We consider a relation to be a system catalog if it has an OID that was |
| 121 | * manually assigned or assigned by genbki.pl. This includes all the |
| 122 | * defined catalogs, their indexes, and their TOAST tables and indexes. |
| 123 | * |
| 124 | * This rule excludes the relations in information_schema, which are not |
| 125 | * integral to the system and can be treated the same as user relations. |
| 126 | * (Since it's valid to drop and recreate information_schema, any rule |
| 127 | * that did not act this way would be wrong.) |
| 128 | * |
| 129 | * This test is reliable since an OID wraparound will skip this range of |
| 130 | * OIDs; see GetNewObjectId(). |
| 131 | */ |
| 132 | return (relid < (Oid) FirstBootstrapObjectId); |
| 133 | } |
| 134 | |
| 135 | /* |
| 136 | * IsToastRelation |
| 137 | * True iff relation is a TOAST support relation (or index). |
| 138 | * |
| 139 | * Does not perform any catalog accesses. |
| 140 | */ |
| 141 | bool |
| 142 | IsToastRelation(Relation relation) |
| 143 | { |
| 144 | /* |
| 145 | * What we actually check is whether the relation belongs to a pg_toast |
| 146 | * namespace. This should be equivalent because of restrictions that are |
| 147 | * enforced elsewhere against creating user relations in, or moving |
| 148 | * relations into/out of, a pg_toast namespace. Notice also that this |
| 149 | * will not say "true" for toast tables belonging to other sessions' temp |
| 150 | * tables; we expect that other mechanisms will prevent access to those. |
| 151 | */ |
| 152 | return IsToastNamespace(RelationGetNamespace(relation)); |
| 153 | } |
| 154 | |
| 155 | /* |
| 156 | * IsToastClass |
| 157 | * Like the above, but takes a Form_pg_class as argument. |
| 158 | * Used when we do not want to open the relation and have to |
| 159 | * search pg_class directly. |
| 160 | */ |
| 161 | bool |
| 162 | IsToastClass(Form_pg_class reltuple) |
| 163 | { |
| 164 | Oid relnamespace = reltuple->relnamespace; |
| 165 | |
| 166 | return IsToastNamespace(relnamespace); |
| 167 | } |
| 168 | |
| 169 | /* |
| 170 | * IsCatalogNamespace |
| 171 | * True iff namespace is pg_catalog. |
| 172 | * |
| 173 | * Does not perform any catalog accesses. |
| 174 | * |
| 175 | * NOTE: the reason this isn't a macro is to avoid having to include |
| 176 | * catalog/pg_namespace.h in a lot of places. |
| 177 | */ |
| 178 | bool |
| 179 | IsCatalogNamespace(Oid namespaceId) |
| 180 | { |
| 181 | return namespaceId == PG_CATALOG_NAMESPACE; |
| 182 | } |
| 183 | |
| 184 | /* |
| 185 | * IsToastNamespace |
| 186 | * True iff namespace is pg_toast or my temporary-toast-table namespace. |
| 187 | * |
| 188 | * Does not perform any catalog accesses. |
| 189 | * |
| 190 | * Note: this will return false for temporary-toast-table namespaces belonging |
| 191 | * to other backends. Those are treated the same as other backends' regular |
| 192 | * temp table namespaces, and access is prevented where appropriate. |
| 193 | * If you need to check for those, you may be able to use isAnyTempNamespace, |
| 194 | * but beware that that does involve a catalog access. |
| 195 | */ |
| 196 | bool |
| 197 | IsToastNamespace(Oid namespaceId) |
| 198 | { |
| 199 | return (namespaceId == PG_TOAST_NAMESPACE) || |
| 200 | isTempToastNamespace(namespaceId); |
| 201 | } |
| 202 | |
| 203 | |
| 204 | /* |
| 205 | * IsReservedName |
| 206 | * True iff name starts with the pg_ prefix. |
| 207 | * |
| 208 | * For some classes of objects, the prefix pg_ is reserved for |
| 209 | * system objects only. As of 8.0, this was only true for |
| 210 | * schema and tablespace names. With 9.6, this is also true |
| 211 | * for roles. |
| 212 | */ |
| 213 | bool |
| 214 | IsReservedName(const char *name) |
| 215 | { |
| 216 | /* ugly coding for speed */ |
| 217 | return (name[0] == 'p' && |
| 218 | name[1] == 'g' && |
| 219 | name[2] == '_'); |
| 220 | } |
| 221 | |
| 222 | |
| 223 | /* |
| 224 | * IsSharedRelation |
| 225 | * Given the OID of a relation, determine whether it's supposed to be |
| 226 | * shared across an entire database cluster. |
| 227 | * |
| 228 | * In older releases, this had to be hard-wired so that we could compute the |
| 229 | * locktag for a relation and lock it before examining its catalog entry. |
| 230 | * Since we now have MVCC catalog access, the race conditions that made that |
| 231 | * a hard requirement are gone, so we could look at relaxing this restriction. |
| 232 | * However, if we scanned the pg_class entry to find relisshared, and only |
| 233 | * then locked the relation, pg_class could get updated in the meantime, |
| 234 | * forcing us to scan the relation again, which would definitely be complex |
| 235 | * and might have undesirable performance consequences. Fortunately, the set |
| 236 | * of shared relations is fairly static, so a hand-maintained list of their |
| 237 | * OIDs isn't completely impractical. |
| 238 | */ |
| 239 | bool |
| 240 | IsSharedRelation(Oid relationId) |
| 241 | { |
| 242 | /* These are the shared catalogs (look for BKI_SHARED_RELATION) */ |
| 243 | if (relationId == AuthIdRelationId || |
| 244 | relationId == AuthMemRelationId || |
| 245 | relationId == DatabaseRelationId || |
| 246 | relationId == PLTemplateRelationId || |
| 247 | relationId == SharedDescriptionRelationId || |
| 248 | relationId == SharedDependRelationId || |
| 249 | relationId == SharedSecLabelRelationId || |
| 250 | relationId == TableSpaceRelationId || |
| 251 | relationId == DbRoleSettingRelationId || |
| 252 | relationId == ReplicationOriginRelationId || |
| 253 | relationId == SubscriptionRelationId) |
| 254 | return true; |
| 255 | /* These are their indexes (see indexing.h) */ |
| 256 | if (relationId == AuthIdRolnameIndexId || |
| 257 | relationId == AuthIdOidIndexId || |
| 258 | relationId == AuthMemRoleMemIndexId || |
| 259 | relationId == AuthMemMemRoleIndexId || |
| 260 | relationId == DatabaseNameIndexId || |
| 261 | relationId == DatabaseOidIndexId || |
| 262 | relationId == PLTemplateNameIndexId || |
| 263 | relationId == SharedDescriptionObjIndexId || |
| 264 | relationId == SharedDependDependerIndexId || |
| 265 | relationId == SharedDependReferenceIndexId || |
| 266 | relationId == SharedSecLabelObjectIndexId || |
| 267 | relationId == TablespaceOidIndexId || |
| 268 | relationId == TablespaceNameIndexId || |
| 269 | relationId == DbRoleSettingDatidRolidIndexId || |
| 270 | relationId == ReplicationOriginIdentIndex || |
| 271 | relationId == ReplicationOriginNameIndex || |
| 272 | relationId == SubscriptionObjectIndexId || |
| 273 | relationId == SubscriptionNameIndexId) |
| 274 | return true; |
| 275 | /* These are their toast tables and toast indexes (see toasting.h) */ |
| 276 | if (relationId == PgAuthidToastTable || |
| 277 | relationId == PgAuthidToastIndex || |
| 278 | relationId == PgDatabaseToastTable || |
| 279 | relationId == PgDatabaseToastIndex || |
| 280 | relationId == PgDbRoleSettingToastTable || |
| 281 | relationId == PgDbRoleSettingToastIndex || |
| 282 | relationId == PgPlTemplateToastTable || |
| 283 | relationId == PgPlTemplateToastIndex || |
| 284 | relationId == PgReplicationOriginToastTable || |
| 285 | relationId == PgReplicationOriginToastIndex || |
| 286 | relationId == PgShdescriptionToastTable || |
| 287 | relationId == PgShdescriptionToastIndex || |
| 288 | relationId == PgShseclabelToastTable || |
| 289 | relationId == PgShseclabelToastIndex || |
| 290 | relationId == PgSubscriptionToastTable || |
| 291 | relationId == PgSubscriptionToastIndex || |
| 292 | relationId == PgTablespaceToastTable || |
| 293 | relationId == PgTablespaceToastIndex) |
| 294 | return true; |
| 295 | return false; |
| 296 | } |
| 297 | |
| 298 | |
| 299 | /* |
| 300 | * GetNewOidWithIndex |
| 301 | * Generate a new OID that is unique within the system relation. |
| 302 | * |
| 303 | * Since the OID is not immediately inserted into the table, there is a |
| 304 | * race condition here; but a problem could occur only if someone else |
| 305 | * managed to cycle through 2^32 OIDs and generate the same OID before we |
| 306 | * finish inserting our row. This seems unlikely to be a problem. Note |
| 307 | * that if we had to *commit* the row to end the race condition, the risk |
| 308 | * would be rather higher; therefore we use SnapshotAny in the test, so that |
| 309 | * we will see uncommitted rows. (We used to use SnapshotDirty, but that has |
| 310 | * the disadvantage that it ignores recently-deleted rows, creating a risk |
| 311 | * of transient conflicts for as long as our own MVCC snapshots think a |
| 312 | * recently-deleted row is live. The risk is far higher when selecting TOAST |
| 313 | * OIDs, because SnapshotToast considers dead rows as active indefinitely.) |
| 314 | * |
| 315 | * Note that we are effectively assuming that the table has a relatively small |
| 316 | * number of entries (much less than 2^32) and there aren't very long runs of |
| 317 | * consecutive existing OIDs. This is a mostly reasonable assumption for |
| 318 | * system catalogs. |
| 319 | * |
| 320 | * This is exported separately because there are cases where we want to use |
| 321 | * an index that will not be recognized by RelationGetOidIndex: TOAST tables |
| 322 | * have indexes that are usable, but have multiple columns and are on |
| 323 | * ordinary columns rather than a true OID column. This code will work |
| 324 | * anyway, so long as the OID is the index's first column. The caller must |
| 325 | * pass in the actual heap attnum of the OID column, however. |
| 326 | * |
| 327 | * Caller must have a suitable lock on the relation. |
| 328 | */ |
| 329 | Oid |
| 330 | GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn) |
| 331 | { |
| 332 | Oid newOid; |
| 333 | SysScanDesc scan; |
| 334 | ScanKeyData key; |
| 335 | bool collides; |
| 336 | |
| 337 | /* Only system relations are supported */ |
| 338 | Assert(IsSystemRelation(relation)); |
| 339 | |
| 340 | /* In bootstrap mode, we don't have any indexes to use */ |
| 341 | if (IsBootstrapProcessingMode()) |
| 342 | return GetNewObjectId(); |
| 343 | |
| 344 | /* |
| 345 | * We should never be asked to generate a new pg_type OID during |
| 346 | * pg_upgrade; doing so would risk collisions with the OIDs it wants to |
| 347 | * assign. Hitting this assert means there's some path where we failed to |
| 348 | * ensure that a type OID is determined by commands in the dump script. |
| 349 | */ |
| 350 | Assert(!IsBinaryUpgrade || RelationGetRelid(relation) != TypeRelationId); |
| 351 | |
| 352 | /* Generate new OIDs until we find one not in the table */ |
| 353 | do |
| 354 | { |
| 355 | CHECK_FOR_INTERRUPTS(); |
| 356 | |
| 357 | newOid = GetNewObjectId(); |
| 358 | |
| 359 | ScanKeyInit(&key, |
| 360 | oidcolumn, |
| 361 | BTEqualStrategyNumber, F_OIDEQ, |
| 362 | ObjectIdGetDatum(newOid)); |
| 363 | |
| 364 | /* see notes above about using SnapshotAny */ |
| 365 | scan = systable_beginscan(relation, indexId, true, |
| 366 | SnapshotAny, 1, &key); |
| 367 | |
| 368 | collides = HeapTupleIsValid(systable_getnext(scan)); |
| 369 | |
| 370 | systable_endscan(scan); |
| 371 | } while (collides); |
| 372 | |
| 373 | return newOid; |
| 374 | } |
| 375 | |
| 376 | /* |
| 377 | * GetNewRelFileNode |
| 378 | * Generate a new relfilenode number that is unique within the |
| 379 | * database of the given tablespace. |
| 380 | * |
| 381 | * If the relfilenode will also be used as the relation's OID, pass the |
| 382 | * opened pg_class catalog, and this routine will guarantee that the result |
| 383 | * is also an unused OID within pg_class. If the result is to be used only |
| 384 | * as a relfilenode for an existing relation, pass NULL for pg_class. |
| 385 | * |
| 386 | * As with GetNewOidWithIndex(), there is some theoretical risk of a race |
| 387 | * condition, but it doesn't seem worth worrying about. |
| 388 | * |
| 389 | * Note: we don't support using this in bootstrap mode. All relations |
| 390 | * created by bootstrap have preassigned OIDs, so there's no need. |
| 391 | */ |
| 392 | Oid |
| 393 | GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) |
| 394 | { |
| 395 | RelFileNodeBackend rnode; |
| 396 | char *rpath; |
| 397 | bool collides; |
| 398 | BackendId backend; |
| 399 | |
| 400 | /* |
| 401 | * If we ever get here during pg_upgrade, there's something wrong; all |
| 402 | * relfilenode assignments during a binary-upgrade run should be |
| 403 | * determined by commands in the dump script. |
| 404 | */ |
| 405 | Assert(!IsBinaryUpgrade); |
| 406 | |
| 407 | switch (relpersistence) |
| 408 | { |
| 409 | case RELPERSISTENCE_TEMP: |
| 410 | backend = BackendIdForTempRelations(); |
| 411 | break; |
| 412 | case RELPERSISTENCE_UNLOGGED: |
| 413 | case RELPERSISTENCE_PERMANENT: |
| 414 | backend = InvalidBackendId; |
| 415 | break; |
| 416 | default: |
| 417 | elog(ERROR, "invalid relpersistence: %c" , relpersistence); |
| 418 | return InvalidOid; /* placate compiler */ |
| 419 | } |
| 420 | |
| 421 | /* This logic should match RelationInitPhysicalAddr */ |
| 422 | rnode.node.spcNode = reltablespace ? reltablespace : MyDatabaseTableSpace; |
| 423 | rnode.node.dbNode = (rnode.node.spcNode == GLOBALTABLESPACE_OID) ? InvalidOid : MyDatabaseId; |
| 424 | |
| 425 | /* |
| 426 | * The relpath will vary based on the backend ID, so we must initialize |
| 427 | * that properly here to make sure that any collisions based on filename |
| 428 | * are properly detected. |
| 429 | */ |
| 430 | rnode.backend = backend; |
| 431 | |
| 432 | do |
| 433 | { |
| 434 | CHECK_FOR_INTERRUPTS(); |
| 435 | |
| 436 | /* Generate the OID */ |
| 437 | if (pg_class) |
| 438 | rnode.node.relNode = GetNewOidWithIndex(pg_class, ClassOidIndexId, |
| 439 | Anum_pg_class_oid); |
| 440 | else |
| 441 | rnode.node.relNode = GetNewObjectId(); |
| 442 | |
| 443 | /* Check for existing file of same name */ |
| 444 | rpath = relpath(rnode, MAIN_FORKNUM); |
| 445 | |
| 446 | if (access(rpath, F_OK) == 0) |
| 447 | { |
| 448 | /* definite collision */ |
| 449 | collides = true; |
| 450 | } |
| 451 | else |
| 452 | { |
| 453 | /* |
| 454 | * Here we have a little bit of a dilemma: if errno is something |
| 455 | * other than ENOENT, should we declare a collision and loop? In |
| 456 | * practice it seems best to go ahead regardless of the errno. If |
| 457 | * there is a colliding file we will get an smgr failure when we |
| 458 | * attempt to create the new relation file. |
| 459 | */ |
| 460 | collides = false; |
| 461 | } |
| 462 | |
| 463 | pfree(rpath); |
| 464 | } while (collides); |
| 465 | |
| 466 | return rnode.node.relNode; |
| 467 | } |
| 468 | |
| 469 | /* |
| 470 | * SQL callable interface for GetNewOidWithIndex(). Outside of initdb's |
| 471 | * direct insertions into catalog tables, and recovering from corruption, this |
| 472 | * should rarely be needed. |
| 473 | * |
| 474 | * Function is intentionally not documented in the user facing docs. |
| 475 | */ |
| 476 | Datum |
| 477 | pg_nextoid(PG_FUNCTION_ARGS) |
| 478 | { |
| 479 | Oid reloid = PG_GETARG_OID(0); |
| 480 | Name attname = PG_GETARG_NAME(1); |
| 481 | Oid idxoid = PG_GETARG_OID(2); |
| 482 | Relation rel; |
| 483 | Relation idx; |
| 484 | HeapTuple atttuple; |
| 485 | Form_pg_attribute attform; |
| 486 | AttrNumber attno; |
| 487 | Oid newoid; |
| 488 | |
| 489 | /* |
| 490 | * As this function is not intended to be used during normal running, and |
| 491 | * only supports system catalogs (which require superuser permissions to |
| 492 | * modify), just checking for superuser ought to not obstruct valid |
| 493 | * usecases. |
| 494 | */ |
| 495 | if (!superuser()) |
| 496 | ereport(ERROR, |
| 497 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 498 | errmsg("must be superuser to call pg_nextoid()" ))); |
| 499 | |
| 500 | rel = table_open(reloid, RowExclusiveLock); |
| 501 | idx = index_open(idxoid, RowExclusiveLock); |
| 502 | |
| 503 | if (!IsSystemRelation(rel)) |
| 504 | ereport(ERROR, |
| 505 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 506 | errmsg("pg_nextoid() can only be used on system catalogs" ))); |
| 507 | |
| 508 | if (idx->rd_index->indrelid != RelationGetRelid(rel)) |
| 509 | ereport(ERROR, |
| 510 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 511 | errmsg("index \"%s\" does not belong to table \"%s\"" , |
| 512 | RelationGetRelationName(idx), |
| 513 | RelationGetRelationName(rel)))); |
| 514 | |
| 515 | atttuple = SearchSysCacheAttName(reloid, NameStr(*attname)); |
| 516 | if (!HeapTupleIsValid(atttuple)) |
| 517 | ereport(ERROR, |
| 518 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 519 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 520 | NameStr(*attname), RelationGetRelationName(rel)))); |
| 521 | |
| 522 | attform = ((Form_pg_attribute) GETSTRUCT(atttuple)); |
| 523 | attno = attform->attnum; |
| 524 | |
| 525 | if (attform->atttypid != OIDOID) |
| 526 | ereport(ERROR, |
| 527 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 528 | errmsg("column \"%s\" is not of type oid" , |
| 529 | NameStr(*attname)))); |
| 530 | |
| 531 | if (IndexRelationGetNumberOfKeyAttributes(idx) != 1 || |
| 532 | idx->rd_index->indkey.values[0] != attno) |
| 533 | ereport(ERROR, |
| 534 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 535 | errmsg("index \"%s\" is not the index for column \"%s\"" , |
| 536 | RelationGetRelationName(idx), |
| 537 | NameStr(*attname)))); |
| 538 | |
| 539 | newoid = GetNewOidWithIndex(rel, idxoid, attno); |
| 540 | |
| 541 | ReleaseSysCache(atttuple); |
| 542 | table_close(rel, RowExclusiveLock); |
| 543 | index_close(idx, RowExclusiveLock); |
| 544 | |
| 545 | return newoid; |
| 546 | } |
| 547 | |