Improve error message for duplicate labels when creating an enum type.

Previously, duplicate labels in CREATE TYPE AS ENUM were caught by
the unique index on pg_enum, resulting in a generic error message.
While this was evidently intentional, it's not terribly user-friendly,
nor consistent with the ALTER TYPE cases which take more care with
such errors.  This patch adds an explicit check to produce a more
user-friendly and descriptive error message.

A potential objection to this implementation is that it adds O(N^2)
work to the creation operation.  However, quick testing finds that
that's pretty negligible below 1000 enum labels, and tolerable even
at 10000.  So it doesn't really seem worth being smarter.

Author: Yugo Nagata <nagata@sraoss.co.jp>
Reviewed-by: Rahila Syed <rahilasyed90@gmail.com>
Reviewed-by: Jim Jones <jim.jones@uni-muenster.de>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/20250704000402.37e605ab0c59c300965a17ee@sraoss.co.jp
master
Tom Lane 1 week ago
parent eccba079c2
commit 1b1960c8c9
  1. 25
      src/backend/catalog/pg_enum.c
  2. 3
      src/test/regress/expected/enum.out
  3. 3
      src/test/regress/sql/enum.sql

@ -110,12 +110,6 @@ EnumValuesCreate(Oid enumTypeOid, List *vals)
num_elems = list_length(vals); num_elems = list_length(vals);
/*
* We do not bother to check the list of values for duplicates --- if you
* have any, you'll get a less-than-friendly unique-index violation. It is
* probably not worth trying harder.
*/
pg_enum = table_open(EnumRelationId, RowExclusiveLock); pg_enum = table_open(EnumRelationId, RowExclusiveLock);
/* /*
@ -164,6 +158,7 @@ EnumValuesCreate(Oid enumTypeOid, List *vals)
{ {
char *lab = strVal(lfirst(lc)); char *lab = strVal(lfirst(lc));
Name enumlabel = palloc0(NAMEDATALEN); Name enumlabel = palloc0(NAMEDATALEN);
ListCell *lc2;
/* /*
* labels are stored in a name field, for easier syscache lookup, so * labels are stored in a name field, for easier syscache lookup, so
@ -176,6 +171,24 @@ EnumValuesCreate(Oid enumTypeOid, List *vals)
errdetail("Labels must be %d bytes or less.", errdetail("Labels must be %d bytes or less.",
NAMEDATALEN - 1))); NAMEDATALEN - 1)));
/*
* Check for duplicate labels. The unique index on pg_enum would catch
* that anyway, but we prefer a friendlier error message.
*/
foreach(lc2, vals)
{
/* Only need to compare lc to earlier entries */
if (lc2 == lc)
break;
if (strcmp(lab, strVal(lfirst(lc2))) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("enum label \"%s\" used more than once",
lab)));
}
/* OK, construct a tuple for this label */
ExecClearTuple(slot[slotCount]); ExecClearTuple(slot[slotCount]);
memset(slot[slotCount]->tts_isnull, false, memset(slot[slotCount]->tts_isnull, false,

@ -52,6 +52,9 @@ hint |
sql_error_code | 22P02 sql_error_code | 22P02
\x \x
-- check for duplicate enum entries
CREATE TYPE dup_enum AS ENUM ('foo','bar','foo');
ERROR: enum label "foo" used more than once
-- --
-- adding new values -- adding new values
-- --

@ -23,6 +23,9 @@ SELECT * FROM pg_input_error_info('mauve', 'rainbow');
SELECT * FROM pg_input_error_info(repeat('too_long', 32), 'rainbow'); SELECT * FROM pg_input_error_info(repeat('too_long', 32), 'rainbow');
\x \x
-- check for duplicate enum entries
CREATE TYPE dup_enum AS ENUM ('foo','bar','foo');
-- --
-- adding new values -- adding new values
-- --

Loading…
Cancel
Save