Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a7b9653

Browse files
committed
Better fix for permissions tests in excluded subqueries.
This reverts the code changes in 50c1374, which turned out to induce crashes and not completely fix the problem anyway. That commit only considered single subqueries that were excluded by constraint-exclusion logic, but actually the problem also exists for subqueries that are appendrel members (ie part of a UNION ALL list). In such cases we can't add a dummy subpath to the appendrel's AppendPath list without defeating the logic that recognizes when an appendrel is completely excluded. Instead, fix the problem by having setrefs.c scan the rangetable an extra time looking for subqueries that didn't get into the plan tree. (This approach depends on the 9.2 change that made set_subquery_pathlist generate dummy paths for excluded single subqueries, so that the exclusion behavior is the same for single subqueries and appendrel members.) Note: it turns out that the appendrel form of the missed-permissions-checks bug exists as far back as 8.4. However, since the practical effect of that bug seems pretty minimal, consensus is to not attempt to fix it in the back branches, at least not yet. Possibly we could back-port this patch once it's gotten a reasonable amount of testing in HEAD. For the moment I'm just going to revert the previous patch in 9.2.
1 parent 4b06c18 commit a7b9653

File tree

5 files changed

+233
-85
lines changed

5 files changed

+233
-85
lines changed

src/backend/optimizer/path/allpaths.c

