Fix things so that you can still do "select foo()" where foo is a SQL
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 11 Jun 2009 17:25:39 +0000 (17:25 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 11 Jun 2009 17:25:39 +0000 (17:25 +0000)
function returning setof record.  This used to work, more or less
accidentally, but I had broken it while extending the code to allow
materialize-mode functions to be called in select lists.  Add a regression
test case so it doesn't get broken again.  Per gripe from Greg Davidson.

src/backend/executor/execQual.c
src/backend/executor/functions.c
src/test/regress/expected/rangefuncs.out
src/test/regress/sql/rangefuncs.sql

index a0e4566630bfdd549e6beec97d65f6858595b739..b1bd9e4e7e157d6cbbfc00d1981e258ed72ac3f8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.249 2009/06/11 14:48:57 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.250 2009/06/11 17:25:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1089,9 +1089,15 @@ init_fcache(Oid foid, FuncExprState *fcache,
                        fcache->funcResultDesc = tupdesc;
                        fcache->funcReturnsTuple = false;
                }
+               else if (functypclass == TYPEFUNC_RECORD)
+               {
+                       /* This will work if function doesn't need an expectedDesc */
+                       fcache->funcResultDesc = NULL;
+                       fcache->funcReturnsTuple = true;
+               }
                else
                {
-                       /* Else, we will complain if function wants materialize mode */
+                       /* Else, we will fail if function needs an expectedDesc */
                        fcache->funcResultDesc = NULL;
                }
 
@@ -1252,18 +1258,32 @@ ExecPrepareTuplestoreResult(FuncExprState *fcache,
        if (fcache->funcResultSlot == NULL)
        {
                /* Create a slot so we can read data out of the tuplestore */
+               TupleDesc       slotDesc;
                MemoryContext oldcontext;
 
-               /* We must have been able to determine the result rowtype */
-               if (fcache->funcResultDesc == NULL)
+               oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt);
+
+               /*
+                * If we were not able to determine the result rowtype from context,
+                * and the function didn't return a tupdesc, we have to fail.
+                */
+               if (fcache->funcResultDesc)
+                       slotDesc = fcache->funcResultDesc;
+               else if (resultDesc)
+               {
+                       /* don't assume resultDesc is long-lived */
+                       slotDesc = CreateTupleDescCopy(resultDesc);
+               }
+               else
+               {
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                         errmsg("function returning setof record called in "
                                                        "context that cannot accept type record")));
+                       slotDesc = NULL;        /* keep compiler quiet */
+               }
 
-               oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt);
-               fcache->funcResultSlot =
-                       MakeSingleTupleTableSlot(fcache->funcResultDesc);
+               fcache->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
                MemoryContextSwitchTo(oldcontext);
        }
 
index b0d8b9008a8e6a1934d21e578ba084a96e674879..fe25798a21e2c8d94422211e6c69e1f8772678ef 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.134 2009/06/11 14:48:57 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.135 2009/06/11 17:25:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -634,11 +634,11 @@ fmgr_sql(PG_FUNCTION_ARGS)
                 * For simplicity, we require callers to support both set eval modes.
                 * There are cases where we must use one or must use the other, and
                 * it's not really worthwhile to postpone the check till we know.
+                * But note we do not require caller to provide an expectedDesc.
                 */
                if (!rsi || !IsA(rsi, ReturnSetInfo) ||
                        (rsi->allowedModes & SFRM_ValuePerCall) == 0 ||
-                       (rsi->allowedModes & SFRM_Materialize) == 0 ||
-                       rsi->expectedDesc == NULL)
+                       (rsi->allowedModes & SFRM_Materialize) == 0)
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                         errmsg("set-valued function called in context that cannot accept a set")));
index a5cff8ca43a9e986d91b253f05a7f295de587eac..486dd3f3fe06f835cf9e7d414645937fc2dc3c6a 100644 (file)
@@ -763,3 +763,70 @@ select t.a, t, t.a from foo1(10000) t limit 1;
 (1 row)
 
 drop function foo1(n integer);
+-- test use of SQL functions returning record
+-- this is supported in some cases where the query doesn't specify
+-- the actual record type ...
+create function array_to_set(anyarray) returns setof record as $$
+  select i AS "index", $1[i] AS "value" from generate_subscripts($1, 1) i
+$$ language sql strict immutable;
+select array_to_set(array['one', 'two']);
+ array_to_set 
+--------------
+ (1,one)
+ (2,two)
+(2 rows)
+
+select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text);
+ f1 | f2  
+----+-----
+  1 | one
+  2 | two
+(2 rows)
+
+select * from array_to_set(array['one', 'two']); -- fail
+ERROR:  a column definition list is required for functions returning "record"
+LINE 1: select * from array_to_set(array['one', 'two']);
+                      ^
+create temp table foo(f1 int8, f2 int8);
+create function testfoo() returns record as $$
+  insert into foo values (1,2) returning *;
+$$ language sql;
+select testfoo();
+ testfoo 
+---------
+ (1,2)
+(1 row)
+
+select * from testfoo() as t(f1 int8,f2 int8);
+ f1 | f2 
+----+----
+  1 |  2
+(1 row)
+
+select * from testfoo(); -- fail
+ERROR:  a column definition list is required for functions returning "record"
+LINE 1: select * from testfoo();
+                      ^
+drop function testfoo();
+create function testfoo() returns setof record as $$
+  insert into foo values (1,2), (3,4) returning *;
+$$ language sql;
+select testfoo();
+ testfoo 
+---------
+ (1,2)
+ (3,4)
+(2 rows)
+
+select * from testfoo() as t(f1 int8,f2 int8);
+ f1 | f2 
+----+----
+  1 |  2
+  3 |  4
+(2 rows)
+
+select * from testfoo(); -- fail
+ERROR:  a column definition list is required for functions returning "record"
+LINE 1: select * from testfoo();
+                      ^
+drop function testfoo();
index 3c68f0aa447111aaa4ec7668d46aaea8b21d007a..3727a36aaff501e3f4e3474fa19f7496015c349e 100644 (file)
@@ -351,3 +351,37 @@ reset work_mem;
 select t.a, t, t.a from foo1(10000) t limit 1;
 
 drop function foo1(n integer);
+
+-- test use of SQL functions returning record
+-- this is supported in some cases where the query doesn't specify
+-- the actual record type ...
+
+create function array_to_set(anyarray) returns setof record as $$
+  select i AS "index", $1[i] AS "value" from generate_subscripts($1, 1) i
+$$ language sql strict immutable;
+
+select array_to_set(array['one', 'two']);
+select * from array_to_set(array['one', 'two']) as t(f1 int,f2 text);
+select * from array_to_set(array['one', 'two']); -- fail
+
+create temp table foo(f1 int8, f2 int8);
+
+create function testfoo() returns record as $$
+  insert into foo values (1,2) returning *;
+$$ language sql;
+
+select testfoo();
+select * from testfoo() as t(f1 int8,f2 int8);
+select * from testfoo(); -- fail
+
+drop function testfoo();
+
+create function testfoo() returns setof record as $$
+  insert into foo values (1,2), (3,4) returning *;
+$$ language sql;
+
+select testfoo();
+select * from testfoo() as t(f1 int8,f2 int8);
+select * from testfoo(); -- fail
+
+drop function testfoo();