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

Commit 1d33858

Browse files
committed
Fix handling of targetlist SRFs when scan/join relation is known empty.
When we introduced separate ProjectSetPath nodes for application of set-returning functions in v10, we inadvertently broke some cases where we're supposed to recognize that the result of a subquery is known to be empty (contain zero rows). That's because IS_DUMMY_REL was just looking for a childless AppendPath without allowing for a ProjectSetPath being possibly stuck on top. In itself, this didn't do anything much worse than produce slightly worse plans for some corner cases. Then in v11, commit 11cf92f rearranged things to allow the scan/join targetlist to be applied directly to partial paths before they get gathered. But it inserted a short-circuit path for dummy relations that was a little too short: it failed to insert a ProjectSetPath node at all for a targetlist containing set-returning functions, resulting in bogus "set-valued function called in context that cannot accept a set" errors, as reported in bug #15669 from Madelaine Thibaut. The best way to fix this mess seems to be to reimplement IS_DUMMY_REL so that it drills down through any ProjectSetPath nodes that might be there (and it seems like we'd better allow for ProjectionPath as well). While we're at it, make it look at rel->pathlist not cheapest_total_path, so that it gives the right answer independently of whether set_cheapest has been done lately. That dependency looks pretty shaky in the context of code like apply_scanjoin_target_to_paths, and even if it's not broken today it'd certainly bite us at some point. (Nastily, unsafe use of the old coding would almost always work; the hazard comes down to possibly looking through a dangling pointer, and only once in a blue moon would you find something there that resulted in the wrong answer.) It now looks like it was a mistake for IS_DUMMY_REL to be a macro: if there are any extensions using it, they'll continue to use the old inadequate logic until they're recompiled, after which they'll fail to load into server versions predating this fix. Hopefully there are few such extensions. Having fixed IS_DUMMY_REL, the special path for dummy rels in apply_scanjoin_target_to_paths is unnecessary as well as being wrong, so we can just drop it. Also change a few places that were testing for partitioned-ness of a planner relation but not using IS_PARTITIONED_REL for the purpose; that seems unsafe as well as inconsistent, plus it required an ugly hack in apply_scanjoin_target_to_paths. In passing, save a few cycles in apply_scanjoin_target_to_paths by skipping processing of pre-existing paths for partitioned rels, and do some cosmetic cleanup and comment adjustment in that function. I renamed IS_DUMMY_PATH to IS_DUMMY_APPEND with the intention of breaking any code that might be using it, since in almost every case that would be wrong; IS_DUMMY_REL is what to be using instead. In HEAD, also make set_dummy_rel_pathlist static (since it's no longer used from outside allpaths.c), and delete is_dummy_plan, since it's no longer used anywhere. Back-patch as appropriate into v11 and v10. Tom Lane and Julien Rouhaud Discussion: https://postgr.es/m/15669-02fb3296cca26203@postgresql.org
1 parent 898e5e3 commit 1d33858

File tree

9 files changed

+182
-139
lines changed

9 files changed

+182
-139
lines changed

src/backend/optimizer/path/allpaths.c

+14-10
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
105105
Relids required_outer);
106106
static void accumulate_append_subpath(Path *path,
107107
List **subpaths, List **special_subpaths);
108+
static void set_dummy_rel_pathlist(RelOptInfo *rel);
108109
static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
109110
Index rti, RangeTblEntry *rte);
110111
static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -1557,7 +1558,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
15571558
* Consider an append of unordered, unparameterized partial paths. Make
15581559
* it parallel-aware if possible.
15591560
*/
1560-
if (partial_subpaths_valid)
1561+
if (partial_subpaths_valid && partial_subpaths != NIL)
15611562
{
15621563
AppendPath *appendpath;
15631564
ListCell *lc;
@@ -1954,11 +1955,13 @@ accumulate_append_subpath(Path *path, List **subpaths, List **special_subpaths)
19541955
* Build a dummy path for a relation that's been excluded by constraints
19551956
*
19561957
* Rather than inventing a special "dummy" path type, we represent this as an
1957-
* AppendPath with no members (see also IS_DUMMY_PATH/IS_DUMMY_REL macros).
1958+
* AppendPath with no members (see also IS_DUMMY_APPEND/IS_DUMMY_REL macros).
19581959
*
1959-
* This is exported because inheritance_planner() has need for it.
1960+
* (See also mark_dummy_rel, which does basically the same thing, but is
1961+
* typically used to change a rel into dummy state after we already made
1962+
* paths for it.)
19601963
*/
1961-
void
1964+
static void
19621965
set_dummy_rel_pathlist(RelOptInfo *rel)
19631966
{
19641967
/* Set dummy size estimates --- we leave attr_widths[] as zeroes */
@@ -1969,14 +1972,15 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
19691972
rel->pathlist = NIL;
19701973
rel->partial_pathlist = NIL;
19711974

1975+
/* Set up the dummy path */
19721976
add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL, NULL,
19731977
0, false, NIL, -1));
19741978

