From b46932b48f491f874a1e74a3b19758e7a84015f3 Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Thu, 22 Oct 2026 14:19:30 +0900 Subject: [PATCH 5/6] Remove the fix for columns that function result types gained This series carried its own fix for the column alias list of a function RTE whose composite result type gained columns after parse, which is not specific to row pattern recognition, and it covered only a single function without WITH ORDINALITY. It goes upstream as a patch of its own. Remove the fix here, the tests for it in create_view, and its mention in README.rpr, whose paragraph on what the guarantee rests on now speaks of the column alias list in general. Remove also the rpr_base test that needs the fix, where a DEFINE clause reads a name that a function's grown column comes to carry. A column added to a relation is the same case for row pattern recognition and is tested already. --- src/backend/executor/README.rpr | 9 +- src/backend/utils/adt/ruleutils.c | 66 -------------- src/test/regress/expected/create_view.out | 97 -------------------- src/test/regress/expected/rpr_base.out | 106 ---------------------- src/test/regress/sql/create_view.sql | 47 ---------- src/test/regress/sql/rpr_base.sql | 42 --------- 6 files changed, 4 insertions(+), 363 deletions(-) diff --git a/src/backend/executor/README.rpr b/src/backend/executor/README.rpr index a3695cf2091..0463da20671 100644 --- a/src/backend/executor/README.rpr +++ b/src/backend/executor/README.rpr @@ -2549,11 +2549,10 @@ pattern variable; nor are whole-row references, which the parser rejects, or dropped columns. The guarantee rests on set_relation_column_names(), whose rules apply to -every query, with a DEFINE clause or not. For a function RTE it appends the -columns the composite result type has gained since parse time -(function_rte_late_colnames(); only for a single function without WITH -ORDINALITY or a column definition list), so a reserved name is kept off -them and the positional column alias list counts them. +every query, with a DEFINE clause or not: a column renamed to keep off a +reserved name is printed on its RTE's column alias list. Where that code +cannot print a renamed column, a DEFINE clause shares the limit with every +other reference to the column. XIII-6. Navigation Names Shadow User Functions diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 0cb462a585e..040ac886be1 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -401,7 +401,6 @@ static void mark_define_column(deparse_namespace *dpns, Var *var); static char *preset_input_colname(deparse_namespace *dpns, int varno, AttrNumber attno); static void reserve_colname(deparse_namespace *dpns, char *colname); -static List *function_rte_late_colnames(RangeTblEntry *rte); static void collapse_define_join_vars(Query *query); static Node *collapse_define_join_vars_mutator(Node *node, collapse_define_context *context); @@ -4741,62 +4740,6 @@ reserve_colname(deparse_namespace *dpns, char *colname) dpns->using_names = lappend(dpns->using_names, colname); } -/* - * function_rte_late_colnames: the names of the columns a function RTE's result - * type has grown since the query was parsed - * - * expandRTE() stops at the column count recorded at parse time, so a column - * the type has gained since then is not in the list the deparser works from. - * Yet it is a column the RTE has now: a name that has to resolve exactly as - * printed -- a globally unique USING name, or one a DEFINE clause reads -- - * must be kept off it, and the column alias list being positional, an - * aliased join above lays its own list over this one end to end, so the - * grown columns have to be counted in. - * - * Returns NIL unless there is one function and no WITH ORDINALITY, those being - * the cases where the grown columns land at the end of the RTE. Anywhere else - * they land in the middle, shifting the attnos this query was parsed with. - */ -static List * -function_rte_late_colnames(RangeTblEntry *rte) -{ - RangeTblFunction *rtfunc; - TypeFuncClass functypclass; - Oid funcrettype; - TupleDesc tupdesc; - List *result = NIL; - int i; - - if (rte->funcordinality || list_length(rte->functions) != 1) - return NIL; - - rtfunc = (RangeTblFunction *) linitial(rte->functions); - - /* A coldeflist fixes the column set, and pins the return type at RECORD */ - if (rtfunc->funccolnames != NIL) - return NIL; - - functypclass = get_expr_result_type(rtfunc->funcexpr, &funcrettype, - &tupdesc); - if (functypclass != TYPEFUNC_COMPOSITE && - functypclass != TYPEFUNC_COMPOSITE_DOMAIN) - return NIL; - - for (i = rtfunc->funccolcount; i < tupdesc->natts; i++) - { - Form_pg_attribute attr = TupleDescAttr(tupdesc, i); - - /* Spell a dropped column the way expandRTE() does */ - if (attr->attisdropped) - result = lappend(result, makeString(pstrdup(""))); - else - result = lappend(result, - makeString(pstrdup(NameStr(attr->attname)))); - } - - return result; -} - /* * set_relation_column_names: select column aliases for a non-join RTE * @@ -4869,15 +4812,6 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, /* Since we're not creating Vars, rtindex etc. don't matter */ expandRTE(rte, 1, 0, VAR_RETURNING_DEFAULT, -1, true /* include dropped */ , &colnames, NULL); - - /* - * Take the columns the result type has grown since parse time as - * well: they are columns the RTE has now, so a name that has to - * resolve exactly as printed must be kept off of them too, and - * the alias list is positional, so they are printed in full. - */ - colnames = list_concat(colnames, - function_rte_late_colnames(rte)); } else colnames = rte->eref->colnames; diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index 68734694ee4..c2892dfb6d0 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -971,103 +971,6 @@ select pg_get_viewdef('view_of_joins_2d', true); JOIN tbl1a USING (a) AS x) y; (1 row) --- The columns a function's result type grows after the view is made are --- columns the RTE has now, and the alias list being positional they are --- printed in full: an aliased join above lays its own list over its inputs' --- lists end to end, so a grown column left off would shift the input after --- it, and a reference into that input would silently land on another column. -create table tblfc (a int, z int); -insert into tblfc values (1, 5); -create function tblfc_f() returns setof tblfc language sql - as $$ select * from tblfc $$; -create table tblfr (z int, val int); -insert into tblfr values (1, 7); -create view view_of_grown_input as -select j.a, j.val from (tblfc_f() f join tblfr on true) j; -select * from view_of_grown_input; - a | val ----+----- - 1 | 7 -(1 row) - -alter table tblfc add column val int, add column spare int; -select pg_get_viewdef('view_of_grown_input', true); - pg_get_viewdef ------------------------------------------------------------ - SELECT j.a, + - j.val + - FROM (tblfc_f() f(a, z, val, spare) + - JOIN tblfr ON true) j(a, z, val_1, spare, z_1, val); -(1 row) - -select 'create view view_of_grown_input_2 as ' - || pg_get_viewdef('view_of_grown_input', true) \gexec -create view view_of_grown_input_2 as SELECT j.a, - j.val - FROM (tblfc_f() f(a, z, val, spare) - JOIN tblfr ON true) j(a, z, val_1, spare, z_1, val); -select pg_get_viewdef('view_of_grown_input', true) - = pg_get_viewdef('view_of_grown_input_2', true) as round_trips; - round_trips -------------- - t -(1 row) - -select * from view_of_grown_input; - a | val ----+----- - 1 | 7 -(1 row) - -select * from view_of_grown_input_2; - a | val ----+----- - 1 | 7 -(1 row) - -drop view view_of_grown_input_2; --- A grown column that is dropped again still takes up its place in the --- positional alias list, as a dropped column; a column grown after it must --- not slide into that place. -alter table tblfc drop column val; -alter table tblfc add column val int; -select pg_get_viewdef('view_of_grown_input', true); - pg_get_viewdef ------------------------------------------------------------ - SELECT j.a, + - j.val + - FROM (tblfc_f() f(a, z, spare, val) + - JOIN tblfr ON true) j(a, z, spare, val_1, z_1, val); -(1 row) - -select 'create view view_of_grown_input_2 as ' - || pg_get_viewdef('view_of_grown_input', true) \gexec -create view view_of_grown_input_2 as SELECT j.a, - j.val - FROM (tblfc_f() f(a, z, spare, val) - JOIN tblfr ON true) j(a, z, spare, val_1, z_1, val); -select pg_get_viewdef('view_of_grown_input', true) - = pg_get_viewdef('view_of_grown_input_2', true) as round_trips; - round_trips -------------- - t -(1 row) - -select * from view_of_grown_input; - a | val ----+----- - 1 | 7 -(1 row) - -select * from view_of_grown_input_2; - a | val ----+----- - 1 | 7 -(1 row) - -drop view view_of_grown_input_2, view_of_grown_input; -drop function tblfc_f(); -drop table tblfc, tblfr; -- Test view decompilation in the face of column addition/deletion/renaming create table tt2 (a int, b int, c int); create table tt3 (ax int8, b int2, c numeric); diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out index 5d9390cff2e..6d03efbf90d 100644 --- a/src/test/regress/expected/rpr_base.out +++ b/src/test/regress/expected/rpr_base.out @@ -4713,112 +4713,6 @@ SELECT pg_get_viewdef('rpr_res_nat_v'::regclass, true) DROP VIEW rpr_res_nat_rt, rpr_res_nat_v; DROP VIEW rpr_res_alias_rt, rpr_res_alias_v; DROP TABLE rpr_res_ja, rpr_res_jb, rpr_res_m1, rpr_res_m2, rpr_res_ordj; --- A column a function's result type grows after the view is made is one the --- deparser does not see at all: expandRTE() stops at the column count the --- query was parsed with. A column it cannot see is one it cannot rename out --- of the way, and the name it collides with here is the one a DEFINE clause --- has to resolve to as printed. So the grown columns are looked up and named --- too, and printed in full, the alias list being positional. -CREATE TABLE rpr_res_fn (id INT, val INT); -INSERT INTO rpr_res_fn VALUES (1, 1), (2, 2), (3, 3); -CREATE TABLE rpr_res_cfg (a INT); -INSERT INTO rpr_res_cfg VALUES (1); -CREATE FUNCTION rpr_res_fcfg() RETURNS SETOF rpr_res_cfg LANGUAGE sql - AS $$ SELECT * FROM rpr_res_cfg $$; -CREATE VIEW rpr_res_fn_v AS -SELECT rpr_res_fn.id, count(*) OVER w AS cnt -FROM rpr_res_fn, rpr_res_fcfg() f -WINDOW w AS (ORDER BY rpr_res_fn.id - ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING - PATTERN (A+) - DEFINE A AS val > 0); --- nothing to keep off yet -SELECT pg_get_viewdef('rpr_res_fn_v'::regclass, true); - pg_get_viewdef ----------------------------------------------------------------------------------------- - SELECT rpr_res_fn.id, + - count(*) OVER w AS cnt + - FROM rpr_res_fn, + - rpr_res_fcfg() f(a) + - WINDOW w AS (ORDER BY rpr_res_fn.id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING+ - AFTER MATCH SKIP PAST LAST ROW + - INITIAL + - PATTERN (a+) + - DEFINE + - a AS val > 0); -(1 row) - -ALTER TABLE rpr_res_cfg ADD COLUMN val INT; -SELECT pg_get_viewdef('rpr_res_fn_v'::regclass, true); - pg_get_viewdef ----------------------------------------------------------------------------------------- - SELECT rpr_res_fn.id, + - count(*) OVER w AS cnt + - FROM rpr_res_fn, + - rpr_res_fcfg() f(a, val_1) + - WINDOW w AS (ORDER BY rpr_res_fn.id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING+ - AFTER MATCH SKIP PAST LAST ROW + - INITIAL + - PATTERN (a+) + - DEFINE + - a AS val > 0); -(1 row) - -SELECT 'CREATE VIEW rpr_res_fn_rt AS ' - || pg_get_viewdef('rpr_res_fn_v'::regclass, true) \gexec -CREATE VIEW rpr_res_fn_rt AS SELECT rpr_res_fn.id, - count(*) OVER w AS cnt - FROM rpr_res_fn, - rpr_res_fcfg() f(a, val_1) - WINDOW w AS (ORDER BY rpr_res_fn.id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING - AFTER MATCH SKIP PAST LAST ROW - INITIAL - PATTERN (a+) - DEFINE - a AS val > 0); -SELECT pg_get_viewdef('rpr_res_fn_v'::regclass, true) - = pg_get_viewdef('rpr_res_fn_rt'::regclass, true) AS round_trips; - round_trips -------------- - t -(1 row) - -SELECT * FROM rpr_res_fn_v; - id | cnt -----+----- - 1 | 3 - 2 | 0 - 3 | 0 -(3 rows) - -SELECT * FROM rpr_res_fn_rt; - id | cnt -----+----- - 1 | 3 - 2 | 0 - 3 | 0 -(3 rows) - --- a grown column that collides with nothing is still on the list -ALTER TABLE rpr_res_cfg ADD COLUMN spare INT; -SELECT pg_get_viewdef('rpr_res_fn_v'::regclass, true); - pg_get_viewdef ----------------------------------------------------------------------------------------- - SELECT rpr_res_fn.id, + - count(*) OVER w AS cnt + - FROM rpr_res_fn, + - rpr_res_fcfg() f(a, val_1, spare) + - WINDOW w AS (ORDER BY rpr_res_fn.id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING+ - AFTER MATCH SKIP PAST LAST ROW + - INITIAL + - PATTERN (a+) + - DEFINE + - a AS val > 0); -(1 row) - -DROP VIEW rpr_res_fn_rt, rpr_res_fn_v; -DROP FUNCTION rpr_res_fcfg(); -DROP TABLE rpr_res_fn, rpr_res_cfg; -- A system column is named from the catalog, not from the deparser's own -- choice, so there is no alias to pick for it and nothing to exempt from -- renaming. Its name still has to be held against the rest of the query, or a diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index 72579c0b645..f80b9823f00 100644 --- a/src/test/regress/sql/create_view.sql +++ b/src/test/regress/sql/create_view.sql @@ -409,53 +409,6 @@ select pg_get_viewdef('view_of_joins_2b', true); select pg_get_viewdef('view_of_joins_2c', true); select pg_get_viewdef('view_of_joins_2d', true); --- The columns a function's result type grows after the view is made are --- columns the RTE has now, and the alias list being positional they are --- printed in full: an aliased join above lays its own list over its inputs' --- lists end to end, so a grown column left off would shift the input after --- it, and a reference into that input would silently land on another column. -create table tblfc (a int, z int); -insert into tblfc values (1, 5); -create function tblfc_f() returns setof tblfc language sql - as $$ select * from tblfc $$; -create table tblfr (z int, val int); -insert into tblfr values (1, 7); - -create view view_of_grown_input as -select j.a, j.val from (tblfc_f() f join tblfr on true) j; - -select * from view_of_grown_input; - -alter table tblfc add column val int, add column spare int; - -select pg_get_viewdef('view_of_grown_input', true); -select 'create view view_of_grown_input_2 as ' - || pg_get_viewdef('view_of_grown_input', true) \gexec -select pg_get_viewdef('view_of_grown_input', true) - = pg_get_viewdef('view_of_grown_input_2', true) as round_trips; -select * from view_of_grown_input; -select * from view_of_grown_input_2; - -drop view view_of_grown_input_2; - --- A grown column that is dropped again still takes up its place in the --- positional alias list, as a dropped column; a column grown after it must --- not slide into that place. -alter table tblfc drop column val; -alter table tblfc add column val int; - -select pg_get_viewdef('view_of_grown_input', true); -select 'create view view_of_grown_input_2 as ' - || pg_get_viewdef('view_of_grown_input', true) \gexec -select pg_get_viewdef('view_of_grown_input', true) - = pg_get_viewdef('view_of_grown_input_2', true) as round_trips; -select * from view_of_grown_input; -select * from view_of_grown_input_2; - -drop view view_of_grown_input_2, view_of_grown_input; -drop function tblfc_f(); -drop table tblfc, tblfr; - -- Test view decompilation in the face of column addition/deletion/renaming create table tt2 (a int, b int, c int); diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql index b7915bd8239..0ff8b378e3c 100644 --- a/src/test/regress/sql/rpr_base.sql +++ b/src/test/regress/sql/rpr_base.sql @@ -2939,48 +2939,6 @@ DROP VIEW rpr_res_nat_rt, rpr_res_nat_v; DROP VIEW rpr_res_alias_rt, rpr_res_alias_v; DROP TABLE rpr_res_ja, rpr_res_jb, rpr_res_m1, rpr_res_m2, rpr_res_ordj; --- A column a function's result type grows after the view is made is one the --- deparser does not see at all: expandRTE() stops at the column count the --- query was parsed with. A column it cannot see is one it cannot rename out --- of the way, and the name it collides with here is the one a DEFINE clause --- has to resolve to as printed. So the grown columns are looked up and named --- too, and printed in full, the alias list being positional. -CREATE TABLE rpr_res_fn (id INT, val INT); -INSERT INTO rpr_res_fn VALUES (1, 1), (2, 2), (3, 3); -CREATE TABLE rpr_res_cfg (a INT); -INSERT INTO rpr_res_cfg VALUES (1); -CREATE FUNCTION rpr_res_fcfg() RETURNS SETOF rpr_res_cfg LANGUAGE sql - AS $$ SELECT * FROM rpr_res_cfg $$; - -CREATE VIEW rpr_res_fn_v AS -SELECT rpr_res_fn.id, count(*) OVER w AS cnt -FROM rpr_res_fn, rpr_res_fcfg() f -WINDOW w AS (ORDER BY rpr_res_fn.id - ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING - PATTERN (A+) - DEFINE A AS val > 0); - --- nothing to keep off yet -SELECT pg_get_viewdef('rpr_res_fn_v'::regclass, true); - -ALTER TABLE rpr_res_cfg ADD COLUMN val INT; - -SELECT pg_get_viewdef('rpr_res_fn_v'::regclass, true); -SELECT 'CREATE VIEW rpr_res_fn_rt AS ' - || pg_get_viewdef('rpr_res_fn_v'::regclass, true) \gexec -SELECT pg_get_viewdef('rpr_res_fn_v'::regclass, true) - = pg_get_viewdef('rpr_res_fn_rt'::regclass, true) AS round_trips; -SELECT * FROM rpr_res_fn_v; -SELECT * FROM rpr_res_fn_rt; - --- a grown column that collides with nothing is still on the list -ALTER TABLE rpr_res_cfg ADD COLUMN spare INT; -SELECT pg_get_viewdef('rpr_res_fn_v'::regclass, true); - -DROP VIEW rpr_res_fn_rt, rpr_res_fn_v; -DROP FUNCTION rpr_res_fcfg(); -DROP TABLE rpr_res_fn, rpr_res_cfg; - -- A system column is named from the catalog, not from the deparser's own -- choice, so there is no alias to pick for it and nothing to exempt from -- renaming. Its name still has to be held against the rest of the query, or a -- 2.54.0 (Apple Git-157)