v2-generate-correct-vartype-for-whole-row-vars-HEAD.patch
text/x-diff
Filename: v2-generate-correct-vartype-for-whole-row-vars-HEAD.patch
Type: text/x-diff
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v2
| File | + | − |
|---|---|---|
| src/backend/nodes/makefuncs.c | 30 | 2 |
| src/backend/optimizer/prep/prepjointree.c | 20 | 0 |
| src/include/nodes/parsenodes.h | 4 | 1 |
| src/test/regress/expected/returning.out | 38 | 1 |
| src/test/regress/sql/returning.sql | 14 | 0 |
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index dbbc2f1e30d..71632512380 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -161,6 +161,34 @@ makeWholeRowVar(RangeTblEntry *rte,
varlevelsup);
break;
+ case RTE_SUBQUERY:
+
+ /*
+ * For a standard subquery, the Var should be of RECORD type.
+ * However, if we're looking at a subquery that was expanded from
+ * a view or SRF (only possible during planning), we must use the
+ * appropriate rowtype, so that the resulting Var has the same
+ * type that we would have produced from the original RTE.
+ */
+ if (OidIsValid(rte->relid))
+ {
+ toid = get_rel_type_id(rte->relid);
+ if (!OidIsValid(toid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("relation \"%s\" does not have a composite type",
+ get_rel_name(rte->relid))));
+ }
+ else
+ toid = RECORDOID;
+ result = makeVar(varno,
+ InvalidAttrNumber,
+ toid,
+ -1,
+ InvalidOid,
+ varlevelsup);
+ break;
+
case RTE_FUNCTION:
/*
@@ -217,8 +245,8 @@ makeWholeRowVar(RangeTblEntry *rte,
default:
/*
- * RTE is a join, subselect, tablefunc, or VALUES. We represent
- * this as a whole-row Var of RECORD type. (Note that in most
+ * RTE is a join, tablefunc, VALUES, CTE, etc. We represent these
+ * cases as a whole-row Var of RECORD type. (Note that in most
* cases the Var will be expanded to a RowExpr during planning,
* but that is not our concern here.)
*/
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index bcc40dd5a84..477ff3fa76d 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -43,6 +43,7 @@
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
+#include "utils/lsyscache.h"
#include "utils/rel.h"
@@ -911,9 +912,28 @@ preprocess_function_rtes(PlannerInfo *root)
if (funcquery)
{
/* Successful expansion, convert the RTE to a subquery */
+ Node *fexpr;
+ Oid toid;
+
rte->rtekind = RTE_SUBQUERY;
rte->subquery = funcquery;
rte->security_barrier = false;
+
+ /*
+ * If the SRF returned a named composite type (not RECORD), we
+ * must also set rte->relid so that makeWholeRowVar can still
+ * produce the same output for the RTE as it did before. This
+ * code relies on the fact that inline_set_returning_function
+ * won't have succeeded unless there is exactly one entry in
+ * rte->functions. Also, this would likely be the wrong thing
+ * for domain-over-composite, but functions returning those
+ * won't get inlined either.
+ */
+ fexpr = ((RangeTblFunction *) linitial(rte->functions))->funcexpr;
+ toid = get_typ_typrelid(exprType(fexpr));
+ if (OidIsValid(toid))
+ rte->relid = toid;
+
/* Clear fields that should not be set in a subquery RTE */
rte->functions = NIL;
rte->funcordinality = false;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 23c9e3c5abf..ef8e4eab9e0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1086,7 +1086,10 @@ typedef struct RangeTblEntry
* containing the view's query. We still need to perform run-time locking
* and permission checks on the view, even though it's not directly used
* in the query anymore, and the most expedient way to do that is to
- * retain these fields from the old state of the RTE.
+ * retain these fields from the old state of the RTE. relid (but not the
+ * other fields) is also set when converting an RTE_FUNCTION RTE to an
+ * RTE_SUBQUERY by inlining a set-returning function that returns a named
+ * composite type.
*
* As a special case, RTE_NAMEDTUPLESTORE can also set relid to indicate
* that the tuple format of the tuplestore is the same as the referenced
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index d1394c67833..9659cae9ce7 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -286,6 +286,42 @@ SELECT * FROM voo;
16 | zoo2
(2 rows)
+-- Check use of a whole-row variable for an un-flattenable view
+CREATE TEMP VIEW foo_v AS SELECT * FROM foo OFFSET 0;
+UPDATE foo SET f2 = foo_v.f2 FROM foo_v WHERE foo_v.f1 = foo.f1
+ RETURNING foo_v;
+ foo_v
+-----------------
+ (2,more,42,141)
+ (16,zoo2,57,99)
+(2 rows)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+----+------+----+-----
+ 2 | more | 42 | 141
+ 16 | zoo2 | 57 | 99
+(2 rows)
+
+-- Check use of a whole-row variable for an inlined set-returning function
+CREATE FUNCTION foo_f() RETURNS SETOF foo AS
+ $$ SELECT * FROM foo OFFSET 0 $$ LANGUAGE sql STABLE;
+UPDATE foo SET f2 = foo_f.f2 FROM foo_f() WHERE foo_f.f1 = foo.f1
+ RETURNING foo_f;
+ foo_f
+-----------------
+ (2,more,42,141)
+ (16,zoo2,57,99)
+(2 rows)
+
+SELECT * FROM foo;
+ f1 | f2 | f3 | f4
+----+------+----+-----
+ 2 | more | 42 | 141
+ 16 | zoo2 | 57 | 99
+(2 rows)
+
+DROP FUNCTION foo_f();
-- Try a join case
CREATE TEMP TABLE joinme (f2j text, other int);
INSERT INTO joinme VALUES('more', 12345);
@@ -726,8 +762,9 @@ NOTICE: UPDATE: (3,zoo2,58,99,54321) -> (3,zoo2,59,7,54321)
-- Test wholerow & dropped column handling
ALTER TABLE foo DROP COLUMN f3 CASCADE;
-NOTICE: drop cascades to 3 other objects
+NOTICE: drop cascades to 4 other objects
DETAIL: drop cascades to rule voo_i on view voo
+drop cascades to view foo_v
drop cascades to view joinview
drop cascades to rule foo_del_rule on table foo
UPDATE foo SET f4 = f4 + 1 RETURNING old.f3; -- should fail
diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql
index 54caf56244c..9aea7fb609f 100644
--- a/src/test/regress/sql/returning.sql
+++ b/src/test/regress/sql/returning.sql
@@ -132,6 +132,20 @@ DELETE FROM foo WHERE f2 = 'zit' RETURNING *;
SELECT * FROM foo;
SELECT * FROM voo;
+-- Check use of a whole-row variable for an un-flattenable view
+CREATE TEMP VIEW foo_v AS SELECT * FROM foo OFFSET 0;
+UPDATE foo SET f2 = foo_v.f2 FROM foo_v WHERE foo_v.f1 = foo.f1
+ RETURNING foo_v;
+SELECT * FROM foo;
+
+-- Check use of a whole-row variable for an inlined set-returning function
+CREATE FUNCTION foo_f() RETURNS SETOF foo AS
+ $$ SELECT * FROM foo OFFSET 0 $$ LANGUAGE sql STABLE;
+UPDATE foo SET f2 = foo_f.f2 FROM foo_f() WHERE foo_f.f1 = foo.f1
+ RETURNING foo_f;
+SELECT * FROM foo;
+DROP FUNCTION foo_f();
+
-- Try a join case
CREATE TEMP TABLE joinme (f2j text, other int);