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 | |