diff --git a/doc/src/sgml/ref/create_cast.sgml b/doc/src/sgml/ref/create_cast.sgml index 5e74efdcacc8bc8eeaf597bdf2324e0ed68f6e73..d1c540da8aac289da11bc66c4b03999f0f767afb 100644 --- a/doc/src/sgml/ref/create_cast.sgml +++ b/doc/src/sgml/ref/create_cast.sgml @@ -38,7 +38,7 @@ CREATE CAST (<replaceable>source_type</replaceable> AS <replaceable>target_type< <para> <command>CREATE CAST</command> defines a new cast. A cast specifies how to perform a conversion between - two data types. For example: + two data types. For example, <programlisting> SELECT CAST(42 AS float8); </programlisting> @@ -64,10 +64,13 @@ SELECT CAST(42 AS float8); </para> <para> - You can define a cast as an <firstterm>I/O conversion cast</> using + You can define a cast as an <firstterm>I/O conversion cast</> by using the <literal>WITH INOUT</literal> syntax. An I/O conversion cast is performed by invoking the output function of the source data type, and - passing the result to the input function of the target data type. + passing the resulting string to the input function of the target data type. + In many common cases, this feature avoids the need to write a separate + cast function for conversion. An I/O conversion cast acts the same as + a regular function-based cast; only the implementation is different. </para> <para> @@ -218,7 +221,7 @@ SELECT CAST ( 2 AS numeric ) + 4.0; <para> Indicates that the cast is an I/O conversion cast, performed by invoking the output function of the source data type, and passing the - result to the input function of the target data type. + resulting string to the input function of the target data type. </para> </listitem> </varlistentry> @@ -278,9 +281,9 @@ SELECT CAST ( 2 AS numeric ) + 4.0; <para> When a cast has different source and target types and a function that takes more than one argument, it - represents converting from one type to another and applying a length + supports converting from one type to another and applying a length coercion in a single step. When no such entry is available, coercion - to a type that uses a type modifier involves two steps, one to + to a type that uses a type modifier involves two cast steps, one to convert between data types and a second to apply the modifier. </para> @@ -366,6 +369,18 @@ SELECT CAST ( 2 AS numeric ) + 4.0; syntax. </para> </note> + + <note> + <para> + There's an exception to the exception, too: I/O conversion casts from + composite types to string types cannot be invoked using functional + syntax, but must be written in explicit cast syntax (either + <literal>CAST</> or <literal>::</> notation). This exception was added + because after the introduction of automatically-provided I/O conversion + casts, it was found too easy to accidentally invoke such a cast when + a function or column reference was intended. + </para> + </note> </refsect1> diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index e8e0da96b9577f5600852fe4e339c5fcbd3fdcd1..2ebea7cf34b719031399ff75cbda550d955cf557 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -1522,6 +1522,19 @@ sqrt(2) The arguments can optionally have names attached. See <xref linkend="sql-syntax-calling-funcs"> for details. </para> + + <note> + <para> + A function that takes a single argument of composite type can + optionally be called using field-selection syntax, and conversely + field selection can be written in functional style. That is, the + notations <literal>col(table)</> and <literal>table.col</> are + interchangeable. This behavior is not SQL-standard but is provided + in <productname>PostgreSQL</> because it allows use of functions to + emulate <quote>computed fields</>. For more information see + <xref linkend="xfunc-sql-composite-functions">. + </para> + </note> </sect2> <sect2 id="syntax-aggregates"> diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 9fb2be4aecba7ddcb7cffab511f1b47cd48bc413..e79c1f29236e5f6468db0afddf31b75e066b4f65 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -271,7 +271,7 @@ $$ LANGUAGE SQL; </para> </sect2> - <sect2> + <sect2 id="xfunc-sql-composite-functions"> <title><acronym>SQL</acronym> Functions on Composite Types</title> <para> @@ -492,6 +492,12 @@ SELECT emp.name, emp.double_salary FROM emp; <literal>double_salary</> isn't a real column of the table. (You can also emulate computed fields with views.) </para> + + <para> + Because of this behavior, it's unwise to give a function that takes + a single composite-type argument the same name as any of the fields of + that composite type. + </para> </tip> <para> diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index b50bce448728280f63c695a688c004bd15bf84cf..9bb100e0c1e83c63b554f65300c03afed563373a 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -985,8 +985,13 @@ func_get_detail(List *funcname, * can't write "foo[] (something)" as a function call. In theory * someone might want to invoke it as "_foo (something)" but we have * never supported that historically, so we can insist that people - * write it as a normal cast instead. Lack of historical support is - * also the reason for not considering composite-type casts here. + * write it as a normal cast instead. + * + * We also reject the specific case of COERCEVIAIO for a composite + * source type and a string-category target type. This is a case that + * find_coercion_pathway() allows by default, but experience has shown + * that it's too commonly invoked by mistake. So, again, insist that + * people use cast syntax if they want to do that. * * NB: it's important that this code does not exceed what coerce_type * can do, because the caller will try to apply coerce_type if we @@ -1017,8 +1022,23 @@ func_get_detail(List *funcname, cpathtype = find_coercion_pathway(targetType, sourceType, COERCION_EXPLICIT, &cfuncid); - iscoercion = (cpathtype == COERCION_PATH_RELABELTYPE || - cpathtype == COERCION_PATH_COERCEVIAIO); + switch (cpathtype) + { + case COERCION_PATH_RELABELTYPE: + iscoercion = true; + break; + case COERCION_PATH_COERCEVIAIO: + if ((sourceType == RECORDOID || + ISCOMPLEX(sourceType)) && + TypeCategory(targetType) == TYPCATEGORY_STRING) + iscoercion = false; + else + iscoercion = true; + break; + default: + iscoercion = false; + break; + } } if (iscoercion) diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index a21f7b8c06b2bb5673857236469adc1b9a4b1522..e5cd71421c636ebd0ddc978e0493cacedf7b8600 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -324,3 +324,49 @@ select * from price; (3 rows) rollback; +-- +-- We allow I/O conversion casts from composite types to strings to be +-- invoked via cast syntax, but not functional syntax. This is because +-- the latter is too prone to be invoked unintentionally. +-- +select cast (fullname as text) from fullname; + fullname +---------- +(0 rows) + +select fullname::text from fullname; + fullname +---------- +(0 rows) + +select text(fullname) from fullname; -- error +ERROR: function text(fullname) does not exist +LINE 1: select text(fullname) from fullname; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select fullname.text from fullname; -- error +ERROR: column fullname.text does not exist +LINE 1: select fullname.text from fullname; + ^ +-- same, but RECORD instead of named composite type: +select cast (row('Jim', 'Beam') as text); + row +------------ + (Jim,Beam) +(1 row) + +select (row('Jim', 'Beam'))::text; + row +------------ + (Jim,Beam) +(1 row) + +select text(row('Jim', 'Beam')); -- error +ERROR: function text(record) does not exist +LINE 1: select text(row('Jim', 'Beam')); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select (row('Jim', 'Beam')).text; -- error +ERROR: could not identify column "text" in record data type +LINE 1: select (row('Jim', 'Beam')).text; + ^ diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index e5a77f79f65c0c0321c5b4c3ddd417cde48510db..9041df147fe8999037ad05241344ae1d527704b6 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -157,3 +157,18 @@ UPDATE price select * from price; rollback; + +-- +-- We allow I/O conversion casts from composite types to strings to be +-- invoked via cast syntax, but not functional syntax. This is because +-- the latter is too prone to be invoked unintentionally. +-- +select cast (fullname as text) from fullname; +select fullname::text from fullname; +select text(fullname) from fullname; -- error +select fullname.text from fullname; -- error +-- same, but RECORD instead of named composite type: +select cast (row('Jim', 'Beam') as text); +select (row('Jim', 'Beam'))::text; +select text(row('Jim', 'Beam')); -- error +select (row('Jim', 'Beam')).text; -- error