From 3a0060e61ce1000da062ddb055eb0f74955f1ed3 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Thu, 21 Aug 2025 18:48:20 +0900 Subject: [PATCH v2 2/2] Fix const-simplification for index expressions and predicate As with constraint expressions and statistics expressions, index expressions and predicate also suffer from the issue where NullTest clauses may not be reduced correctly during const-simplification. Because we need to cache the const-simplified index expressions and predicate in the relcache entry, we cannot fix the Vars before applying eval_const_expressions. To ensure proper reduction of NullTest clauses, this patch runs eval_const_expressions a second time -- after the Vars have been fixed and with a valid root. Bug: #19007 Reported-by: Bryan Fox Author: Richard Guo Discussion: https://postgr.es/m/19007-4cc6e252ed8aa54a@postgresql.org --- src/backend/optimizer/util/plancat.c | 48 ++++++++++++++++++++----- src/test/regress/expected/join.out | 2 +- src/test/regress/expected/predicate.out | 24 +++++++++++++ src/test/regress/sql/join.sql | 2 +- src/test/regress/sql/predicate.sql | 13 +++++++ 5 files changed, 79 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 4536bdd6cb4..9330c4c6306 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -428,13 +428,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * modify the copies we obtain from the relcache to have the * correct varno for the parent relation, so that they match up * correctly against qual clauses. + * + * We need to run the index expressions and predicate through + * const-simplification again after fixing the Vars to have the + * correct varno, and with a valid "root". This ensures that + * NullTest quals for Vars can be properly reduced. */ info->indexprs = RelationGetIndexExpressions(indexRelation); info->indpred = RelationGetIndexPredicate(indexRelation); - if (info->indexprs && varno != 1) - ChangeVarNodes((Node *) info->indexprs, 1, varno, 0); - if (info->indpred && varno != 1) - ChangeVarNodes((Node *) info->indpred, 1, varno, 0); + if (info->indexprs) + { + if (varno != 1) + ChangeVarNodes((Node *) info->indexprs, 1, varno, 0); + + info->indexprs = (List *) + eval_const_expressions(root, (Node *) info->indexprs); + } + if (info->indpred) + { + if (varno != 1) + ChangeVarNodes((Node *) info->indpred, 1, varno, 0); + + info->indpred = (List *) + eval_const_expressions(root, + (Node *) make_ands_explicit(info->indpred)); + info->indpred = make_ands_implicit((Expr *) info->indpred); + } /* Build targetlist using the completed indexprs data */ info->indextlist = build_index_tlist(root, info, relation); @@ -966,8 +985,13 @@ infer_arbiter_indexes(PlannerInfo *root) /* Expression attributes (if any) must match */ idxExprs = RelationGetIndexExpressions(idxRel); - if (idxExprs && varno != 1) - ChangeVarNodes((Node *) idxExprs, 1, varno, 0); + if (idxExprs) + { + if (varno != 1) + ChangeVarNodes((Node *) idxExprs, 1, varno, 0); + + idxExprs = (List *) eval_const_expressions(root, (Node *) idxExprs); + } foreach(el, onconflict->arbiterElems) { @@ -1020,8 +1044,16 @@ infer_arbiter_indexes(PlannerInfo *root) * CONFLICT's WHERE clause. */ predExprs = RelationGetIndexPredicate(idxRel); - if (predExprs && varno != 1) - ChangeVarNodes((Node *) predExprs, 1, varno, 0); + if (predExprs) + { + if (varno != 1) + ChangeVarNodes((Node *) predExprs, 1, varno, 0); + + predExprs = (List *) + eval_const_expressions(root, + (Node *) make_ands_explicit(predExprs)); + predExprs = make_ands_implicit((Expr *) predExprs); + } if (!predicate_implied_by(predExprs, (List *) onconflict->arbiterWhere, false)) goto next; diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 98b05c94a11..90c8fbdb4f7 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -9355,7 +9355,7 @@ left join j2 on j1.id1 = j2.id1 where j1.id2 = 1; Output: j2.id1, j2.id2 (8 rows) -create unique index j1_id2_idx on j1(id2) where id2 is not null; +create unique index j1_id2_idx on j1(id2) where id2 > 0; -- ensure we don't use a partial unique index as unique proofs explain (verbose, costs off) select * from j1 diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out index 1aff0b59ff8..c22016af171 100644 --- a/src/test/regress/expected/predicate.out +++ b/src/test/regress/expected/predicate.out @@ -436,3 +436,27 @@ SELECT * FROM pred_tab2, pred_tab1 WHERE pred_tab1.a IS NULL OR pred_tab1.b < 2; RESET constraint_exclusion; DROP TABLE pred_tab1; DROP TABLE pred_tab2; +-- Validate that NullTest quals in index predicate are reduced correctly +CREATE TABLE pred_tab (a int, b int NOT NULL, c int NOT NULL); +INSERT INTO pred_tab SELECT i, i, i FROM generate_series(1, 1000) i; +CREATE INDEX ON pred_tab (a) WHERE b IS NOT NULL AND c IS NOT NULL; +ANALYZE pred_tab; +-- Ensure that the partial index is usable +EXPLAIN (COSTS OFF) +SELECT * FROM pred_tab WHERE a < 5 AND b IS NOT NULL AND c IS NOT NULL; + QUERY PLAN +--------------------------------------------- + Index Scan using pred_tab_a_idx on pred_tab + Index Cond: (a < 5) +(2 rows) + +SELECT * FROM pred_tab WHERE a < 5 AND b IS NOT NULL AND c IS NOT NULL; + a | b | c +---+---+--- + 1 | 1 | 1 + 2 | 2 | 2 + 3 | 3 | 3 + 4 | 4 | 4 +(4 rows) + +DROP TABLE pred_tab; diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 5f0a475894d..0adb58455c8 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -3509,7 +3509,7 @@ explain (verbose, costs off) select * from j1 left join j2 on j1.id1 = j2.id1 where j1.id2 = 1; -create unique index j1_id2_idx on j1(id2) where id2 is not null; +create unique index j1_id2_idx on j1(id2) where id2 > 0; -- ensure we don't use a partial unique index as unique proofs explain (verbose, costs off) diff --git a/src/test/regress/sql/predicate.sql b/src/test/regress/sql/predicate.sql index 32302d60b6d..4b776c6d09d 100644 --- a/src/test/regress/sql/predicate.sql +++ b/src/test/regress/sql/predicate.sql @@ -221,3 +221,16 @@ SELECT * FROM pred_tab2, pred_tab1 WHERE pred_tab1.a IS NULL OR pred_tab1.b < 2; RESET constraint_exclusion; DROP TABLE pred_tab1; DROP TABLE pred_tab2; + +-- Validate that NullTest quals in index predicate are reduced correctly +CREATE TABLE pred_tab (a int, b int NOT NULL, c int NOT NULL); +INSERT INTO pred_tab SELECT i, i, i FROM generate_series(1, 1000) i; +CREATE INDEX ON pred_tab (a) WHERE b IS NOT NULL AND c IS NOT NULL; +ANALYZE pred_tab; + +-- Ensure that the partial index is usable +EXPLAIN (COSTS OFF) +SELECT * FROM pred_tab WHERE a < 5 AND b IS NOT NULL AND c IS NOT NULL; +SELECT * FROM pred_tab WHERE a < 5 AND b IS NOT NULL AND c IS NOT NULL; + +DROP TABLE pred_tab; -- 2.43.0