Allow extracting fields from a ROW() expression in more cases.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 28 Oct 2019 19:08:24 +0000 (15:08 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 28 Oct 2019 19:08:24 +0000 (15:08 -0400)
Teach get_expr_result_type() to manufacture a tuple descriptor directly
from a RowExpr node.  If the RowExpr has type RECORD, this is the only
way to get a tupdesc for its result, since even if the rowtype has been
blessed, we don't have its typmod available at this point.  (If the
RowExpr has some named composite type, we continue to let the existing
code handle it, since the RowExpr might well not have the correct column
names embedded in it.)

This fixes assorted corner cases illustrated by the added regression
tests.

Discussion: https://postgr.es/m/10872.1572202006@sss.pgh.pa.us

src/backend/utils/fmgr/funcapi.c
src/test/regress/expected/rowtypes.out
src/test/regress/sql/rowtypes.sql

index b7fac5d2954983a04e9594a6127fa2896c85d9e4..4688fbc50c9c185eb46eaceeae6759055c3a18ed 100644 (file)
@@ -227,6 +227,38 @@ get_expr_result_type(Node *expr,
                                          NULL,
                                          resultTypeId,
                                          resultTupleDesc);
+   else if (expr && IsA(expr, RowExpr) &&
+            ((RowExpr *) expr)->row_typeid == RECORDOID)
+   {
+       /* We can resolve the record type by generating the tupdesc directly */
+       RowExpr    *rexpr = (RowExpr *) expr;
+       TupleDesc   tupdesc;
+       AttrNumber  i = 1;
+       ListCell   *lcc,
+                  *lcn;
+
+       tupdesc = CreateTemplateTupleDesc(list_length(rexpr->args));
+       Assert(list_length(rexpr->args) == list_length(rexpr->colnames));
+       forboth(lcc, rexpr->args, lcn, rexpr->colnames)
+       {
+           Node       *col = (Node *) lfirst(lcc);
+           char       *colname = strVal(lfirst(lcn));
+
+           TupleDescInitEntry(tupdesc, i,
+                              colname,
+                              exprType(col),
+                              exprTypmod(col),
+                              0);
+           TupleDescInitEntryCollation(tupdesc, i,
+                                       exprCollation(col));
+           i++;
+       }
+       if (resultTypeId)
+           *resultTypeId = rexpr->row_typeid;
+       if (resultTupleDesc)
+           *resultTupleDesc = BlessTupleDesc(tupdesc);
+       return TYPEFUNC_COMPOSITE;
+   }
    else
    {
        /* handle as a generic expression; no chance to resolve RECORD */
index a272305eb558b5a2ed00a75b4b7a7e58bdd772b3..2a273f84049918a09a8ff5348a64615d7198feca 100644 (file)
@@ -436,6 +436,45 @@ where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
  4567890123456789 | 123
 (2 rows)
 
+-- Check ability to select columns from an anonymous rowtype
+select (row(1, 2.0)).f1;
+ f1 
+----
+  1
+(1 row)
+
+select (row(1, 2.0)).f2;
+ f2  
+-----
+ 2.0
+(1 row)
+
+select (row(1, 2.0)).nosuch;  -- fail
+ERROR:  could not identify column "nosuch" in record data type
+LINE 1: select (row(1, 2.0)).nosuch;
+                ^
+select (row(1, 2.0)).*;
+ f1 | f2  
+----+-----
+  1 | 2.0
+(1 row)
+
+select (r).f1 from (select row(1, 2.0) as r) ss;
+ f1 
+----
+  1
+(1 row)
+
+select (r).f3 from (select row(1, 2.0) as r) ss;  -- fail
+ERROR:  could not identify column "f3" in record data type
+LINE 1: select (r).f3 from (select row(1, 2.0) as r) ss;
+                ^
+select (r).* from (select row(1, 2.0) as r) ss;
+ f1 | f2  
+----+-----
+  1 | 2.0
+(1 row)
+
 -- Check some corner cases involving empty rowtypes
 select ROW();
  row 
index 7e080c034cdc7205399b1ed84d8d1e18d6711bc9..83cf4a1e791593596353a7d88bfe167814907e8c 100644 (file)
@@ -171,6 +171,15 @@ where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
 select * from int8_tbl i8
 where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
 
+-- Check ability to select columns from an anonymous rowtype
+select (row(1, 2.0)).f1;
+select (row(1, 2.0)).f2;
+select (row(1, 2.0)).nosuch;  -- fail
+select (row(1, 2.0)).*;
+select (r).f1 from (select row(1, 2.0) as r) ss;
+select (r).f3 from (select row(1, 2.0) as r) ss;  -- fail
+select (r).* from (select row(1, 2.0) as r) ss;
+
 -- Check some corner cases involving empty rowtypes
 select ROW();
 select ROW() IS NULL;