Skip to content

Commit 2c4debb

Browse files
committed
Make new expression eval code reject references to dropped columns.
Formerly, a Var referencing an already-dropped column was allowed and would always produce a NULL value. However, that behavior was implemented in slot_getattr which the new expression code doesn't use; thus there is now a risk of returning theoretically-deleted data. We had regression test cases that purported to exercise this, but they failed to expose any problem, apparently because plpgsql filters the dropped column and produces an output tuple that has a NULL there already. Ideally the DROP or ALTER attempt in these test cases would get rejected due to dependency checks; but until that happens, let's modify the behavior so that we fail the query during executor start. This was already true for the related case of a column having changed type underneath us, and there's no obvious reason why we need to be laxer for dropped columns. In passing, adjust the error messages in CheckVarSlotCompatibility to include the composite type name. In the cases shown in the regression tests this is always just "record", but it should be more useful in actual stale-plan cases, where the slot tupdesc would be a table's tupdesc directly. Discussion: https://postgr.es/m/16803.1490723570@sss.pgh.pa.us
1 parent ce96ce6 commit 2c4debb

File tree

5 files changed

+95
-47
lines changed

5 files changed

+95
-47
lines changed

src/backend/executor/execExprInterp.c

+23-22
Original file line numberDiff line numberDiff line change
@@ -1516,22 +1516,20 @@ CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype)
15161516
{
15171517
/*
15181518
* What we have to check for here is the possibility of an attribute
1519-
* having been changed in type since the plan tree was created. Ideally
1520-
* the plan will get invalidated and not re-used, but just in case, we
1521-
* keep these defenses. Fortunately it's sufficient to check once on the
1522-
* first time through.
1523-
*
1524-
* System attributes don't require checking since their types never
1525-
* change.
1526-
*
1527-
* Note: we allow a reference to a dropped attribute. slot_getattr will
1528-
* force a NULL result in such cases.
1519+
* having been dropped or changed in type since the plan tree was created.
1520+
* Ideally the plan will get invalidated and not re-used, but just in
1521+
* case, we keep these defenses. Fortunately it's sufficient to check
1522+
* once on the first time through.
15291523
*
15301524
* Note: ideally we'd check typmod as well as typid, but that seems
15311525
* impractical at the moment: in many cases the tupdesc will have been
15321526
* generated by ExecTypeFromTL(), and that can't guarantee to generate an
15331527
* accurate typmod in all cases, because some expression node types don't
1534-
* carry typmod.
1528+
* carry typmod. Fortunately, for precisely that reason, there should be
1529+
* no places with a critical dependency on the typmod of a value.
1530+
*
1531+
* System attributes don't require checking since their types never
1532+
* change.
15351533
*/
15361534
if (attnum > 0)
15371535
{
@@ -1544,17 +1542,20 @@ CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype)
15441542

15451543
attr = slot_tupdesc->attrs[attnum - 1];
15461544

1547-
/* can't check type if dropped, since atttypid is probably 0 */
1548-
if (!attr->attisdropped)
1549-
{
1550-
if (vartype != attr->atttypid)
1551-
ereport(ERROR,
1552-
(errcode(ERRCODE_DATATYPE_MISMATCH),
1553-
errmsg("attribute %d has wrong type", attnum),
1554-
errdetail("Table has type %s, but query expects %s.",
1555-
format_type_be(attr->atttypid),
1556-
format_type_be(vartype))));
1557-
}
1545+
if (attr->attisdropped)
1546+
ereport(ERROR,
1547+
(errcode(ERRCODE_UNDEFINED_COLUMN),
1548+
errmsg("attribute %d of type %s has been dropped",
1549+
attnum, format_type_be(slot_tupdesc->tdtypeid))));
1550+
1551+
if (vartype != attr->atttypid)
1552+
ereport(ERROR,
1553+
(errcode(ERRCODE_DATATYPE_MISMATCH),
1554+
errmsg("attribute %d of type %s has wrong type",
1555+
attnum, format_type_be(slot_tupdesc->tdtypeid)),
1556+
errdetail("Table has type %s, but query expects %s.",
1557+
format_type_be(attr->atttypid),
1558+
format_type_be(vartype))));
15581559
}
15591560
}
15601561

src/test/regress/expected/create_view.out

+38-9
Original file line numberDiff line numberDiff line change
@@ -1396,10 +1396,10 @@ select pg_get_viewdef('vv6', true);
13961396
(1 row)
13971397

13981398
--
1399-
-- Check some cases involving dropped columns in a function's rowtype result
1399+
-- Check cases involving dropped/altered columns in a function's rowtype result
14001400
--
14011401
create table tt14t (f1 text, f2 text, f3 text, f4 text);
1402-
insert into tt14t values('foo', 'bar', 'baz', 'quux');
1402+
insert into tt14t values('foo', 'bar', 'baz', '42');
14031403
alter table tt14t drop column f2;
14041404
create function tt14f() returns setof tt14t as
14051405
$$
@@ -1424,14 +1424,15 @@ select pg_get_viewdef('tt14v', true);
14241424
(1 row)
14251425

14261426
select * from tt14v;
1427-
f1 | f3 | f4
1428-
-----+-----+------
1429-
foo | baz | quux
1427+
f1 | f3 | f4
1428+
-----+-----+----
1429+
foo | baz | 42
14301430
(1 row)
14311431

1432+
begin;
14321433
-- this perhaps should be rejected, but it isn't:
14331434
alter table tt14t drop column f3;
1434-
-- f3 is still in the view but will read as nulls
1435+
-- f3 is still in the view ...
14351436
select pg_get_viewdef('tt14v', true);
14361437
pg_get_viewdef
14371438
--------------------------------
@@ -1441,12 +1442,40 @@ select pg_get_viewdef('tt14v', true);
14411442
FROM tt14f() t(f1, f3, f4);
14421443
(1 row)
14431444