19751979
/*
1976-
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
1977-
* will recognize the relation as dummy if anyone asks. This is redundant
1978-
* when we're called from set_rel_size(), but not when called from
1979-
* elsewhere, and doing it twice is harmless anyway.
1980+
* We set the cheapest-path fields immediately, just in case they were
1981+
* pointing at some discarded path. This is redundant when we're called
1982+
* from set_rel_size(), but not when called from elsewhere, and doing it
1983+
* twice is harmless anyway.
19801984
*/
19811985
set_cheapest(rel);
19821986
}
@@ -3532,12 +3536,12 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel)
35323536
/* Add partitionwise join paths for partitioned child-joins. */
35333537
generate_partitionwise_join_paths(root, child_rel);
35343538

3539+
set_cheapest(child_rel);
3540+
35353541
/* Dummy children will not be scanned, so ignore those. */
35363542
if (IS_DUMMY_REL(child_rel))
35373543
continue;
35383544

3539-
set_cheapest(child_rel);
3540-
35413545
#ifdef OPTIMIZER_DEBUG
35423546
debug_print_rel(root, child_rel);
35433547
#endif

src/backend/optimizer/path/joinrels.c

+30-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ static void make_rels_by_clauseless_joins(PlannerInfo *root,
3434
ListCell *other_rels);
3535
static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
3636
static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
37-
static bool is_dummy_rel(RelOptInfo *rel);
3837
static bool restriction_is_constant_false(List *restrictlist,
3938
RelOptInfo *joinrel,
4039
bool only_pushed_down);
@@ -1196,10 +1195,38 @@ have_dangerous_phv(PlannerInfo *root,
11961195
/*
11971196
* is_dummy_rel --- has relation been proven empty?
11981197
*/
1199-
static bool
1198+
bool
12001199
is_dummy_rel(RelOptInfo *rel)
12011200
{
1202-
return IS_DUMMY_REL(rel);
1201+
Path *path;
1202+
1203+
/*
1204+
* A rel that is known dummy will have just one path that is a childless
1205+
* Append. (Even if somehow it has more paths, a childless Append will
1206+
* have cost zero and hence should be at the front of the pathlist.)
1207+
*/
1208+
if (rel->pathlist == NIL)
1209+
return false;
1210+
path = (Path *) linitial(rel->pathlist);
1211+
1212+
/*
1213+
* Initially, a dummy path will just be a childless Append. But in later
1214+
* planning stages we might stick a ProjectSetPath and/or ProjectionPath
1215+
* on top, since Append can't project. Rather than make assumptions about
1216+
* which combinations can occur, just descend through whatever we find.
1217+
*/
1218+
for (;;)
1219+
{
1220+
if (IsA(path, ProjectionPath))
1221+
path = ((ProjectionPath *) path)->subpath;
1222+
else if (IsA(path, ProjectSetPath))
1223+
path = ((ProjectSetPath *) path)->subpath;
1224+
else
1225+
break;
1226+
}
1227+
if (IS_DUMMY_APPEND(path))
1228+
return true;
1229+
return false;
12031230
}
12041231

12051232
/*

src/backend/optimizer/plan/createplan.c

+5-6
Original file line numberDiff line numberDiff line change
@@ -1072,7 +1072,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
10721072
*
10731073
* Note that an AppendPath with no members is also generated in certain
10741074
* cases where there was no appending construct at all, but we know the
1075-
* relation is empty (see set_dummy_rel_pathlist).
1075+
* relation is empty (see set_dummy_rel_pathlist and mark_dummy_rel).
10761076
*/
10771077
if (best_path->subpaths == NIL)
10781078
{
@@ -6585,12 +6585,11 @@ is_projection_capable_path(Path *path)
65856585
case T_Append:
65866586

65876587
/*
6588-
* Append can't project, but if it's being used to represent a
6589-
* dummy path, claim that it can project. This prevents us from
6590-
* converting a rel from dummy to non-dummy status by applying a
6591-
* projection to its dummy path.
6588+
* Append can't project, but if an AppendPath is being used to
6589+
* represent a dummy path, what will actually be generated is a
6590+
* Result which can project.
65926591
*/
6593-
return IS_DUMMY_PATH(path);
6592+
return IS_DUMMY_APPEND(path);
65946593
case T_ProjectSet:
65956594

65966595
/*

0 commit comments

Comments
 (0)