+1-3
Original file line numberDiff line numberDiff line change
@@ -1159,9 +1159,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
11591159
/*
11601160
* It's possible that constraint exclusion proved the subquery empty. If
11611161
* so, it's convenient to turn it back into a dummy path so that we will
1162-
* recognize appropriate optimizations at this query level. (But see
1163-
* create_append_plan in createplan.c, which has to reverse this
1164-
* substitution.)
1162+
* recognize appropriate optimizations at this query level.
11651163
*/
11661164
if (is_dummy_plan(rel->subplan))
11671165
{

src/backend/optimizer/plan/createplan.c

+7-26
Original file line numberDiff line numberDiff line change
@@ -678,8 +678,7 @@ static Plan *
678678
create_append_plan(PlannerInfo *root, AppendPath *best_path)
679679
{
680680
Append *plan;
681-
RelOptInfo *rel = best_path->path.parent;
682-
List *tlist = build_relation_tlist(rel);
681+
List *tlist = build_relation_tlist(best_path->path.parent);
683682
List *subplans = NIL;
684683
ListCell *subpaths;
685684

@@ -694,30 +693,12 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
694693
*/
695694
if (best_path->subpaths == NIL)
696695
{
697-
/*
698-
* If this is a dummy path for a subquery, we have to wrap the
699-
* subquery's original plan in a SubqueryScan so that setrefs.c will
700-
* do the right things. (In particular, it must pull up the
701-
* subquery's rangetable so that the executor will apply permissions
702-
* checks to those rels at runtime.)
703-
*/
704-
if (rel->rtekind == RTE_SUBQUERY)
705-
{
706-
Assert(is_dummy_plan(rel->subplan));
707-
return (Plan *) make_subqueryscan(tlist,
708-
NIL,
709-
rel->relid,
710-
rel->subplan);
711-
}
712-
else
713-
{
714-
/* Generate a Result plan with constant-FALSE gating qual */
715-
return (Plan *) make_result(root,
716-
tlist,
717-
(Node *) list_make1(makeBoolConst(false,
718-
false)),
719-
NULL);
720-
}
696+
/* Generate a Result plan with constant-FALSE gating qual */
697+
return (Plan *) make_result(root,
698+
tlist,
699+
(Node *) list_make1(makeBoolConst(false,
700+
false)),
701+
NULL);
721702
}
722703

723704
/* Build the plan for each child */

src/backend/optimizer/plan/setrefs.c

+195-56
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "nodes/nodeFuncs.h"
2222
#include "optimizer/pathnode.h"
2323
#include "optimizer/planmain.h"
24+
#include "optimizer/planner.h"
2425
#include "optimizer/tlist.h"
2526
#include "tcop/utility.h"
2627
#include "utils/lsyscache.h"
@@ -81,6 +82,10 @@ typedef struct
8182
#define fix_scan_list(root, lst, rtoffset) \
8283
((List *) fix_scan_expr(root, (Node *) (lst), rtoffset))
8384

85+
static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
86+
static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
87+
static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
88+
static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
8489
static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
8590
static Plan *set_indexonlyscan_references(PlannerInfo *root,
8691
IndexOnlyScan *plan,
@@ -196,63 +201,11 @@ set_plan_references(PlannerInfo *root, Plan *plan)
196201
ListCell *lc;
197202

198203
/*
199-
* In the flat rangetable, we zero out substructure pointers that are not
200-
* needed by the executor; this reduces the storage space and copying cost
201-
* for cached plans. We keep only the alias and eref Alias fields, which
202-
* are needed by EXPLAIN, and the selectedCols and modifiedCols bitmaps,
203-
* which are needed for executor-startup permissions checking and for
204-
* trigger event checking.
204+
* Add all the query's RTEs to the flattened rangetable. The live ones
205+
* will have their rangetable indexes increased by rtoffset. (Additional
206+
* RTEs, not referenced by the Plan tree, might get added after those.)
205207
*/
206-
foreach(lc, root->parse->rtable)
207-
{
208-
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
209-
RangeTblEntry *newrte;
210-
211-
/* flat copy to duplicate all the scalar fields */
212-
newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
213-
memcpy(newrte, rte, sizeof(RangeTblEntry));
214-
215-
/* zap unneeded sub-structure */
216-
newrte->subquery = NULL;
217-
newrte->joinaliasvars = NIL;
218-
newrte->funcexpr = NULL;
219-
newrte->funccoltypes = NIL;
220-
newrte->funccoltypmods = NIL;
221-
newrte->funccolcollations = NIL;
222-
newrte->values_lists = NIL;
223-
newrte->values_collations = NIL;
224-
newrte->ctecoltypes = NIL;
225-
newrte->ctecoltypmods = NIL;
226-
newrte->ctecolcollations = NIL;
227-
228-
glob->finalrtable = lappend(glob->finalrtable, newrte);
229-
230-
/*
231-
* If it's a plain relation RTE, add the table to relationOids.
232-
*
233-
* We do this even though the RTE might be unreferenced in the plan
234-
* tree; this would correspond to cases such as views that were
235-
* expanded, child tables that were eliminated by constraint
236-
* exclusion, etc. Schema invalidation on such a rel must still force
237-
* rebuilding of the plan.
238-
*
239-
* Note we don't bother to avoid duplicate list entries. We could,
240-
* but it would probably cost more cycles than it would save.
241-
*/
242-
if (newrte->rtekind == RTE_RELATION)
243-
glob->relationOids = lappend_oid(glob->relationOids,
244-
newrte->relid);
245-
}
246-
247-
/*
248-
* Check for RT index overflow; it's very unlikely, but if it did happen,
249-
* the executor would get confused by varnos that match the special varno
250-
* values.
251-
*/
252-
if (IS_SPECIAL_VARNO(list_length(glob->finalrtable)))
253-
ereport(ERROR,
254-
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
255-
errmsg("too many range table entries")));
208+
add_rtes_to_flat_rtable(root, false);
256209

257210
/*
258211
* Adjust RT indexes of PlanRowMarks and add to final rowmarks list
@@ -279,6 +232,192 @@ set_plan_references(PlannerInfo *root, Plan *plan)
279232
return set_plan_refs(root, plan, rtoffset);
280233
}
281234

235+
/*
236+
* Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
237+
*
238+
* This can recurse into subquery plans; "recursing" is true if so.
239+
*/
240+
static void
241+
add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
242+
{
243+
PlannerGlobal *glob = root->glob;
244+
Index rti;
245+
ListCell *lc;
246+
247+
/*
248+
* Add the query's own RTEs to the flattened rangetable.
249+
*
250+
* At top level, we must add all RTEs so that their indexes in the
251+
* flattened rangetable match up with their original indexes. When
252+
* recursing, we only care about extracting relation RTEs.
253+
*/
254+
foreach(lc, root->parse->rtable)
255+
{
256+
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
257+
258+
if (!recursing || rte->rtekind == RTE_RELATION)
259+
add_rte_to_flat_rtable(glob, rte);
260+
}
261+
262+
/*
263+
* If there are any dead subqueries, they are not referenced in the Plan
264+
* tree, so we must add RTEs contained in them to the flattened rtable
265+
* separately. (If we failed to do this, the executor would not perform
266+
* expected permission checks for tables mentioned in such subqueries.)
267+
*
268+
* Note: this pass over the rangetable can't be combined with the previous
269+
* one, because that would mess up the numbering of the live RTEs in the
270+
* flattened rangetable.
271+
*/
272+
rti = 1;
273+
foreach(lc, root->parse->rtable)
274+
{
275+
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
276+
277+
/*
278+
* We should ignore inheritance-parent RTEs: their contents have been
279+
* pulled up into our rangetable already. Also ignore any subquery
280+
* RTEs without matching RelOptInfos, as they likewise have been
281+
* pulled up.
282+
*/
283+
if (rte->rtekind == RTE_SUBQUERY && !rte->inh)
284+
{
285+
RelOptInfo *rel = root->simple_rel_array[rti];
286+
287+
if (rel != NULL)
288+
{
289+
Assert(rel->relid == rti); /* sanity check on array */
290+
291+
/*
292+
* The subquery might never have been planned at all, if it
293+
* was excluded on the basis of self-contradictory constraints
294+
* in our query level. In this case apply
295+
* flatten_unplanned_rtes.
296+
*
297+
* If it was planned but the plan is dummy, we assume that it
298+
* has been omitted from our plan tree (see
299+
* set_subquery_pathlist), and recurse to pull up its RTEs.
300+
*
301+
* Otherwise, it should be represented by a SubqueryScan node
302+
* somewhere in our plan tree, and we'll pull up its RTEs when
303+
* we process that plan node.
304+
*
305+
* However, if we're recursing, then we should pull up RTEs
306+
* whether the subplan is dummy or not, because we've found
307+
* that some upper query level is treating this one as dummy,
308+
* and so we won't scan this level's plan tree at all.
309+
*/
310+
if (rel->subplan == NULL)
311+
flatten_unplanned_rtes(glob, rte);
312+
else if (recursing || is_dummy_plan(rel->subplan))
313+
{
314+
Assert(rel->subroot != NULL);
315+
add_rtes_to_flat_rtable(rel->subroot, true);
316+
}
317+
}
318+
}
319+
rti++;
320+
}
321+
}
322+
323+
/*
324+
* Extract RangeTblEntries from a subquery that was never planned at all
325+
*/
326+
static void
327+
flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
328+
{
329+
/* Use query_tree_walker to find all RTEs in the parse tree */
330+
(void) query_tree_walker(rte->subquery,
331+
flatten_rtes_walker,
332+
(void *) glob,
333+
QTW_EXAMINE_RTES);
334+
}
335+
336+
static bool
337+
flatten_rtes_walker(Node *node, PlannerGlobal *glob)
338+
{
339+
if (node == NULL)
340+
return false;
341+
if (IsA(node, RangeTblEntry))
342+
{
343+
RangeTblEntry *rte = (RangeTblEntry *) node;
344+
345+
/* As above, we need only save relation RTEs */
346+
if (rte->rtekind == RTE_RELATION)
347+
add_rte_to_flat_rtable(glob, rte);
348+
return false;
349+
}
350+
if (IsA(node, Query))
351+
{
352+
/* Recurse into subselects */
353+
return query_tree_walker((Query *) node,
354+
flatten_rtes_walker,
355+
(void *) glob,
356+
QTW_EXAMINE_RTES);
357+
}
358+
return expression_tree_walker(node, flatten_rtes_walker,
359+
(void *) glob);
360+
}
361+
362+
/*
363+
* Add (a copy of) the given RTE to the final rangetable
364+
*
365+
* In the flat rangetable, we zero out substructure pointers that are not
366+
* needed by the executor; this reduces the storage space and copying cost
367+
* for cached plans. We keep only the alias and eref Alias fields, which
368+
* are needed by EXPLAIN, and the selectedCols and modifiedCols bitmaps,
369+
* which are needed for executor-startup permissions checking and for
370+
* trigger event checking.
371+
*/
372+
static void
373+
add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
374+
{
375+
RangeTblEntry *newrte;
376+
377+
/* flat copy to duplicate all the scalar fields */
378+
newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
379+
memcpy(newrte, rte, sizeof(RangeTblEntry));
380+
381+
/* zap unneeded sub-structure */
382+
newrte->subquery = NULL;
383+
newrte->joinaliasvars = NIL;
384+
newrte->funcexpr = NULL;
385+
newrte->funccoltypes = NIL;
386+
newrte->funccoltypmods = NIL;
387+
newrte->funccolcollations = NIL;
388+
newrte->values_lists = NIL;
389+
newrte->values_collations = NIL;
390+
newrte->ctecoltypes = NIL;
391+
newrte->ctecoltypmods = NIL;
392+
newrte->ctecolcollations = NIL;
393+
394+
glob->finalrtable = lappend(glob->finalrtable, newrte);
395+
396+
/*
397+
* Check for RT index overflow; it's very unlikely, but if it did happen,
398+
* the executor would get confused by varnos that match the special varno
399+
* values.
400+
*/
401+
if (IS_SPECIAL_VARNO(list_length(glob->finalrtable)))
402+
ereport(ERROR,
403+
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
404+
errmsg("too many range table entries")));
405+
406+
/*
407+
* If it's a plain relation RTE, add the table to relationOids.
408+
*
409+
* We do this even though the RTE might be unreferenced in the plan tree;
410+
* this would correspond to cases such as views that were expanded, child
411+
* tables that were eliminated by constraint exclusion, etc. Schema
412+
* invalidation on such a rel must still force rebuilding of the plan.
413+
*
414+
* Note we don't bother to avoid making duplicate list entries. We could,
415+
* but it would probably cost more cycles than it would save.
416+
*/
417+
if (newrte->rtekind == RTE_RELATION)
418+
glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
419+
}
420+
282421
/*
283422
* set_plan_refs: recurse through the Plan nodes of a single subquery level
284423
*/

src/test/regress/expected/privileges.out

+15
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,21 @@ SELECT * FROM atestv3; -- ok
228228

229229
SELECT * FROM atestv0; -- fail
230230
ERROR: permission denied for relation atestv0
231+
-- Appendrels excluded by constraints failed to check permissions in 8.4-9.2.
232+
select * from
233+
((select a.q1 as x from int8_tbl a offset 0)
234+
union all
235+
(select b.q2 as x from int8_tbl b offset 0)) ss
236+
where false;
237+
ERROR: permission denied for relation int8_tbl
238+
set constraint_exclusion = on;
239+
select * from
240+
((select a.q1 as x, random() from int8_tbl a where q1 > 0)
241+
union all
242+
(select b.q2 as x, random() from int8_tbl b where q2 > 0)) ss
243+
where x < 0;
244+
ERROR: permission denied for relation int8_tbl
245+
reset constraint_exclusion;
231246
CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view
232247
SELECT * FROM atestv4; -- ok
233248
one | two | three

src/test/regress/sql/privileges.sql

+15
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,21 @@ SELECT * FROM atestv2; -- fail
162162
SELECT * FROM atestv3; -- ok
163163
SELECT * FROM atestv0; -- fail
164164

165+
-- Appendrels excluded by constraints failed to check permissions in 8.4-9.2.
166+
select * from
167+
((select a.q1 as x from int8_tbl a offset 0)
168+
union all
169+
(select b.q2 as x from int8_tbl b offset 0)) ss
170+
where false;
171+
172+
set constraint_exclusion = on;
173+
select * from
174+
((select a.q1 as x, random() from int8_tbl a where q1 > 0)
175+
union all
176+
(select b.q2 as x, random() from int8_tbl b where q2 > 0)) ss
177+
where x < 0;
178+
reset constraint_exclusion;
179+
165180
CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view
166181
SELECT * FROM atestv4; -- ok
167182
GRANT SELECT ON atestv4 TO regressuser2;

0 commit comments

Comments
 (0)