1445+
-- but will fail at execution
1446+
select f1, f4 from tt14v;
1447+
f1 | f4
1448+
-----+----
1449+
foo | 42
1450+
(1 row)
1451+
14441452
select * from tt14v;
1445-
f1 | f3 | f4
1446-
-----+----+------
1447-
foo | | quux
1453+
ERROR: attribute 3 of type record has been dropped
1454+
rollback;
1455+
begin;
1456+
-- this perhaps should be rejected, but it isn't:
1457+
alter table tt14t alter column f4 type integer using f4::integer;
1458+
-- f4 is still in the view ...
1459+
select pg_get_viewdef('tt14v', true);
1460+
pg_get_viewdef
1461+
--------------------------------
1462+
SELECT t.f1, +
1463+
t.f3, +
1464+
t.f4 +
1465+
FROM tt14f() t(f1, f3, f4);
14481466
(1 row)
14491467

1468+
-- but will fail at execution
1469+
select f1, f3 from tt14v;
1470+
f1 | f3
1471+
-----+-----
1472+
foo | baz
1473+
(1 row)
1474+
1475+
select * from tt14v;
1476+
ERROR: attribute 4 of type record has wrong type
1477+
DETAIL: Table has type integer, but query expects text.
1478+
rollback;
14501479
-- check display of whole-row variables in some corner cases
14511480
create type nestedcomposite as (x int8_tbl);
14521481
create view tt15v as select row(i)::nestedcomposite from int8_tbl i;

src/test/regress/expected/rangefuncs.out

+8-11
Original file line numberDiff line numberDiff line change
@@ -1909,25 +1909,22 @@ select * from usersview;
19091909
id2 | 2 | email2 | 12 | t | 11 | 2
19101910
(2 rows)
19111911

1912-
alter table users drop column moredrop;
1913-
select * from usersview;
1914-
userid | seq | email | moredrop | enabled | generate_series | ordinality
1915-
--------+-----+--------+----------+---------+-----------------+------------
1916-
id | 1 | email | | t | 10 | 1
1917-
id2 | 2 | email2 | | t | 11 | 2
1918-
(2 rows)
1919-
19201912
alter table users add column junk text;
19211913
select * from usersview;
19221914
userid | seq | email | moredrop | enabled | generate_series | ordinality
19231915
--------+-----+--------+----------+---------+-----------------+------------
1924-
id | 1 | email | | t | 10 | 1
1925-
id2 | 2 | email2 | | t | 11 | 2
1916+
id | 1 | email | 11 | t | 10 | 1
1917+
id2 | 2 | email2 | 12 | t | 11 | 2
19261918
(2 rows)
19271919

1920+
begin;
1921+
alter table users drop column moredrop;
1922+
select * from usersview; -- expect clean failure
1923+
ERROR: attribute 5 of type record has been dropped
1924+
rollback;
19281925
alter table users alter column seq type numeric;
19291926
select * from usersview; -- expect clean failure
1930-
ERROR: attribute 2 has wrong type
1927+
ERROR: attribute 2 of type record has wrong type
19311928
DETAIL: Table has type numeric, but query expects integer.
19321929
drop view usersview;
19331930
drop function get_first_user();

src/test/regress/sql/create_view.sql

+22-3
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,11 @@ alter table tt11 add column z int;
457457
select pg_get_viewdef('vv6', true);
458458

459459
--
460-
-- Check some cases involving dropped columns in a function's rowtype result
460+
-- Check cases involving dropped/altered columns in a function's rowtype result
461461
--
462462

463463
create table tt14t (f1 text, f2 text, f3 text, f4 text);
464-
insert into tt14t values('foo', 'bar', 'baz', 'quux');
464+
insert into tt14t values('foo', 'bar', 'baz', '42');
465465

466466
alter table tt14t drop column f2;
467467

@@ -483,13 +483,32 @@ create view tt14v as select t.* from tt14f() t;
483483
select pg_get_viewdef('tt14v', true);
484484
select * from tt14v;
485485

486+
begin;
487+
486488
-- this perhaps should be rejected, but it isn't:
487489
alter table tt14t drop column f3;
488490

489-
-- f3 is still in the view but will read as nulls
491+
-- f3 is still in the view ...
490492
select pg_get_viewdef('tt14v', true);
493+
-- but will fail at execution
494+
select f1, f4 from tt14v;
491495
select * from tt14v;
492496

497+
rollback;
498+
499+
begin;
500+
501+
-- this perhaps should be rejected, but it isn't:
502+
alter table tt14t alter column f4 type integer using f4::integer;
503+
504+
-- f4 is still in the view ...
505+
select pg_get_viewdef('tt14v', true);
506+
-- but will fail at execution
507+
select f1, f3 from tt14v;
508+
select * from tt14v;
509+
510+
rollback;
511+
493512
-- check display of whole-row variables in some corner cases
494513

495514
create type nestedcomposite as (x int8_tbl);

src/test/regress/sql/rangefuncs.sql

+4-2
Original file line numberDiff line numberDiff line change
@@ -555,11 +555,13 @@ SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
555555
create temp view usersview as
556556
SELECT * FROM ROWS FROM(get_users(), generate_series(10,11)) WITH ORDINALITY;
557557

558-
select * from usersview;
559-
alter table users drop column moredrop;
560558
select * from usersview;
561559
alter table users add column junk text;
562560
select * from usersview;
561+
begin;
562+
alter table users drop column moredrop;
563+
select * from usersview; -- expect clean failure
564+
rollback;
563565
alter table users alter column seq type numeric;
564566
select * from usersview; -- expect clean failure
565567

0 commit comments

Comments
 (0)