From 34138a8e754234bfb691d340393fe3b39d2a9c2b Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Wed, 22 Jul 2026 23:44:22 +0200 Subject: [PATCH v9 12/21] Add assert to prevent "partial" filters When adding a path (add_path or add_partial_path), make sure none of the expected filters overlaps with the relids of the relation. Such filters are invalid, we can't apply them incrementally. We must get all build relids at once. Fix a bug in compute_join_expected_filters(), violating this. It failed to reject filters matching only some of the build relids. --- src/backend/optimizer/path/joinpath.c | 9 ++++ src/backend/optimizer/util/pathnode.c | 42 ++++++++++++++++++ src/test/regress/expected/join.out | 64 +++++++++++++++------------ 3 files changed, 86 insertions(+), 29 deletions(-) diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index b661a5b800f..40f762ccc19 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -1088,6 +1088,15 @@ compute_join_expected_filters(PlannerInfo *root, goto contradiction; } } + else if (bms_overlap(f->build_relids, join_relids)) + { + /* + * We have some relids needed to build the filter, but not all + * of them. We can't apply filters incrementally, either we + * have all the relids in other_relids, or none of them. + */ + goto contradiction; + } else { /* diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 26fecc89cd9..2e7c9c8bb18 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -613,6 +613,27 @@ add_path(RelOptInfo *parent_rel, Path *new_path) */ CHECK_FOR_INTERRUPTS(); + /* + * If the new path expects filters, none of the filters can intersect + * with the rel. Relids of the relation must have no overlap with the + * build relids of the filter. + * + * XXX Do it here, because all paths have to go either through add_path + * or add_partial_path. But maybe there's a better place. + */ +#if USE_ASSERT_CHECKING + { + ListCell *lc; + + foreach (lc, new_path->expected_filters) + { + ExpectedFilter *f = (ExpectedFilter *) lfirst(lc); + + Assert(!bms_overlap(f->build_relids, parent_rel->relids)); + } + } +#endif + /* Pretend parameterized paths have no pathkeys, per comment above */ new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys; @@ -974,6 +995,27 @@ add_partial_path(RelOptInfo *parent_rel, Path *new_path) /* Relation should be OK for parallelism, too. */ Assert(parent_rel->consider_parallel); + /* + * If the new path expects filters, none of the filters can intersect + * with the rel. Relids of the relation must have no overlap with the + * build relids of the filter. + * + * XXX Do it here, because all paths have to go either through add_path + * or add_partial_path. But maybe there's a better place. + */ +#if USE_ASSERT_CHECKING + { + ListCell *lc; + + foreach (lc, new_path->expected_filters) + { + ExpectedFilter *f = (ExpectedFilter *) lfirst(lc); + + Assert(!bms_overlap(f->build_relids, parent_rel->relids)); + } + } +#endif + /* * As in add_path, throw out any paths which are dominated by the new * path, but throw out the new path if some existing path dominates it. diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 00fb9efc69d..d7b8412320d 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -4110,38 +4110,44 @@ select ss1.d1 from on i8.q1 = i4.f1 on t1.tenthous = ss1.d1 where t1.unique1 < i4.f1; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop - Output: (64)::information_schema.cardinal_number - -> Seq Scan on public.tenk1 t3 - Output: t3.unique1, t3.unique2, t3.two, t3.four, t3.ten, t3.twenty, t3.hundred, t3.thousand, t3.twothousand, t3.fivethous, t3.tenthous, t3.odd, t3.even, t3.stringu1, t3.stringu2, t3.string4 - Filter: (t3.fivethous < 0) - -> Nested Loop - Output: t1.tenthous, t2.ten + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Hash Join + Output: ((64)::information_schema.cardinal_number) + Hash Cond: (t2.ten = t1.tenthous) + -> Seq Scan on public.tenk1 t2 + Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4 + Bloom Filter 1: keys=(t2.ten) + -> Hash + Output: t1.tenthous, ((64)::information_schema.cardinal_number) + Bloom Filter 1 -> Nested Loop - Output: t1.tenthous, t2.ten, i4.f1 - Join Filter: (t1.unique1 < i4.f1) + Output: t1.tenthous, (64)::information_schema.cardinal_number + Join Filter: (t1.tenthous = ((64)::information_schema.cardinal_number)::integer) + -> Seq Scan on public.tenk1 t3 + Output: t3.unique1, t3.unique2, t3.two, t3.four, t3.ten, t3.twenty, t3.hundred, t3.thousand, t3.twothousand, t3.fivethous, t3.tenthous, t3.odd, t3.even, t3.stringu1, t3.stringu2, t3.string4 + Filter: (t3.fivethous < 0) -> Nested Loop - Output: t1.tenthous, t1.unique1, t2.ten - Join Filter: (t1.tenthous = ((64)::information_schema.cardinal_number)::integer) + Output: t1.tenthous -> Nested Loop - Output: t2.ten - Join Filter: (((64)::information_schema.cardinal_number)::integer = t2.ten) - -> Result - Output: ((abs(t3.unique1))::double precision + random()) - -> Seq Scan on public.tenk1 t2 - Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4 - -> Index Scan using tenk1_thous_tenthous on public.tenk1 t1 - Output: t1.unique1, t1.unique2, t1.two, t1.four, t1.ten, t1.twenty, t1.hundred, t1.thousand, t1.twothousand, t1.fivethous, t1.tenthous, t1.odd, t1.even, t1.stringu1, t1.stringu2, t1.string4 - Index Cond: (t1.tenthous = t2.ten) - -> Seq Scan on public.int4_tbl i4 - Output: i4.f1 - Filter: (i4.f1 = ((64)::information_schema.cardinal_number)::integer) - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - Filter: (i8.q1 = ((64)::information_schema.cardinal_number)::integer) -(29 rows) + Output: t1.tenthous, i4.f1 + Join Filter: (t1.unique1 < i4.f1) + -> Nested Loop + Output: t1.tenthous, t1.unique1 + -> Subquery Scan on ss0 + Output: ss0.x, (64)::information_schema.cardinal_number + -> Result + Output: ((abs(t3.unique1))::double precision + random()) + -> Index Scan using tenk1_thous_tenthous on public.tenk1 t1 + Output: t1.unique1, t1.unique2, t1.two, t1.four, t1.ten, t1.twenty, t1.hundred, t1.thousand, t1.twothousand, t1.fivethous, t1.tenthous, t1.odd, t1.even, t1.stringu1, t1.stringu2, t1.string4 + Index Cond: (t1.tenthous = (((64)::information_schema.cardinal_number))::integer) + -> Seq Scan on public.int4_tbl i4 + Output: i4.f1 + Filter: (i4.f1 = ((64)::information_schema.cardinal_number)::integer) + -> Seq Scan on public.int8_tbl i8 + Output: i8.q1, i8.q2 + Filter: (i8.q1 = ((64)::information_schema.cardinal_number)::integer) +(35 rows) select ss1.d1 from tenk1 as t1 -- 2.50.1 (Apple Git-155)