Commit 84c123be authored by Tom Lane's avatar Tom Lane

Allow new values to be added to an existing enum type.

After much expenditure of effort, we've got this to the point where the
performance penalty is pretty minimal in typical cases.

Andrew Dunstan, reviewed by Brendan Jurd, Dean Rasheed, and Tom Lane
parent 24b29ca8
...@@ -79,7 +79,7 @@ install_support_functions(void) ...@@ -79,7 +79,7 @@ install_support_functions(void)
"LANGUAGE C STRICT;")); "LANGUAGE C STRICT;"));
PQclear(executeQueryOrDie(conn, PQclear(executeQueryOrDie(conn,
"CREATE OR REPLACE FUNCTION " "CREATE OR REPLACE FUNCTION "
" binary_upgrade.add_pg_enum_label(OID, OID, NAME) " " binary_upgrade.set_next_pg_enum_oid(OID) "
"RETURNS VOID " "RETURNS VOID "
"AS '$libdir/pg_upgrade_support' " "AS '$libdir/pg_upgrade_support' "
"LANGUAGE C STRICT;")); "LANGUAGE C STRICT;"));
......
...@@ -16,13 +16,6 @@ ...@@ -16,13 +16,6 @@
/* THIS IS USED ONLY FOR PG >= 9.0 */ /* THIS IS USED ONLY FOR PG >= 9.0 */
/*
* Cannot include "catalog/pg_enum.h" here because we might
* not be compiling against PG 9.0.
*/
extern void EnumValuesCreate(Oid enumTypeOid, List *vals,
Oid binary_upgrade_next_pg_enum_oid);
#ifdef PG_MODULE_MAGIC #ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC; PG_MODULE_MAGIC;
#endif #endif
...@@ -33,6 +26,7 @@ extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid; ...@@ -33,6 +26,7 @@ extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_toast_oid;
extern PGDLLIMPORT Oid binary_upgrade_next_heap_relfilenode; extern PGDLLIMPORT Oid binary_upgrade_next_heap_relfilenode;
extern PGDLLIMPORT Oid binary_upgrade_next_toast_relfilenode; extern PGDLLIMPORT Oid binary_upgrade_next_toast_relfilenode;
extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode; extern PGDLLIMPORT Oid binary_upgrade_next_index_relfilenode;
extern PGDLLIMPORT Oid binary_upgrade_next_pg_enum_oid;
Datum set_next_pg_type_oid(PG_FUNCTION_ARGS); Datum set_next_pg_type_oid(PG_FUNCTION_ARGS);
Datum set_next_pg_type_array_oid(PG_FUNCTION_ARGS); Datum set_next_pg_type_array_oid(PG_FUNCTION_ARGS);
...@@ -40,7 +34,7 @@ Datum set_next_pg_type_toast_oid(PG_FUNCTION_ARGS); ...@@ -40,7 +34,7 @@ Datum set_next_pg_type_toast_oid(PG_FUNCTION_ARGS);
Datum set_next_heap_relfilenode(PG_FUNCTION_ARGS); Datum set_next_heap_relfilenode(PG_FUNCTION_ARGS);
Datum set_next_toast_relfilenode(PG_FUNCTION_ARGS); Datum set_next_toast_relfilenode(PG_FUNCTION_ARGS);
Datum set_next_index_relfilenode(PG_FUNCTION_ARGS); Datum set_next_index_relfilenode(PG_FUNCTION_ARGS);
Datum add_pg_enum_label(PG_FUNCTION_ARGS); Datum set_next_pg_enum_oid(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(set_next_pg_type_oid); PG_FUNCTION_INFO_V1(set_next_pg_type_oid);
PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid); PG_FUNCTION_INFO_V1(set_next_pg_type_array_oid);
...@@ -48,7 +42,7 @@ PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid); ...@@ -48,7 +42,7 @@ PG_FUNCTION_INFO_V1(set_next_pg_type_toast_oid);
PG_FUNCTION_INFO_V1(set_next_heap_relfilenode); PG_FUNCTION_INFO_V1(set_next_heap_relfilenode);
PG_FUNCTION_INFO_V1(set_next_toast_relfilenode); PG_FUNCTION_INFO_V1(set_next_toast_relfilenode);
PG_FUNCTION_INFO_V1(set_next_index_relfilenode); PG_FUNCTION_INFO_V1(set_next_index_relfilenode);
PG_FUNCTION_INFO_V1(add_pg_enum_label); PG_FUNCTION_INFO_V1(set_next_pg_enum_oid);
Datum Datum
set_next_pg_type_oid(PG_FUNCTION_ARGS) set_next_pg_type_oid(PG_FUNCTION_ARGS)
...@@ -111,14 +105,11 @@ set_next_index_relfilenode(PG_FUNCTION_ARGS) ...@@ -111,14 +105,11 @@ set_next_index_relfilenode(PG_FUNCTION_ARGS)
} }
Datum Datum
add_pg_enum_label(PG_FUNCTION_ARGS) set_next_pg_enum_oid(PG_FUNCTION_ARGS)
{ {
Oid enumoid = PG_GETARG_OID(0); Oid enumoid = PG_GETARG_OID(0);
Oid typoid = PG_GETARG_OID(1);
Name label = PG_GETARG_NAME(2);
EnumValuesCreate(typoid, list_make1(makeString(NameStr(*label))), binary_upgrade_next_pg_enum_oid = enumoid;
enumoid);
PG_RETURN_VOID(); PG_RETURN_VOID();
} }
...@@ -2623,12 +2623,9 @@ ...@@ -2623,12 +2623,9 @@
<para> <para>
The <structname>pg_enum</structname> catalog contains entries The <structname>pg_enum</structname> catalog contains entries
matching enum types to their associated values and labels. The showing the values and labels for each enum type. The
internal representation of a given enum value is actually the OID internal representation of a given enum value is actually the OID
of its associated row in <structname>pg_enum</structname>. The of its associated row in <structname>pg_enum</structname>.
OIDs for a particular enum type are guaranteed to be ordered in
the way the type should sort, but there is no guarantee about the
ordering of OIDs of unrelated enum types.
</para> </para>
<table> <table>
...@@ -2652,6 +2649,13 @@ ...@@ -2652,6 +2649,13 @@
<entry>The OID of the <structname>pg_type</> entry owning this enum value</entry> <entry>The OID of the <structname>pg_type</> entry owning this enum value</entry>
</row> </row>
<row>
<entry><structfield>enumsortorder</structfield></entry>
<entry><type>float4</type></entry>
<entry></entry>
<entry>The sort position of this enum value within its enum type</entry>
</row>
<row> <row>
<entry><structfield>enumlabel</structfield></entry> <entry><structfield>enumlabel</structfield></entry>
<entry><type>name</type></entry> <entry><type>name</type></entry>
...@@ -2661,6 +2665,26 @@ ...@@ -2661,6 +2665,26 @@
</tbody> </tbody>
</tgroup> </tgroup>
</table> </table>
<para>
The OIDs for <structname>pg_enum</structname> rows follow a special
rule: even-numbered OIDs are guaranteed to be ordered in the same way
as the sort ordering of their enum type. That is, if two even OIDs
belong to the same enum type, the smaller OID must have the smaller
<structfield>enumsortorder</structfield> value. Odd-numbered OID values
need bear no relationship to the sort order. This rule allows the
enum comparison routines to avoid catalog lookups in many common cases.
The routines that create and alter enum types attempt to assign even
OIDs to enum values whenever possible.
</para>
<para>
When an enum type is created, its members are assigned sort-order
positions 1..<replaceable>n</>. But members added later might be given
negative or fractional values of <structfield>enumsortorder</structfield>.
The only requirement on these values is that they be correctly
ordered and unique within each enum type.
</para>
</sect1> </sect1>
......
...@@ -28,6 +28,7 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> OWNER TO <replaceab ...@@ -28,6 +28,7 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> OWNER TO <replaceab
ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME ATTRIBUTE <replaceable class="PARAMETER">attribute_name</replaceable> TO <replaceable class="PARAMETER">new_attribute_name</replaceable> ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME ATTRIBUTE <replaceable class="PARAMETER">attribute_name</replaceable> TO <replaceable class="PARAMETER">new_attribute_name</replaceable>
ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable> ALTER TYPE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable> ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
ALTER TYPE <replaceable class="PARAMETER">name</replaceable> ADD <replaceable class="PARAMETER">new_enum_value</replaceable> [ { BEFORE | AFTER } <replaceable class="PARAMETER">existing_enum_value</replaceable> ]
<phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase> <phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
...@@ -103,6 +104,18 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace ...@@ -103,6 +104,18 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><literal>ADD [ BEFORE | AFTER ]</literal></term>
<listitem>
<para>
This form adds a new value to an enum type. If the new value's place in
the enum's ordering is not specified using <literal>BEFORE</literal> or
<literal>AFTER</literal>, then the new item is placed at the end of the
list of values.
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</para> </para>
...@@ -181,7 +194,7 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace ...@@ -181,7 +194,7 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
<term><replaceable class="PARAMETER">new_attribute_name</replaceable></term> <term><replaceable class="PARAMETER">new_attribute_name</replaceable></term>
<listitem> <listitem>
<para> <para>
The new name of the attribute begin renamed. The new name of the attribute to be renamed.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -196,10 +209,53 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace ...@@ -196,10 +209,53 @@ ALTER TYPE <replaceable class="PARAMETER">name</replaceable> SET SCHEMA <replace
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">new_enum_value</replaceable></term>
<listitem>
<para>
The new value to be added to an enum type's list of values.
Like all enum literals, it needs to be quoted.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">existing_enum_value</replaceable></term>
<listitem>
<para>
The existing enum value that the new value should be added immediately
before or after in the enum type's sort ordering.
Like all enum literals, it needs to be quoted.
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
</para> </para>
</refsect1> </refsect1>
<refsect1>
<title>Notes</title>
<para>
<command>ALTER TYPE ... ADD</> (the form that adds a new value to an
enum type) cannot be executed inside a transaction block.
</para>
<para>
Comparisons involving an added enum value will sometimes be slower than
comparisons involving only original members of the enum type. This will
usually only occur if <literal>BEFORE</literal> or <literal>AFTER</literal>
is used to set the new value's sort position somewhere other than at the
end of the list. However, sometimes it will happen even though the new
value is added at the end (this occurs if the OID counter <quote>wrapped
around</> since the original creation of the enum type). The slowdown is
usually insignificant; but if it matters, optimal performance can be
regained by dropping and recreating the enum type, or by dumping and
reloading the database.
</para>
</refsect1>
<refsect1> <refsect1>
<title>Examples</title> <title>Examples</title>
...@@ -230,6 +286,13 @@ ALTER TYPE email SET SCHEMA customers; ...@@ -230,6 +286,13 @@ ALTER TYPE email SET SCHEMA customers;
To add a new attribute to a type: To add a new attribute to a type:
<programlisting> <programlisting>
ALTER TYPE compfoo ADD ATTRIBUTE f3 int; ALTER TYPE compfoo ADD ATTRIBUTE f3 int;
</programlisting>
</para>
<para>
To add a new value to an enum type in a particular sort position:
<programlisting>
ALTER TYPE colors ADD 'orange' AFTER 'red';
</programlisting> </programlisting>
</para> </para>
</refsect1> </refsect1>
......
This diff is collapsed.
...@@ -84,7 +84,8 @@ static Oid findTypeTypmodinFunction(List *procname); ...@@ -84,7 +84,8 @@ static Oid findTypeTypmodinFunction(List *procname);
static Oid findTypeTypmodoutFunction(List *procname); static Oid findTypeTypmodoutFunction(List *procname);
static Oid findTypeAnalyzeFunction(List *procname, Oid typeOid); static Oid findTypeAnalyzeFunction(List *procname, Oid typeOid);
static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode); static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
static void checkDomainOwner(HeapTuple tup, TypeName *typename); static void checkDomainOwner(HeapTuple tup);
static void checkEnumOwner(HeapTuple tup);
static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
Oid baseTypeOid, Oid baseTypeOid,
int typMod, Constraint *constr, int typMod, Constraint *constr,
...@@ -1150,7 +1151,7 @@ DefineEnum(CreateEnumStmt *stmt) ...@@ -1150,7 +1151,7 @@ DefineEnum(CreateEnumStmt *stmt)
false); /* Type NOT NULL */ false); /* Type NOT NULL */
/* Enter the enum's values into pg_enum */ /* Enter the enum's values into pg_enum */
EnumValuesCreate(enumTypeOid, stmt->vals, InvalidOid); EnumValuesCreate(enumTypeOid, stmt->vals);
/* /*
* Create the array type that goes with it. * Create the array type that goes with it.
...@@ -1191,6 +1192,60 @@ DefineEnum(CreateEnumStmt *stmt) ...@@ -1191,6 +1192,60 @@ DefineEnum(CreateEnumStmt *stmt)
pfree(enumArrayName); pfree(enumArrayName);
} }
/*
* AlterEnum
* Adds a new label to an existing enum.
*/
void
AlterEnum(AlterEnumStmt *stmt)
{
Oid enum_type_oid;
TypeName *typename;
HeapTuple tup;
/* Make a TypeName so we can use standard type lookup machinery */
typename = makeTypeNameFromNameList(stmt->typeName);
enum_type_oid = typenameTypeId(NULL, typename, NULL);
tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(enum_type_oid));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for type %u", enum_type_oid);
/* Check it's an enum and check user has permission to ALTER the enum */
checkEnumOwner(tup);
/* Add the new label */
AddEnumLabel(enum_type_oid, stmt->newVal,
stmt->newValNeighbor, stmt->newValIsAfter);
ReleaseSysCache(tup);
}
/*
* checkEnumOwner
*
* Check that the type is actually an enum and that the current user
* has permission to do ALTER TYPE on it. Throw an error if not.
*/
static void
checkEnumOwner(HeapTuple tup)
{
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
/* Check that this is actually an enum */
if (typTup->typtype != TYPTYPE_ENUM)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("%s is not an enum",
format_type_be(HeapTupleGetOid(tup)))));
/* Permission check: must own type */
if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
format_type_be(HeapTupleGetOid(tup)));
}
/* /*
* Find suitable I/O functions for a type. * Find suitable I/O functions for a type.
...@@ -1576,7 +1631,7 @@ AlterDomainDefault(List *names, Node *defaultRaw) ...@@ -1576,7 +1631,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
typTup = (Form_pg_type) GETSTRUCT(tup); typTup = (Form_pg_type) GETSTRUCT(tup);
/* Check it's a domain and check user has permission for ALTER DOMAIN */ /* Check it's a domain and check user has permission for ALTER DOMAIN */
checkDomainOwner(tup, typename); checkDomainOwner(tup);
/* Setup new tuple */ /* Setup new tuple */
MemSet(new_record, (Datum) 0, sizeof(new_record)); MemSet(new_record, (Datum) 0, sizeof(new_record));
...@@ -1702,7 +1757,7 @@ AlterDomainNotNull(List *names, bool notNull) ...@@ -1702,7 +1757,7 @@ AlterDomainNotNull(List *names, bool notNull)
typTup = (Form_pg_type) GETSTRUCT(tup); typTup = (Form_pg_type) GETSTRUCT(tup);
/* Check it's a domain and check user has permission for ALTER DOMAIN */ /* Check it's a domain and check user has permission for ALTER DOMAIN */
checkDomainOwner(tup, typename); checkDomainOwner(tup);
/* Is the domain already set to the desired constraint? */ /* Is the domain already set to the desired constraint? */
if (typTup->typnotnull == notNull) if (typTup->typnotnull == notNull)
...@@ -1801,7 +1856,7 @@ AlterDomainDropConstraint(List *names, const char *constrName, ...@@ -1801,7 +1856,7 @@ AlterDomainDropConstraint(List *names, const char *constrName,
elog(ERROR, "cache lookup failed for type %u", domainoid); elog(ERROR, "cache lookup failed for type %u", domainoid);
/* Check it's a domain and check user has permission for ALTER DOMAIN */ /* Check it's a domain and check user has permission for ALTER DOMAIN */
checkDomainOwner(tup, typename); checkDomainOwner(tup);
/* Grab an appropriate lock on the pg_constraint relation */ /* Grab an appropriate lock on the pg_constraint relation */
conrel = heap_open(ConstraintRelationId, RowExclusiveLock); conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
...@@ -1875,7 +1930,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint) ...@@ -1875,7 +1930,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint)
typTup = (Form_pg_type) GETSTRUCT(tup); typTup = (Form_pg_type) GETSTRUCT(tup);
/* Check it's a domain and check user has permission for ALTER DOMAIN */ /* Check it's a domain and check user has permission for ALTER DOMAIN */
checkDomainOwner(tup, typename); checkDomainOwner(tup);
if (!IsA(newConstraint, Constraint)) if (!IsA(newConstraint, Constraint))
elog(ERROR, "unrecognized node type: %d", elog(ERROR, "unrecognized node type: %d",
...@@ -2187,7 +2242,7 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode) ...@@ -2187,7 +2242,7 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
* has permission to do ALTER DOMAIN on it. Throw an error if not. * has permission to do ALTER DOMAIN on it. Throw an error if not.
*/ */
static void static void
checkDomainOwner(HeapTuple tup, TypeName *typename) checkDomainOwner(HeapTuple tup)
{ {
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
...@@ -2195,8 +2250,8 @@ checkDomainOwner(HeapTuple tup, TypeName *typename) ...@@ -2195,8 +2250,8 @@ checkDomainOwner(HeapTuple tup, TypeName *typename)
if (typTup->typtype != TYPTYPE_DOMAIN) if (typTup->typtype != TYPTYPE_DOMAIN)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a domain", errmsg("%s is not a domain",
TypeNameToString(typename)))); format_type_be(HeapTupleGetOid(tup)))));
/* Permission check: must own type */ /* Permission check: must own type */
if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId())) if (!pg_type_ownercheck(HeapTupleGetOid(tup), GetUserId()))
......
...@@ -2901,6 +2901,19 @@ _copyCreateEnumStmt(CreateEnumStmt *from) ...@@ -2901,6 +2901,19 @@ _copyCreateEnumStmt(CreateEnumStmt *from)
return newnode; return newnode;
} }
static AlterEnumStmt *
_copyAlterEnumStmt(AlterEnumStmt *from)
{
AlterEnumStmt *newnode = makeNode(AlterEnumStmt);
COPY_NODE_FIELD(typeName);
COPY_STRING_FIELD(newVal);
COPY_STRING_FIELD(newValNeighbor);
COPY_SCALAR_FIELD(newValIsAfter);
return newnode;
}
static ViewStmt * static ViewStmt *
_copyViewStmt(ViewStmt *from) _copyViewStmt(ViewStmt *from)
{ {
...@@ -4064,6 +4077,9 @@ copyObject(void *from) ...@@ -4064,6 +4077,9 @@ copyObject(void *from)
case T_CreateEnumStmt: case T_CreateEnumStmt:
retval = _copyCreateEnumStmt(from); retval = _copyCreateEnumStmt(from);
break; break;
case T_AlterEnumStmt:
retval = _copyAlterEnumStmt(from);
break;
case T_ViewStmt: case T_ViewStmt:
retval = _copyViewStmt(from); retval = _copyViewStmt(from);
break; break;
......
...@@ -1392,6 +1392,17 @@ _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b) ...@@ -1392,6 +1392,17 @@ _equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b)
return true; return true;
} }
static bool
_equalAlterEnumStmt(AlterEnumStmt *a, AlterEnumStmt *b)
{
COMPARE_NODE_FIELD(typeName);
COMPARE_STRING_FIELD(newVal);
COMPARE_STRING_FIELD(newValNeighbor);
COMPARE_SCALAR_FIELD(newValIsAfter);
return true;
}
static bool static bool
_equalViewStmt(ViewStmt *a, ViewStmt *b) _equalViewStmt(ViewStmt *a, ViewStmt *b)
{ {
...@@ -2700,6 +2711,9 @@ equal(void *a, void *b) ...@@ -2700,6 +2711,9 @@ equal(void *a, void *b)
case T_CreateEnumStmt: case T_CreateEnumStmt:
retval = _equalCreateEnumStmt(a, b); retval = _equalCreateEnumStmt(a, b);
break; break;
case T_AlterEnumStmt:
retval = _equalAlterEnumStmt(a, b);
break;
case T_ViewStmt: case T_ViewStmt:
retval = _equalViewStmt(a, b); retval = _equalViewStmt(a, b);
break; break;
......
...@@ -182,8 +182,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ ...@@ -182,8 +182,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
} }
%type <node> stmt schema_stmt %type <node> stmt schema_stmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterForeignServerStmt AlterGroupStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
AlterRoleStmt AlterRoleSetStmt AlterRoleStmt AlterRoleSetStmt
...@@ -652,6 +652,7 @@ stmt : ...@@ -652,6 +652,7 @@ stmt :
| AlterDatabaseSetStmt | AlterDatabaseSetStmt
| AlterDefaultPrivilegesStmt | AlterDefaultPrivilegesStmt
| AlterDomainStmt | AlterDomainStmt
| AlterEnumStmt
| AlterFdwStmt | AlterFdwStmt
| AlterForeignServerStmt | AlterForeignServerStmt
| AlterFunctionStmt | AlterFunctionStmt
...@@ -3863,6 +3864,42 @@ enum_val_list: Sconst ...@@ -3863,6 +3864,42 @@ enum_val_list: Sconst
{ $$ = lappend($1, makeString($3)); } { $$ = lappend($1, makeString($3)); }
; ;
/*****************************************************************************
*
* ALTER TYPE enumtype ADD ...
*
*****************************************************************************/
AlterEnumStmt:
ALTER TYPE_P any_name ADD_P Sconst
{
AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3;
n->newVal = $5;
n->newValNeighbor = NULL;
n->newValIsAfter = true;
$$ = (Node *) n;
}
| ALTER TYPE_P any_name ADD_P Sconst BEFORE Sconst
{
AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3;
n->newVal = $5;
n->newValNeighbor = $7;
n->newValIsAfter = false;
$$ = (Node *) n;
}
| ALTER TYPE_P any_name ADD_P Sconst AFTER Sconst
{
AlterEnumStmt *n = makeNode(AlterEnumStmt);
n->typeName = $3;
n->newVal = $5;
n->newValNeighbor = $7;
n->newValIsAfter = true;
$$ = (Node *) n;
}
;
/***************************************************************************** /*****************************************************************************
* *
......
...@@ -190,6 +190,7 @@ check_xact_readonly(Node *parsetree) ...@@ -190,6 +190,7 @@ check_xact_readonly(Node *parsetree)
case T_CreateTrigStmt: case T_CreateTrigStmt:
case T_CompositeTypeStmt: case T_CompositeTypeStmt:
case T_CreateEnumStmt: case T_CreateEnumStmt:
case T_AlterEnumStmt:
case T_ViewStmt: case T_ViewStmt:
case T_DropCastStmt: case T_DropCastStmt:
case T_DropStmt: case T_DropStmt:
...@@ -860,6 +861,16 @@ standard_ProcessUtility(Node *parsetree, ...@@ -860,6 +861,16 @@ standard_ProcessUtility(Node *parsetree,
DefineEnum((CreateEnumStmt *) parsetree); DefineEnum((CreateEnumStmt *) parsetree);
break; break;
case T_AlterEnumStmt: /* ALTER TYPE (enum) */
/*
* We disallow this in transaction blocks, because we can't cope
* with enum OID values getting into indexes and then having their
* defining pg_enum entries go away.
*/
PreventTransactionChain(isTopLevel, "ALTER TYPE ... ADD");
AlterEnum((AlterEnumStmt *) parsetree);
break;
case T_ViewStmt: /* CREATE VIEW */ case T_ViewStmt: /* CREATE VIEW */
DefineView((ViewStmt *) parsetree, queryString); DefineView((ViewStmt *) parsetree, queryString);
break; break;
...@@ -1868,6 +1879,10 @@ CreateCommandTag(Node *parsetree) ...@@ -1868,6 +1879,10 @@ CreateCommandTag(Node *parsetree)
tag = "CREATE TYPE"; tag = "CREATE TYPE";
break; break;
case T_AlterEnumStmt:
tag = "ALTER TYPE";
break;
case T_ViewStmt: case T_ViewStmt:
tag = "CREATE VIEW"; tag = "CREATE VIEW";
break; break;
...@@ -2410,6 +2425,10 @@ GetCommandLogLevel(Node *parsetree) ...@@ -2410,6 +2425,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL; lev = LOGSTMT_DDL;
break; break;
case T_AlterEnumStmt:
lev = LOGSTMT_DDL;
break;
case T_ViewStmt: case T_ViewStmt:
lev = LOGSTMT_DDL; lev = LOGSTMT_DDL;
break; break;
......
...@@ -13,18 +13,22 @@ ...@@ -13,18 +13,22 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "catalog/pg_enum.h" #include "catalog/pg_enum.h"
#include "fmgr.h" #include "fmgr.h"
#include "libpq/pqformat.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/lsyscache.h" #include "utils/fmgroids.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h" #include "utils/syscache.h"
#include "libpq/pqformat.h" #include "utils/typcache.h"
#include "miscadmin.h"
static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction);
static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper); static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper);
static int enum_elem_cmp(const void *left, const void *right);
/* Basic I/O support */ /* Basic I/O support */
...@@ -155,13 +159,63 @@ enum_send(PG_FUNCTION_ARGS) ...@@ -155,13 +159,63 @@ enum_send(PG_FUNCTION_ARGS)
/* Comparison functions and related */ /* Comparison functions and related */
/*
* enum_cmp_internal is the common engine for all the visible comparison
* functions, except for enum_eq and enum_ne which can just check for OID
* equality directly.
*/
static int
enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo)
{
TypeCacheEntry *tcache;
/* Equal OIDs are equal no matter what */
if (arg1 == arg2)
return 0;
/* Fast path: even-numbered Oids are known to compare correctly */
if ((arg1 & 1) == 0 && (arg2 & 1) == 0)
{
if (arg1 < arg2)
return -1;
else
return 1;
}
/* Locate the typcache entry for the enum type */
tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
if (tcache == NULL)
{
HeapTuple enum_tup;
Form_pg_enum en;
Oid typeoid;
/* Get the OID of the enum type containing arg1 */
enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1));
if (!HeapTupleIsValid(enum_tup))
ereport(ERROR,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("invalid internal value for enum: %u",
arg1)));
en = (Form_pg_enum) GETSTRUCT(enum_tup);
typeoid = en->enumtypid;
ReleaseSysCache(enum_tup);
/* Now locate and remember the typcache entry */
tcache = lookup_type_cache(typeoid, 0);
fcinfo->flinfo->fn_extra = (void *) tcache;
}
/* The remaining comparison logic is in typcache.c */
return compare_values_of_enum(tcache, arg1, arg2);
}
Datum Datum
enum_lt(PG_FUNCTION_ARGS) enum_lt(PG_FUNCTION_ARGS)
{ {
Oid a = PG_GETARG_OID(0); Oid a = PG_GETARG_OID(0);
Oid b = PG_GETARG_OID(1); Oid b = PG_GETARG_OID(1);
PG_RETURN_BOOL(a < b); PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0);
} }
Datum Datum
...@@ -170,7 +224,7 @@ enum_le(PG_FUNCTION_ARGS) ...@@ -170,7 +224,7 @@ enum_le(PG_FUNCTION_ARGS)
Oid a = PG_GETARG_OID(0); Oid a = PG_GETARG_OID(0);
Oid b = PG_GETARG_OID(1); Oid b = PG_GETARG_OID(1);
PG_RETURN_BOOL(a <= b); PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0);
} }
Datum Datum
...@@ -197,7 +251,7 @@ enum_ge(PG_FUNCTION_ARGS) ...@@ -197,7 +251,7 @@ enum_ge(PG_FUNCTION_ARGS)
Oid a = PG_GETARG_OID(0); Oid a = PG_GETARG_OID(0);
Oid b = PG_GETARG_OID(1); Oid b = PG_GETARG_OID(1);
PG_RETURN_BOOL(a >= b); PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0);
} }
Datum Datum
...@@ -206,7 +260,7 @@ enum_gt(PG_FUNCTION_ARGS) ...@@ -206,7 +260,7 @@ enum_gt(PG_FUNCTION_ARGS)
Oid a = PG_GETARG_OID(0); Oid a = PG_GETARG_OID(0);
Oid b = PG_GETARG_OID(1); Oid b = PG_GETARG_OID(1);
PG_RETURN_BOOL(a > b); PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0);
} }
Datum Datum
...@@ -215,7 +269,7 @@ enum_smaller(PG_FUNCTION_ARGS) ...@@ -215,7 +269,7 @@ enum_smaller(PG_FUNCTION_ARGS)
Oid a = PG_GETARG_OID(0); Oid a = PG_GETARG_OID(0);
Oid b = PG_GETARG_OID(1); Oid b = PG_GETARG_OID(1);
PG_RETURN_OID(a <= b ? a : b); PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b);
} }
Datum Datum
...@@ -224,7 +278,7 @@ enum_larger(PG_FUNCTION_ARGS) ...@@ -224,7 +278,7 @@ enum_larger(PG_FUNCTION_ARGS)
Oid a = PG_GETARG_OID(0); Oid a = PG_GETARG_OID(0);
Oid b = PG_GETARG_OID(1); Oid b = PG_GETARG_OID(1);
PG_RETURN_OID(a >= b ? a : b); PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b);
} }
Datum Datum
...@@ -233,24 +287,63 @@ enum_cmp(PG_FUNCTION_ARGS) ...@@ -233,24 +287,63 @@ enum_cmp(PG_FUNCTION_ARGS)
Oid a = PG_GETARG_OID(0); Oid a = PG_GETARG_OID(0);
Oid b = PG_GETARG_OID(1); Oid b = PG_GETARG_OID(1);
if (a > b) if (a == b)
PG_RETURN_INT32(1);
else if (a == b)
PG_RETURN_INT32(0); PG_RETURN_INT32(0);
else if (enum_cmp_internal(a, b, fcinfo) > 0)
PG_RETURN_INT32(1);
else else
PG_RETURN_INT32(-1); PG_RETURN_INT32(-1);
} }
/* Enum programming support functions */ /* Enum programming support functions */
/*
* enum_endpoint: common code for enum_first/enum_last
*/
static Oid
enum_endpoint(Oid enumtypoid, ScanDirection direction)
{
Relation enum_rel;
Relation enum_idx;
SysScanDesc enum_scan;
HeapTuple enum_tuple;
ScanKeyData skey;
Oid minmax;
/*
* Find the first/last enum member using pg_enum_typid_sortorder_index.
* Note we must not use the syscache, and must use an MVCC snapshot here.
* See comments for RenumberEnumType in catalog/pg_enum.c for more info.
*/
ScanKeyInit(&skey,
Anum_pg_enum_enumtypid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(enumtypoid));
enum_rel = heap_open(EnumRelationId, AccessShareLock);
enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
enum_scan = systable_beginscan_ordered(enum_rel, enum_idx,
GetTransactionSnapshot(),
1, &skey);
enum_tuple = systable_getnext_ordered(enum_scan, direction);
if (HeapTupleIsValid(enum_tuple))
minmax = HeapTupleGetOid(enum_tuple);
else
minmax = InvalidOid;
systable_endscan_ordered(enum_scan);
index_close(enum_idx, AccessShareLock);
heap_close(enum_rel, AccessShareLock);
return minmax;
}
Datum Datum
enum_first(PG_FUNCTION_ARGS) enum_first(PG_FUNCTION_ARGS)
{ {
Oid enumtypoid; Oid enumtypoid;
Oid min = InvalidOid; Oid min;
CatCList *list;
int num,
i;
/* /*
* We rely on being able to get the specific enum type from the calling * We rely on being able to get the specific enum type from the calling
...@@ -263,21 +356,14 @@ enum_first(PG_FUNCTION_ARGS) ...@@ -263,21 +356,14 @@ enum_first(PG_FUNCTION_ARGS)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("could not determine actual enum type"))); errmsg("could not determine actual enum type")));
list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); /* Get the OID using the index */
num = list->n_members; min = enum_endpoint(enumtypoid, ForwardScanDirection);
for (i = 0; i < num; i++)
{
Oid valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data);
if (!OidIsValid(min) || valoid < min) if (!OidIsValid(min))
min = valoid; ereport(ERROR,
} (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("enum %s contains no values",
ReleaseCatCacheList(list); format_type_be(enumtypoid))));
if (!OidIsValid(min)) /* should not happen */
elog(ERROR, "no values found for enum %s",
format_type_be(enumtypoid));
PG_RETURN_OID(min); PG_RETURN_OID(min);
} }
...@@ -286,10 +372,7 @@ Datum ...@@ -286,10 +372,7 @@ Datum
enum_last(PG_FUNCTION_ARGS) enum_last(PG_FUNCTION_ARGS)
{ {
Oid enumtypoid; Oid enumtypoid;
Oid max = InvalidOid; Oid max;
CatCList *list;
int num,
i;
/* /*
* We rely on being able to get the specific enum type from the calling * We rely on being able to get the specific enum type from the calling
...@@ -302,21 +385,14 @@ enum_last(PG_FUNCTION_ARGS) ...@@ -302,21 +385,14 @@ enum_last(PG_FUNCTION_ARGS)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("could not determine actual enum type"))); errmsg("could not determine actual enum type")));
list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); /* Get the OID using the index */
num = list->n_members; max = enum_endpoint(enumtypoid, BackwardScanDirection);
for (i = 0; i < num; i++)
{
Oid valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data);
if (!OidIsValid(max) || valoid > max)
max = valoid;
}
ReleaseCatCacheList(list);
if (!OidIsValid(max)) /* should not happen */ if (!OidIsValid(max))
elog(ERROR, "no values found for enum %s", ereport(ERROR,
format_type_be(enumtypoid)); (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("enum %s contains no values",
format_type_be(enumtypoid))));
PG_RETURN_OID(max); PG_RETURN_OID(max);
} }
...@@ -377,51 +453,68 @@ static ArrayType * ...@@ -377,51 +453,68 @@ static ArrayType *
enum_range_internal(Oid enumtypoid, Oid lower, Oid upper) enum_range_internal(Oid enumtypoid, Oid lower, Oid upper)
{ {
ArrayType *result; ArrayType *result;
CatCList *list; Relation enum_rel;
int total, Relation enum_idx;
i, SysScanDesc enum_scan;
j; HeapTuple enum_tuple;
ScanKeyData skey;
Datum *elems; Datum *elems;
int max,
cnt;
bool left_found;
list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); /*
total = list->n_members; * Scan the enum members in order using pg_enum_typid_sortorder_index.
* Note we must not use the syscache, and must use an MVCC snapshot here.
* See comments for RenumberEnumType in catalog/pg_enum.c for more info.
*/
ScanKeyInit(&skey,
Anum_pg_enum_enumtypid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(enumtypoid));
enum_rel = heap_open(EnumRelationId, AccessShareLock);
enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock);
enum_scan = systable_beginscan_ordered(enum_rel, enum_idx,
GetTransactionSnapshot(),
1, &skey);
max = 64;
elems = (Datum *) palloc(max * sizeof(Datum));
cnt = 0;
left_found = !OidIsValid(lower);
while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection)))
{
Oid enum_oid = HeapTupleGetOid(enum_tuple);
elems = (Datum *) palloc(total * sizeof(Datum)); if (!left_found && lower == enum_oid)
left_found = true;
j = 0; if (left_found)
for (i = 0; i < total; i++)
{ {
Oid val = HeapTupleGetOid(&(list->members[i]->tuple)); if (cnt >= max)
{
max *= 2;
elems = (Datum *) repalloc(elems, max * sizeof(Datum));
}
if ((!OidIsValid(lower) || lower <= val) && elems[cnt++] = ObjectIdGetDatum(enum_oid);
(!OidIsValid(upper) || val <= upper))
elems[j++] = ObjectIdGetDatum(val);
} }
/* shouldn't need the cache anymore */ if (OidIsValid(upper) && upper == enum_oid)
ReleaseCatCacheList(list); break;
}
/* sort results into OID order */ systable_endscan_ordered(enum_scan);
qsort(elems, j, sizeof(Datum), enum_elem_cmp); index_close(enum_idx, AccessShareLock);
heap_close(enum_rel, AccessShareLock);
/* and build the result array */
/* note this hardwires some details about the representation of Oid */ /* note this hardwires some details about the representation of Oid */
result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i'); result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i');
pfree(elems); pfree(elems);
return result; return result;
} }
/* qsort comparison function for Datums that are OIDs */
static int
enum_elem_cmp(const void *left, const void *right)
{
Oid l = DatumGetObjectId(*((const Datum *) left));
Oid r = DatumGetObjectId(*((const Datum *) right));
if (l < r)
return -1;
if (l > r)
return 1;
return 0;
}
This diff is collapsed.
...@@ -6657,9 +6657,16 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo) ...@@ -6657,9 +6657,16 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
Oid enum_oid; Oid enum_oid;
char *label; char *label;
/* Set proper schema search path so regproc references list correctly */ /* Set proper schema search path */
selectSourceSchema(tyinfo->dobj.namespace->dobj.name); selectSourceSchema("pg_catalog");
if (fout->remoteVersion >= 90100)
appendPQExpBuffer(query, "SELECT oid, enumlabel "
"FROM pg_catalog.pg_enum "
"WHERE enumtypid = '%u'"
"ORDER BY enumsortorder",
tyinfo->dobj.catId.oid);
else
appendPQExpBuffer(query, "SELECT oid, enumlabel " appendPQExpBuffer(query, "SELECT oid, enumlabel "
"FROM pg_catalog.pg_enum " "FROM pg_catalog.pg_enum "
"WHERE enumtypid = '%u'" "WHERE enumtypid = '%u'"
...@@ -6713,13 +6720,15 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo) ...@@ -6713,13 +6720,15 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
if (i == 0) if (i == 0)
appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n"); appendPQExpBuffer(q, "\n-- For binary upgrade, must preserve pg_enum oids\n");
appendPQExpBuffer(q, appendPQExpBuffer(q,
"SELECT binary_upgrade.add_pg_enum_label('%u'::pg_catalog.oid, " "SELECT binary_upgrade.set_next_pg_enum_oid('%u'::pg_catalog.oid);\n",
"'%u'::pg_catalog.oid, ", enum_oid);
enum_oid, tyinfo->dobj.catId.oid); appendPQExpBuffer(q, "ALTER TYPE %s.",
fmtId(tyinfo->dobj.namespace->dobj.name));
appendPQExpBuffer(q, "%s ADD ",
fmtId(tyinfo->dobj.name));
appendStringLiteralAH(q, label, fout); appendStringLiteralAH(q, label, fout);
appendPQExpBuffer(q, ");\n"); appendPQExpBuffer(q, ";\n\n");
} }
appendPQExpBuffer(q, "\n");
} }
ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId, ArchiveEntry(fout, tyinfo->dobj.catId, tyinfo->dobj.dumpId,
......
...@@ -473,17 +473,27 @@ describeTypes(const char *pattern, bool verbose, bool showSystem) ...@@ -473,17 +473,27 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Internal name"), gettext_noop("Internal name"),
gettext_noop("Size")); gettext_noop("Size"));
if (verbose && pset.sversion >= 80300) if (verbose && pset.sversion >= 80300)
{
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
" pg_catalog.array_to_string(\n" " pg_catalog.array_to_string(\n"
" ARRAY(\n" " ARRAY(\n"
" SELECT e.enumlabel\n" " SELECT e.enumlabel\n"
" FROM pg_catalog.pg_enum e\n" " FROM pg_catalog.pg_enum e\n"
" WHERE e.enumtypid = t.oid\n" " WHERE e.enumtypid = t.oid\n");
" ORDER BY e.oid\n"
if (pset.sversion >= 90100)
appendPQExpBuffer(&buf,
" ORDER BY e.enumsortorder\n");
else
appendPQExpBuffer(&buf,
" ORDER BY e.oid\n");
appendPQExpBuffer(&buf,
" ),\n" " ),\n"
" E'\\n'\n" " E'\\n'\n"
" ) AS \"%s\",\n", " ) AS \"%s\",\n",
gettext_noop("Elements")); gettext_noop("Elements"));
}
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
" pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n", " pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201010201 #define CATALOG_VERSION_NO 201010241
#endif #endif
...@@ -147,6 +147,8 @@ DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops ...@@ -147,6 +147,8 @@ DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops
#define EnumOidIndexId 3502 #define EnumOidIndexId 3502
DECLARE_UNIQUE_INDEX(pg_enum_typid_label_index, 3503, on pg_enum using btree(enumtypid oid_ops, enumlabel name_ops)); DECLARE_UNIQUE_INDEX(pg_enum_typid_label_index, 3503, on pg_enum using btree(enumtypid oid_ops, enumlabel name_ops));
#define EnumTypIdLabelIndexId 3503 #define EnumTypIdLabelIndexId 3503
DECLARE_UNIQUE_INDEX(pg_enum_typid_sortorder_index, 3534, on pg_enum using btree(enumtypid oid_ops, enumsortorder float4_ops));
#define EnumTypIdSortOrderIndexId 3534
/* This following index is not used for a cache and is not unique */ /* This following index is not used for a cache and is not unique */
DECLARE_INDEX(pg_index_indrelid_index, 2678, on pg_index using btree(indrelid oid_ops)); DECLARE_INDEX(pg_index_indrelid_index, 2678, on pg_index using btree(indrelid oid_ops));
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
CATALOG(pg_enum,3501) CATALOG(pg_enum,3501)
{ {
Oid enumtypid; /* OID of owning enum type */ Oid enumtypid; /* OID of owning enum type */
float4 enumsortorder; /* sort position of this enum value */
NameData enumlabel; /* text representation of enum value */ NameData enumlabel; /* text representation of enum value */
} FormData_pg_enum; } FormData_pg_enum;
...@@ -48,9 +49,10 @@ typedef FormData_pg_enum *Form_pg_enum; ...@@ -48,9 +49,10 @@ typedef FormData_pg_enum *Form_pg_enum;
* compiler constants for pg_enum * compiler constants for pg_enum
* ---------------- * ----------------
*/ */
#define Natts_pg_enum 2 #define Natts_pg_enum 3
#define Anum_pg_enum_enumtypid 1 #define Anum_pg_enum_enumtypid 1
#define Anum_pg_enum_enumlabel 2 #define Anum_pg_enum_enumsortorder 2
#define Anum_pg_enum_enumlabel 3
/* ---------------- /* ----------------
* pg_enum has no initial contents * pg_enum has no initial contents
...@@ -60,8 +62,9 @@ typedef FormData_pg_enum *Form_pg_enum; ...@@ -60,8 +62,9 @@ typedef FormData_pg_enum *Form_pg_enum;
/* /*
* prototypes for functions in pg_enum.c * prototypes for functions in pg_enum.c
*/ */
extern void EnumValuesCreate(Oid enumTypeOid, List *vals, extern void EnumValuesCreate(Oid enumTypeOid, List *vals);
Oid binary_upgrade_next_pg_enum_oid);
extern void EnumValuesDelete(Oid enumTypeOid); extern void EnumValuesDelete(Oid enumTypeOid);
extern void AddEnumLabel(Oid enumTypeOid, const char *newVal,
const char *neighbor, bool newValIsAfter);
#endif /* PG_ENUM_H */ #endif /* PG_ENUM_H */
...@@ -24,6 +24,7 @@ extern void RemoveTypes(DropStmt *drop); ...@@ -24,6 +24,7 @@ extern void RemoveTypes(DropStmt *drop);
extern void RemoveTypeById(Oid typeOid); extern void RemoveTypeById(Oid typeOid);
extern void DefineDomain(CreateDomainStmt *stmt); extern void DefineDomain(CreateDomainStmt *stmt);
extern void DefineEnum(CreateEnumStmt *stmt); extern void DefineEnum(CreateEnumStmt *stmt);
extern void AlterEnum(AlterEnumStmt *stmt);
extern Oid DefineCompositeType(const RangeVar *typevar, List *coldeflist); extern Oid DefineCompositeType(const RangeVar *typevar, List *coldeflist);
extern Oid AssignTypeArrayOid(void); extern Oid AssignTypeArrayOid(void);
......
...@@ -338,6 +338,7 @@ typedef enum NodeTag ...@@ -338,6 +338,7 @@ typedef enum NodeTag
T_ReassignOwnedStmt, T_ReassignOwnedStmt,
T_CompositeTypeStmt, T_CompositeTypeStmt,
T_CreateEnumStmt, T_CreateEnumStmt,
T_AlterEnumStmt,
T_AlterTSDictionaryStmt, T_AlterTSDictionaryStmt,
T_AlterTSConfigurationStmt, T_AlterTSConfigurationStmt,
T_CreateFdwStmt, T_CreateFdwStmt,
......
...@@ -2193,6 +2193,18 @@ typedef struct CreateEnumStmt ...@@ -2193,6 +2193,18 @@ typedef struct CreateEnumStmt
List *vals; /* enum values (list of Value strings) */ List *vals; /* enum values (list of Value strings) */
} CreateEnumStmt; } CreateEnumStmt;
/* ----------------------
* Alter Type Statement, enum types
* ----------------------
*/
typedef struct AlterEnumStmt
{
NodeTag type;
List *typeName; /* qualified name (list of Value strings) */
char *newVal; /* new enum value's name */
char *newValNeighbor; /* neighboring enum value, if specified */
bool newValIsAfter; /* place new enum value after neighbor? */
} AlterEnumStmt;
/* ---------------------- /* ----------------------
* Create View Statement * Create View Statement
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
#include "fmgr.h" #include "fmgr.h"
/* TypeCacheEnumData is an opaque struct known only within typcache.c */
struct TypeCacheEnumData;
typedef struct TypeCacheEntry typedef struct TypeCacheEntry
{ {
/* typeId is the hash lookup key and MUST BE FIRST */ /* typeId is the hash lookup key and MUST BE FIRST */
...@@ -63,6 +66,12 @@ typedef struct TypeCacheEntry ...@@ -63,6 +66,12 @@ typedef struct TypeCacheEntry
* reference-counted tupledesc.) * reference-counted tupledesc.)
*/ */
TupleDesc tupDesc; TupleDesc tupDesc;
/*
* Private information about an enum type. NULL if not enum or
* information hasn't been requested.
*/
struct TypeCacheEnumData *enumData;
} TypeCacheEntry; } TypeCacheEntry;
/* Bit flags to indicate which fields a given caller needs to have set */ /* Bit flags to indicate which fields a given caller needs to have set */
...@@ -86,4 +95,6 @@ extern TupleDesc lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod); ...@@ -86,4 +95,6 @@ extern TupleDesc lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod);
extern void assign_record_type_typmod(TupleDesc tupDesc); extern void assign_record_type_typmod(TupleDesc tupDesc);
extern int compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2);
#endif /* TYPCACHE_H */ #endif /* TYPCACHE_H */
...@@ -24,6 +24,155 @@ SELECT 'mauve'::rainbow; ...@@ -24,6 +24,155 @@ SELECT 'mauve'::rainbow;
ERROR: invalid input value for enum rainbow: "mauve" ERROR: invalid input value for enum rainbow: "mauve"
LINE 1: SELECT 'mauve'::rainbow; LINE 1: SELECT 'mauve'::rainbow;
^ ^
--
-- adding new values
--
CREATE TYPE planets AS ENUM ( 'venus', 'earth', 'mars' );
SELECT enumlabel, enumsortorder
FROM pg_enum
WHERE enumtypid = 'planets'::regtype
ORDER BY 2;
enumlabel | enumsortorder
-----------+---------------
venus | 1
earth | 2
mars | 3
(3 rows)
ALTER TYPE planets ADD 'uranus';
SELECT enumlabel, enumsortorder
FROM pg_enum
WHERE enumtypid = 'planets'::regtype
ORDER BY 2;
enumlabel | enumsortorder
-----------+---------------
venus | 1
earth | 2
mars | 3
uranus | 4
(4 rows)
ALTER TYPE planets ADD 'mercury' BEFORE 'venus';
ALTER TYPE planets ADD 'saturn' BEFORE 'uranus';
ALTER TYPE planets ADD 'jupiter' AFTER 'mars';
ALTER TYPE planets ADD 'neptune' AFTER 'uranus';
SELECT enumlabel, enumsortorder
FROM pg_enum
WHERE enumtypid = 'planets'::regtype
ORDER BY 2;
enumlabel | enumsortorder
-----------+---------------
mercury | 0
venus | 1
earth | 2
mars | 3
jupiter | 3.25
saturn | 3.5
uranus | 4
neptune | 5
(8 rows)
SELECT enumlabel, enumsortorder
FROM pg_enum
WHERE enumtypid = 'planets'::regtype
ORDER BY enumlabel::planets;
enumlabel | enumsortorder
-----------+---------------
mercury | 0
venus | 1
earth | 2
mars | 3
jupiter | 3.25
saturn | 3.5
uranus | 4
neptune | 5
(8 rows)
-- errors for adding labels
ALTER TYPE planets ADD
'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
ERROR: invalid enum label "plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto"
DETAIL: Labels must be 63 characters or less.
ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
ERROR: "zeus" is not an existing enum label
--
-- Test inserting so many values that we have to renumber
--
create type insenum as enum ('L1', 'L2');
alter type insenum add 'i1' before 'L2';
alter type insenum add 'i2' before 'L2';
alter type insenum add 'i3' before 'L2';
alter type insenum add 'i4' before 'L2';
alter type insenum add 'i5' before 'L2';
alter type insenum add 'i6' before 'L2';
alter type insenum add 'i7' before 'L2';
alter type insenum add 'i8' before 'L2';
alter type insenum add 'i9' before 'L2';
alter type insenum add 'i10' before 'L2';
alter type insenum add 'i11' before 'L2';
alter type insenum add 'i12' before 'L2';
alter type insenum add 'i13' before 'L2';
alter type insenum add 'i14' before 'L2';
alter type insenum add 'i15' before 'L2';
alter type insenum add 'i16' before 'L2';
alter type insenum add 'i17' before 'L2';
alter type insenum add 'i18' before 'L2';
alter type insenum add 'i19' before 'L2';
alter type insenum add 'i20' before 'L2';
alter type insenum add 'i21' before 'L2';
alter type insenum add 'i22' before 'L2';
alter type insenum add 'i23' before 'L2';
alter type insenum add 'i24' before 'L2';
alter type insenum add 'i25' before 'L2';
alter type insenum add 'i26' before 'L2';
alter type insenum add 'i27' before 'L2';
alter type insenum add 'i28' before 'L2';
alter type insenum add 'i29' before 'L2';
alter type insenum add 'i30' before 'L2';
-- The exact values of enumsortorder will now depend on the local properties
-- of float4, but in any reasonable implementation we should get at least
-- 20 splits before having to renumber; so only hide values > 20.
SELECT enumlabel,
case when enumsortorder > 20 then null else enumsortorder end as so
FROM pg_enum
WHERE enumtypid = 'insenum'::regtype
ORDER BY enumsortorder;
enumlabel | so
-----------+----
L1 | 1
i1 | 2
i2 | 3
i3 | 4
i4 | 5
i5 | 6
i6 | 7
i7 | 8
i8 | 9
i9 | 10
i10 | 11
i11 | 12
i12 | 13
i13 | 14
i14 | 15
i15 | 16
i16 | 17
i17 | 18
i18 | 19
i19 | 20
i20 |
i21 |
i22 |
i23 |
i24 |
i25 |
i26 |
i27 |
i28 |
i29 |
i30 |
L2 |
(32 rows)
-- --
-- Basic table creation, row selection -- Basic table creation, row selection
-- --
...@@ -403,7 +552,7 @@ SELECT COUNT(*) FROM pg_type WHERE typname = 'rainbow'; ...@@ -403,7 +552,7 @@ SELECT COUNT(*) FROM pg_type WHERE typname = 'rainbow';
SELECT * FROM pg_enum WHERE NOT EXISTS SELECT * FROM pg_enum WHERE NOT EXISTS
(SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid); (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid);
enumtypid | enumlabel enumtypid | enumsortorder | enumlabel
-----------+----------- -----------+---------------+-----------
(0 rows) (0 rows)
...@@ -15,6 +15,92 @@ SELECT COUNT(*) FROM pg_enum WHERE enumtypid = 'rainbow'::regtype; ...@@ -15,6 +15,92 @@ SELECT COUNT(*) FROM pg_enum WHERE enumtypid = 'rainbow'::regtype;
SELECT 'red'::rainbow; SELECT 'red'::rainbow;
SELECT 'mauve'::rainbow; SELECT 'mauve'::rainbow;
--
-- adding new values
--
CREATE TYPE planets AS ENUM ( 'venus', 'earth', 'mars' );
SELECT enumlabel, enumsortorder
FROM pg_enum
WHERE enumtypid = 'planets'::regtype
ORDER BY 2;
ALTER TYPE planets ADD 'uranus';
SELECT enumlabel, enumsortorder
FROM pg_enum
WHERE enumtypid = 'planets'::regtype
ORDER BY 2;
ALTER TYPE planets ADD 'mercury' BEFORE 'venus';
ALTER TYPE planets ADD 'saturn' BEFORE 'uranus';
ALTER TYPE planets ADD 'jupiter' AFTER 'mars';
ALTER TYPE planets ADD 'neptune' AFTER 'uranus';
SELECT enumlabel, enumsortorder
FROM pg_enum
WHERE enumtypid = 'planets'::regtype
ORDER BY 2;
SELECT enumlabel, enumsortorder
FROM pg_enum
WHERE enumtypid = 'planets'::regtype
ORDER BY enumlabel::planets;
-- errors for adding labels
ALTER TYPE planets ADD
'plutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutoplutopluto';
ALTER TYPE planets ADD 'pluto' AFTER 'zeus';
--
-- Test inserting so many values that we have to renumber
--
create type insenum as enum ('L1', 'L2');
alter type insenum add 'i1' before 'L2';
alter type insenum add 'i2' before 'L2';
alter type insenum add 'i3' before 'L2';
alter type insenum add 'i4' before 'L2';
alter type insenum add 'i5' before 'L2';
alter type insenum add 'i6' before 'L2';
alter type insenum add 'i7' before 'L2';
alter type insenum add 'i8' before 'L2';
alter type insenum add 'i9' before 'L2';
alter type insenum add 'i10' before 'L2';
alter type insenum add 'i11' before 'L2';
alter type insenum add 'i12' before 'L2';
alter type insenum add 'i13' before 'L2';
alter type insenum add 'i14' before 'L2';
alter type insenum add 'i15' before 'L2';
alter type insenum add 'i16' before 'L2';
alter type insenum add 'i17' before 'L2';
alter type insenum add 'i18' before 'L2';
alter type insenum add 'i19' before 'L2';
alter type insenum add 'i20' before 'L2';
alter type insenum add 'i21' before 'L2';
alter type insenum add 'i22' before 'L2';
alter type insenum add 'i23' before 'L2';
alter type insenum add 'i24' before 'L2';
alter type insenum add 'i25' before 'L2';
alter type insenum add 'i26' before 'L2';
alter type insenum add 'i27' before 'L2';
alter type insenum add 'i28' before 'L2';
alter type insenum add 'i29' before 'L2';
alter type insenum add 'i30' before 'L2';
-- The exact values of enumsortorder will now depend on the local properties
-- of float4, but in any reasonable implementation we should get at least
-- 20 splits before having to renumber; so only hide values > 20.
SELECT enumlabel,
case when enumsortorder > 20 then null else enumsortorder end as so
FROM pg_enum
WHERE enumtypid = 'insenum'::regtype
ORDER BY enumsortorder;
-- --
-- Basic table creation, row selection -- Basic table creation, row selection
-- --
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment