diff options
author | Tom Lane | 2007-04-02 03:49:42 +0000 |
---|---|---|
committer | Tom Lane | 2007-04-02 03:49:42 +0000 |
commit | d211f21f984a29a982ac5bbb8c7a2e6ff57c02dc (patch) | |
tree | d20b8fda588739f155f82c3fadd2a1a986c22472 | |
parent | 7b070ac899d7f263b2f558057b4793737558dfe4 (diff) |
Support enum data types. Along the way, use macros for the values of
pg_type.typtype whereever practical. Tom Dunstan, with some kibitzing
from Tom Lane.
74 files changed, 2334 insertions, 268 deletions
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 0399654366..aeb4246489 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -129,6 +129,11 @@ </row> <row> + <entry><link linkend="catalog-pg-enum"><structname>pg_enum</structname></link></entry> + <entry>enum label and value definitions</entry> + </row> + + <row> <entry><link linkend="catalog-pg-index"><structname>pg_index</structname></link></entry> <entry>additional index information</entry> </row> @@ -1425,11 +1430,7 @@ in which the source and target types are the same, if the associated function takes more than one argument. Such entries represent <quote>length coercion functions</> that coerce values of the type - to be legal for a particular type modifier value. Note however that - at present there is no support for associating non-default type - modifiers with user-created data types, and so this facility is only - of use for the small number of built-in types that have type modifier - syntax built into the grammar. + to be legal for a particular type modifier value. </para> <para> @@ -2413,6 +2414,55 @@ </sect1> + <sect1 id="catalog-pg-enum"> + <title><structname>pg_enum</structname></title> + + <indexterm zone="catalog-pg-enum"> + <primary>pg_enum</primary> + </indexterm> + + <para> + The <structname>pg_enum</structname> catalog contains entries + matching enum types to their associated values and labels. The + internal representation of a given enum value is actually the OID + of its associated row in <structname>pg_enum</structname>. The + 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> + + <table> + <title><structname>pg_enum</> Columns</title> + + <tgroup cols=4> + <thead> + <row> + <entry>Name</entry> + <entry>Type</entry> + <entry>References</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><structfield>enumtypid</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry> + <entry>The OID of the <structname>pg_type</> entry owning this enum value</entry> + </row> + + <row> + <entry><structfield>enumlabel</structfield></entry> + <entry><type>name</type></entry> + <entry></entry> + <entry>The textual label for this enum value</entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + <sect1 id="catalog-pg-index"> <title><structname>pg_index</structname></title> @@ -4395,11 +4445,13 @@ <entry><type>char</type></entry> <entry></entry> <entry> - <structfield>typtype</structfield> is <literal>b</literal> for - a base type, <literal>c</literal> for a composite type (e.g., a - table's row type), <literal>d</literal> for a domain, or - <literal>p</literal> for a pseudo-type. See also - <structfield>typrelid</structfield> and + <structfield>typtype</structfield> is + <literal>b</literal> for a base type, + <literal>c</literal> for a composite type (e.g., a table's row type), + <literal>d</literal> for a domain, + <literal>e</literal> for an enum type, + or <literal>p</literal> for a pseudo-type. + See also <structfield>typrelid</structfield> and <structfield>typbasetype</structfield> </entry> </row> diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 38b1571af8..aa6ec983c9 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -2424,6 +2424,161 @@ SELECT * FROM test1 WHERE a; </para> </sect1> + <sect1 id="datatype-enum"> + <title>Enumerated Types</title> + + <indexterm zone="datatype-enum"> + <primary>data type</primary> + <secondary>enumerated (enum)</secondary> + </indexterm> + + <para> + Enumerated (enum) types are data types that + are comprised of a static, predefined set of values with a + specific order. They are equivalent to the <type>enum</type> + types in a number of programming languages. An example of an enum + type might be the days of the week, or a set of status values for + a piece of data. + </para> + + <sect2> + <title>Declaration of Enumerated Types</title> + + <para> + Enum types are created using the <xref + linkend="sql-createtype" endterm="sql-createtype-title"> command, + for example: + +<programlisting> +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +</programlisting> + + Once created, the enum type can be used in table and function + definitions much like any other type: + </para> + + <example> + <title>Basic Enum Usage</title> +<programlisting> +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +CREATE TABLE person ( + name text, + current_mood mood +); +INSERT INTO person VALUES ('Moe', 'happy'); +SELECT * FROM person WHERE current_mood = 'happy'; + name | current_mood +------+-------------- + Moe | happy +(1 row) +</programlisting> + </example> + </sect2> + + <sect2> + <title>Ordering</title> + + <para> + The ordering of the values in an enum type is the + order in which the values were listed when the type was declared. + All standard comparison operators and related + aggregate functions are supported for enums. For example: + </para> + + <example> + <title>Enum Ordering</title> +<programlisting> +INSERT INTO person VALUES ('Larry', 'sad'); +INSERT INTO person VALUES ('Curly', 'ok'); +SELECT * FROM person WHERE current_mood > 'sad'; + name | current_mood +-------+-------------- + Moe | happy + Curly | ok +(2 rows) + +SELECT * FROM person WHERE current_mood > 'sad' ORDER BY current_mood; + name | current_mood +-------+-------------- + Curly | ok + Moe | happy +(2 rows) + +SELECT name FROM person + WHERE current_mood = (SELECT MIN(current_mood) FROM person); + name +------- + Larry +(1 row) +</programlisting> + </example> + </sect2> + + <sect2> + <title>Type Safety</title> + + <para> + Enumerated types are completely separate data types and may not + be compared with each other. + </para> + + <example> + <title>Lack of Casting</title> +<programlisting> +CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic'); +CREATE TABLE holidays ( + num_weeks int, + happiness happiness +); +INSERT INTO holidays(num_weeks,happiness) VALUES (4, 'happy'); +INSERT INTO holidays(num_weeks,happiness) VALUES (6, 'very happy'); +INSERT INTO holidays(num_weeks,happiness) VALUES (8, 'ecstatic'); +INSERT INTO holidays(num_weeks,happiness) VALUES (2, 'sad'); +ERROR: invalid input value for enum happiness: "sad" +SELECT person.name, holidays.num_weeks FROM person, holidays + WHERE person.current_mood = holidays.happiness; +ERROR: operator does not exist: mood = happiness +</programlisting> + </example> + + <para> + If you really need to do something like that, you can either + write a custom operator or add explicit casts to your query: + </para> + + <example> + <title>Comparing Different Enums by Casting to Text</title> +<programlisting> +SELECT person.name, holidays.num_weeks FROM person, holidays + WHERE person.current_mood::text = holidays.happiness::text; + name | num_weeks +------+----------- + Moe | 4 +(1 row) + +</programlisting> + </example> + </sect2> + + <sect2> + <title>Implementation Details</title> + + <para> + An enum value occupies four bytes on disk. The length of an enum + value's textual label is limited by the <symbol>NAMEDATALEN</symbol> + setting compiled into <productname>PostgreSQL</productname>; in standard + builds this means at most 63 bytes. + </para> + + <para> + Enum labels are case sensitive, so + <type>'happy'</type> is not the same as <type>'HAPPY'</type>. + Spaces in the labels are significant, too. + </para> + + </sect2> + </sect1> + <sect1 id="datatype-geometric"> <title>Geometric Types</title> @@ -3279,6 +3434,10 @@ SELECT * FROM pg_attribute </indexterm> <indexterm zone="datatype-pseudo"> + <primary>anyenum</primary> + </indexterm> + + <indexterm zone="datatype-pseudo"> <primary>void</primary> </indexterm> @@ -3344,6 +3503,13 @@ SELECT * FROM pg_attribute </row> <row> + <entry><type>anyenum</></entry> + <entry>Indicates that a function accepts any enum data type + (see <xref linkend="extend-types-polymorphic"> and + <xref linkend="datatype-enum">).</entry> + </row> + + <row> <entry><type>cstring</></entry> <entry>Indicates that a function accepts or returns a null-terminated C string.</entry> </row> @@ -3395,8 +3561,8 @@ SELECT * FROM pg_attribute languages all forbid use of a pseudo-type as argument type, and allow only <type>void</> and <type>record</> as a result type (plus <type>trigger</> when the function is used as a trigger). Some also - support polymorphic functions using the types <type>anyarray</> and - <type>anyelement</>. + support polymorphic functions using the types <type>anyarray</>, + <type>anyelement</> and <type>anyenum</>. </para> <para> diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml index 3974ba3a8b..2159436ed6 100644 --- a/doc/src/sgml/extend.sgml +++ b/doc/src/sgml/extend.sgml @@ -193,9 +193,10 @@ </indexterm> <para> - Two pseudo-types of special interest are <type>anyelement</> and - <type>anyarray</>, which are collectively called <firstterm>polymorphic - types</>. Any function declared using these types is said to be + Three pseudo-types of special interest are <type>anyelement</>, + <type>anyarray</>, and <type>anyenum</>, + which are collectively called <firstterm>polymorphic types</>. + Any function declared using these types is said to be a <firstterm>polymorphic function</>. A polymorphic function can operate on many different data types, with the specific data type(s) being determined by the data types actually passed to it in a particular @@ -215,6 +216,9 @@ <type>anyelement</type>, the actual array type in the <type>anyarray</type> positions must be an array whose elements are the same type appearing in the <type>anyelement</type> positions. + <type>anyenum</> is treated exactly the same as <type>anyelement</>, + but adds the additional constraint that the actual type must + be an enum type. </para> <para> @@ -234,7 +238,9 @@ implements subscripting as <literal>subscript(anyarray, integer) returns anyelement</>. This declaration constrains the actual first argument to be an array type, and allows the parser to infer the correct - result type from the actual first argument's type. + result type from the actual first argument's type. Another example + is that a function declared as <literal>f(anyarray) returns anyenum</> + will only accept arrays of enum types. </para> </sect2> </sect1> diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index c5e7b9a46c..5f476b6e22 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -6646,6 +6646,87 @@ SELECT pg_sleep(1.5); </sect1> + <sect1 id="functions-enum"> + <title>Enum Support Functions</title> + + <para> + For enum types (described in <xref linkend="datatype-enum">), + there are several functions that allow cleaner programming without + hard-coding particular values of an enum type. + These are listed in <xref linkend="functions-enum-table">. The examples + assume an enum type created as: + +<programlisting> +CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple'); +</programlisting> + + </para> + + <table id="functions-enum-table"> + <title>Enum Support Functions</title> + <tgroup cols="4"> + <thead> + <row> + <entry>Function</entry> + <entry>Description</entry> + <entry>Example</entry> + <entry>Example Result</entry> + </row> + </thead> + <tbody> + <row> + <entry><literal>enum_first(anyenum)</literal></entry> + <entry>Returns the first value of the input enum type</entry> + <entry><literal>enum_first(null::rainbow)</literal></entry> + <entry><literal>red</literal></entry> + </row> + <row> + <entry><literal>enum_last(anyenum)</literal></entry> + <entry>Returns the last value of the input enum type</entry> + <entry><literal>enum_last(null::rainbow)</literal></entry> + <entry><literal>purple</literal></entry> + </row> + <row> + <entry><literal>enum_range(anyenum)</literal></entry> + <entry>Returns all values of the input enum type in an ordered array</entry> + <entry><literal>enum_range(null::rainbow)</literal></entry> + <entry><literal>{red,orange,yellow,green,blue,purple}</literal></entry> + </row> + <row> + <entry morerows="2"><literal>enum_range(anyenum, anyenum)</literal></entry> + <entry morerows="2"> + Returns the range between the two given enum values, as an ordered + array. The values must be from the same enum type. If the first + parameter is null, the result will start with the first value of + the enum type. + If the second parameter is null, the result will end with the last + value of the enum type. + </entry> + <entry><literal>enum_range('orange'::rainbow, 'green'::rainbow)</literal></entry> + <entry><literal>{orange,yellow,green}</literal></entry> + </row> + <row> + <entry><literal>enum_range(NULL, 'green'::rainbow)</literal></entry> + <entry><literal>{red,orange,yellow,green}</literal></entry> + </row> + <row> + <entry><literal>enum_range('orange'::rainbow, NULL)</literal></entry> + <entry><literal>{orange,yellow,green,blue,purple}</literal></entry> + </row> + </tbody> + </tgroup> + </table> + + <para> + Notice that except for the two-argument form of <function>enum_range</>, + these functions disregard the specific value passed to them; they care + only about its declared datatype. Either NULL or a specific value of + the type can be passed, with the same result. It is more common to + apply these functions to a table column or function argument than to + a hardwired type name as suggested by the examples. + </para> + </sect1> + <sect1 id="functions-geometry"> <title>Geometric Functions and Operators</title> diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index e52466aefc..ca4651e8cc 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -210,7 +210,8 @@ $$ LANGUAGE plpgsql; <para> <application>PL/pgSQL</> functions can also be declared to accept and return the polymorphic types - <type>anyelement</type> and <type>anyarray</type>. The actual + <type>anyelement</type>, <type>anyarray</type>, and <type>anyenum</>. + The actual data types handled by a polymorphic function can vary from call to call, as discussed in <xref linkend="extend-types-polymorphic">. An example is shown in <xref linkend="plpgsql-declaration-aliases">. @@ -698,8 +699,9 @@ $$ LANGUAGE plpgsql; <para> When the return type of a <application>PL/pgSQL</application> - function is declared as a polymorphic type (<type>anyelement</type> - or <type>anyarray</type>), a special parameter <literal>$0</literal> + function is declared as a polymorphic type (<type>anyelement</type>, + <type>anyarray</type>, or <type>anyenum</>), + a special parameter <literal>$0</literal> is created. Its data type is the actual return type of the function, as deduced from the actual input types (see <xref linkend="extend-types-polymorphic">). @@ -726,7 +728,7 @@ $$ LANGUAGE plpgsql; <para> The same effect can be had by declaring one or more output parameters as - <type>anyelement</type> or <type>anyarray</type>. In this case the + polymorphic types. In this case the special <literal>$0</literal> parameter is not used; the output parameters themselves serve the same purpose. For example: diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml index e7cd55d7aa..f8682506ac 100644 --- a/doc/src/sgml/ref/create_type.sgml +++ b/doc/src/sgml/ref/create_type.sgml @@ -23,6 +23,9 @@ PostgreSQL documentation CREATE TYPE <replaceable class="parameter">name</replaceable> AS ( <replaceable class="PARAMETER">attribute_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [, ... ] ) +CREATE TYPE <replaceable class="parameter">name</replaceable> AS ENUM + ( '<replaceable class="parameter">label</replaceable>' [, ... ] ) + CREATE TYPE <replaceable class="parameter">name</replaceable> ( INPUT = <replaceable class="parameter">input_function</replaceable>, OUTPUT = <replaceable class="parameter">output_function</replaceable> @@ -78,10 +81,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </refsect2> <refsect2> + <title>Enumerated Types</title> + + <para> + The second form of <command>CREATE TYPE</command> creates an enumerated + (enum) type, as described in <xref linkend="datatype-enum">. + Enum types take a list of one or more quoted labels, each of which + must be less than <symbol>NAMEDATALEN</symbol> bytes long (64 in a standard + <productname>PostgreSQL</productname> build). + </para> + </refsect2> + + <refsect2> <title>Base Types</title> <para> - The second form of <command>CREATE TYPE</command> creates a new base type + The third form of <command>CREATE TYPE</command> creates a new base type (scalar type). The parameters can appear in any order, not only that illustrated above, and most are optional. You must register two or more functions (using <command>CREATE FUNCTION</command>) before @@ -297,7 +312,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> <title>Array Types</title> <para> - Whenever a user-defined base data type is created, + Whenever a user-defined base or enum data type is created, <productname>PostgreSQL</productname> automatically creates an associated array type, whose name consists of the base type's name prepended with an underscore. The parser understands this @@ -364,6 +379,16 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> </varlistentry> <varlistentry> + <term><replaceable class="parameter">label</replaceable></term> + <listitem> + <para> + A string literal representing the textual label associated with + one value of an enum type. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><replaceable class="parameter">input_function</replaceable></term> <listitem> <para> @@ -568,6 +593,20 @@ $$ LANGUAGE SQL; </para> <para> + This example creates an enumerated type and uses it in + a table definition: +<programlisting> +CREATE TYPE bug_status AS ENUM ('new', 'open', 'closed'); + +CREATE TABLE bug ( + serial id, + description text, + status bug_status +); +</programlisting> + </para> + + <para> This example creates the base data type <type>box</type> and then uses the type in a table definition: <programlisting> diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index bc289650dc..b9ed111657 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -717,8 +717,8 @@ SELECT name, listchildren(name) FROM nodes; <para> <acronym>SQL</acronym> functions can be declared to accept and - return the polymorphic types <type>anyelement</type> and - <type>anyarray</type>. See <xref + return the polymorphic types <type>anyelement</type>, + <type>anyarray</type>, and <type>anyenum</type>. See <xref linkend="extend-types-polymorphic"> for a more detailed explanation of polymorphic functions. Here is a polymorphic function <function>make_array</function> that builds up an array @@ -746,7 +746,7 @@ SELECT make_array(1, 2) AS intarray, make_array('a'::text, 'b') AS textarray; Without the typecast, you will get errors like this: <screen> <computeroutput> -ERROR: could not determine "anyarray"/"anyelement" type because input has type "unknown" +ERROR: could not determine polymorphic type because input has type "unknown" </computeroutput> </screen> </para> @@ -769,7 +769,7 @@ CREATE FUNCTION invalid_func() RETURNS anyelement AS $$ SELECT 1; $$ LANGUAGE SQL; ERROR: cannot determine result data type -DETAIL: A function returning "anyarray" or "anyelement" must have at least one argument of either type. +DETAIL: A function returning a polymorphic type must have at least one polymorphic argument. </screen> </para> @@ -2831,7 +2831,7 @@ CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer, <para> C-language functions can be declared to accept and return the polymorphic types - <type>anyelement</type> and <type>anyarray</type>. + <type>anyelement</type>, <type>anyarray</type>, and <type>anyenum</type>. See <xref linkend="extend-types-polymorphic"> for a more detailed explanation of polymorphic functions. When function arguments or return types are defined as polymorphic types, the function author cannot know diff --git a/src/backend/access/hash/hashfunc.c b/src/backend/access/hash/hashfunc.c index 907c713f7a..33cdb9ce78 100644 --- a/src/backend/access/hash/hashfunc.c +++ b/src/backend/access/hash/hashfunc.c @@ -73,6 +73,12 @@ hashoid(PG_FUNCTION_ARGS) } Datum +hashenum(PG_FUNCTION_ARGS) +{ + PG_RETURN_UINT32(~((uint32) PG_GETARG_OID(0))); +} + +Datum hashfloat4(PG_FUNCTION_ARGS) { float4 key = PG_GETARG_FLOAT4(0); diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 33458b5e03..2a93dcbd87 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -11,7 +11,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ - pg_aggregate.o pg_constraint.o pg_conversion.o pg_depend.o \ + pg_aggregate.o pg_constraint.o pg_conversion.o pg_depend.o pg_enum.o \ pg_largeobject.o pg_namespace.o pg_operator.o pg_proc.o pg_shdepend.o \ pg_type.o toasting.o @@ -32,7 +32,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \ pg_language.h pg_largeobject.h pg_aggregate.h pg_statistic.h \ pg_rewrite.h pg_trigger.h pg_listener.h pg_description.h pg_cast.h \ - pg_namespace.h pg_conversion.h pg_depend.h \ + pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ pg_database.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ toasting.h indexing.h \ diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 71b9a0e3a2..693a7bfe55 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -412,7 +412,7 @@ CheckAttributeType(const char *attname, Oid atttypid) (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("column \"%s\" has type \"unknown\"", attname), errdetail("Proceeding with relation creation anyway."))); - else if (att_typtype == 'p') + else if (att_typtype == TYPTYPE_PSEUDO) { /* Special hack for pg_statistic: allow ANYARRAY during initdb */ if (atttypid != ANYARRAYOID || IsUnderPostmaster) @@ -718,7 +718,7 @@ AddNewRelationType(const char *typeName, new_rel_oid, /* relation oid */ new_rel_kind, /* relation kind */ -1, /* internal size (varlena) */ - 'c', /* type-type (complex) */ + TYPTYPE_COMPOSITE, /* type-type (composite) */ ',', /* default array delimiter */ F_RECORD_IN, /* input procedure */ F_RECORD_OUT, /* output procedure */ diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 1f592fd382..30f6bf3b23 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -80,8 +80,7 @@ AggregateCreate(const char *aggName, hasPolyArg = false; for (i = 0; i < numArgs; i++) { - if (aggArgTypes[i] == ANYARRAYOID || - aggArgTypes[i] == ANYELEMENTOID) + if (IsPolymorphicType(aggArgTypes[i])) { hasPolyArg = true; break; @@ -92,12 +91,11 @@ AggregateCreate(const char *aggName, * If transtype is polymorphic, must have polymorphic argument also; else * we will have no way to deduce the actual transtype. */ - if (!hasPolyArg && - (aggTransType == ANYARRAYOID || aggTransType == ANYELEMENTOID)) + if (IsPolymorphicType(aggTransType) && !hasPolyArg) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine transition data type"), - errdetail("An aggregate using \"anyarray\" or \"anyelement\" as transition type must have at least one argument of either type."))); + errdetail("An aggregate using a polymorphic transition type must have at least one polymorphic argument."))); /* find the transfn */ nargs_transfn = numArgs + 1; @@ -170,13 +168,12 @@ AggregateCreate(const char *aggName, * that itself violates the rule against polymorphic result with no * polymorphic input.) */ - if (!hasPolyArg && - (finaltype == ANYARRAYOID || finaltype == ANYELEMENTOID)) + if (IsPolymorphicType(finaltype) && !hasPolyArg) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot determine result data type"), - errdetail("An aggregate returning \"anyarray\" or \"anyelement\" " - "must have at least one argument of either type."))); + errdetail("An aggregate returning a polymorphic type " + "must have at least one polymorphic argument."))); /* handle sortop, if supplied */ if (aggsortopName) @@ -329,8 +326,7 @@ lookup_agg_function(List *fnName, */ for (i = 0; i < nargs; i++) { - if (input_types[i] != ANYARRAYOID && - input_types[i] != ANYELEMENTOID) + if (!IsPolymorphicType(input_types[i])) { allPolyArgs = false; break; @@ -351,8 +347,7 @@ lookup_agg_function(List *fnName, */ for (i = 0; i < nargs; i++) { - if (true_oid_array[i] != ANYARRAYOID && - true_oid_array[i] != ANYELEMENTOID && + if (!IsPolymorphicType(true_oid_array[i]) && !IsBinaryCoercible(input_types[i], true_oid_array[i])) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), diff --git a/src/backend/catalog/pg_enum.c b/src/backend/catalog/pg_enum.c new file mode 100644 index 0000000000..c0332ccfc0 --- /dev/null +++ b/src/backend/catalog/pg_enum.c @@ -0,0 +1,146 @@ +/*------------------------------------------------------------------------- + * + * pg_enum.c + * routines to support manipulation of the pg_enum relation + * + * Copyright (c) 2006-2007, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/pg_enum.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" + +static int oid_cmp(const void *p1, const void *p2); + + +/* + * EnumValuesCreate + * Create an entry in pg_enum for each of the supplied enum values. + * + * vals is a list of Value strings. + */ +void +EnumValuesCreate(Oid enumTypeOid, List *vals) +{ + Relation pg_enum; + TupleDesc tupDesc; + NameData enumlabel; + Oid *oids; + int i, n; + Datum values[Natts_pg_enum]; + char nulls[Natts_pg_enum]; + ListCell *lc; + HeapTuple tup; + + n = list_length(vals); + + /* + * XXX 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. Is it worth trying harder? + */ + + pg_enum = heap_open(EnumRelationId, RowExclusiveLock); + tupDesc = pg_enum->rd_att; + + /* + * Allocate oids. While this method does not absolutely guarantee + * that we generate no duplicate oids (since we haven't entered each + * oid into the table before allocating the next), trouble could only + * occur if the oid counter wraps all the way around before we finish. + * Which seems unlikely. + */ + oids = (Oid *) palloc(n * sizeof(Oid)); + for(i = 0; i < n; i++) + { + oids[i] = GetNewOid(pg_enum); + } + + /* sort them, just in case counter wrapped from high to low */ + qsort(oids, n, sizeof(Oid), oid_cmp); + + /* and make the entries */ + memset(nulls, ' ', sizeof(nulls)); + + i = 0; + foreach(lc, vals) + { + char *lab = strVal(lfirst(lc)); + + values[Anum_pg_enum_enumtypid - 1] = ObjectIdGetDatum(enumTypeOid); + namestrcpy(&enumlabel, lab); + values[Anum_pg_enum_enumlabel - 1] = NameGetDatum(&enumlabel); + + tup = heap_formtuple(tupDesc, values, nulls); + HeapTupleSetOid(tup, oids[i]); + + simple_heap_insert(pg_enum, tup); + CatalogUpdateIndexes(pg_enum, tup); + heap_freetuple(tup); + + i++; + } + + /* clean up */ + pfree(oids); + heap_close(pg_enum, RowExclusiveLock); +} + + +/* + * EnumValuesDelete + * Remove all the pg_enum entries for the specified enum type. + */ +void +EnumValuesDelete(Oid enumTypeOid) +{ + Relation pg_enum; + ScanKeyData key[1]; + SysScanDesc scan; + HeapTuple tup; + + pg_enum = heap_open(EnumRelationId, RowExclusiveLock); + + ScanKeyInit(&key[0], + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(enumTypeOid)); + + scan = systable_beginscan(pg_enum, EnumTypIdLabelIndexId, true, + SnapshotNow, 1, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + simple_heap_delete(pg_enum, &tup->t_self); + } + + systable_endscan(scan); + + heap_close(pg_enum, RowExclusiveLock); +} + + +/* qsort comparison function */ +static int +oid_cmp(const void *p1, const void *p2) +{ + Oid v1 = *((const Oid *) p1); + Oid v2 = *((const Oid *) p2); + + if (v1 < v2) + return -1; + if (v1 > v2) + return 1; + return 0; +} diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 284d41b1b6..ca803040fd 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -137,9 +137,9 @@ ProcedureCreate(const char *procedureName, } /* - * Do not allow return type ANYARRAY or ANYELEMENT unless at least one - * input argument is ANYARRAY or ANYELEMENT. Also, do not allow return - * type INTERNAL unless at least one input argument is INTERNAL. + * Do not allow polymorphic return type unless at least one input argument + * is polymorphic. Also, do not allow return type INTERNAL unless at + * least one input argument is INTERNAL. */ for (i = 0; i < parameterCount; i++) { @@ -147,6 +147,7 @@ ProcedureCreate(const char *procedureName, { case ANYARRAYOID: case ANYELEMENTOID: + case ANYENUMOID: genericInParam = true; break; case INTERNALOID: @@ -169,6 +170,7 @@ ProcedureCreate(const char *procedureName, { case ANYARRAYOID: case ANYELEMENTOID: + case ANYENUMOID: genericOutParam = true; break; case INTERNALOID: @@ -178,12 +180,12 @@ ProcedureCreate(const char *procedureName, } } - if ((returnType == ANYARRAYOID || returnType == ANYELEMENTOID || - genericOutParam) && !genericInParam) + if ((IsPolymorphicType(returnType) || genericOutParam) + && !genericInParam) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine result data type"), - errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type."))); + errdetail("A function returning a polymorphic type must have at least one polymorphic argument."))); if ((returnType == INTERNALOID || internalOutParam) && !internalInParam) ereport(ERROR, @@ -533,26 +535,24 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) proc = (Form_pg_proc) GETSTRUCT(tuple); /* Disallow pseudotype result */ - /* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */ - if (get_typtype(proc->prorettype) == 'p' && + /* except for RECORD, VOID, or polymorphic */ + if (get_typtype(proc->prorettype) == TYPTYPE_PSEUDO && proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && - proc->prorettype != ANYARRAYOID && - proc->prorettype != ANYELEMENTOID) + !IsPolymorphicType(proc->prorettype)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("SQL functions cannot return type %s", format_type_be(proc->prorettype)))); /* Disallow pseudotypes in arguments */ - /* except for ANYARRAY or ANYELEMENT */ + /* except for polymorphic */ haspolyarg = false; for (i = 0; i < proc->pronargs; i++) { - if (get_typtype(proc->proargtypes.values[i]) == 'p') + if (get_typtype(proc->proargtypes.values[i]) == TYPTYPE_PSEUDO) { - if (proc->proargtypes.values[i] == ANYARRAYOID || - proc->proargtypes.values[i] == ANYELEMENTOID) + if (IsPolymorphicType(proc->proargtypes.values[i])) haspolyarg = true; else ereport(ERROR, diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 0be8bb6809..c412a1c432 100644 --- a/src/backend/catalog/pg_type.c +++ b/src/backend/catalog/pg_type.c @@ -75,7 +75,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace) * * The representational details are the same as int4 ... it doesn't really * matter what they are so long as they are consistent. Also note that we - * give it typtype = 'p' (pseudotype) as extra insurance that it won't be + * give it typtype = TYPTYPE_PSEUDO as extra insurance that it won't be * mistaken for a usable type. */ i = 0; @@ -85,7 +85,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace) values[i++] = ObjectIdGetDatum(GetUserId()); /* typowner */ values[i++] = Int16GetDatum(sizeof(int4)); /* typlen */ values[i++] = BoolGetDatum(true); /* typbyval */ - values[i++] = CharGetDatum('p'); /* typtype */ + values[i++] = CharGetDatum(TYPTYPE_PSEUDO); /* typtype */ values[i++] = BoolGetDatum(false); /* typisdefined */ values[i++] = CharGetDatum(DEFAULT_TYPDELIM); /* typdelim */ values[i++] = ObjectIdGetDatum(InvalidOid); /* typrelid */ @@ -159,7 +159,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace) Oid TypeCreate(const char *typeName, Oid typeNamespace, - Oid relationOid, /* only for 'c'atalog types */ + Oid relationOid, /* only for composite types */ char relationKind, /* ditto */ int16 internalSize, char typeType, @@ -243,7 +243,8 @@ TypeCreate(const char *typeName, values[i++] = CharGetDatum(typeType); /* typtype */ values[i++] = BoolGetDatum(true); /* typisdefined */ values[i++] = CharGetDatum(typDelim); /* typdelim */ - values[i++] = ObjectIdGetDatum(typeType == 'c' ? relationOid : InvalidOid); /* typrelid */ + values[i++] = ObjectIdGetDatum(typeType == TYPTYPE_COMPOSITE ? + relationOid : InvalidOid); /* typrelid */ values[i++] = ObjectIdGetDatum(elementType); /* typelem */ values[i++] = ObjectIdGetDatum(inputProcedure); /* typinput */ values[i++] = ObjectIdGetDatum(outputProcedure); /* typoutput */ @@ -377,7 +378,7 @@ TypeCreate(const char *typeName, void GenerateTypeDependencies(Oid typeNamespace, Oid typeObjectId, - Oid relationOid, /* only for 'c'atalog types */ + Oid relationOid, /* only for composite types */ char relationKind, /* ditto */ Oid owner, Oid inputProcedure, diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c index c414728a81..c6a9f1e43a 100644 --- a/src/backend/commands/aggregatecmds.c +++ b/src/backend/commands/aggregatecmds.c @@ -176,9 +176,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) * in some cases (AggregateCreate will check). */ transTypeId = typenameTypeId(NULL, transType); - if (get_typtype(transTypeId) == 'p' && - transTypeId != ANYARRAYOID && - transTypeId != ANYELEMENTOID) + if (get_typtype(transTypeId) == TYPTYPE_PSEUDO && + !IsPolymorphicType(transTypeId)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate transition data type cannot be %s", diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 416055eb60..37944788a2 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -1259,13 +1259,13 @@ CreateCast(CreateCastStmt *stmt) targettypeid = typenameTypeId(NULL, stmt->targettype); /* No pseudo-types allowed */ - if (get_typtype(sourcetypeid) == 'p') + if (get_typtype(sourcetypeid) == TYPTYPE_PSEUDO) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("source data type %s is a pseudo-type", TypeNameToString(stmt->sourcetype)))); - if (get_typtype(targettypeid) == 'p') + if (get_typtype(targettypeid) == TYPTYPE_PSEUDO) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("target data type %s is a pseudo-type", diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index ce0f97f341..c70a784c21 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -39,6 +39,7 @@ #include "catalog/indexing.h" #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" +#include "catalog/pg_enum.h" #include "catalog/pg_namespace.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -205,7 +206,7 @@ DefineType(List *names, List *parameters) { elemType = typenameTypeId(NULL, defGetTypeName(defel)); /* disallow arrays of pseudotypes */ - if (get_typtype(elemType) == 'p') + if (get_typtype(elemType) == TYPTYPE_PSEUDO) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("array element type cannot be %s", @@ -404,7 +405,7 @@ DefineType(List *names, List *parameters) InvalidOid, /* relation oid (n/a here) */ 0, /* relation kind (ditto) */ internalLength, /* internal size */ - 'b', /* type-type (base type) */ + TYPTYPE_BASE, /* type-type (base type) */ delimiter, /* array element delimiter */ inputOid, /* input procedure */ outputOid, /* output procedure */ @@ -438,7 +439,7 @@ DefineType(List *names, List *parameters) InvalidOid, /* relation oid (n/a here) */ 0, /* relation kind (ditto) */ -1, /* internal size */ - 'b', /* type-type (base type) */ + TYPTYPE_BASE, /* type-type (base type) */ DEFAULT_TYPDELIM, /* array element delimiter */ F_ARRAY_IN, /* input procedure */ F_ARRAY_OUT, /* output procedure */ @@ -543,6 +544,14 @@ RemoveTypeById(Oid typeOid) simple_heap_delete(relation, &tup->t_self); + /* + * If it is an enum, delete the pg_enum entries too; we don't bother + * with making dependency entries for those, so it has to be done + * "by hand" here. + */ + if (((Form_pg_type) GETSTRUCT(tup))->typtype == TYPTYPE_ENUM) + EnumValuesDelete(typeOid); + ReleaseSysCache(tup); heap_close(relation, RowExclusiveLock); @@ -620,12 +629,15 @@ DefineDomain(CreateDomainStmt *stmt) basetypeMod = typenameTypeMod(NULL, stmt->typename, basetypeoid); /* - * Base type must be a plain base type or another domain. Domains over - * pseudotypes would create a security hole. Domains over composite types - * might be made to work in the future, but not today. + * Base type must be a plain base type, another domain or an enum. + * Domains over pseudotypes would create a security hole. Domains + * over composite types might be made to work in the future, but not + * today. */ typtype = baseType->typtype; - if (typtype != 'b' && typtype != 'd') + if (typtype != TYPTYPE_BASE && + typtype != TYPTYPE_DOMAIN && + typtype != TYPTYPE_ENUM) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("\"%s\" is not a valid base type for a domain", @@ -798,7 +810,7 @@ DefineDomain(CreateDomainStmt *stmt) InvalidOid, /* relation oid (n/a here) */ 0, /* relation kind (ditto) */ internalLength, /* internal size */ - 'd', /* type-type (domain type) */ + TYPTYPE_DOMAIN, /* type-type (domain type) */ delimiter, /* array element delimiter */ inputProcedure, /* input procedure */ outputProcedure, /* output procedure */ @@ -907,7 +919,7 @@ RemoveDomain(List *names, DropBehavior behavior, bool missing_ok) /* Check that this is actually a domain */ typtype = ((Form_pg_type) GETSTRUCT(tup))->typtype; - if (typtype != 'd') + if (typtype != TYPTYPE_DOMAIN) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a domain", @@ -925,6 +937,100 @@ RemoveDomain(List *names, DropBehavior behavior, bool missing_ok) performDeletion(&object, behavior); } +/* + * DefineEnum + * Registers a new enum. + */ +void +DefineEnum(CreateEnumStmt *stmt) +{ + char *enumName; + char *enumArrayName; + Oid enumNamespace; + Oid enumTypeOid; + AclResult aclresult; + + /* Convert list of names to a name and namespace */ + enumNamespace = QualifiedNameGetCreationNamespace(stmt->typename, + &enumName); + + /* Check we have creation rights in target namespace */ + aclresult = pg_namespace_aclcheck(enumNamespace, GetUserId(), ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_NAMESPACE, + get_namespace_name(enumNamespace)); + + /* + * Type names must be one character shorter than other names, allowing + * room to create the corresponding array type name with prepended "_". + */ + if (strlen(enumName) > (NAMEDATALEN - 2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("type names must be %d characters or less", + NAMEDATALEN - 2))); + + /* Create the pg_type entry */ + enumTypeOid = + TypeCreate(enumName, /* type name */ + enumNamespace, /* namespace */ + InvalidOid, /* relation oid (n/a here) */ + 0, /* relation kind (ditto) */ + sizeof(Oid), /* internal size */ + TYPTYPE_ENUM, /* type-type (enum type) */ + DEFAULT_TYPDELIM, /* array element delimiter */ + F_ENUM_IN, /* input procedure */ + F_ENUM_OUT, /* output procedure */ + InvalidOid, /* receive procedure - none */ + InvalidOid, /* send procedure - none */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + InvalidOid, /* analyze procedure - default */ + InvalidOid, /* element type ID */ + InvalidOid, /* base type ID (only for domains) */ + NULL, /* never a default type value */ + NULL, /* binary default isn't sent either */ + true, /* always passed by value */ + 'i', /* int alignment */ + 'p', /* TOAST strategy always plain */ + -1, /* typMod (Domains only) */ + 0, /* Array dimensions of typbasetype */ + false); /* Type NOT NULL */ + + /* Enter the enum's values into pg_enum */ + EnumValuesCreate(enumTypeOid, stmt->vals); + + /* Create array type for enum */ + enumArrayName = makeArrayTypeName(enumName); + + TypeCreate(enumArrayName, /* type name */ + enumNamespace, /* namespace */ + InvalidOid, /* relation oid (n/a here) */ + 0, /* relation kind (ditto) */ + -1, /* internal size */ + TYPTYPE_BASE, /* type-type (base type) */ + DEFAULT_TYPDELIM, /* array element delimiter */ + F_ARRAY_IN, /* input procedure */ + F_ARRAY_OUT, /* output procedure */ + F_ARRAY_RECV, /* receive procedure */ + F_ARRAY_SEND, /* send procedure */ + InvalidOid, /* typmodin procedure - none */ + InvalidOid, /* typmodout procedure - none */ + InvalidOid, /* analyze procedure - default */ + enumTypeOid, /* element type ID */ + InvalidOid, /* base type ID */ + NULL, /* never a default type value */ + NULL, /* binary default isn't sent either */ + false, /* never passed by value */ + 'i', /* enums have align i, so do their arrays */ + 'x', /* ARRAY is always toastable */ + -1, /* typMod (Domains only) */ + 0, /* Array dimensions of typbasetype */ + false); /* Type NOT NULL */ + + pfree(enumArrayName); +} + /* * Find suitable I/O functions for a type. @@ -1835,7 +1941,7 @@ checkDomainOwner(HeapTuple tup, TypeName *typename) Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); /* Check that this is actually a domain */ - if (typTup->typtype != 'd') + if (typTup->typtype != TYPTYPE_DOMAIN) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a domain", @@ -2021,7 +2127,7 @@ GetDomainConstraints(Oid typeOid) elog(ERROR, "cache lookup failed for type %u", typeOid); typTup = (Form_pg_type) GETSTRUCT(tup); - if (typTup->typtype != 'd') + if (typTup->typtype != TYPTYPE_DOMAIN) { /* Not a domain, so done */ ReleaseSysCache(tup); @@ -2148,7 +2254,7 @@ AlterTypeOwner(List *names, Oid newOwnerId) * free-standing composite type, and not a table's underlying type. We * want people to use ALTER TABLE not ALTER TYPE for that case. */ - if (typTup->typtype == 'c' && + if (typTup->typtype == TYPTYPE_COMPOSITE && get_rel_relkind(typTup->typrelid) != RELKIND_COMPOSITE_TYPE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -2325,11 +2431,12 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, /* Detect whether type is a composite type (but not a table rowtype) */ isCompositeType = - (typform->typtype == 'c' && + (typform->typtype == TYPTYPE_COMPOSITE && get_rel_relkind(typform->typrelid) == RELKIND_COMPOSITE_TYPE); /* Enforce not-table-type if requested */ - if (typform->typtype == 'c' && !isCompositeType && errorOnTableType) + if (typform->typtype == TYPTYPE_COMPOSITE && !isCompositeType && + errorOnTableType) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s is a table's row type", @@ -2376,14 +2483,14 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, else { /* If it's a domain, it might have constraints */ - if (typform->typtype == 'd') + if (typform->typtype == TYPTYPE_DOMAIN) AlterConstraintNamespaces(typeOid, oldNspOid, nspOid, true); /* * Update dependency on schema, if any --- a table rowtype has not got * one. */ - if (typform->typtype != 'c') + if (typform->typtype != TYPTYPE_COMPOSITE) if (changeDependencyFor(TypeRelationId, typeOid, NamespaceRelationId, oldNspOid, nspOid) != 1) elog(ERROR, "failed to change schema dependency for type %s", diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index b44b11cd79..c0609d8ed2 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -182,7 +182,7 @@ init_sql_fcache(FmgrInfo *finfo) */ rettype = procedureStruct->prorettype; - if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) + if (IsPolymorphicType(rettype)) { rettype = get_fn_expr_rettype(finfo); if (rettype == InvalidOid) /* this probably should not happen */ @@ -218,7 +218,7 @@ init_sql_fcache(FmgrInfo *finfo) { Oid argtype = argOidVect[argnum]; - if (argtype == ANYARRAYOID || argtype == ANYELEMENTOID) + if (IsPolymorphicType(argtype)) { argtype = get_fn_expr_argtype(finfo, argnum); if (argtype == InvalidOid) @@ -845,9 +845,9 @@ ShutdownSQLFunction(Datum arg) * to be sure that the user is returning the type he claims. * * For a polymorphic function the passed rettype must be the actual resolved - * output type of the function; we should never see ANYARRAY or ANYELEMENT - * as rettype. (This means we can't check the type during function definition - * of a polymorphic function.) + * output type of the function; we should never see ANYARRAY, ANYENUM or + * ANYELEMENT as rettype. (This means we can't check the type during function + * definition of a polymorphic function.) * * The return value is true if the function returns the entire tuple result * of its final SELECT, and false otherwise. Note that because we allow @@ -925,7 +925,9 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, fn_typtype = get_typtype(rettype); - if (fn_typtype == 'b' || fn_typtype == 'd') + if (fn_typtype == TYPTYPE_BASE || + fn_typtype == TYPTYPE_DOMAIN || + fn_typtype == TYPTYPE_ENUM) { /* * For base-type returns, the target list should have exactly one @@ -948,7 +950,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, errdetail("Actual return type is %s.", format_type_be(restype)))); } - else if (fn_typtype == 'c' || rettype == RECORDOID) + else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID) { /* Returns a rowtype */ TupleDesc tupdesc; @@ -1053,13 +1055,13 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, /* Report that we are returning entire tuple result */ return true; } - else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) + else if (IsPolymorphicType(rettype)) { /* This should already have been caught ... */ ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine result data type"), - errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type."))); + errdetail("A function returning a polymorphic type must have at least one polymorphic argument."))); } else ereport(ERROR, diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index b3e5de1b63..4f800d1916 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -1363,8 +1363,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* * Get actual datatypes of the inputs. These could be different from - * the agg's declared input types, when the agg accepts ANY, ANYARRAY - * or ANYELEMENT. + * the agg's declared input types, when the agg accepts ANY or + * a polymorphic type. */ i = 0; foreach(lc, aggref->args) @@ -1421,7 +1421,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* resolve actual type of transition state, if polymorphic */ aggtranstype = aggform->aggtranstype; - if (aggtranstype == ANYARRAYOID || aggtranstype == ANYELEMENTOID) + if (IsPolymorphicType(aggtranstype)) { /* have to fetch the agg's declared input types... */ Oid *declaredArgTypes; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 93d8cda90f..1648713307 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2361,6 +2361,17 @@ _copyCompositeTypeStmt(CompositeTypeStmt *from) return newnode; } +static CreateEnumStmt * +_copyCreateEnumStmt(CreateEnumStmt *from) +{ + CreateEnumStmt *newnode = makeNode(CreateEnumStmt); + + COPY_NODE_FIELD(typename); + COPY_NODE_FIELD(vals); + + return newnode; +} + static ViewStmt * _copyViewStmt(ViewStmt *from) { @@ -3312,6 +3323,9 @@ copyObject(void *from) case T_CompositeTypeStmt: retval = _copyCompositeTypeStmt(from); break; + case T_CreateEnumStmt: + retval = _copyCreateEnumStmt(from); + break; case T_ViewStmt: retval = _copyViewStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 2a02ad2770..0af004ad1d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1187,6 +1187,15 @@ _equalCompositeTypeStmt(CompositeTypeStmt *a, CompositeTypeStmt *b) } static bool +_equalCreateEnumStmt(CreateEnumStmt *a, CreateEnumStmt *b) +{ + COMPARE_NODE_FIELD(typename); + COMPARE_NODE_FIELD(vals); + + return true; +} + +static bool _equalViewStmt(ViewStmt *a, ViewStmt *b) { COMPARE_NODE_FIELD(view); @@ -2247,6 +2256,9 @@ equal(void *a, void *b) case T_CompositeTypeStmt: retval = _equalCompositeTypeStmt(a, b); break; + case T_CreateEnumStmt: + retval = _equalCreateEnumStmt(a, b); + break; case T_ViewStmt: retval = _equalViewStmt(a, b); break; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 998ada671b..d6c3217e3f 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -435,7 +435,7 @@ count_agg_clauses_walker(Node *node, AggClauseCounts *counts) ReleaseSysCache(aggTuple); /* resolve actual type of transition state, if polymorphic */ - if (aggtranstype == ANYARRAYOID || aggtranstype == ANYELEMENTOID) + if (IsPolymorphicType(aggtranstype)) { /* have to fetch the agg's declared input types... */ Oid *declaredArgTypes; @@ -2907,8 +2907,7 @@ inline_function(Oid funcid, Oid result_type, List *args, funcform->pronargs * sizeof(Oid)); for (i = 0; i < funcform->pronargs; i++) { - if (argtypes[i] == ANYARRAYOID || - argtypes[i] == ANYELEMENTOID) + if (IsPolymorphicType(argtypes[i])) { argtypes[i] = exprType((Node *) list_nth(args, i)); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 25a5be3435..6fe3acd8f0 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -248,6 +248,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) TableFuncElementList opt_type_modifiers prep_type_clause execute_param_clause using_clause returning_clause + enum_val_list %type <range> OptTempTableName %type <into> into_clause create_as_target @@ -383,7 +384,7 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC DISABLE_P DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP - EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ESCAPE EXCEPT EXCLUDING + EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOR FORCE FOREIGN FORWARD @@ -2922,6 +2923,13 @@ DefineStmt: n->coldeflist = $6; $$ = (Node *)n; } + | CREATE TYPE_P any_name AS ENUM_P '(' enum_val_list ')' + { + CreateEnumStmt *n = makeNode(CreateEnumStmt); + n->typename = $3; + n->vals = $7; + $$ = (Node *)n; + } ; definition: '(' def_list ')' { $$ = $2; } @@ -2966,6 +2974,12 @@ old_aggr_elem: IDENT '=' def_arg } ; +enum_val_list: Sconst + { $$ = list_make1(makeString($1)); } + | enum_val_list ',' Sconst + { $$ = lappend($1, makeString($3)); } + ; + /***************************************************************************** * @@ -8760,6 +8774,7 @@ unreserved_keyword: | ENABLE_P | ENCODING | ENCRYPTED + | ENUM_P | ESCAPE | EXCLUDING | EXCLUSIVE diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 0f1d06f6ea..6a1ca7f3a8 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -136,6 +136,7 @@ static const ScanKeyword ScanKeywords[] = { {"encoding", ENCODING}, {"encrypted", ENCRYPTED}, {"end", END_P}, + {"enum", ENUM_P}, {"escape", ESCAPE}, {"except", EXCEPT}, {"excluding", EXCLUDING}, diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 0383288476..c520236105 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -132,7 +132,8 @@ coerce_type(ParseState *pstate, Node *node, } if (targetTypeId == ANYOID || targetTypeId == ANYELEMENTOID || - (targetTypeId == ANYARRAYOID && inputTypeId != UNKNOWNOID)) + (targetTypeId == ANYARRAYOID && inputTypeId != UNKNOWNOID) || + (targetTypeId == ANYENUMOID && inputTypeId != UNKNOWNOID)) { /* * Assume can_coerce_type verified that implicit coercion is okay. @@ -143,7 +144,8 @@ coerce_type(ParseState *pstate, Node *node, * since an UNKNOWN value is still a perfectly valid Datum. However * an UNKNOWN value is definitely *not* an array, and so we mustn't * accept it for ANYARRAY. (Instead, we will call anyarray_in below, - * which will produce an error.) + * which will produce an error.) Likewise, UNKNOWN input is no good + * for ANYENUM. * * NB: we do NOT want a RelabelType here. */ @@ -406,9 +408,8 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids, if (targetTypeId == ANYOID) continue; - /* accept if target is ANYARRAY or ANYELEMENT, for now */ - if (targetTypeId == ANYARRAYOID || - targetTypeId == ANYELEMENTOID) + /* accept if target is polymorphic, for now */ + if (IsPolymorphicType(targetTypeId)) { have_generics = true; /* do more checking later */ continue; @@ -1048,6 +1049,9 @@ coerce_to_common_type(ParseState *pstate, Node *node, * 3) If there are arguments of both ANYELEMENT and ANYARRAY, make sure * the actual ANYELEMENT datatype is in fact the element type for * the actual ANYARRAY datatype. + * 4) ANYENUM is treated the same as ANYELEMENT except that if it is used + * (alone or in combination with plain ANYELEMENT), we add the extra + * condition that the ANYELEMENT type must be an enum. * * If we have UNKNOWN input (ie, an untyped literal) for any ANYELEMENT * or ANYARRAY argument, assume it is okay. @@ -1070,6 +1074,7 @@ check_generic_type_consistency(Oid *actual_arg_types, Oid array_typeid = InvalidOid; Oid array_typelem; bool have_anyelement = false; + bool have_anyenum = false; /* * Loop through the arguments to see if we have any that are ANYARRAY or @@ -1079,9 +1084,12 @@ check_generic_type_consistency(Oid *actual_arg_types, { Oid actual_type = actual_arg_types[j]; - if (declared_arg_types[j] == ANYELEMENTOID) + if (declared_arg_types[j] == ANYELEMENTOID || + declared_arg_types[j] == ANYENUMOID) { have_anyelement = true; + if (declared_arg_types[j] == ANYENUMOID) + have_anyenum = true; if (actual_type == UNKNOWNOID) continue; if (OidIsValid(elem_typeid) && actual_type != elem_typeid) @@ -1127,6 +1135,13 @@ check_generic_type_consistency(Oid *actual_arg_types, } } + if (have_anyenum) + { + /* require the element type to be an enum */ + if (!type_is_enum(elem_typeid)) + return false; + } + /* Looks valid */ return true; } @@ -1136,18 +1151,18 @@ check_generic_type_consistency(Oid *actual_arg_types, * Make sure a polymorphic function is legally callable, and * deduce actual argument and result types. * - * If ANYARRAY or ANYELEMENT is used for a function's arguments or + * If ANYARRAY, ANYELEMENT, or ANYENUM is used for a function's arguments or * return type, we make sure the actual data types are consistent with * each other. The argument consistency rules are shown above for * check_generic_type_consistency(). * - * If we have UNKNOWN input (ie, an untyped literal) for any ANYELEMENT - * or ANYARRAY argument, we attempt to deduce the actual type it should - * have. If successful, we alter that position of declared_arg_types[] - * so that make_fn_arguments will coerce the literal to the right thing. + * If we have UNKNOWN input (ie, an untyped literal) for any polymorphic + * argument, we attempt to deduce the actual type it should have. If + * successful, we alter that position of declared_arg_types[] so that + * make_fn_arguments will coerce the literal to the right thing. * * Rules are applied to the function's return type (possibly altering it) - * if it is declared ANYARRAY or ANYELEMENT: + * if it is declared as a polymorphic type: * * 1) If return type is ANYARRAY, and any argument is ANYARRAY, use the * argument's actual type as the function's return type. @@ -1167,6 +1182,9 @@ check_generic_type_consistency(Oid *actual_arg_types, * 6) If return type is ANYELEMENT, no argument is ANYARRAY or ANYELEMENT, * generate an ERROR. This condition is prevented by CREATE FUNCTION * and is therefore not expected here. + * 7) ANYENUM is treated the same as ANYELEMENT except that if it is used + * (alone or in combination with plain ANYELEMENT), we add the extra + * condition that the ANYELEMENT type must be an enum. */ Oid enforce_generic_type_consistency(Oid *actual_arg_types, @@ -1180,7 +1198,9 @@ enforce_generic_type_consistency(Oid *actual_arg_types, Oid elem_typeid = InvalidOid; Oid array_typeid = InvalidOid; Oid array_typelem; - bool have_anyelement = (rettype == ANYELEMENTOID); + bool have_anyelement = (rettype == ANYELEMENTOID || + rettype == ANYENUMOID); + bool have_anyenum = (rettype == ANYENUMOID); /* * Loop through the arguments to see if we have any that are ANYARRAY or @@ -1190,9 +1210,12 @@ enforce_generic_type_consistency(Oid *actual_arg_types, { Oid actual_type = actual_arg_types[j]; - if (declared_arg_types[j] == ANYELEMENTOID) + if (declared_arg_types[j] == ANYELEMENTOID || + declared_arg_types[j] == ANYENUMOID) { have_generics = have_anyelement = true; + if (declared_arg_types[j] == ANYENUMOID) + have_anyenum = true; if (actual_type == UNKNOWNOID) { have_unknowns = true; @@ -1227,8 +1250,8 @@ enforce_generic_type_consistency(Oid *actual_arg_types, } /* - * Fast Track: if none of the arguments are ANYARRAY or ANYELEMENT, return - * the unmodified rettype. + * Fast Track: if none of the arguments are polymorphic, return the + * unmodified rettype. We assume it can't be polymorphic either. */ if (!have_generics) return rettype; @@ -1274,7 +1297,17 @@ enforce_generic_type_consistency(Oid *actual_arg_types, /* Only way to get here is if all the generic args are UNKNOWN */ ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("could not determine anyarray/anyelement type because input has type \"unknown\""))); + errmsg("could not determine polymorphic type because input has type \"unknown\""))); + } + + if (have_anyenum) + { + /* require the element type to be an enum */ + if (!type_is_enum(elem_typeid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type matched to anyenum is not an enum type: %s", + format_type_be(elem_typeid)))); } /* @@ -1289,7 +1322,8 @@ enforce_generic_type_consistency(Oid *actual_arg_types, if (actual_type != UNKNOWNOID) continue; - if (declared_arg_types[j] == ANYELEMENTOID) + if (declared_arg_types[j] == ANYELEMENTOID || + declared_arg_types[j] == ANYENUMOID) declared_arg_types[j] = elem_typeid; else if (declared_arg_types[j] == ANYARRAYOID) { @@ -1307,7 +1341,7 @@ enforce_generic_type_consistency(Oid *actual_arg_types, } } - /* if we return ANYARRAYOID use the appropriate argument type */ + /* if we return ANYARRAY use the appropriate argument type */ if (rettype == ANYARRAYOID) { if (!OidIsValid(array_typeid)) @@ -1322,8 +1356,8 @@ enforce_generic_type_consistency(Oid *actual_arg_types, return array_typeid; } - /* if we return ANYELEMENTOID use the appropriate argument type */ - if (rettype == ANYELEMENTOID) + /* if we return ANYELEMENT use the appropriate argument type */ + if (rettype == ANYELEMENTOID || rettype == ANYENUMOID) return elem_typeid; /* we don't return a generic type; send back the original return type */ @@ -1333,7 +1367,7 @@ enforce_generic_type_consistency(Oid *actual_arg_types, /* * resolve_generic_type() * Deduce an individual actual datatype on the assumption that - * the rules for ANYARRAY/ANYELEMENT are being followed. + * the rules for polymorphic types are being followed. * * declared_type is the declared datatype we want to resolve. * context_actual_type is the actual input datatype to some argument @@ -1362,7 +1396,8 @@ resolve_generic_type(Oid declared_type, format_type_be(context_actual_type)))); return context_actual_type; } - else if (context_declared_type == ANYELEMENTOID) + else if (context_declared_type == ANYELEMENTOID || + context_declared_type == ANYENUMOID) { /* Use the array type corresponding to actual type */ Oid array_typeid = get_array_type(context_actual_type); @@ -1375,7 +1410,7 @@ resolve_generic_type(Oid declared_type, return array_typeid; } } - else if (declared_type == ANYELEMENTOID) + else if (declared_type == ANYELEMENTOID || declared_type == ANYENUMOID) { if (context_declared_type == ANYARRAYOID) { @@ -1389,7 +1424,8 @@ resolve_generic_type(Oid declared_type, format_type_be(context_actual_type)))); return array_typelem; } - else if (context_declared_type == ANYELEMENTOID) + else if (context_declared_type == ANYELEMENTOID || + context_declared_type == ANYENUMOID) { /* Use the actual type; it doesn't matter if array or not */ return context_actual_type; @@ -1402,7 +1438,7 @@ resolve_generic_type(Oid declared_type, } /* If we get here, declared_type is polymorphic and context isn't */ /* NB: this is a calling-code logic error, not a user error */ - elog(ERROR, "could not determine ANYARRAY/ANYELEMENT type because context isn't polymorphic"); + elog(ERROR, "could not determine polymorphic type because context isn't polymorphic"); return InvalidOid; /* keep compiler quiet */ } @@ -1502,6 +1538,7 @@ TypeCategory(Oid inType) case (INTERNALOID): case (OPAQUEOID): case (ANYELEMENTOID): + case (ANYENUMOID): result = GENERIC_TYPE; break; @@ -1647,6 +1684,11 @@ IsBinaryCoercible(Oid srctype, Oid targettype) if (get_element_type(srctype) != InvalidOid) return true; + /* Also accept any enum type as coercible to ANYENUM */ + if (targettype == ANYENUMOID) + if (type_is_enum(srctype)) + return true; + /* Else look in pg_cast */ tuple = SearchSysCache(CASTSOURCETARGET, ObjectIdGetDatum(srctype), @@ -1777,6 +1819,22 @@ find_coercion_pathway(Oid targetTypeId, Oid sourceTypeId, result = true; } } + + /* + * If we still haven't found a possibility, check for enums, + * and retry looking for a cast to or from ANYENUM. But don't + * mistakenly conclude that ANYENUM-to-some-enum-type is a + * trivial cast. + */ + if (!result) + { + if (type_is_enum(sourceTypeId)) + result = find_coercion_pathway(targetTypeId, ANYENUMOID, + ccontext, funcid, arrayCoerce); + else if (sourceTypeId != ANYENUMOID && type_is_enum(targetTypeId)) + result = find_coercion_pathway(ANYENUMOID, sourceTypeId, + ccontext, funcid, arrayCoerce); + } } return result; @@ -1813,7 +1871,7 @@ find_typmod_coercion_function(Oid typeId, /* Check for a varlena array type (and not a domain) */ if (typeForm->typelem != InvalidOid && typeForm->typlen == -1 && - typeForm->typtype != 'd') + typeForm->typtype != TYPTYPE_DOMAIN) { /* Yes, switch our attention to the element type */ typeId = typeForm->typelem; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 08c78a7bcc..84dd12a756 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1627,7 +1627,7 @@ transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname, break; case RTE_FUNCTION: toid = exprType(rte->funcexpr); - if (toid == RECORDOID || get_typtype(toid) == 'c') + if (type_is_rowtype(toid)) { /* func returns composite; same as relation case */ result = (Node *) makeVar(vnum, diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c index 5a2414d594..82f4b3377a 100644 --- a/src/backend/parser/parse_oper.c +++ b/src/backend/parser/parse_oper.c @@ -886,8 +886,8 @@ make_scalar_array_op(ParseState *pstate, List *opname, declared_arg_types[1] = opform->oprright; /* - * enforce consistency with ANYARRAY and ANYELEMENT argument and return - * types, possibly adjusting return type or declared_arg_types (which will + * enforce consistency with polymorphic argument and return types, + * possibly adjusting return type or declared_arg_types (which will * be used as the cast destination by make_fn_arguments) */ rettype = enforce_generic_type_consistency(actual_arg_types, @@ -911,15 +911,25 @@ make_scalar_array_op(ParseState *pstate, List *opname, /* * Now switch back to the array type on the right, arranging for any - * needed cast to be applied. + * needed cast to be applied. Beware of polymorphic operators here; + * enforce_generic_type_consistency may or may not have replaced a + * polymorphic type with a real one. */ - res_atypeId = get_array_type(declared_arg_types[1]); - if (!OidIsValid(res_atypeId)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find array type for data type %s", - format_type_be(declared_arg_types[1])), - parser_errposition(pstate, location))); + if (IsPolymorphicType(declared_arg_types[1])) + { + /* assume the actual array type is OK */ + res_atypeId = atypeId; + } + else + { + res_atypeId = get_array_type(declared_arg_types[1]); + if (!OidIsValid(res_atypeId)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(declared_arg_types[1])), + parser_errposition(pstate, location))); + } actual_arg_types[1] = atypeId; declared_arg_types[1] = res_atypeId; @@ -986,8 +996,8 @@ make_op_expr(ParseState *pstate, Operator op, } /* - * enforce consistency with ANYARRAY and ANYELEMENT argument and return - * types, possibly adjusting return type or declared_arg_types (which will + * enforce consistency with polymorphic argument and return types, + * possibly adjusting return type or declared_arg_types (which will * be used as the cast destination by make_fn_arguments) */ rettype = enforce_generic_type_consistency(actual_arg_types, diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 0f888d36f4..535f11a031 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -398,16 +398,6 @@ typeByVal(Type t) return typ->typbyval; } -/* given type (as type struct), return the value of its 'typtype' attribute.*/ -char -typeTypType(Type t) -{ - Form_pg_type typ; - - typ = (Form_pg_type) GETSTRUCT(t); - return typ->typtype; -} - /* given type (as type struct), return the name of type */ char * typeTypeName(Type t) diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 34c3d8e4a4..fe8e656a64 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -337,6 +337,7 @@ check_xact_readonly(Node *parsetree) case T_CreateTableSpaceStmt: case T_CreateTrigStmt: case T_CompositeTypeStmt: + case T_CreateEnumStmt: case T_ViewStmt: case T_DropCastStmt: case T_DropStmt: @@ -779,6 +780,10 @@ ProcessUtility(Node *parsetree, } break; + case T_CreateEnumStmt: /* CREATE TYPE (enum) */ + DefineEnum((CreateEnumStmt *) parsetree); + break; + case T_ViewStmt: /* CREATE VIEW */ DefineView((ViewStmt *) parsetree, queryString); break; @@ -1640,6 +1645,10 @@ CreateCommandTag(Node *parsetree) tag = "CREATE TYPE"; break; + case T_CreateEnumStmt: + tag = "CREATE TYPE"; + break; + case T_ViewStmt: tag = "CREATE VIEW"; break; @@ -2075,6 +2084,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_CreateEnumStmt: + lev = LOGSTMT_DDL; + break; + case T_ViewStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index c0f885bb69..6057871831 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -17,7 +17,7 @@ endif OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \ cash.o char.o date.o datetime.o datum.o domains.o \ - float.o format_type.o \ + enum.o float.o format_type.o \ geo_ops.o geo_selfuncs.o int.o int8.o like.o lockfuncs.o \ misc.o nabstime.o name.o not_in.o numeric.o numutils.o \ oid.o oracle_compat.o pseudotypes.o rowtypes.o \ diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c new file mode 100644 index 0000000000..677c0ba6e1 --- /dev/null +++ b/src/backend/utils/adt/enum.c @@ -0,0 +1,409 @@ +/*------------------------------------------------------------------------- + * + * enum.c + * I/O functions, operators, aggregates etc for enum types + * + * Copyright (c) 2006-2007, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_enum.h" +#include "fmgr.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + + +static Oid cstring_enum(char *name, Oid enumtypoid); +static char *enum_cstring(Oid enumval); +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 */ + +Datum +enum_in(PG_FUNCTION_ARGS) +{ + char *name = PG_GETARG_CSTRING(0); + Oid enumtypoid = PG_GETARG_OID(1); + + PG_RETURN_OID(cstring_enum(name, enumtypoid)); +} + +/* guts of enum_in and text-to-enum */ +static Oid +cstring_enum(char *name, Oid enumtypoid) +{ + HeapTuple tup; + Oid enumoid; + + tup = SearchSysCache(ENUMTYPOIDNAME, + ObjectIdGetDatum(enumtypoid), + CStringGetDatum(name), + 0, 0); + if (tup == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + enumoid = HeapTupleGetOid(tup); + + ReleaseSysCache(tup); + return enumoid; +} + +Datum +enum_out(PG_FUNCTION_ARGS) +{ + Oid enumoid = PG_GETARG_OID(0); + + PG_RETURN_CSTRING(enum_cstring(enumoid)); +} + +/* guts of enum_out and enum-to-text */ +static char * +enum_cstring(Oid enumval) +{ + HeapTuple tup; + Form_pg_enum en; + char *label; + + tup = SearchSysCache(ENUMOID, + ObjectIdGetDatum(enumval), + 0, 0, 0); + if (tup == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid internal value for enum: %u", + enumval))); + en = (Form_pg_enum) GETSTRUCT(tup); + + label = pstrdup(NameStr(en->enumlabel)); + + ReleaseSysCache(tup); + return label; +} + +/* Comparison functions and related */ + +Datum +enum_lt(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a < b); +} + +Datum +enum_le(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a <= b); +} + +Datum +enum_eq(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a == b); +} + +Datum +enum_ne(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a != b); +} + +Datum +enum_ge(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a >= b); +} + +Datum +enum_gt(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a > b); +} + +Datum +enum_smaller(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_OID(a <= b ? a : b); +} + +Datum +enum_larger(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_OID(a >= b ? a : b); +} + +Datum +enum_cmp(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + if (a > b) + PG_RETURN_INT32(1); + else if (a == b) + PG_RETURN_INT32(0); + else + PG_RETURN_INT32(-1); +} + +/* Casts between text and enum */ + +Datum +enum_text(PG_FUNCTION_ARGS) +{ + Oid enumval = PG_GETARG_OID(0); + text *result; + char *cstr; + int len; + + cstr = enum_cstring(enumval); + len = strlen(cstr); + result = (text *) palloc(VARHDRSZ + len); + SET_VARSIZE(result, VARHDRSZ + len); + memcpy(VARDATA(result), cstr, len); + pfree(cstr); + PG_RETURN_TEXT_P(result); +} + +Datum +text_enum(PG_FUNCTION_ARGS) +{ + text *textval = PG_GETARG_TEXT_P(0); + Oid enumtypoid; + char *str; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. + */ + enumtypoid = get_fn_expr_rettype(fcinfo->flinfo); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + str = DatumGetCString(DirectFunctionCall1(textout, + PointerGetDatum(textval))); + PG_RETURN_OID(cstring_enum(str, enumtypoid)); +} + +/* Enum programming support functions */ + +Datum +enum_first(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + Oid min = InvalidOid; + CatCList *list; + int num, i; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + list = SearchSysCacheList(ENUMTYPOIDNAME, 1, + ObjectIdGetDatum(enumtypoid), + 0, 0, 0); + num = list->n_members; + for (i = 0; i < num; i++) + { + Oid valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data); + if (!OidIsValid(min) || valoid < min) + min = valoid; + } + + ReleaseCatCacheList(list); + + if (!OidIsValid(min)) /* should not happen */ + elog(ERROR, "no values found for enum %s", + format_type_be(enumtypoid)); + + PG_RETURN_OID(min); +} + +Datum +enum_last(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + Oid max = InvalidOid; + CatCList *list; + int num, i; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + list = SearchSysCacheList(ENUMTYPOIDNAME, 1, + ObjectIdGetDatum(enumtypoid), + 0, 0, 0); + num = list->n_members; + 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 */ + elog(ERROR, "no values found for enum %s", + format_type_be(enumtypoid)); + + PG_RETURN_OID(max); +} + +/* 2-argument variant of enum_range */ +Datum +enum_range_bounds(PG_FUNCTION_ARGS) +{ + Oid lower; + Oid upper; + Oid enumtypoid; + + if (PG_ARGISNULL(0)) + lower = InvalidOid; + else + lower = PG_GETARG_OID(0); + if (PG_ARGISNULL(1)) + upper = InvalidOid; + else + upper = PG_GETARG_OID(1); + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. The generic type mechanism should have ensured that + * both are of the same type. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper)); +} + +/* 1-argument variant of enum_range */ +Datum +enum_range_all(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, + InvalidOid, InvalidOid)); +} + +static ArrayType * +enum_range_internal(Oid enumtypoid, Oid lower, Oid upper) +{ + ArrayType *result; + CatCList *list; + int total, i, j; + Datum *elems; + + list = SearchSysCacheList(ENUMTYPOIDNAME, 1, + ObjectIdGetDatum(enumtypoid), + 0, 0, 0); + total = list->n_members; + + elems = (Datum *) palloc(total * sizeof(Datum)); + + j = 0; + for (i = 0; i < total; i++) + { + Oid val = HeapTupleGetOid(&(list->members[i]->tuple)); + + if ((!OidIsValid(lower) || lower <= val) && + (!OidIsValid(upper) || val <= upper)) + elems[j++] = ObjectIdGetDatum(val); + } + + /* shouldn't need the cache anymore */ + ReleaseCatCacheList(list); + + /* sort results into OID order */ + qsort(elems, j, sizeof(Datum), enum_elem_cmp); + + /* note this hardwires some details about the representation of Oid */ + result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i'); + + pfree(elems); + + 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; +} diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index be9507cd90..664abb5379 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -148,7 +148,7 @@ format_type_internal(Oid type_oid, int32 typemod, if (array_base_type != InvalidOid && typeform->typstorage != 'p' && - typeform->typtype != 'd') + typeform->typtype != TYPTYPE_DOMAIN) { /* Switch our attention to the array element type */ ReleaseSysCache(tuple); diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index 7e91f1a369..eee20e8dbe 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -164,6 +164,31 @@ anyarray_send(PG_FUNCTION_ARGS) /* + * anyenum_in - input routine for pseudo-type ANYENUM. + */ +Datum +anyenum_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type anyenum"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +/* + * anyenum_out - output routine for pseudo-type ANYENUM. + * + * We may as well allow this, since enum_out will in fact work. + */ +Datum +anyenum_out(PG_FUNCTION_ARGS) +{ + return enum_out(fcinfo); +} + + +/* * void_in - input routine for pseudo-type VOID. * * We allow this so that PL functions can return VOID without any special diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 15a96bd43c..ef877b8eac 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -2648,7 +2648,7 @@ map_sql_type_to_xml_name(Oid typeoid, int typmod) Form_pg_type typtuple = (Form_pg_type) GETSTRUCT(tuple); appendStringInfoString(&result, - map_multipart_sql_identifier_to_xml_name((typtuple->typtype == 'd') ? "Domain" : "UDT", + map_multipart_sql_identifier_to_xml_name((typtuple->typtype == TYPTYPE_DOMAIN) ? "Domain" : "UDT", get_database_name(MyDatabaseId), get_namespace_name(typtuple->typnamespace), NameStr(typtuple->typname))); @@ -2877,7 +2877,7 @@ map_sql_type_to_xmlschema_type(Oid typeoid, int typmod) break; default: - if (get_typtype(typeoid) == 'd') + if (get_typtype(typeoid) == TYPTYPE_DOMAIN) { Oid base_typeoid; int32 base_typmod = -1; diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 21d0e82d51..5c6f6d3920 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -1770,7 +1770,7 @@ getTypeIOParam(HeapTuple typeTuple) * own type OID as parameter. (As of 8.2, domains must get their own OID * even if their base type is an array.) */ - if (typeStruct->typtype == 'b' && OidIsValid(typeStruct->typelem)) + if (typeStruct->typtype == TYPTYPE_BASE && OidIsValid(typeStruct->typelem)) return typeStruct->typelem; else return HeapTupleGetOid(typeTuple); @@ -2022,7 +2022,7 @@ getBaseTypeAndTypmod(Oid typid, int32 *typmod) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for type %u", typid); typTup = (Form_pg_type) GETSTRUCT(tup); - if (typTup->typtype != 'd') + if (typTup->typtype != TYPTYPE_DOMAIN) { /* Not a domain, so done */ ReleaseSysCache(tup); @@ -2128,7 +2128,17 @@ get_typtype(Oid typid) bool type_is_rowtype(Oid typid) { - return (typid == RECORDOID || get_typtype(typid) == 'c'); + return (typid == RECORDOID || get_typtype(typid) == TYPTYPE_COMPOSITE); +} + +/* + * type_is_enum + * Returns true if the given type is an enum type. + */ +bool +type_is_enum(Oid typid) +{ + return (get_typtype(typid) == TYPTYPE_ENUM); } /* diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index cde1d6afe3..08cfd2a785 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -31,6 +31,7 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" +#include "catalog/pg_enum.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" @@ -335,6 +336,30 @@ static const struct cachedesc cacheinfo[] = { }, 4 }, + {EnumRelationId, /* ENUMOID */ + EnumOidIndexId, + 0, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 256 + }, + {EnumRelationId, /* ENUMTYPOIDNAME */ + EnumTypIdLabelIndexId, + 0, + 2, + { + Anum_pg_enum_enumtypid, + Anum_pg_enum_enumlabel, + 0, + 0 + }, + 256 + }, {IndexRelationId, /* INDEXRELID */ IndexRelidIndexId, Anum_pg_index_indrelid, diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 8a40ee6b4b..53e58159b6 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -275,7 +275,7 @@ lookup_type_cache(Oid type_id, int flags) */ if ((flags & TYPECACHE_TUPDESC) && typentry->tupDesc == NULL && - typentry->typtype == 'c') + typentry->typtype == TYPTYPE_COMPOSITE) { Relation rel; diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 526be5b237..84a9bdf789 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -193,8 +193,8 @@ shutdown_MultiFuncCall(Datum arg) * only when we couldn't resolve the actual rowtype for lack of information. * * The other hard case that this handles is resolution of polymorphism. - * We will never return ANYELEMENT or ANYARRAY, either as a scalar result - * type or as a component of a rowtype. + * We will never return ANYELEMENT, ANYARRAY or ANYENUM, either as a scalar + * result type or as a component of a rowtype. * * This function is relatively expensive --- in a function returning set, * try to call it only the first time through. @@ -338,7 +338,7 @@ internal_get_result_type(Oid funcid, /* * If scalar polymorphic result, try to resolve it. */ - if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) + if (IsPolymorphicType(rettype)) { Oid newrettype = exprType(call_expr); @@ -389,8 +389,8 @@ internal_get_result_type(Oid funcid, /* * Given the result tuple descriptor for a function with OUT parameters, - * replace any polymorphic columns (ANYELEMENT/ANYARRAY) with correct data - * types deduced from the input arguments. Returns TRUE if able to deduce + * replace any polymorphic columns (ANYELEMENT/ANYARRAY/ANYENUM) with correct + * data types deduced from the input arguments. Returns TRUE if able to deduce * all types, FALSE if not. */ static bool @@ -401,6 +401,7 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, int nargs = declared_args->dim1; bool have_anyelement_result = false; bool have_anyarray_result = false; + bool have_anyenum = false; Oid anyelement_type = InvalidOid; Oid anyarray_type = InvalidOid; int i; @@ -416,6 +417,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, case ANYARRAYOID: have_anyarray_result = true; break; + case ANYENUMOID: + have_anyelement_result = true; + have_anyenum = true; + break; default: break; } @@ -435,6 +440,7 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, switch (declared_args->values[i]) { case ANYELEMENTOID: + case ANYENUMOID: if (!OidIsValid(anyelement_type)) anyelement_type = get_call_expr_argtype(call_expr, i); break; @@ -461,12 +467,17 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, anyelement_type, ANYELEMENTOID); + /* Check for enum if needed */ + if (have_anyenum && !type_is_enum(anyelement_type)) + return false; + /* And finally replace the tuple column types as needed */ for (i = 0; i < natts; i++) { switch (tupdesc->attrs[i]->atttypid) { case ANYELEMENTOID: + case ANYENUMOID: TupleDescInitEntry(tupdesc, i + 1, NameStr(tupdesc->attrs[i]->attname), anyelement_type, @@ -490,8 +501,8 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, /* * Given the declared argument types and modes for a function, - * replace any polymorphic types (ANYELEMENT/ANYARRAY) with correct data - * types deduced from the input arguments. Returns TRUE if able to deduce + * replace any polymorphic types (ANYELEMENT/ANYARRAY/ANYENUM) with correct + * data types deduced from the input arguments. Returns TRUE if able to deduce * all types, FALSE if not. This is the same logic as * resolve_polymorphic_tupdesc, but with a different argument representation. * @@ -517,6 +528,7 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, switch (argtypes[i]) { case ANYELEMENTOID: + case ANYENUMOID: if (argmode == PROARGMODE_OUT) have_anyelement_result = true; else @@ -571,12 +583,15 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, anyelement_type, ANYELEMENTOID); + /* XXX do we need to enforce ANYENUM here? I think not */ + /* And finally replace the output column types as needed */ for (i = 0; i < numargs; i++) { switch (argtypes[i]) { case ANYELEMENTOID: + case ANYENUMOID: argtypes[i] = anyelement_type; break; case ANYARRAYOID: @@ -603,12 +618,13 @@ get_type_func_class(Oid typid) { switch (get_typtype(typid)) { - case 'c': + case TYPTYPE_COMPOSITE: return TYPEFUNC_COMPOSITE; - case 'b': - case 'd': + case TYPTYPE_BASE: + case TYPTYPE_DOMAIN: + case TYPTYPE_ENUM: return TYPEFUNC_SCALAR; - case 'p': + case TYPTYPE_PSEUDO: if (typid == RECORDOID) return TYPEFUNC_RECORD; @@ -840,7 +856,7 @@ get_func_result_name(Oid functionId) * Given a pg_proc row for a function, return a tuple descriptor for the * result rowtype, or NULL if the function does not have OUT parameters. * - * Note that this does not handle resolution of ANYELEMENT/ANYARRAY types; + * Note that this does not handle resolution of polymorphic types; * that is deliberate. */ TupleDesc diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 39ea09d466..f00c889296 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -137,6 +137,7 @@ static void dumpDumpableObject(Archive *fout, DumpableObject *dobj); static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo); static void dumpType(Archive *fout, TypeInfo *tinfo); static void dumpBaseType(Archive *fout, TypeInfo *tinfo); +static void dumpEnumType(Archive *fout, TypeInfo *tinfo); static void dumpDomain(Archive *fout, TypeInfo *tinfo); static void dumpCompositeType(Archive *fout, TypeInfo *tinfo); static void dumpShellType(Archive *fout, ShellTypeInfo *stinfo); @@ -2085,7 +2086,7 @@ getTypes(int *numTypes) */ tinfo[i].nDomChecks = 0; tinfo[i].domChecks = NULL; - if (tinfo[i].dobj.dump && tinfo[i].typtype == 'd') + if (tinfo[i].dobj.dump && tinfo[i].typtype == TYPTYPE_DOMAIN) getDomainConstraints(&(tinfo[i])); /* @@ -2097,7 +2098,7 @@ getTypes(int *numTypes) * should copy the base type's catId, but then it might capture the * pg_depend entries for the type, which we don't want. */ - if (tinfo[i].dobj.dump && tinfo[i].typtype == 'b') + if (tinfo[i].dobj.dump && tinfo[i].typtype == TYPTYPE_BASE) { stinfo = (ShellTypeInfo *) malloc(sizeof(ShellTypeInfo)); stinfo->dobj.objType = DO_SHELL_TYPE; @@ -5119,12 +5120,91 @@ dumpType(Archive *fout, TypeInfo *tinfo) return; /* Dump out in proper style */ - if (tinfo->typtype == 'b') + if (tinfo->typtype == TYPTYPE_BASE) dumpBaseType(fout, tinfo); - else if (tinfo->typtype == 'd') + else if (tinfo->typtype == TYPTYPE_DOMAIN) dumpDomain(fout, tinfo); - else if (tinfo->typtype == 'c') + else if (tinfo->typtype == TYPTYPE_COMPOSITE) dumpCompositeType(fout, tinfo); + else if (tinfo->typtype == TYPTYPE_ENUM) + dumpEnumType(fout, tinfo); +} + +/* + * dumpEnumType + * writes out to fout the queries to recreate a user-defined enum type + */ +static void +dumpEnumType(Archive *fout, TypeInfo *tinfo) +{ + PQExpBuffer q = createPQExpBuffer(); + PQExpBuffer delq = createPQExpBuffer(); + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + int num, i; + char *label; + + /* Set proper schema search path so regproc references list correctly */ + selectSourceSchema(tinfo->dobj.namespace->dobj.name); + + appendPQExpBuffer(query, "SELECT enumlabel FROM pg_catalog.pg_enum " + "WHERE enumtypid = '%u'" + "ORDER BY oid", + tinfo->dobj.catId.oid); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + num = PQntuples(res); + /* should be at least 1 value */ + if (num == 0) + { + write_msg(NULL, "No rows found for enum"); + exit_nicely(); + } + + /* + * DROP must be fully qualified in case same name appears in pg_catalog. + * CASCADE shouldn't be required here as for normal types since the + * I/O functions are generic and do not get dropped. + */ + appendPQExpBuffer(delq, "DROP TYPE %s.", + fmtId(tinfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(delq, "%s;\n", + fmtId(tinfo->dobj.name)); + appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (\n", + fmtId(tinfo->dobj.name)); + for (i = 0; i < num; i++) + { + label = PQgetvalue(res, i, 0); + if (i > 0) + appendPQExpBuffer(q, ",\n"); + appendPQExpBuffer(q, " "); + appendStringLiteralAH(q, label, fout); + } + appendPQExpBuffer(q, "\n);\n"); + + ArchiveEntry(fout, tinfo->dobj.catId, tinfo->dobj.dumpId, + tinfo->dobj.name, + tinfo->dobj.namespace->dobj.name, + NULL, + tinfo->rolname, false, + "TYPE", q->data, delq->data, NULL, + tinfo->dobj.dependencies, tinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Type Comments */ + resetPQExpBuffer(q); + + appendPQExpBuffer(q, "TYPE %s", fmtId(tinfo->dobj.name)); + dumpComment(fout, q->data, + tinfo->dobj.namespace->dobj.name, tinfo->rolname, + tinfo->dobj.catId, 0, tinfo->dobj.dumpId); + + PQclear(res); + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); + destroyPQExpBuffer(query); } /* diff --git a/src/include/access/hash.h b/src/include/access/hash.h index 42ac16e8af..8dc65a92d1 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -258,6 +258,7 @@ extern Datum hashint2(PG_FUNCTION_ARGS); extern Datum hashint4(PG_FUNCTION_ARGS); extern Datum hashint8(PG_FUNCTION_ARGS); extern Datum hashoid(PG_FUNCTION_ARGS); +extern Datum hashenum(PG_FUNCTION_ARGS); extern Datum hashfloat4(PG_FUNCTION_ARGS); extern Datum hashfloat8(PG_FUNCTION_ARGS); extern Datum hashoidvector(PG_FUNCTION_ARGS); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 08f6828c86..bd9cca6428 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200704011 +#define CATALOG_VERSION_NO 200704012 #endif diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 1ae2d9de9c..48ecf1f4d0 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -146,6 +146,11 @@ DECLARE_UNIQUE_INDEX(pg_description_o_c_o_index, 2675, on pg_description using b DECLARE_UNIQUE_INDEX(pg_shdescription_o_c_index, 2397, on pg_shdescription using btree(objoid oid_ops, classoid oid_ops)); #define SharedDescriptionObjIndexId 2397 +DECLARE_UNIQUE_INDEX(pg_enum_oid_index, 3502, on pg_enum using btree(oid oid_ops)); +#define EnumOidIndexId 3502 +DECLARE_UNIQUE_INDEX(pg_enum_typid_label_index, 3503, on pg_enum using btree(enumtypid oid_ops, enumlabel name_ops)); +#define EnumTypIdLabelIndexId 3503 + /* 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)); #define IndexIndrelidIndexId 2678 diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h index df393643a0..0c11d4f7be 100644 --- a/src/include/catalog/pg_aggregate.h +++ b/src/include/catalog/pg_aggregate.h @@ -118,6 +118,7 @@ DATA(insert ( 2130 numeric_larger - 1756 1700 _null_ )); DATA(insert ( 2050 array_larger - 1073 2277 _null_ )); DATA(insert ( 2244 bpchar_larger - 1060 1042 _null_ )); DATA(insert ( 2797 tidlarger - 2800 27 _null_ )); +DATA(insert ( 3526 enum_larger - 3519 3500 _null_ )); /* min */ DATA(insert ( 2131 int8smaller - 412 20 _null_ )); @@ -139,6 +140,7 @@ DATA(insert ( 2146 numeric_smaller - 1754 1700 _null_ )); DATA(insert ( 2051 array_smaller - 1072 2277 _null_ )); DATA(insert ( 2245 bpchar_smaller - 1058 1042 _null_ )); DATA(insert ( 2798 tidsmaller - 2799 27 _null_ )); +DATA(insert ( 3527 enum_smaller - 3518 3500 _null_ )); /* count */ DATA(insert ( 2147 int8inc_any - 0 20 "0" )); diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index 0cf908db1e..fb8b377f6d 100644 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -636,4 +636,18 @@ DATA(insert ( 2745 2277 2277 2 f 2751 2742 )); DATA(insert ( 2745 2277 2277 3 t 2752 2742 )); DATA(insert ( 2745 2277 2277 4 t 1070 2742 )); +/* + * btree enum_ops + */ +DATA(insert ( 3522 3500 3500 1 f 3518 403 )); +DATA(insert ( 3522 3500 3500 2 f 3520 403 )); +DATA(insert ( 3522 3500 3500 3 f 3516 403 )); +DATA(insert ( 3522 3500 3500 4 f 3521 403 )); +DATA(insert ( 3522 3500 3500 5 f 3519 403 )); + +/* + * hash enum_ops + */ +DATA(insert ( 3523 3500 3500 1 f 3516 405 )); + #endif /* PG_AMOP_H */ diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index 3ada37a951..e021822dad 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -128,6 +128,7 @@ DATA(insert ( 2233 703 703 1 380 )); DATA(insert ( 2234 704 704 1 381 )); DATA(insert ( 2789 27 27 1 2794 )); DATA(insert ( 2968 2950 2950 1 2960 )); +DATA(insert ( 3522 3500 3500 1 3514 )); /* hash */ @@ -162,6 +163,7 @@ DATA(insert ( 2231 1042 1042 1 456 )); DATA(insert ( 2232 19 19 1 455 )); DATA(insert ( 2235 1033 1033 1 329 )); DATA(insert ( 2969 2950 2950 1 2963 )); +DATA(insert ( 3523 3500 3500 1 3515 )); /* gist */ diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h index 4c06f905b4..ae6d5c22eb 100644 --- a/src/include/catalog/pg_cast.h +++ b/src/include/catalog/pg_cast.h @@ -403,4 +403,10 @@ DATA(insert ( 2950 25 2965 a )); DATA(insert ( 1043 2950 2964 a )); DATA(insert ( 2950 1043 2965 a )); +/* + * enums + */ +DATA(insert ( 3500 25 3532 e )); +DATA(insert ( 25 3500 3533 e )); + #endif /* PG_CAST_H */ diff --git a/src/include/catalog/pg_enum.h b/src/include/catalog/pg_enum.h new file mode 100644 index 0000000000..7dd54f6e4b --- /dev/null +++ b/src/include/catalog/pg_enum.h @@ -0,0 +1,72 @@ +/*------------------------------------------------------------------------- + * + * pg_enum.h + * definition of the system "enum" relation (pg_enum) + * along with the relation's initial contents. + * + * + * Copyright (c) 2006-2007, PostgreSQL Global Development Group + * + * $PostgreSQL$ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + * XXX do NOT break up DATA() statements into multiple lines! + * the scripts are not as smart as you might think... + * + *------------------------------------------------------------------------- + */ +#ifndef PG_ENUM_H +#define PG_ENUM_H + +#include "nodes/pg_list.h" + +/* ---------------- + * postgres.h contains the system type definitions and the + * CATALOG(), BKI_BOOTSTRAP and DATA() sugar words so this file + * can be read by both genbki.sh and the C compiler. + * ---------------- + */ + +/* ---------------- + * pg_enum definition. cpp turns this into + * typedef struct FormData_pg_enum + * ---------------- + */ +#define EnumRelationId 3501 + +CATALOG(pg_enum,3501) +{ + Oid enumtypid; /* OID of owning enum type */ + NameData enumlabel; /* text representation of enum value */ +} FormData_pg_enum; + +/* ---------------- + * Form_pg_enum corresponds to a pointer to a tuple with + * the format of pg_enum relation. + * ---------------- + */ +typedef FormData_pg_enum *Form_pg_enum; + +/* ---------------- + * compiler constants for pg_enum + * ---------------- + */ +#define Natts_pg_enum 2 +#define Anum_pg_enum_enumtypid 1 +#define Anum_pg_enum_enumlabel 2 + +/* ---------------- + * pg_enum has no initial contents + * ---------------- + */ + +/* + * prototypes for functions in pg_enum.c + */ +extern void EnumValuesCreate(Oid enumTypeOid, List *vals); +extern void EnumValuesDelete(Oid enumTypeOid); + +#endif /* PG_ENUM_H */ diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index 0666097b68..5f8337fe88 100644 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -200,7 +200,9 @@ DATA(insert ( 2742 _timestamp_ops PGNSP PGUID 2745 1115 t 1114 )); DATA(insert ( 2742 _money_ops PGNSP PGUID 2745 791 t 790 )); DATA(insert ( 2742 _reltime_ops PGNSP PGUID 2745 1024 t 703 )); DATA(insert ( 2742 _tinterval_ops PGNSP PGUID 2745 1025 t 704 )); -DATA(insert ( 403 uuid_ops PGNSP PGUID 2968 2950 t 0 )); -DATA(insert ( 405 uuid_ops PGNSP PGUID 2969 2950 t 0 )); +DATA(insert ( 403 uuid_ops PGNSP PGUID 2968 2950 t 0 )); +DATA(insert ( 405 uuid_ops PGNSP PGUID 2969 2950 t 0 )); +DATA(insert ( 403 enum_ops PGNSP PGUID 3522 3500 t 0 )); +DATA(insert ( 405 enum_ops PGNSP PGUID 3523 3500 t 0 )); #endif /* PG_OPCLASS_H */ diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index ac05e785f0..ee61d4c89d 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -903,6 +903,14 @@ DATA(insert OID = 2975 ( ">" PGNSP PGUID b f f 2950 2950 16 2974 2976 uuid_g DATA(insert OID = 2976 ( "<=" PGNSP PGUID b f f 2950 2950 16 2977 2975 uuid_le scalarltsel scalarltjoinsel )); DATA(insert OID = 2977 ( ">=" PGNSP PGUID b f f 2950 2950 16 2976 2974 uuid_ge scalargtsel scalargtjoinsel )); +/* enum operators */ +DATA(insert OID = 3516 ( "=" PGNSP PGUID b t t 3500 3500 16 3516 3517 enum_eq eqsel eqjoinsel )); +DATA(insert OID = 3517 ( "<>" PGNSP PGUID b f f 3500 3500 16 3517 3516 enum_ne neqsel neqjoinsel )); +DATA(insert OID = 3518 ( "<" PGNSP PGUID b f f 3500 3500 16 3519 3521 enum_lt scalarltsel scalarltjoinsel )); +DATA(insert OID = 3519 ( ">" PGNSP PGUID b f f 3500 3500 16 3518 3520 enum_gt scalargtsel scalargtjoinsel )); +DATA(insert OID = 3520 ( "<=" PGNSP PGUID b f f 3500 3500 16 3521 3519 enum_le scalarltsel scalarltjoinsel )); +DATA(insert OID = 3521 ( ">=" PGNSP PGUID b f f 3500 3500 16 3520 3518 enum_ge scalargtsel scalargtjoinsel )); + /* * function prototypes diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index a74f85b7bb..0c139c3250 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -136,6 +136,8 @@ DATA(insert OID = 2595 ( 783 circle_ops PGNSP PGUID )); DATA(insert OID = 2745 ( 2742 array_ops PGNSP PGUID )); DATA(insert OID = 2968 ( 403 uuid_ops PGNSP PGUID )); DATA(insert OID = 2969 ( 405 uuid_ops PGNSP PGUID )); +DATA(insert OID = 3522 ( 403 enum_ops PGNSP PGUID )); +DATA(insert OID = 3523 ( 405 enum_ops PGNSP PGUID )); #endif /* PG_OPFAMILY_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 6bf8b67227..33dbec32f8 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4149,6 +4149,46 @@ DESCR("convert text to uuid"); DATA(insert OID = 2965 ( text PGNSP PGUID 12 1 0 f f t f i 1 25 "2950" _null_ _null_ _null_ uuid_text - _null_ )); DESCR("convert uuid to text"); +/* enum related procs */ +DATA(insert OID = 3504 ( anyenum_in PGNSP PGUID 12 1 0 f f t f i 1 3500 "2275" _null_ _null_ _null_ anyenum_in - _null_ )); +DESCR("I/O"); +DATA(insert OID = 3505 ( anyenum_out PGNSP PGUID 12 1 0 f f t f s 1 2275 "3500" _null_ _null_ _null_ anyenum_out - _null_ )); +DESCR("I/O"); +DATA(insert OID = 3506 ( enum_in PGNSP PGUID 12 1 0 f f t f s 2 3500 "2275 26" _null_ _null_ _null_ enum_in - _null_ )); +DESCR("I/O"); +DATA(insert OID = 3507 ( enum_out PGNSP PGUID 12 1 0 f f t f s 1 2275 "3500" _null_ _null_ _null_ enum_out - _null_ )); +DESCR("I/O"); +DATA(insert OID = 3508 ( enum_eq PGNSP PGUID 12 1 0 f f t f i 2 16 "3500 3500" _null_ _null_ _null_ enum_eq - _null_ )); +DESCR("equal"); +DATA(insert OID = 3509 ( enum_ne PGNSP PGUID 12 1 0 f f t f i 2 16 "3500 3500" _null_ _null_ _null_ enum_ne - _null_ )); +DESCR("not equal"); +DATA(insert OID = 3510 ( enum_lt PGNSP PGUID 12 1 0 f f t f i 2 16 "3500 3500" _null_ _null_ _null_ enum_lt - _null_ )); +DESCR("less-than"); +DATA(insert OID = 3511 ( enum_gt PGNSP PGUID 12 1 0 f f t f i 2 16 "3500 3500" _null_ _null_ _null_ enum_gt - _null_ )); +DESCR("greater-than"); +DATA(insert OID = 3512 ( enum_le PGNSP PGUID 12 1 0 f f t f i 2 16 "3500 3500" _null_ _null_ _null_ enum_le - _null_ )); +DESCR("less-than-or-equal"); +DATA(insert OID = 3513 ( enum_ge PGNSP PGUID 12 1 0 f f t f i 2 16 "3500 3500" _null_ _null_ _null_ enum_ge - _null_ )); +DESCR("greater-than-or-equal"); +DATA(insert OID = 3514 ( enum_cmp PGNSP PGUID 12 1 0 f f t f i 2 23 "3500 3500" _null_ _null_ _null_ enum_cmp - _null_ )); +DESCR("btree-less-equal-greater"); +DATA(insert OID = 3515 ( hashenum PGNSP PGUID 12 1 0 f f t f i 1 23 "3500" _null_ _null_ _null_ hashenum - _null_ )); +DESCR("hash"); +DATA(insert OID = 3524 ( enum_smaller PGNSP PGUID 12 1 0 f f t f i 2 3500 "3500 3500" _null_ _null_ _null_ enum_smaller - _null_ )); +DESCR("smaller of two"); +DATA(insert OID = 3525 ( enum_larger PGNSP PGUID 12 1 0 f f t f i 2 3500 "3500 3500" _null_ _null_ _null_ enum_larger - _null_ )); +DESCR("larger of two"); +DATA(insert OID = 3526 ( max PGNSP PGUID 12 1 0 t f f f i 1 3500 "3500" _null_ _null_ _null_ aggregate_dummy - _null_ )); +DATA(insert OID = 3527 ( min PGNSP PGUID 12 1 0 t f f f i 1 3500 "3500" _null_ _null_ _null_ aggregate_dummy - _null_ )); +DATA(insert OID = 3528 ( enum_first PGNSP PGUID 12 1 0 f f f f s 1 3500 "3500" _null_ _null_ _null_ enum_first - _null_ )); +DATA(insert OID = 3529 ( enum_last PGNSP PGUID 12 1 0 f f f f s 1 3500 "3500" _null_ _null_ _null_ enum_last - _null_ )); +DATA(insert OID = 3530 ( enum_range PGNSP PGUID 12 1 0 f f f f s 2 2277 "3500 3500" _null_ _null_ _null_ enum_range_bounds - _null_ )); +DATA(insert OID = 3531 ( enum_range PGNSP PGUID 12 1 0 f f f f s 1 2277 "3500" _null_ _null_ _null_ enum_range_all - _null_ )); +DATA(insert OID = 3532 ( text PGNSP PGUID 12 1 0 f f t f s 1 25 "3500" _null_ _null_ _null_ enum_text - _null_ )); +DESCR("convert enum to text"); +DATA(insert OID = 3533 ( enum PGNSP PGUID 12 1 0 f f t f s 1 3500 "25" _null_ _null_ _null_ text_enum - _null_ )); +DESCR("convert text to enum"); + /* * Symbolic values for provolatile column: these indicate whether the result * of a function is dependent *only* on the values of its explicit arguments, diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 4ae7bb8a57..13ef5f2c52 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -66,8 +66,9 @@ CATALOG(pg_type,1247) BKI_BOOTSTRAP bool typbyval; /* - * typtype is 'b' for a basic type, 'c' for a complex type (ie a table's - * rowtype), 'd' for a domain type, or 'p' for a pseudo type. + * typtype is 'b' for a base type, 'c' for a composite type (e.g., + * a table's rowtype), 'd' for a domain type, 'e' for an enum type, + * or 'p' for a pseudo-type. (Use the TYPTYPE macros below.) * * If typtype is 'c', typrelid is the OID of the class' entry in pg_class. */ @@ -531,6 +532,11 @@ DATA(insert OID = 2210 ( _regclass PGNSP PGUID -1 f b t \054 0 2205 array_in DATA(insert OID = 2211 ( _regtype PGNSP PGUID -1 f b t \054 0 2206 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); #define REGTYPEARRAYOID 2211 +/* uuid */ +DATA(insert OID = 2950 ( uuid PGNSP PGUID 16 f b t \054 0 0 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ )); +DESCR("UUID datatype"); +DATA(insert OID = 2951 ( _uuid PGNSP PGUID -1 f b t \054 0 2950 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); + /* * pseudo-types * @@ -560,11 +566,24 @@ DATA(insert OID = 2282 ( opaque PGNSP PGUID 4 t p t \054 0 0 opaque_in opaque #define OPAQUEOID 2282 DATA(insert OID = 2283 ( anyelement PGNSP PGUID 4 t p t \054 0 0 anyelement_in anyelement_out - - - - - i p f 0 -1 0 _null_ _null_ )); #define ANYELEMENTOID 2283 +DATA(insert OID = 3500 ( anyenum PGNSP PGUID 4 t p t \054 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ )); +#define ANYENUMOID 3500 -/* uuid */ -DATA(insert OID = 2950 ( uuid PGNSP PGUID 16 f b t \054 0 0 uuid_in uuid_out uuid_recv uuid_send - - - c p f 0 -1 0 _null_ _null_ )); -DESCR("UUID datatype"); -DATA(insert OID = 2951 ( _uuid PGNSP PGUID -1 f b t \054 0 2950 array_in array_out array_recv array_send - - - i x f 0 -1 0 _null_ _null_ )); + +/* + * macros + */ +#define TYPTYPE_BASE 'b' /* base type (ordinary scalar type) */ +#define TYPTYPE_COMPOSITE 'c' /* composite (e.g., table's rowtype) */ +#define TYPTYPE_DOMAIN 'd' /* domain over another type */ +#define TYPTYPE_ENUM 'e' /* enumerated type */ +#define TYPTYPE_PSEUDO 'p' /* pseudo-type */ + +/* Is a type OID a polymorphic pseudotype? (Beware of multiple evaluation) */ +#define IsPolymorphicType(typid) \ + ((typid) == ANYELEMENTOID || \ + (typid) == ANYARRAYOID || \ + (typid) == ANYENUMOID) /* * prototypes for functions in pg_type.c diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h index 332ea7c9f2..1467f9d5fb 100644 --- a/src/include/commands/typecmds.h +++ b/src/include/commands/typecmds.h @@ -24,6 +24,7 @@ extern void RemoveType(List *names, DropBehavior behavior, bool missing_ok); extern void RemoveTypeById(Oid typeOid); extern void DefineDomain(CreateDomainStmt *stmt); extern void RemoveDomain(List *names, DropBehavior behavior, bool missing_ok); +extern void DefineEnum(CreateEnumStmt *stmt); extern Oid DefineCompositeType(const RangeVar *typevar, List *coldeflist); extern void AlterDomainDefault(List *names, Node *defaultRaw); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 9a6271bc66..0acf89443d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -307,6 +307,7 @@ typedef enum NodeTag T_DropOwnedStmt, T_ReassignOwnedStmt, T_CompositeTypeStmt, + T_CreateEnumStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 395eef2d4f..952c6aab3c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1706,6 +1706,17 @@ typedef struct CompositeTypeStmt List *coldeflist; /* list of ColumnDef nodes */ } CompositeTypeStmt; +/* ---------------------- + * Create Type Statement, enum types + * ---------------------- + */ +typedef struct CreateEnumStmt +{ + NodeTag type; + List *typename; /* qualified name (list of Value strings) */ + List *vals; /* enum values (list of Value strings) */ +} CreateEnumStmt; + /* ---------------------- * Create View Statement diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h index 5f0635a3c5..5f2e72ab0e 100644 --- a/src/include/parser/parse_type.h +++ b/src/include/parser/parse_type.h @@ -33,7 +33,6 @@ extern Type typeidType(Oid id); extern Oid typeTypeId(Type tp); extern int16 typeLen(Type t); extern bool typeByVal(Type t); -extern char typeTypType(Type t); extern char *typeTypeName(Type t); extern Oid typeTypeRelid(Type typ); extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index b125e3d3af..9554179337 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -103,6 +103,25 @@ extern Datum char_text(PG_FUNCTION_ARGS); extern Datum domain_in(PG_FUNCTION_ARGS); extern Datum domain_recv(PG_FUNCTION_ARGS); +/* enum.c */ +extern Datum enum_in(PG_FUNCTION_ARGS); +extern Datum enum_out(PG_FUNCTION_ARGS); +extern Datum enum_lt(PG_FUNCTION_ARGS); +extern Datum enum_le(PG_FUNCTION_ARGS); +extern Datum enum_eq(PG_FUNCTION_ARGS); +extern Datum enum_ne(PG_FUNCTION_ARGS); +extern Datum enum_ge(PG_FUNCTION_ARGS); +extern Datum enum_gt(PG_FUNCTION_ARGS); +extern Datum enum_cmp(PG_FUNCTION_ARGS); +extern Datum enum_text(PG_FUNCTION_ARGS); +extern Datum text_enum(PG_FUNCTION_ARGS); +extern Datum enum_smaller(PG_FUNCTION_ARGS); +extern Datum enum_larger(PG_FUNCTION_ARGS); +extern Datum enum_first(PG_FUNCTION_ARGS); +extern Datum enum_last(PG_FUNCTION_ARGS); +extern Datum enum_range_bounds(PG_FUNCTION_ARGS); +extern Datum enum_range_all(PG_FUNCTION_ARGS); + /* int.c */ extern Datum int2in(PG_FUNCTION_ARGS); extern Datum int2out(PG_FUNCTION_ARGS); @@ -450,6 +469,8 @@ extern Datum anyarray_in(PG_FUNCTION_ARGS); extern Datum anyarray_out(PG_FUNCTION_ARGS); extern Datum anyarray_recv(PG_FUNCTION_ARGS); extern Datum anyarray_send(PG_FUNCTION_ARGS); +extern Datum anyenum_in(PG_FUNCTION_ARGS); +extern Datum anyenum_out(PG_FUNCTION_ARGS); extern Datum void_in(PG_FUNCTION_ARGS); extern Datum void_out(PG_FUNCTION_ARGS); extern Datum trigger_in(PG_FUNCTION_ARGS); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 71fa4631c4..f96446c6ed 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -105,6 +105,7 @@ extern char get_typstorage(Oid typid); extern Node *get_typdefault(Oid typid); extern char get_typtype(Oid typid); extern bool type_is_rowtype(Oid typid); +extern bool type_is_enum(Oid typid); extern Oid get_typ_typrelid(Oid typid); extern Oid get_element_type(Oid typid); extern Oid get_array_type(Oid typid); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index c889d4f28b..a56b2817da 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -48,23 +48,25 @@ #define CONSTROID 17 #define CONVOID 18 #define DATABASEOID 19 -#define INDEXRELID 20 -#define LANGNAME 21 -#define LANGOID 22 -#define NAMESPACENAME 23 -#define NAMESPACEOID 24 -#define OPERNAMENSP 25 -#define OPEROID 26 -#define OPFAMILYAMNAMENSP 27 -#define OPFAMILYOID 28 -#define PROCNAMEARGSNSP 29 -#define PROCOID 30 -#define RELNAMENSP 31 -#define RELOID 32 -#define RULERELNAME 33 -#define STATRELATT 34 -#define TYPENAMENSP 35 -#define TYPEOID 36 +#define ENUMOID 20 +#define ENUMTYPOIDNAME 21 +#define INDEXRELID 22 +#define LANGNAME 23 +#define LANGOID 24 +#define NAMESPACENAME 25 +#define NAMESPACEOID 26 +#define OPERNAMENSP 27 +#define OPEROID 28 +#define OPFAMILYAMNAMENSP 29 +#define OPFAMILYOID 30 +#define PROCNAMEARGSNSP 31 +#define PROCOID 32 +#define RELNAMENSP 33 +#define RELOID 34 +#define RULERELNAME 35 +#define STATRELATT 36 +#define TYPENAMENSP 37 +#define TYPEOID 38 extern void InitCatalogCache(void); extern void InitCatalogCachePhase2(void); diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index c12c30cde3..12b7ce3fbb 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -843,7 +843,7 @@ plperl_validator(PG_FUNCTION_ARGS) /* Disallow pseudotype result */ /* except for TRIGGER, RECORD, or VOID */ - if (functyptype == 'p') + if (functyptype == TYPTYPE_PSEUDO) { /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || @@ -862,7 +862,7 @@ plperl_validator(PG_FUNCTION_ARGS) &argtypes, &argnames, &argmodes); for (i = 0; i < numargs; i++) { - if (get_typtype(argtypes[i]) == 'p') + if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("plperl functions cannot take type %s", @@ -1525,7 +1525,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype result, except VOID or RECORD */ - if (typeStruct->typtype == 'p') + if (typeStruct->typtype == TYPTYPE_PSEUDO) { if (procStruct->prorettype == VOIDOID || procStruct->prorettype == RECORDOID) @@ -1552,8 +1552,8 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) prodesc->result_oid = procStruct->prorettype; prodesc->fn_retisset = procStruct->proretset; - prodesc->fn_retistuple = (typeStruct->typtype == 'c' || - procStruct->prorettype == RECORDOID); + prodesc->fn_retistuple = (procStruct->prorettype == RECORDOID || + typeStruct->typtype == TYPTYPE_COMPOSITE); prodesc->fn_retisarray = (typeStruct->typlen == -1 && typeStruct->typelem); @@ -1586,7 +1586,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype argument */ - if (typeStruct->typtype == 'p') + if (typeStruct->typtype == TYPTYPE_PSEUDO) { free(prodesc->proname); free(prodesc); @@ -1596,7 +1596,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) format_type_be(procStruct->proargtypes.values[i])))); } - if (typeStruct->typtype == 'c') + if (typeStruct->typtype == TYPTYPE_COMPOSITE) prodesc->arg_is_rowtype[i] = true; else { diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index cf90703009..2007de06b9 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -406,7 +406,7 @@ do_compile(FunctionCallInfo fcinfo, argdtype = plpgsql_build_datatype(argtypeid, -1); /* Disallow pseudotype argument */ - /* (note we already replaced ANYARRAY/ANYELEMENT) */ + /* (note we already replaced polymorphic types) */ /* (build_variable would do this, but wrong message) */ if (argdtype->ttype != PLPGSQL_TTYPE_SCALAR && argdtype->ttype != PLPGSQL_TTYPE_ROW) @@ -474,7 +474,7 @@ do_compile(FunctionCallInfo fcinfo, * the info available. */ rettypeid = procStruct->prorettype; - if (rettypeid == ANYARRAYOID || rettypeid == ANYELEMENTOID) + if (IsPolymorphicType(rettypeid)) { if (forValidator) { @@ -482,6 +482,7 @@ do_compile(FunctionCallInfo fcinfo, rettypeid = INT4ARRAYOID; else rettypeid = INT4OID; + /* XXX what could we use for ANYENUM? */ } else { @@ -512,8 +513,8 @@ do_compile(FunctionCallInfo fcinfo, typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype result, except VOID or RECORD */ - /* (note we already replaced ANYARRAY/ANYELEMENT) */ - if (typeStruct->typtype == 'p') + /* (note we already replaced polymorphic types) */ + if (typeStruct->typtype == TYPTYPE_PSEUDO) { if (rettypeid == VOIDOID || rettypeid == RECORDOID) @@ -544,8 +545,7 @@ do_compile(FunctionCallInfo fcinfo, * types, and not when the return is specified through an * output parameter. */ - if ((procStruct->prorettype == ANYARRAYOID || - procStruct->prorettype == ANYELEMENTOID) && + if (IsPolymorphicType(procStruct->prorettype) && num_out_args == 0) { (void) plpgsql_build_variable("$0", 0, @@ -1785,15 +1785,16 @@ build_datatype(HeapTuple typeTup, int32 typmod) typ->typoid = HeapTupleGetOid(typeTup); switch (typeStruct->typtype) { - case 'b': /* base type */ - case 'd': /* domain */ + case TYPTYPE_BASE: + case TYPTYPE_DOMAIN: + case TYPTYPE_ENUM: typ->ttype = PLPGSQL_TTYPE_SCALAR; break; - case 'c': /* composite, ie, rowtype */ + case TYPTYPE_COMPOSITE: Assert(OidIsValid(typeStruct->typrelid)); typ->ttype = PLPGSQL_TTYPE_ROW; break; - case 'p': /* pseudo */ + case TYPTYPE_PSEUDO: if (typ->typoid == RECORDOID) typ->ttype = PLPGSQL_TTYPE_REC; else @@ -2026,6 +2027,7 @@ plpgsql_resolve_polymorphic_argtypes(int numargs, switch (argtypes[i]) { case ANYELEMENTOID: + case ANYENUMOID: /* XXX dubious */ argtypes[i] = INT4OID; break; case ANYARRAYOID: diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 7d13d53ac0..8167f34ad2 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3294,8 +3294,7 @@ exec_assign_value(PLpgSQL_execstate *estate, PLpgSQL_row *row = (PLpgSQL_row *) target; /* Source must be of RECORD or composite type */ - if (!(valtype == RECORDOID || - get_typtype(valtype) == 'c')) + if (!type_is_rowtype(valtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot assign non-composite value to a row variable"))); @@ -3337,8 +3336,7 @@ exec_assign_value(PLpgSQL_execstate *estate, PLpgSQL_rec *rec = (PLpgSQL_rec *) target; /* Source must be of RECORD or composite type */ - if (!(valtype == RECORDOID || - get_typtype(valtype) == 'c')) + if (!type_is_rowtype(valtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot assign non-composite value to a record variable"))); diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 3ba8712367..2393f10d1d 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -147,8 +147,8 @@ plpgsql_validator(PG_FUNCTION_ARGS) functyptype = get_typtype(proc->prorettype); /* Disallow pseudotype result */ - /* except for TRIGGER, RECORD, VOID, ANYARRAY, or ANYELEMENT */ - if (functyptype == 'p') + /* except for TRIGGER, RECORD, VOID, or polymorphic */ + if (functyptype == TYPTYPE_PSEUDO) { /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || @@ -156,8 +156,7 @@ plpgsql_validator(PG_FUNCTION_ARGS) istrigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && - proc->prorettype != ANYARRAYOID && - proc->prorettype != ANYELEMENTOID) + !IsPolymorphicType(proc->prorettype)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("plpgsql functions cannot return type %s", @@ -165,15 +164,14 @@ plpgsql_validator(PG_FUNCTION_ARGS) } /* Disallow pseudotypes in arguments (either IN or OUT) */ - /* except for ANYARRAY or ANYELEMENT */ + /* except for polymorphic */ numargs = get_func_arg_info(tuple, &argtypes, &argnames, &argmodes); for (i = 0; i < numargs; i++) { - if (get_typtype(argtypes[i]) == 'p') + if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO) { - if (argtypes[i] != ANYARRAYOID && - argtypes[i] != ANYELEMENTOID) + if (!IsPolymorphicType(argtypes[i])) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("plpgsql functions cannot take type %s", diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index aeaccf6810..959a4ace6d 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -1185,7 +1185,7 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); /* Disallow pseudotype result, except for void */ - if (rvTypeStruct->typtype == 'p' && + if (rvTypeStruct->typtype == TYPTYPE_PSEUDO && procStruct->prorettype != VOIDOID) { if (procStruct->prorettype == TRIGGEROID) @@ -1199,7 +1199,7 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, format_type_be(procStruct->prorettype)))); } - if (rvTypeStruct->typtype == 'c') + if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE) { /* * Tuple: set up later, during first call to @@ -1258,13 +1258,13 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); /* Disallow pseudotype argument */ - if (argTypeStruct->typtype == 'p') + if (argTypeStruct->typtype == TYPTYPE_PSEUDO) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("plpython functions cannot take type %s", format_type_be(procStruct->proargtypes.values[i])))); - if (argTypeStruct->typtype != 'c') + if (argTypeStruct->typtype != TYPTYPE_COMPOSITE) PLy_input_datum_func(&(proc->args[i]), procStruct->proargtypes.values[i], argTypeTup); @@ -2338,7 +2338,7 @@ PLy_spi_prepare(PyObject * self, PyObject * args) plan->types[i] = typeId; typeStruct = (Form_pg_type) GETSTRUCT(typeTup); - if (typeStruct->typtype != 'c') + if (typeStruct->typtype != TYPTYPE_COMPOSITE) PLy_output_datum_func(&plan->args[i], typeTup); else elog(ERROR, "tuples not handled in plpy.prepare, yet."); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index d03aaed0eb..5e4e52ed8e 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -1051,7 +1051,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype result, except VOID */ - if (typeStruct->typtype == 'p') + if (typeStruct->typtype == TYPTYPE_PSEUDO) { if (procStruct->prorettype == VOIDOID) /* okay */ ; @@ -1074,7 +1074,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) } } - if (typeStruct->typtype == 'c') + if (typeStruct->typtype == TYPTYPE_COMPOSITE) { free(prodesc->proname); free(prodesc); @@ -1112,7 +1112,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) typeStruct = (Form_pg_type) GETSTRUCT(typeTup); /* Disallow pseudotype argument */ - if (typeStruct->typtype == 'p') + if (typeStruct->typtype == TYPTYPE_PSEUDO) { free(prodesc->proname); free(prodesc); @@ -1122,7 +1122,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) format_type_be(procStruct->proargtypes.values[i])))); } - if (typeStruct->typtype == 'c') + if (typeStruct->typtype == TYPTYPE_COMPOSITE) { prodesc->arg_is_rowtype[i] = true; snprintf(buf, sizeof(buf), "__PLTcl_Tup_%d", i + 1); diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out new file mode 100644 index 0000000000..4fa2d07ada --- /dev/null +++ b/src/test/regress/expected/enum.out @@ -0,0 +1,407 @@ +-- +-- Enum tests +-- +CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple'); +-- +-- Did it create the right number of rows? +-- +SELECT COUNT(*) FROM pg_enum WHERE enumtypid = 'rainbow'::regtype; + count +------- + 6 +(1 row) + +-- +-- I/O functions +-- +SELECT 'red'::rainbow; + rainbow +--------- + red +(1 row) + +SELECT 'mauve'::rainbow; +ERROR: invalid input value for enum rainbow: "mauve" +-- +-- Basic table creation, row selection +-- +CREATE TABLE enumtest (col rainbow); +INSERT INTO enumtest values ('red'), ('orange'), ('yellow'), ('green'); +COPY enumtest FROM stdin; +SELECT * FROM enumtest; + col +-------- + red + orange + yellow + green + blue + purple +(6 rows) + +-- +-- Operators, no index +-- +SELECT * FROM enumtest WHERE col = 'orange'; + col +-------- + orange +(1 row) + +SELECT * FROM enumtest WHERE col <> 'orange' ORDER BY col; + col +-------- + red + yellow + green + blue + purple +(5 rows) + +SELECT * FROM enumtest WHERE col > 'yellow' ORDER BY col; + col +-------- + green + blue + purple +(3 rows) + +SELECT * FROM enumtest WHERE col >= 'yellow' ORDER BY col; + col +-------- + yellow + green + blue + purple +(4 rows) + +SELECT * FROM enumtest WHERE col < 'green' ORDER BY col; + col +-------- + red + orange + yellow +(3 rows) + +SELECT * FROM enumtest WHERE col <= 'green' ORDER BY col; + col +-------- + red + orange + yellow + green +(4 rows) + +-- +-- Cast to/from text +-- +SELECT 'red'::rainbow::text || 'hithere'; + ?column? +------------ + redhithere +(1 row) + +SELECT 'red'::text::rainbow = 'red'::rainbow; + ?column? +---------- + t +(1 row) + +-- +-- Aggregates +-- +SELECT min(col) FROM enumtest; + min +----- + red +(1 row) + +SELECT max(col) FROM enumtest; + max +-------- + purple +(1 row) + +SELECT max(col) FROM enumtest WHERE col < 'green'; + max +-------- + yellow +(1 row) + +-- +-- Index tests, force use of index +-- +SET enable_seqscan = off; +SET enable_bitmapscan = off; +-- +-- Btree index / opclass with the various operators +-- +CREATE UNIQUE INDEX enumtest_btree ON enumtest USING btree (col); +SELECT * FROM enumtest WHERE col = 'orange'; + col +-------- + orange +(1 row) + +SELECT * FROM enumtest WHERE col <> 'orange' ORDER BY col; + col +-------- + red + yellow + green + blue + purple +(5 rows) + +SELECT * FROM enumtest WHERE col > 'yellow' ORDER BY col; + col +-------- + green + blue + purple +(3 rows) + +SELECT * FROM enumtest WHERE col >= 'yellow' ORDER BY col; + col +-------- + yellow + green + blue + purple +(4 rows) + +SELECT * FROM enumtest WHERE col < 'green' ORDER BY col; + col +-------- + red + orange + yellow +(3 rows) + +SELECT * FROM enumtest WHERE col <= 'green' ORDER BY col; + col +-------- + red + orange + yellow + green +(4 rows) + +SELECT min(col) FROM enumtest; + min +----- + red +(1 row) + +SELECT max(col) FROM enumtest; + max +-------- + purple +(1 row) + +SELECT max(col) FROM enumtest WHERE col < 'green'; + max +-------- + yellow +(1 row) + +DROP INDEX enumtest_btree; +-- +-- Hash index / opclass with the = operator +-- +CREATE INDEX enumtest_hash ON enumtest USING hash (col); +SELECT * FROM enumtest WHERE col = 'orange'; + col +-------- + orange +(1 row) + +DROP INDEX enumtest_hash; +-- +-- End index tests +-- +RESET enable_seqscan; +RESET enable_bitmapscan; +-- +-- Domains over enums +-- +CREATE DOMAIN rgb AS rainbow CHECK (VALUE IN ('red', 'green', 'blue')); +SELECT 'red'::rgb; + rgb +----- + red +(1 row) + +SELECT 'purple'::rgb; +ERROR: value for domain rgb violates check constraint "rgb_check" +SELECT 'purple'::rainbow::rgb; +ERROR: value for domain rgb violates check constraint "rgb_check" +DROP DOMAIN rgb; +-- +-- Arrays +-- +SELECT '{red,green,blue}'::rainbow[]; + rainbow +------------------ + {red,green,blue} +(1 row) + +SELECT ('{red,green,blue}'::rainbow[])[2]; + rainbow +--------- + green +(1 row) + +SELECT 'red' = ANY ('{red,green,blue}'::rainbow[]); + ?column? +---------- + t +(1 row) + +SELECT 'yellow' = ANY ('{red,green,blue}'::rainbow[]); + ?column? +---------- + f +(1 row) + +SELECT 'red' = ALL ('{red,green,blue}'::rainbow[]); + ?column? +---------- + f +(1 row) + +SELECT 'red' = ALL ('{red,red}'::rainbow[]); + ?column? +---------- + t +(1 row) + +-- +-- Support functions +-- +SELECT enum_first(NULL::rainbow); + enum_first +------------ + red +(1 row) + +SELECT enum_last('green'::rainbow); + enum_last +----------- + purple +(1 row) + +SELECT enum_range(NULL::rainbow); + enum_range +--------------------------------------- + {red,orange,yellow,green,blue,purple} +(1 row) + +SELECT enum_range('orange'::rainbow, 'green'::rainbow); + enum_range +----------------------- + {orange,yellow,green} +(1 row) + +SELECT enum_range(NULL, 'green'::rainbow); + enum_range +--------------------------- + {red,orange,yellow,green} +(1 row) + +SELECT enum_range('orange'::rainbow, NULL); + enum_range +----------------------------------- + {orange,yellow,green,blue,purple} +(1 row) + +SELECT enum_range(NULL::rainbow, NULL); + enum_range +--------------------------------------- + {red,orange,yellow,green,blue,purple} +(1 row) + +-- +-- User functions, can't test perl/python etc here since may not be compiled. +-- +CREATE FUNCTION echo_me(anyenum) RETURNS text AS $$ +BEGIN +RETURN $1::text || 'omg'; +END +$$ LANGUAGE plpgsql; +SELECT echo_me('red'::rainbow); + echo_me +--------- + redomg +(1 row) + +-- +-- Concrete function should override generic one +-- +CREATE FUNCTION echo_me(rainbow) RETURNS text AS $$ +BEGIN +RETURN $1::text || 'wtf'; +END +$$ LANGUAGE plpgsql; +SELECT echo_me('red'::rainbow); + echo_me +--------- + redwtf +(1 row) + +-- +-- If we drop the original generic one, we don't have to qualify the type +-- anymore, since there's only one match +-- +DROP FUNCTION echo_me(anyenum); +SELECT echo_me('red'); + echo_me +--------- + redwtf +(1 row) + +DROP FUNCTION echo_me(rainbow); +-- +-- RI triggers on enum types +-- +CREATE TABLE enumtest_parent (id rainbow PRIMARY KEY); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "enumtest_parent_pkey" for table "enumtest_parent" +CREATE TABLE enumtest_child (parent rainbow REFERENCES enumtest_parent); +INSERT INTO enumtest_parent VALUES ('red'); +INSERT INTO enumtest_child VALUES ('red'); +INSERT INTO enumtest_child VALUES ('blue'); -- fail +ERROR: insert or update on table "enumtest_child" violates foreign key constraint "enumtest_child_parent_fkey" +DETAIL: Key (parent)=(blue) is not present in table "enumtest_parent". +DELETE FROM enumtest_parent; -- fail +ERROR: update or delete on table "enumtest_parent" violates foreign key constraint "enumtest_child_parent_fkey" on table "enumtest_child" +DETAIL: Key (id)=(red) is still referenced from table "enumtest_child". +-- +-- cross-type RI should fail +-- +CREATE TYPE bogus AS ENUM('good', 'bad', 'ugly'); +CREATE TABLE enumtest_bogus_child(parent bogus REFERENCES enumtest_parent); +ERROR: foreign key constraint "enumtest_bogus_child_parent_fkey" cannot be implemented +DETAIL: Key columns "parent" and "id" are of incompatible types: bogus and rainbow. +DROP TYPE bogus; +-- +-- Cleanup +-- +DROP TABLE enumtest_child; +DROP TABLE enumtest_parent; +DROP TABLE enumtest; +DROP TYPE rainbow; +-- +-- Verify properly cleaned up +-- +SELECT COUNT(*) FROM pg_type WHERE typname = 'rainbow'; + count +------- + 0 +(1 row) + +SELECT * FROM pg_enum WHERE NOT EXISTS + (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid); + enumtypid | enumlabel +-----------+----------- +(0 rows) + diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out index 841d77c78e..29a0a190d6 100644 --- a/src/test/regress/expected/polymorphism.out +++ b/src/test/regress/expected/polymorphism.out @@ -80,7 +80,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[], CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- N P -- should CREATE CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[], @@ -92,11 +92,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[], CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- Case2 (R = P) && ((B = P) || (B = N)) -- ------------------------------------- -- S tf1 B tf2 @@ -151,13 +151,13 @@ ERROR: function tfp(integer[], anyelement) does not exist CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- P N N P -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement) CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- P N P N -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp, @@ -173,21 +173,21 @@ ERROR: function tf2p(anyarray, anyelement) does not exist CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- P P N P -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement) CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, FINALFUNC = ffp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- P P P N -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p, @@ -217,11 +217,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[], CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- N P -- should CREATE CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[], @@ -231,7 +231,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[], CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- Case4 (R = N) && ((B = P) || (B = N)) -- ------------------------------------- -- S tf1 B tf2 @@ -285,21 +285,21 @@ ERROR: function tfp(integer[], anyelement) does not exist CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- P N N P -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement) CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- P N P N -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int) CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp, @@ -321,13 +321,13 @@ ERROR: function tf2p(anyarray, anyelement) does not exist CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- P P N P -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement) CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray, FINALFUNC = ffnp, INITCOND = '{}'); ERROR: cannot determine transition data type -DETAIL: An aggregate using "anyarray" or "anyelement" as transition type must have at least one argument of either type. +DETAIL: An aggregate using a polymorphic transition type must have at least one polymorphic argument. -- P P P N -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int) CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p, diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 8abc323a70..957bd49f24 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -500,7 +500,7 @@ SELECT dup(22); (1 row) SELECT dup('xyz'); -- fails -ERROR: could not determine anyarray/anyelement type because input has type "unknown" +ERROR: could not determine polymorphic type because input has type "unknown" SELECT dup('xyz'::text); dup ------------------- @@ -527,4 +527,4 @@ DROP FUNCTION dup(anyelement); CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray) AS 'select $1, array[$1,$1]' LANGUAGE sql; ERROR: cannot determine result data type -DETAIL: A function returning "anyarray" or "anyelement" must have at least one argument of either type. +DETAIL: A function returning a polymorphic type must have at least one polymorphic argument. diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 5e6a570e24..6a31d52bcf 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -97,6 +97,7 @@ SELECT relname, relhasindex pg_database | t pg_depend | t pg_description | t + pg_enum | t pg_index | t pg_inherits | t pg_language | t @@ -141,7 +142,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(130 rows) +(131 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 11c9298e9a..b2db870da4 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -17,7 +17,7 @@ SELECT p1.oid, p1.typname FROM pg_type as p1 WHERE p1.typnamespace = 0 OR (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR - (p1.typtype not in ('b', 'c', 'd', 'p')) OR + (p1.typtype not in ('b', 'c', 'd', 'e', 'p')) OR NOT p1.typisdefined OR (p1.typalign not in ('c', 's', 'i', 'd')) OR (p1.typstorage not in ('p', 'x', 'e', 'm')); @@ -55,11 +55,11 @@ WHERE (p1.typtype = 'c' AND p1.typrelid = 0) OR -----+--------- (0 rows) --- Look for basic types that don't have an array type. +-- Look for basic or enum types that don't have an array type. -- NOTE: as of 8.0, this check finds smgr and unknown. SELECT p1.oid, p1.typname FROM pg_type as p1 -WHERE p1.typtype in ('b') AND p1.typname NOT LIKE E'\\_%' AND NOT EXISTS +WHERE p1.typtype in ('b','e') AND p1.typname NOT LIKE E'\\_%' AND NOT EXISTS (SELECT 1 FROM pg_type as p2 WHERE p2.typname = ('_' || p1.typname)::name AND p2.typelem = p1.oid); diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 8619b20d79..46e60653aa 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -2,7 +2,7 @@ # The first group of parallel test # $PostgreSQL$ # ---------- -test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric uuid +test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric uuid enum # Depends on things setup during char, varchar and text test: strings diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 5ab1929370..73650c3732 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -14,6 +14,7 @@ test: float8 test: bit test: numeric test: uuid +test: enum test: strings test: numerology test: point diff --git a/src/test/regress/sql/enum.sql b/src/test/regress/sql/enum.sql new file mode 100644 index 0000000000..387e8e72ed --- /dev/null +++ b/src/test/regress/sql/enum.sql @@ -0,0 +1,171 @@ +-- +-- Enum tests +-- + +CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple'); + +-- +-- Did it create the right number of rows? +-- +SELECT COUNT(*) FROM pg_enum WHERE enumtypid = 'rainbow'::regtype; + +-- +-- I/O functions +-- +SELECT 'red'::rainbow; +SELECT 'mauve'::rainbow; + +-- +-- Basic table creation, row selection +-- +CREATE TABLE enumtest (col rainbow); +INSERT INTO enumtest values ('red'), ('orange'), ('yellow'), ('green'); +COPY enumtest FROM stdin; +blue +purple +\. +SELECT * FROM enumtest; + +-- +-- Operators, no index +-- +SELECT * FROM enumtest WHERE col = 'orange'; +SELECT * FROM enumtest WHERE col <> 'orange' ORDER BY col; +SELECT * FROM enumtest WHERE col > 'yellow' ORDER BY col; +SELECT * FROM enumtest WHERE col >= 'yellow' ORDER BY col; +SELECT * FROM enumtest WHERE col < 'green' ORDER BY col; +SELECT * FROM enumtest WHERE col <= 'green' ORDER BY col; + +-- +-- Cast to/from text +-- +SELECT 'red'::rainbow::text || 'hithere'; +SELECT 'red'::text::rainbow = 'red'::rainbow; + +-- +-- Aggregates +-- +SELECT min(col) FROM enumtest; +SELECT max(col) FROM enumtest; +SELECT max(col) FROM enumtest WHERE col < 'green'; + +-- +-- Index tests, force use of index +-- +SET enable_seqscan = off; +SET enable_bitmapscan = off; + +-- +-- Btree index / opclass with the various operators +-- +CREATE UNIQUE INDEX enumtest_btree ON enumtest USING btree (col); +SELECT * FROM enumtest WHERE col = 'orange'; +SELECT * FROM enumtest WHERE col <> 'orange' ORDER BY col; +SELECT * FROM enumtest WHERE col > 'yellow' ORDER BY col; +SELECT * FROM enumtest WHERE col >= 'yellow' ORDER BY col; +SELECT * FROM enumtest WHERE col < 'green' ORDER BY col; +SELECT * FROM enumtest WHERE col <= 'green' ORDER BY col; +SELECT min(col) FROM enumtest; +SELECT max(col) FROM enumtest; +SELECT max(col) FROM enumtest WHERE col < 'green'; +DROP INDEX enumtest_btree; + +-- +-- Hash index / opclass with the = operator +-- +CREATE INDEX enumtest_hash ON enumtest USING hash (col); +SELECT * FROM enumtest WHERE col = 'orange'; +DROP INDEX enumtest_hash; + +-- +-- End index tests +-- +RESET enable_seqscan; +RESET enable_bitmapscan; + +-- +-- Domains over enums +-- +CREATE DOMAIN rgb AS rainbow CHECK (VALUE IN ('red', 'green', 'blue')); +SELECT 'red'::rgb; +SELECT 'purple'::rgb; +SELECT 'purple'::rainbow::rgb; +DROP DOMAIN rgb; + +-- +-- Arrays +-- +SELECT '{red,green,blue}'::rainbow[]; +SELECT ('{red,green,blue}'::rainbow[])[2]; +SELECT 'red' = ANY ('{red,green,blue}'::rainbow[]); +SELECT 'yellow' = ANY ('{red,green,blue}'::rainbow[]); +SELECT 'red' = ALL ('{red,green,blue}'::rainbow[]); +SELECT 'red' = ALL ('{red,red}'::rainbow[]); + +-- +-- Support functions +-- +SELECT enum_first(NULL::rainbow); +SELECT enum_last('green'::rainbow); +SELECT enum_range(NULL::rainbow); +SELECT enum_range('orange'::rainbow, 'green'::rainbow); +SELECT enum_range(NULL, 'green'::rainbow); +SELECT enum_range('orange'::rainbow, NULL); +SELECT enum_range(NULL::rainbow, NULL); + +-- +-- User functions, can't test perl/python etc here since may not be compiled. +-- +CREATE FUNCTION echo_me(anyenum) RETURNS text AS $$ +BEGIN +RETURN $1::text || 'omg'; +END +$$ LANGUAGE plpgsql; +SELECT echo_me('red'::rainbow); +-- +-- Concrete function should override generic one +-- +CREATE FUNCTION echo_me(rainbow) RETURNS text AS $$ +BEGIN +RETURN $1::text || 'wtf'; +END +$$ LANGUAGE plpgsql; +SELECT echo_me('red'::rainbow); +-- +-- If we drop the original generic one, we don't have to qualify the type +-- anymore, since there's only one match +-- +DROP FUNCTION echo_me(anyenum); +SELECT echo_me('red'); +DROP FUNCTION echo_me(rainbow); + +-- +-- RI triggers on enum types +-- +CREATE TABLE enumtest_parent (id rainbow PRIMARY KEY); +CREATE TABLE enumtest_child (parent rainbow REFERENCES enumtest_parent); +INSERT INTO enumtest_parent VALUES ('red'); +INSERT INTO enumtest_child VALUES ('red'); +INSERT INTO enumtest_child VALUES ('blue'); -- fail +DELETE FROM enumtest_parent; -- fail +-- +-- cross-type RI should fail +-- +CREATE TYPE bogus AS ENUM('good', 'bad', 'ugly'); +CREATE TABLE enumtest_bogus_child(parent bogus REFERENCES enumtest_parent); +DROP TYPE bogus; + +-- +-- Cleanup +-- +DROP TABLE enumtest_child; +DROP TABLE enumtest_parent; +DROP TABLE enumtest; +DROP TYPE rainbow; + +-- +-- Verify properly cleaned up +-- +SELECT COUNT(*) FROM pg_type WHERE typname = 'rainbow'; +SELECT * FROM pg_enum WHERE NOT EXISTS + (SELECT 1 FROM pg_type WHERE pg_type.oid = enumtypid); diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index 3677b90ceb..b1caa33efb 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -20,7 +20,7 @@ SELECT p1.oid, p1.typname FROM pg_type as p1 WHERE p1.typnamespace = 0 OR (p1.typlen <= 0 AND p1.typlen != -1 AND p1.typlen != -2) OR - (p1.typtype not in ('b', 'c', 'd', 'p')) OR + (p1.typtype not in ('b', 'c', 'd', 'e', 'p')) OR NOT p1.typisdefined OR (p1.typalign not in ('c', 's', 'i', 'd')) OR (p1.typstorage not in ('p', 'x', 'e', 'm')); @@ -49,12 +49,12 @@ FROM pg_type as p1 WHERE (p1.typtype = 'c' AND p1.typrelid = 0) OR (p1.typtype != 'c' AND p1.typrelid != 0); --- Look for basic types that don't have an array type. +-- Look for basic or enum types that don't have an array type. -- NOTE: as of 8.0, this check finds smgr and unknown. SELECT p1.oid, p1.typname FROM pg_type as p1 -WHERE p1.typtype in ('b') AND p1.typname NOT LIKE E'\\_%' AND NOT EXISTS +WHERE p1.typtype in ('b','e') AND p1.typname NOT LIKE E'\\_%' AND NOT EXISTS (SELECT 1 FROM pg_type as p2 WHERE p2.typname = ('_' || p1.typname)::name AND p2.typelem = p1.oid); |