Fix permission tests for views/tables proven empty by constraint exclusion.

A view defined as "select <something> where false" had the curious property
that the system wouldn't check whether users had the privileges necessary
to select from it.  More generally, permissions checks could be skipped
for tables referenced in sub-selects or views that were proven empty by
constraint exclusion (although some quick testing suggests this seldom
happens in cases of practical interest).  This happened because the planner
failed to include rangetable entries for such tables in the finished plan.

This was noticed in connection with erroneous handling of materialized
views, but actually the issue is quite unrelated to matviews.  Therefore,
revert commit 200ba1667b in favor of a more
direct test for the real problem.

Back-patch to 9.2 where the bug was introduced (by commit
7741dd6590).
This commit is contained in:
Tom Lane 2013-05-01 18:26:50 -04:00
parent 200ba1667b
commit 50c137487c
6 changed files with 52 additions and 34 deletions

View File

@ -1159,7 +1159,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/*
* It's possible that constraint exclusion proved the subquery empty. If
* so, it's convenient to turn it back into a dummy path so that we will
* recognize appropriate optimizations at this level.
* recognize appropriate optimizations at this query level. (But see
* create_append_plan in createplan.c, which has to reverse this
* substitution.)
*/
if (is_dummy_plan(rel->subplan))
{

View File

@ -678,30 +678,49 @@ static Plan *
create_append_plan(PlannerInfo *root, AppendPath *best_path)
{
Append *plan;
List *tlist = build_relation_tlist(best_path->path.parent);
RelOptInfo *rel = best_path->path.parent;
List *tlist = build_relation_tlist(rel);
List *subplans = NIL;
ListCell *subpaths;
/*
* It is possible for the subplans list to contain only one entry, or even
* no entries. Handle these cases specially.
* The subpaths list could be empty, if every child was proven empty by
* constraint exclusion. In that case generate a dummy plan that returns
* no rows.
*
* XXX ideally, if there's just one entry, we'd not bother to generate an
* Append node but just return the single child. At the moment this does
* not work because the varno of the child scan plan won't match the
* parent-rel Vars it'll be asked to emit.
* Note that an AppendPath with no members is also generated in certain
* cases where there was no appending construct at all, but we know the
* relation is empty (see set_dummy_rel_pathlist).
*/
if (best_path->subpaths == NIL)
{
/* Generate a Result plan with constant-FALSE gating qual */
return (Plan *) make_result(root,
tlist,
(Node *) list_make1(makeBoolConst(false,
false)),
NULL);
/*
* If this is a dummy path for a subquery, we have to wrap the
* subquery's original plan in a SubqueryScan so that setrefs.c will
* do the right things. (In particular, it must pull up the
* subquery's rangetable so that the executor will apply permissions
* checks to those rels at runtime.)
*/
if (rel->rtekind == RTE_SUBQUERY)
{
Assert(is_dummy_plan(rel->subplan));
return (Plan *) make_subqueryscan(tlist,
NIL,
rel->relid,
rel->subplan);
}
else
{
/* Generate a Result plan with constant-FALSE gating qual */
return (Plan *) make_result(root,
tlist,
(Node *) list_make1(makeBoolConst(false,
false)),
NULL);
}
}
/* Normal case with multiple subpaths */
/* Build the plan for each child */
foreach(subpaths, best_path->subpaths)
{
Path *subpath = (Path *) lfirst(subpaths);
@ -709,6 +728,13 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
subplans = lappend(subplans, create_plan_recurse(root, subpath));
}
/*
* XXX ideally, if there's just one child, we'd not bother to generate an
* Append node but just return the single child. At the moment this does
* not work because the varno of the child scan plan won't match the
* parent-rel Vars it'll be asked to emit.
*/
plan = make_append(subplans, tlist);
return (Plan *) plan;

View File

@ -418,15 +418,3 @@ NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to view v_test2
drop cascades to materialized view mv_test2
drop cascades to materialized view mv_test3
-- test a corner case for "with no data" versus a query which yields no rows
CREATE MATERIALIZED VIEW matview_unit_false AS SELECT false WHERE false WITH NO DATA;
SELECT * FROM matview_unit_false;
ERROR: materialized view "matview_unit_false" has not been populated
HINT: Use the REFRESH MATERIALIZED VIEW command.
REFRESH MATERIALIZED VIEW matview_unit_false;
SELECT * FROM matview_unit_false;
bool
------
(0 rows)
DROP MATERIALIZED VIEW matview_unit_false;

View File

@ -198,6 +198,8 @@ CREATE VIEW atestv1 AS SELECT * FROM atest1; -- ok
/* The next *should* fail, but it's not implemented that way yet. */
CREATE VIEW atestv2 AS SELECT * FROM atest2;
CREATE VIEW atestv3 AS SELECT * FROM atest3; -- ok
/* Empty view is a corner case that failed in 9.2. */
CREATE VIEW atestv0 AS SELECT 0 as x WHERE false; -- ok
SELECT * FROM atestv1; -- ok
a | b
---+-----
@ -224,6 +226,8 @@ SELECT * FROM atestv3; -- ok
-----+-----+-------
(0 rows)
SELECT * FROM atestv0; -- fail
ERROR: permission denied for relation atestv0
CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view
SELECT * FROM atestv4; -- ok
one | two | three
@ -1386,6 +1390,7 @@ drop table dep_priv_test;
drop sequence x_seq;
DROP FUNCTION testfunc2(int);
DROP FUNCTION testfunc4(boolean);
DROP VIEW atestv0;
DROP VIEW atestv1;
DROP VIEW atestv2;
-- this should cascade to drop atestv4

View File

@ -129,10 +129,3 @@ CREATE MATERIALIZED VIEW mv_test3 AS SELECT * FROM mv_test2 WHERE moo = 12345;
SELECT pg_relation_is_scannable('mv_test3'::regclass);
DROP VIEW v_test1 CASCADE;
-- test a corner case for "with no data" versus a query which yields no rows
CREATE MATERIALIZED VIEW matview_unit_false AS SELECT false WHERE false WITH NO DATA;
SELECT * FROM matview_unit_false;
REFRESH MATERIALIZED VIEW matview_unit_false;
SELECT * FROM matview_unit_false;
DROP MATERIALIZED VIEW matview_unit_false;

View File

@ -147,6 +147,8 @@ CREATE VIEW atestv1 AS SELECT * FROM atest1; -- ok
/* The next *should* fail, but it's not implemented that way yet. */
CREATE VIEW atestv2 AS SELECT * FROM atest2;
CREATE VIEW atestv3 AS SELECT * FROM atest3; -- ok
/* Empty view is a corner case that failed in 9.2. */
CREATE VIEW atestv0 AS SELECT 0 as x WHERE false; -- ok
SELECT * FROM atestv1; -- ok
SELECT * FROM atestv2; -- fail
@ -158,6 +160,7 @@ SET SESSION AUTHORIZATION regressuser4;
SELECT * FROM atestv1; -- ok
SELECT * FROM atestv2; -- fail
SELECT * FROM atestv3; -- ok
SELECT * FROM atestv0; -- fail
CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view
SELECT * FROM atestv4; -- ok
@ -828,6 +831,7 @@ drop sequence x_seq;
DROP FUNCTION testfunc2(int);
DROP FUNCTION testfunc4(boolean);
DROP VIEW atestv0;
DROP VIEW atestv1;
DROP VIEW atestv2;
-- this should cascade to drop atestv4