From 09fd03378d926ec6b899a234287113aa6eb6271e Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Wed, 22 Jul 2026 23:44:22 +0200
Subject: [PATCH v7 12/12] 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 +++++++++++++++++++++++++++
 2 files changed, 51 insertions(+)

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.
-- 
2.55.0

