| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * pg_enum.c |
| 4 | * routines to support manipulation of the pg_enum relation |
| 5 | * |
| 6 | * Copyright (c) 2006-2019, PostgreSQL Global Development Group |
| 7 | * |
| 8 | * |
| 9 | * IDENTIFICATION |
| 10 | * src/backend/catalog/pg_enum.c |
| 11 | * |
| 12 | *------------------------------------------------------------------------- |
| 13 | */ |
| 14 | #include "postgres.h" |
| 15 | |
| 16 | #include "access/genam.h" |
| 17 | #include "access/htup_details.h" |
| 18 | #include "access/table.h" |
| 19 | #include "access/xact.h" |
| 20 | #include "catalog/binary_upgrade.h" |
| 21 | #include "catalog/catalog.h" |
| 22 | #include "catalog/indexing.h" |
| 23 | #include "catalog/pg_enum.h" |
| 24 | #include "catalog/pg_type.h" |
| 25 | #include "storage/lmgr.h" |
| 26 | #include "miscadmin.h" |
| 27 | #include "nodes/value.h" |
| 28 | #include "utils/builtins.h" |
| 29 | #include "utils/catcache.h" |
| 30 | #include "utils/fmgroids.h" |
| 31 | #include "utils/hsearch.h" |
| 32 | #include "utils/memutils.h" |
| 33 | #include "utils/syscache.h" |
| 34 | |
| 35 | |
| 36 | /* Potentially set by pg_upgrade_support functions */ |
| 37 | Oid binary_upgrade_next_pg_enum_oid = InvalidOid; |
| 38 | |
| 39 | /* |
| 40 | * Hash table of enum value OIDs created during the current transaction by |
| 41 | * AddEnumLabel. We disallow using these values until the transaction is |
| 42 | * committed; otherwise, they might get into indexes where we can't clean |
| 43 | * them up, and then if the transaction rolls back we have a broken index. |
| 44 | * (See comments for check_safe_enum_use() in enum.c.) Values created by |
| 45 | * EnumValuesCreate are *not* blacklisted; we assume those are created during |
| 46 | * CREATE TYPE, so they can't go away unless the enum type itself does. |
| 47 | */ |
| 48 | static HTAB *enum_blacklist = NULL; |
| 49 | |
| 50 | static void RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems); |
| 51 | static int sort_order_cmp(const void *p1, const void *p2); |
| 52 | |
| 53 | |
| 54 | /* |
| 55 | * EnumValuesCreate |
| 56 | * Create an entry in pg_enum for each of the supplied enum values. |
| 57 | * |
| 58 | * vals is a list of Value strings. |
| 59 | */ |
| 60 | void |
| 61 | EnumValuesCreate(Oid enumTypeOid, List *vals) |
| 62 | { |
| 63 | Relation pg_enum; |
| 64 | NameData enumlabel; |
| 65 | Oid *oids; |
| 66 | int elemno, |
| 67 | num_elems; |
| 68 | Datum values[Natts_pg_enum]; |
| 69 | bool nulls[Natts_pg_enum]; |
| 70 | ListCell *lc; |
| 71 | HeapTuple tup; |
| 72 | |
| 73 | num_elems = list_length(vals); |
| 74 | |
| 75 | /* |
| 76 | * We do not bother to check the list of values for duplicates --- if you |
| 77 | * have any, you'll get a less-than-friendly unique-index violation. It is |
| 78 | * probably not worth trying harder. |
| 79 | */ |
| 80 | |
| 81 | pg_enum = table_open(EnumRelationId, RowExclusiveLock); |
| 82 | |
| 83 | /* |
| 84 | * Allocate OIDs for the enum's members. |
| 85 | * |
| 86 | * While this method does not absolutely guarantee that we generate no |
| 87 | * duplicate OIDs (since we haven't entered each oid into the table before |
| 88 | * allocating the next), trouble could only occur if the OID counter wraps |
| 89 | * all the way around before we finish. Which seems unlikely. |
| 90 | */ |
| 91 | oids = (Oid *) palloc(num_elems * sizeof(Oid)); |
| 92 | |
| 93 | for (elemno = 0; elemno < num_elems; elemno++) |
| 94 | { |
| 95 | /* |
| 96 | * We assign even-numbered OIDs to all the new enum labels. This |
| 97 | * tells the comparison functions the OIDs are in the correct sort |
| 98 | * order and can be compared directly. |
| 99 | */ |
| 100 | Oid new_oid; |
| 101 | |
| 102 | do |
| 103 | { |
| 104 | new_oid = GetNewOidWithIndex(pg_enum, EnumOidIndexId, |
| 105 | Anum_pg_enum_oid); |
| 106 | } while (new_oid & 1); |
| 107 | oids[elemno] = new_oid; |
| 108 | } |
| 109 | |
| 110 | /* sort them, just in case OID counter wrapped from high to low */ |
| 111 | qsort(oids, num_elems, sizeof(Oid), oid_cmp); |
| 112 | |
| 113 | /* and make the entries */ |
| 114 | memset(nulls, false, sizeof(nulls)); |
| 115 | |
| 116 | elemno = 0; |
| 117 | foreach(lc, vals) |
| 118 | { |
| 119 | char *lab = strVal(lfirst(lc)); |
| 120 | |
| 121 | /* |
| 122 | * labels are stored in a name field, for easier syscache lookup, so |
| 123 | * check the length to make sure it's within range. |
| 124 | */ |
| 125 | if (strlen(lab) > (NAMEDATALEN - 1)) |
| 126 | ereport(ERROR, |
| 127 | (errcode(ERRCODE_INVALID_NAME), |
| 128 | errmsg("invalid enum label \"%s\"" , lab), |
| 129 | errdetail("Labels must be %d characters or less." , |
| 130 | NAMEDATALEN - 1))); |
| 131 | |
| 132 | values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(oids[elemno]); |
| 133 | values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid); |
| 134 | values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(elemno + 1); |
| 135 | namestrcpy(&enumlabel, lab); |
| 136 | values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel); |
| 137 | |
| 138 | tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls); |
| 139 | |
| 140 | CatalogTupleInsert(pg_enum, tup); |
| 141 | heap_freetuple(tup); |
| 142 | |
| 143 | elemno++; |
| 144 | } |
| 145 | |
| 146 | /* clean up */ |
| 147 | pfree(oids); |
| 148 | table_close(pg_enum, RowExclusiveLock); |
| 149 | } |
| 150 | |
| 151 | |
| 152 | /* |
| 153 | * EnumValuesDelete |
| 154 | * Remove all the pg_enum entries for the specified enum type. |
| 155 | */ |
| 156 | void |
| 157 | EnumValuesDelete(Oid enumTypeOid) |
| 158 | { |
| 159 | Relation pg_enum; |
| 160 | ScanKeyData key[1]; |
| 161 | SysScanDesc scan; |
| 162 | HeapTuple tup; |
| 163 | |
| 164 | pg_enum = table_open(EnumRelationId, RowExclusiveLock); |
| 165 | |
| 166 | ScanKeyInit(&key[0], |
| 167 | Anum_pg_enum_enumtypid, |
| 168 | BTEqualStrategyNumber, F_OIDEQ, |
| 169 | ObjectIdGetDatum(enumTypeOid)); |
| 170 | |
| 171 | scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true, |
| 172 | NULL, 1, key); |
| 173 | |
| 174 | while (HeapTupleIsValid(tup = systable_getnext(scan))) |
| 175 | { |
| 176 | CatalogTupleDelete(pg_enum, &tup->t_self); |
| 177 | } |
| 178 | |
| 179 | systable_endscan(scan); |
| 180 | |
| 181 | table_close(pg_enum, RowExclusiveLock); |
| 182 | } |
| 183 | |
| 184 | /* |
| 185 | * Initialize the enum blacklist for this transaction. |
| 186 | */ |
| 187 | static void |
| 188 | init_enum_blacklist(void) |
| 189 | { |
| 190 | HASHCTL hash_ctl; |
| 191 | |
| 192 | memset(&hash_ctl, 0, sizeof(hash_ctl)); |
| 193 | hash_ctl.keysize = sizeof(Oid); |
| 194 | hash_ctl.entrysize = sizeof(Oid); |
| 195 | hash_ctl.hcxt = TopTransactionContext; |
| 196 | enum_blacklist = hash_create("Enum value blacklist" , |
| 197 | 32, |
| 198 | &hash_ctl, |
| 199 | HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); |
| 200 | } |
| 201 | |
| 202 | /* |
| 203 | * AddEnumLabel |
| 204 | * Add a new label to the enum set. By default it goes at |
| 205 | * the end, but the user can choose to place it before or |
| 206 | * after any existing set member. |
| 207 | */ |
| 208 | void |
| 209 | AddEnumLabel(Oid enumTypeOid, |
| 210 | const char *newVal, |
| 211 | const char *neighbor, |
| 212 | bool newValIsAfter, |
| 213 | bool skipIfExists) |
| 214 | { |
| 215 | Relation pg_enum; |
| 216 | Oid newOid; |
| 217 | Datum values[Natts_pg_enum]; |
| 218 | bool nulls[Natts_pg_enum]; |
| 219 | NameData enumlabel; |
| 220 | HeapTuple enum_tup; |
| 221 | float4 newelemorder; |
| 222 | HeapTuple *existing; |
| 223 | CatCList *list; |
| 224 | int nelems; |
| 225 | int i; |
| 226 | |
| 227 | /* check length of new label is ok */ |
| 228 | if (strlen(newVal) > (NAMEDATALEN - 1)) |
| 229 | ereport(ERROR, |
| 230 | (errcode(ERRCODE_INVALID_NAME), |
| 231 | errmsg("invalid enum label \"%s\"" , newVal), |
| 232 | errdetail("Labels must be %d characters or less." , |
| 233 | NAMEDATALEN - 1))); |
| 234 | |
| 235 | /* |
| 236 | * Acquire a lock on the enum type, which we won't release until commit. |
| 237 | * This ensures that two backends aren't concurrently modifying the same |
| 238 | * enum type. Without that, we couldn't be sure to get a consistent view |
| 239 | * of the enum members via the syscache. Note that this does not block |
| 240 | * other backends from inspecting the type; see comments for |
| 241 | * RenumberEnumType. |
| 242 | */ |
| 243 | LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock); |
| 244 | |
| 245 | /* |
| 246 | * Check if label is already in use. The unique index on pg_enum would |
| 247 | * catch this anyway, but we prefer a friendlier error message, and |
| 248 | * besides we need a check to support IF NOT EXISTS. |
| 249 | */ |
| 250 | enum_tup = SearchSysCache2(ENUMTYPOIDNAME, |
| 251 | ObjectIdGetDatum(enumTypeOid), |
| 252 | CStringGetDatum(newVal)); |
| 253 | if (HeapTupleIsValid(enum_tup)) |
| 254 | { |
| 255 | ReleaseSysCache(enum_tup); |
| 256 | if (skipIfExists) |
| 257 | { |
| 258 | ereport(NOTICE, |
| 259 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
| 260 | errmsg("enum label \"%s\" already exists, skipping" , |
| 261 | newVal))); |
| 262 | return; |
| 263 | } |
| 264 | else |
| 265 | ereport(ERROR, |
| 266 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
| 267 | errmsg("enum label \"%s\" already exists" , |
| 268 | newVal))); |
| 269 | } |
| 270 | |
| 271 | pg_enum = table_open(EnumRelationId, RowExclusiveLock); |
| 272 | |
| 273 | /* If we have to renumber the existing members, we restart from here */ |
| 274 | restart: |
| 275 | |
| 276 | /* Get the list of existing members of the enum */ |
| 277 | list = SearchSysCacheList1(ENUMTYPOIDNAME, |
| 278 | ObjectIdGetDatum(enumTypeOid)); |
| 279 | nelems = list->n_members; |
| 280 | |
| 281 | /* Sort the existing members by enumsortorder */ |
| 282 | existing = (HeapTuple *) palloc(nelems * sizeof(HeapTuple)); |
| 283 | for (i = 0; i < nelems; i++) |
| 284 | existing[i] = &(list->members[i]->tuple); |
| 285 | |
| 286 | qsort(existing, nelems, sizeof(HeapTuple), sort_order_cmp); |
| 287 | |
| 288 | if (neighbor == NULL) |
| 289 | { |
| 290 | /* |
| 291 | * Put the new label at the end of the list. No change to existing |
| 292 | * tuples is required. |
| 293 | */ |
| 294 | if (nelems > 0) |
| 295 | { |
| 296 | Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nelems - 1]); |
| 297 | |
| 298 | newelemorder = en->enumsortorder + 1; |
| 299 | } |
| 300 | else |
| 301 | newelemorder = 1; |
| 302 | } |
| 303 | else |
| 304 | { |
| 305 | /* BEFORE or AFTER was specified */ |
| 306 | int nbr_index; |
| 307 | int other_nbr_index; |
| 308 | Form_pg_enum nbr_en; |
| 309 | Form_pg_enum other_nbr_en; |
| 310 | |
| 311 | /* Locate the neighbor element */ |
| 312 | for (nbr_index = 0; nbr_index < nelems; nbr_index++) |
| 313 | { |
| 314 | Form_pg_enum en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]); |
| 315 | |
| 316 | if (strcmp(NameStr(en->enumlabel), neighbor) == 0) |
| 317 | break; |
| 318 | } |
| 319 | if (nbr_index >= nelems) |
| 320 | ereport(ERROR, |
| 321 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 322 | errmsg("\"%s\" is not an existing enum label" , |
| 323 | neighbor))); |
| 324 | nbr_en = (Form_pg_enum) GETSTRUCT(existing[nbr_index]); |
| 325 | |
| 326 | /* |
| 327 | * Attempt to assign an appropriate enumsortorder value: one less than |
| 328 | * the smallest member, one more than the largest member, or halfway |
| 329 | * between two existing members. |
| 330 | * |
| 331 | * In the "halfway" case, because of the finite precision of float4, |
| 332 | * we might compute a value that's actually equal to one or the other |
| 333 | * of its neighbors. In that case we renumber the existing members |
| 334 | * and try again. |
| 335 | */ |
| 336 | if (newValIsAfter) |
| 337 | other_nbr_index = nbr_index + 1; |
| 338 | else |
| 339 | other_nbr_index = nbr_index - 1; |
| 340 | |
| 341 | if (other_nbr_index < 0) |
| 342 | newelemorder = nbr_en->enumsortorder - 1; |
| 343 | else if (other_nbr_index >= nelems) |
| 344 | newelemorder = nbr_en->enumsortorder + 1; |
| 345 | else |
| 346 | { |
| 347 | /* |
| 348 | * The midpoint value computed here has to be rounded to float4 |
| 349 | * precision, else our equality comparisons against the adjacent |
| 350 | * values are meaningless. The most portable way of forcing that |
| 351 | * to happen with non-C-standard-compliant compilers is to store |
| 352 | * it into a volatile variable. |
| 353 | */ |
| 354 | volatile float4 midpoint; |
| 355 | |
| 356 | other_nbr_en = (Form_pg_enum) GETSTRUCT(existing[other_nbr_index]); |
| 357 | midpoint = (nbr_en->enumsortorder + |
| 358 | other_nbr_en->enumsortorder) / 2; |
| 359 | |
| 360 | if (midpoint == nbr_en->enumsortorder || |
| 361 | midpoint == other_nbr_en->enumsortorder) |
| 362 | { |
| 363 | RenumberEnumType(pg_enum, existing, nelems); |
| 364 | /* Clean up and start over */ |
| 365 | pfree(existing); |
| 366 | ReleaseCatCacheList(list); |
| 367 | goto restart; |
| 368 | } |
| 369 | |
| 370 | newelemorder = midpoint; |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | /* Get a new OID for the new label */ |
| 375 | if (IsBinaryUpgrade) |
| 376 | { |
| 377 | if (!OidIsValid(binary_upgrade_next_pg_enum_oid)) |
| 378 | ereport(ERROR, |
| 379 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 380 | errmsg("pg_enum OID value not set when in binary upgrade mode" ))); |
| 381 | |
| 382 | /* |
| 383 | * Use binary-upgrade override for pg_enum.oid, if supplied. During |
| 384 | * binary upgrade, all pg_enum.oid's are set this way so they are |
| 385 | * guaranteed to be consistent. |
| 386 | */ |
| 387 | if (neighbor != NULL) |
| 388 | ereport(ERROR, |
| 389 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 390 | errmsg("ALTER TYPE ADD BEFORE/AFTER is incompatible with binary upgrade" ))); |
| 391 | |
| 392 | newOid = binary_upgrade_next_pg_enum_oid; |
| 393 | binary_upgrade_next_pg_enum_oid = InvalidOid; |
| 394 | } |
| 395 | else |
| 396 | { |
| 397 | /* |
| 398 | * Normal case: we need to allocate a new Oid for the value. |
| 399 | * |
| 400 | * We want to give the new element an even-numbered Oid if it's safe, |
| 401 | * which is to say it compares correctly to all pre-existing even |
| 402 | * numbered Oids in the enum. Otherwise, we must give it an odd Oid. |
| 403 | */ |
| 404 | for (;;) |
| 405 | { |
| 406 | bool sorts_ok; |
| 407 | |
| 408 | /* Get a new OID (different from all existing pg_enum tuples) */ |
| 409 | newOid = GetNewOidWithIndex(pg_enum, EnumOidIndexId, |
| 410 | Anum_pg_enum_oid); |
| 411 | |
| 412 | /* |
| 413 | * Detect whether it sorts correctly relative to existing |
| 414 | * even-numbered labels of the enum. We can ignore existing |
| 415 | * labels with odd Oids, since a comparison involving one of those |
| 416 | * will not take the fast path anyway. |
| 417 | */ |
| 418 | sorts_ok = true; |
| 419 | for (i = 0; i < nelems; i++) |
| 420 | { |
| 421 | HeapTuple exists_tup = existing[i]; |
| 422 | Form_pg_enum exists_en = (Form_pg_enum) GETSTRUCT(exists_tup); |
| 423 | Oid exists_oid = exists_en->oid; |
| 424 | |
| 425 | if (exists_oid & 1) |
| 426 | continue; /* ignore odd Oids */ |
| 427 | |
| 428 | if (exists_en->enumsortorder < newelemorder) |
| 429 | { |
| 430 | /* should sort before */ |
| 431 | if (exists_oid >= newOid) |
| 432 | { |
| 433 | sorts_ok = false; |
| 434 | break; |
| 435 | } |
| 436 | } |
| 437 | else |
| 438 | { |
| 439 | /* should sort after */ |
| 440 | if (exists_oid <= newOid) |
| 441 | { |
| 442 | sorts_ok = false; |
| 443 | break; |
| 444 | } |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | if (sorts_ok) |
| 449 | { |
| 450 | /* If it's even and sorts OK, we're done. */ |
| 451 | if ((newOid & 1) == 0) |
| 452 | break; |
| 453 | |
| 454 | /* |
| 455 | * If it's odd, and sorts OK, loop back to get another OID and |
| 456 | * try again. Probably, the next available even OID will sort |
| 457 | * correctly too, so it's worth trying. |
| 458 | */ |
| 459 | } |
| 460 | else |
| 461 | { |
| 462 | /* |
| 463 | * If it's odd, and does not sort correctly, we're done. |
| 464 | * (Probably, the next available even OID would sort |
| 465 | * incorrectly too, so no point in trying again.) |
| 466 | */ |
| 467 | if (newOid & 1) |
| 468 | break; |
| 469 | |
| 470 | /* |
| 471 | * If it's even, and does not sort correctly, loop back to get |
| 472 | * another OID and try again. (We *must* reject this case.) |
| 473 | */ |
| 474 | } |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | /* Done with info about existing members */ |
| 479 | pfree(existing); |
| 480 | ReleaseCatCacheList(list); |
| 481 | |
| 482 | /* Create the new pg_enum entry */ |
| 483 | memset(nulls, false, sizeof(nulls)); |
| 484 | values[Anum_pg_enum_oid - 1] = ObjectIdGetDatum(newOid); |
| 485 | values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid); |
| 486 | values[Anum_pg_enum_enumsortorder - 1] = Float4GetDatum(newelemorder); |
| 487 | namestrcpy(&enumlabel, newVal); |
| 488 | values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel); |
| 489 | enum_tup = heap_form_tuple(RelationGetDescr(pg_enum), values, nulls); |
| 490 | CatalogTupleInsert(pg_enum, enum_tup); |
| 491 | heap_freetuple(enum_tup); |
| 492 | |
| 493 | table_close(pg_enum, RowExclusiveLock); |
| 494 | |
| 495 | /* Set up the blacklist hash if not already done in this transaction */ |
| 496 | if (enum_blacklist == NULL) |
| 497 | init_enum_blacklist(); |
| 498 | |
| 499 | /* Add the new value to the blacklist */ |
| 500 | (void) hash_search(enum_blacklist, &newOid, HASH_ENTER, NULL); |
| 501 | } |
| 502 | |
| 503 | |
| 504 | /* |
| 505 | * RenameEnumLabel |
| 506 | * Rename a label in an enum set. |
| 507 | */ |
| 508 | void |
| 509 | RenameEnumLabel(Oid enumTypeOid, |
| 510 | const char *oldVal, |
| 511 | const char *newVal) |
| 512 | { |
| 513 | Relation pg_enum; |
| 514 | HeapTuple enum_tup; |
| 515 | Form_pg_enum en; |
| 516 | CatCList *list; |
| 517 | int nelems; |
| 518 | HeapTuple old_tup; |
| 519 | bool found_new; |
| 520 | int i; |
| 521 | |
| 522 | /* check length of new label is ok */ |
| 523 | if (strlen(newVal) > (NAMEDATALEN - 1)) |
| 524 | ereport(ERROR, |
| 525 | (errcode(ERRCODE_INVALID_NAME), |
| 526 | errmsg("invalid enum label \"%s\"" , newVal), |
| 527 | errdetail("Labels must be %d characters or less." , |
| 528 | NAMEDATALEN - 1))); |
| 529 | |
| 530 | /* |
| 531 | * Acquire a lock on the enum type, which we won't release until commit. |
| 532 | * This ensures that two backends aren't concurrently modifying the same |
| 533 | * enum type. Since we are not changing the type's sort order, this is |
| 534 | * probably not really necessary, but there seems no reason not to take |
| 535 | * the lock to be sure. |
| 536 | */ |
| 537 | LockDatabaseObject(TypeRelationId, enumTypeOid, 0, ExclusiveLock); |
| 538 | |
| 539 | pg_enum = table_open(EnumRelationId, RowExclusiveLock); |
| 540 | |
| 541 | /* Get the list of existing members of the enum */ |
| 542 | list = SearchSysCacheList1(ENUMTYPOIDNAME, |
| 543 | ObjectIdGetDatum(enumTypeOid)); |
| 544 | nelems = list->n_members; |
| 545 | |
| 546 | /* |
| 547 | * Locate the element to rename and check if the new label is already in |
| 548 | * use. (The unique index on pg_enum would catch that anyway, but we |
| 549 | * prefer a friendlier error message.) |
| 550 | */ |
| 551 | old_tup = NULL; |
| 552 | found_new = false; |
| 553 | for (i = 0; i < nelems; i++) |
| 554 | { |
| 555 | enum_tup = &(list->members[i]->tuple); |
| 556 | en = (Form_pg_enum) GETSTRUCT(enum_tup); |
| 557 | if (strcmp(NameStr(en->enumlabel), oldVal) == 0) |
| 558 | old_tup = enum_tup; |
| 559 | if (strcmp(NameStr(en->enumlabel), newVal) == 0) |
| 560 | found_new = true; |
| 561 | } |
| 562 | if (!old_tup) |
| 563 | ereport(ERROR, |
| 564 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 565 | errmsg("\"%s\" is not an existing enum label" , |
| 566 | oldVal))); |
| 567 | if (found_new) |
| 568 | ereport(ERROR, |
| 569 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
| 570 | errmsg("enum label \"%s\" already exists" , |
| 571 | newVal))); |
| 572 | |
| 573 | /* OK, make a writable copy of old tuple */ |
| 574 | enum_tup = heap_copytuple(old_tup); |
| 575 | en = (Form_pg_enum) GETSTRUCT(enum_tup); |
| 576 | |
| 577 | ReleaseCatCacheList(list); |
| 578 | |
| 579 | /* Update the pg_enum entry */ |
| 580 | namestrcpy(&en->enumlabel, newVal); |
| 581 | CatalogTupleUpdate(pg_enum, &enum_tup->t_self, enum_tup); |
| 582 | heap_freetuple(enum_tup); |
| 583 | |
| 584 | table_close(pg_enum, RowExclusiveLock); |
| 585 | } |
| 586 | |
| 587 | |
| 588 | /* |
| 589 | * Test if the given enum value is on the blacklist |
| 590 | */ |
| 591 | bool |
| 592 | EnumBlacklisted(Oid enum_id) |
| 593 | { |
| 594 | bool found; |
| 595 | |
| 596 | /* If we've made no blacklist table, all values are safe */ |
| 597 | if (enum_blacklist == NULL) |
| 598 | return false; |
| 599 | |
| 600 | /* Else, is it in the table? */ |
| 601 | (void) hash_search(enum_blacklist, &enum_id, HASH_FIND, &found); |
| 602 | return found; |
| 603 | } |
| 604 | |
| 605 | |
| 606 | /* |
| 607 | * Clean up enum stuff after end of top-level transaction. |
| 608 | */ |
| 609 | void |
| 610 | AtEOXact_Enum(void) |
| 611 | { |
| 612 | /* |
| 613 | * Reset the blacklist table, as all our enum values are now committed. |
| 614 | * The memory will go away automatically when TopTransactionContext is |
| 615 | * freed; it's sufficient to clear our pointer. |
| 616 | */ |
| 617 | enum_blacklist = NULL; |
| 618 | } |
| 619 | |
| 620 | |
| 621 | /* |
| 622 | * RenumberEnumType |
| 623 | * Renumber existing enum elements to have sort positions 1..n. |
| 624 | * |
| 625 | * We avoid doing this unless absolutely necessary; in most installations |
| 626 | * it will never happen. The reason is that updating existing pg_enum |
| 627 | * entries creates hazards for other backends that are concurrently reading |
| 628 | * pg_enum. Although system catalog scans now use MVCC semantics, the |
| 629 | * syscache machinery might read different pg_enum entries under different |
| 630 | * snapshots, so some other backend might get confused about the proper |
| 631 | * ordering if a concurrent renumbering occurs. |
| 632 | * |
| 633 | * We therefore make the following choices: |
| 634 | * |
| 635 | * 1. Any code that is interested in the enumsortorder values MUST read |
| 636 | * all the relevant pg_enum entries with a single MVCC snapshot, or else |
| 637 | * acquire lock on the enum type to prevent concurrent execution of |
| 638 | * AddEnumLabel(). |
| 639 | * |
| 640 | * 2. Code that is not examining enumsortorder can use a syscache |
| 641 | * (for example, enum_in and enum_out do so). |
| 642 | */ |
| 643 | static void |
| 644 | RenumberEnumType(Relation pg_enum, HeapTuple *existing, int nelems) |
| 645 | { |
| 646 | int i; |
| 647 | |
| 648 | /* |
| 649 | * We should only need to increase existing elements' enumsortorders, |
| 650 | * never decrease them. Therefore, work from the end backwards, to avoid |
| 651 | * unwanted uniqueness violations. |
| 652 | */ |
| 653 | for (i = nelems - 1; i >= 0; i--) |
| 654 | { |
| 655 | HeapTuple newtup; |
| 656 | Form_pg_enum en; |
| 657 | float4 newsortorder; |
| 658 | |
| 659 | newtup = heap_copytuple(existing[i]); |
| 660 | en = (Form_pg_enum) GETSTRUCT(newtup); |
| 661 | |
| 662 | newsortorder = i + 1; |
| 663 | if (en->enumsortorder != newsortorder) |
| 664 | { |
| 665 | en->enumsortorder = newsortorder; |
| 666 | |
| 667 | CatalogTupleUpdate(pg_enum, &newtup->t_self, newtup); |
| 668 | } |
| 669 | |
| 670 | heap_freetuple(newtup); |
| 671 | } |
| 672 | |
| 673 | /* Make the updates visible */ |
| 674 | CommandCounterIncrement(); |
| 675 | } |
| 676 | |
| 677 | |
| 678 | /* qsort comparison function for tuples by sort order */ |
| 679 | static int |
| 680 | sort_order_cmp(const void *p1, const void *p2) |
| 681 | { |
| 682 | HeapTuple v1 = *((const HeapTuple *) p1); |
| 683 | HeapTuple v2 = *((const HeapTuple *) p2); |
| 684 | Form_pg_enum en1 = (Form_pg_enum) GETSTRUCT(v1); |
| 685 | Form_pg_enum en2 = (Form_pg_enum) GETSTRUCT(v2); |
| 686 | |
| 687 | if (en1->enumsortorder < en2->enumsortorder) |
| 688 | return -1; |
| 689 | else if (en1->enumsortorder > en2->enumsortorder) |
| 690 | return 1; |
| 691 | else |
| 692 | return 0; |
| 693 | } |
| 694 | |
| 695 | Size |
| 696 | EstimateEnumBlacklistSpace(void) |
| 697 | { |
| 698 | size_t entries; |
| 699 | |
| 700 | if (enum_blacklist) |
| 701 | entries = hash_get_num_entries(enum_blacklist); |
| 702 | else |
| 703 | entries = 0; |
| 704 | |
| 705 | /* Add one for the terminator. */ |
| 706 | return sizeof(Oid) * (entries + 1); |
| 707 | } |
| 708 | |
| 709 | void |
| 710 | SerializeEnumBlacklist(void *space, Size size) |
| 711 | { |
| 712 | Oid *serialized = (Oid *) space; |
| 713 | |
| 714 | /* |
| 715 | * Make sure the hash table hasn't changed in size since the caller |
| 716 | * reserved the space. |
| 717 | */ |
| 718 | Assert(size == EstimateEnumBlacklistSpace()); |
| 719 | |
| 720 | /* Write out all the values from the hash table, if there is one. */ |
| 721 | if (enum_blacklist) |
| 722 | { |
| 723 | HASH_SEQ_STATUS status; |
| 724 | Oid *value; |
| 725 | |
| 726 | hash_seq_init(&status, enum_blacklist); |
| 727 | while ((value = (Oid *) hash_seq_search(&status))) |
| 728 | *serialized++ = *value; |
| 729 | } |
| 730 | |
| 731 | /* Write out the terminator. */ |
| 732 | *serialized = InvalidOid; |
| 733 | |
| 734 | /* |
| 735 | * Make sure the amount of space we actually used matches what was |
| 736 | * estimated. |
| 737 | */ |
| 738 | Assert((char *) (serialized + 1) == ((char *) space) + size); |
| 739 | } |
| 740 | |
| 741 | void |
| 742 | RestoreEnumBlacklist(void *space) |
| 743 | { |
| 744 | Oid *serialized = (Oid *) space; |
| 745 | |
| 746 | Assert(!enum_blacklist); |
| 747 | |
| 748 | /* |
| 749 | * As a special case, if the list is empty then don't even bother to |
| 750 | * create the hash table. This is the usual case, since enum alteration |
| 751 | * is expected to be rare. |
| 752 | */ |
| 753 | if (!OidIsValid(*serialized)) |
| 754 | return; |
| 755 | |
| 756 | /* Read all the values into a new hash table. */ |
| 757 | init_enum_blacklist(); |
| 758 | do |
| 759 | { |
| 760 | hash_search(enum_blacklist, serialized++, HASH_ENTER, NULL); |
| 761 | } while (OidIsValid(*serialized)); |
| 762 | } |
| 763 | |