diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 9573a9bb0e6..e686a6c199d 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2505,8 +2505,14 @@ _outValue(StringInfo str, const Value *value) appendStringInfoString(str, value->val.str); break; case T_String: + + /* + * We use _outToken to provide escaping of the string's content, + * but we don't want it to do anything with an empty string. + */ appendStringInfoChar(str, '"'); - _outToken(str, value->val.str); + if (value->val.str[0] != '\0') + _outToken(str, value->val.str); appendStringInfoChar(str, '"'); break; case T_BitString: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 0781ac826b3..7237e5de839 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3086,7 +3086,16 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte, i = 0; foreach(lc, rte->eref->colnames) { - real_colnames[i] = strVal(lfirst(lc)); + /* + * If the column name shown in eref is an empty string, then it's + * a column that was dropped at the time of parsing the query, so + * treat it as dropped. + */ + char *cname = strVal(lfirst(lc)); + + if (cname[0] == '\0') + cname = NULL; + real_colnames[i] = cname; i++; } } diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index 06b203793a3..e2d42766759 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -1332,6 +1332,58 @@ select pg_get_viewdef('vv6', true); JOIN tt13 USING (z); (1 row) +-- +-- Check some cases involving dropped columns in a function's rowtype result +-- +create table tt14t (f1 text, f2 text, f3 text, f4 text); +insert into tt14t values('foo', 'bar', 'baz', 'quux'); +alter table tt14t drop column f2; +create function tt14f() returns setof tt14t as +$$ +declare + rec1 record; +begin + for rec1 in select * from tt14t + loop + return next rec1; + end loop; +end; +$$ +language plpgsql; +create view tt14v as select t.* from tt14f() t; +select pg_get_viewdef('tt14v', true); + pg_get_viewdef +-------------------------------- + SELECT t.f1, + + t.f3, + + t.f4 + + FROM tt14f() t(f1, f3, f4); +(1 row) + +select * from tt14v; + f1 | f3 | f4 +-----+-----+------ + foo | baz | quux +(1 row) + +-- this perhaps should be rejected, but it isn't: +alter table tt14t drop column f3; +-- f3 is still in the view but will read as nulls +select pg_get_viewdef('tt14v', true); + pg_get_viewdef +-------------------------------- + SELECT t.f1, + + t.f3, + + t.f4 + + FROM tt14f() t(f1, f3, f4); +(1 row) + +select * from tt14v; + f1 | f3 | f4 +-----+----+------ + foo | | quux +(1 row) + -- clean up all the random objects we made above set client_min_messages = warning; DROP SCHEMA temp_view_test CASCADE; diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index e09bc1a2799..d3b3f128bb3 100644 --- a/src/test/regress/sql/create_view.sql +++ b/src/test/regress/sql/create_view.sql @@ -435,6 +435,40 @@ alter table tt11 add column z int; select pg_get_viewdef('vv6', true); +-- +-- Check some cases involving dropped columns in a function's rowtype result +-- + +create table tt14t (f1 text, f2 text, f3 text, f4 text); +insert into tt14t values('foo', 'bar', 'baz', 'quux'); + +alter table tt14t drop column f2; + +create function tt14f() returns setof tt14t as +$$ +declare + rec1 record; +begin + for rec1 in select * from tt14t + loop + return next rec1; + end loop; +end; +$$ +language plpgsql; + +create view tt14v as select t.* from tt14f() t; + +select pg_get_viewdef('tt14v', true); +select * from tt14v; + +-- this perhaps should be rejected, but it isn't: +alter table tt14t drop column f3; + +-- f3 is still in the view but will read as nulls +select pg_get_viewdef('tt14v', true); +select * from tt14v; + -- clean up all the random objects we made above set client_min_messages = warning; DROP SCHEMA temp_view_test CASCADE;