From dc4afaeeb7e2df8ace968038fa40836a80fb015f Mon Sep 17 00:00:00 2001 From: Tender Wang Date: Wed, 11 Nov 2026 10:20:27 +0800 Subject: [PATCH] Skip SJE if jointree containing VOLATILE function --- src/backend/optimizer/plan/analyzejoins.c | 9 +++++-- src/test/regress/expected/join.out | 29 +++++++++++++++++++++++ src/test/regress/sql/join.sql | 19 +++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 25a863efbde..b198d8f72f0 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -2144,9 +2144,14 @@ self_join_candidates_cmp(const void *a, const void *b) bool remove_useless_self_joins(PlannerInfo *root, List *joinlist) { - /* Skip if SJE is disabled, or if the joinlist has less than 2 members. */ + /* + * Skip if SJE is disabled, if the joinlist has less than 2 members, or if + * eliminating a relation could change the number or timing of volatile + * function evaluations. + */ if (!enable_self_join_elimination || joinlist == NIL || - (list_length(joinlist) == 1 && !IsA(linitial(joinlist), List))) + (list_length(joinlist) == 1 && !IsA(linitial(joinlist), List)) || + contain_volatile_functions((Node *) root->parse->jointree)) return false; /* Try to merge pairs of self-joined relations. */ diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 7773852e61b..314636903bf 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -13073,3 +13073,32 @@ WHERE s.x = s.c; (1 row) ROLLBACK; +-- Self-join elimination must preserve separately observable evaluations of +-- volatile quals, even when Var replacement makes the quals identical. +BEGIN; +CREATE TEMP TABLE sje_volatile (id int PRIMARY KEY); +CREATE TEMP SEQUENCE sje_volatile_seq; +INSERT INTO sje_volatile VALUES (1); +SELECT setval('sje_volatile_seq', 1, false); + setval +-------- + 1 +(1 row) + +SELECT count(*) +FROM sje_volatile a +JOIN sje_volatile b ON a.id = b.id +WHERE (nextval('sje_volatile_seq') + a.id) % 2 = 0 + AND (nextval('sje_volatile_seq') + b.id) % 2 = 0; + count +------- + 0 +(1 row) + +SELECT last_value FROM sje_volatile_seq; + last_value +------------ + 2 +(1 row) + +ROLLBACK; diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index a49d343ec32..efae6809099 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -4978,3 +4978,22 @@ FROM (VALUES (1)) AS v(z) LEFT JOIN WHERE s.x = s.c; ROLLBACK; + +-- Self-join elimination must preserve separately observable evaluations of +-- volatile quals, even when Var replacement makes the quals identical. +BEGIN; + +CREATE TEMP TABLE sje_volatile (id int PRIMARY KEY); +CREATE TEMP SEQUENCE sje_volatile_seq; +INSERT INTO sje_volatile VALUES (1); + +SELECT setval('sje_volatile_seq', 1, false); +SELECT count(*) +FROM sje_volatile a +JOIN sje_volatile b ON a.id = b.id +WHERE (nextval('sje_volatile_seq') + a.id) % 2 = 0 + AND (nextval('sje_volatile_seq') + b.id) % 2 = 0; + +SELECT last_value FROM sje_volatile_seq; + +ROLLBACK; -- 2.43.0