From a9c2bc9af186f2d8febfc9cba2adc6cff5b3e91d Mon Sep 17 00:00:00 2001 From: Kamil Monicz Date: Sat, 6 Dec 2025 22:03:07 +0000 Subject: [PATCH] Fix hint matching for outer joins --- expected/ut-J.out | 100 ++++++++++++++++ expected/ut-R.out | 262 +++++++++++++++++++++++++++++++++++++++++ make_join_rel.c | 13 +- pg_hint_plan.c | 12 +- sql/ut-J.sql | 22 ++++ sql/ut-R.sql | 86 ++++++++++++++ update_copied_funcs.pl | 15 ++- 7 files changed, 504 insertions(+), 6 deletions(-) diff --git a/expected/ut-J.out b/expected/ut-J.out index add3ebf..7db550d 100644 --- a/expected/ut-J.out +++ b/expected/ut-J.out @@ -4775,3 +4775,103 @@ error hint: -> Seq Scan on t3 (11 rows) +---- +---- No. J-4-1 outer join support +---- +-- No. J-4-1-1 nested outer join as inner side of join +EXPLAIN (COSTS false) SELECT * FROM s1.t1 JOIN (s1.t2 LEFT JOIN s1.t3 ON t2.c1 = t3.c1) ON t1.c1 = t2.c1; + QUERY PLAN +------------------------------------------ + Merge Join + Merge Cond: (t1.c1 = t2.c1) + -> Index Scan using t1_i1 on t1 + -> Sort + Sort Key: t2.c1 + -> Hash Right Join + Hash Cond: (t3.c1 = t2.c1) + -> Seq Scan on t3 + -> Hash + -> Seq Scan on t2 +(10 rows) + +/*+Leading((t1 (t2 t3))) NestLoop(t1 t2 t3)*/ +EXPLAIN (COSTS false) SELECT * FROM s1.t1 JOIN (s1.t2 LEFT JOIN s1.t3 ON t2.c1 = t3.c1) ON t1.c1 = t2.c1; +LOG: pg_hint_plan: +used hint: +NestLoop(t1 t2 t3) +Leading((t1 (t2 t3))) +not used hint: +duplication hint: +error hint: + + QUERY PLAN +------------------------------------------ + Nested Loop + Join Filter: (t1.c1 = t2.c1) + -> Seq Scan on t1 + -> Materialize + -> Hash Left Join + Hash Cond: (t2.c1 = t3.c1) + -> Seq Scan on t2 + -> Hash + -> Seq Scan on t3 +(9 rows) + +-- No. J-4-1-2 SEMI join +EXPLAIN (COSTS false) SELECT * FROM s1.t1 WHERE EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); + QUERY PLAN +------------------------------------ + Merge Join + Merge Cond: (t1.c1 = t2.c1) + -> Index Scan using t1_i1 on t1 + -> Sort + Sort Key: t2.c1 + -> Seq Scan on t2 +(6 rows) + +/*+HashJoin(t1 t2)*/ +EXPLAIN (COSTS false) SELECT * FROM s1.t1 WHERE EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +LOG: pg_hint_plan: +used hint: +HashJoin(t1 t2) +not used hint: +duplication hint: +error hint: + + QUERY PLAN +------------------------------ + Hash Join + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 + -> Hash + -> Seq Scan on t2 +(5 rows) + +-- No. J-4-1-3 ANTI join +EXPLAIN (COSTS false) SELECT * FROM s1.t1 WHERE NOT EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); + QUERY PLAN +------------------------------ + Hash Anti Join + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 + -> Hash + -> Seq Scan on t2 +(5 rows) + +/*+NestLoop(t1 t2)*/ +EXPLAIN (COSTS false) SELECT * FROM s1.t1 WHERE NOT EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +LOG: pg_hint_plan: +used hint: +NestLoop(t1 t2) +not used hint: +duplication hint: +error hint: + + QUERY PLAN +----------------------------------------- + Nested Loop Anti Join + -> Seq Scan on t1 + -> Index Only Scan using t2_i1 on t2 + Index Cond: (c1 = t1.c1) +(4 rows) + diff --git a/expected/ut-R.out b/expected/ut-R.out index 5f708e4..554d3cd 100644 --- a/expected/ut-R.out +++ b/expected/ut-R.out @@ -4941,3 +4941,265 @@ error hint: -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) (6 rows) +SET client_min_messages TO LOG; +---- +---- No. R-4-1 outer join support +---- +-- No. R-4-1-1 LEFT JOIN +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1; +'); + explain_filter +---------------------------------------------------------------- + Hash Left Join (cost=xxx..xxx rows=1000 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(5 rows) + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1; +'); +LOG: pg_hint_plan: +used hint: +Rows(t1 t2 #1) +not used hint: +duplication hint: +error hint: + + explain_filter +---------------------------------------------------------------- + Hash Left Join (cost=xxx..xxx rows=1 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(5 rows) + +-- No. R-4-1-2 RIGHT JOIN +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 RIGHT JOIN s1.t2 ON t1.c1 = t2.c1; +'); + explain_filter +------------------------------------------------------------------------- + Merge Right Join (cost=xxx..xxx rows=100 width=xxx) + Merge Cond: (t1.c1 = t2.c1) + -> Index Scan using t1_i1 on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Sort (cost=xxx..xxx rows=100 width=xxx) + Sort Key: t2.c1 + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(6 rows) + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 RIGHT JOIN s1.t2 ON t1.c1 = t2.c1; +'); +LOG: pg_hint_plan: +used hint: +Rows(t1 t2 #1) +not used hint: +duplication hint: +error hint: + + explain_filter +------------------------------------------------------------------------- + Merge Right Join (cost=xxx..xxx rows=1 width=xxx) + Merge Cond: (t1.c1 = t2.c1) + -> Index Scan using t1_i1 on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Sort (cost=xxx..xxx rows=100 width=xxx) + Sort Key: t2.c1 + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(6 rows) + +-- No. R-4-1-3 FULL JOIN +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 FULL JOIN s1.t2 ON t1.c1 = t2.c1; +'); + explain_filter +---------------------------------------------------------------- + Hash Full Join (cost=xxx..xxx rows=1000 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(5 rows) + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 FULL JOIN s1.t2 ON t1.c1 = t2.c1; +'); +LOG: pg_hint_plan: +used hint: +Rows(t1 t2 #1) +not used hint: +duplication hint: +error hint: + + explain_filter +---------------------------------------------------------------- + Hash Full Join (cost=xxx..xxx rows=1 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(5 rows) + +-- No. R-4-1-4 nested outer joins +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1 LEFT JOIN s1.t3 ON t2.c1 = t3.c1; +'); + explain_filter +---------------------------------------------------------------------------- + Hash Left Join (cost=xxx..xxx rows=1000 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Hash Right Join (cost=xxx..xxx rows=100 width=xxx) + Hash Cond: (t3.c1 = t2.c1) + -> Seq Scan on t3 (cost=xxx..xxx rows=1130 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(9 rows) + +SELECT explain_filter(' +/*+Rows(t2 t3 #1)*/ +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1 LEFT JOIN s1.t3 ON t2.c1 = t3.c1; +'); +LOG: pg_hint_plan: +used hint: +Rows(t2 t3 #1) +not used hint: +duplication hint: +error hint: + + explain_filter +---------------------------------------------------------------------------- + Hash Left Join (cost=xxx..xxx rows=1000 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=1 width=xxx) + -> Hash Right Join (cost=xxx..xxx rows=1 width=xxx) + Hash Cond: (t3.c1 = t2.c1) + -> Seq Scan on t3 (cost=xxx..xxx rows=1130 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(9 rows) + +---- +---- No. R-4-2 SEMI/ANTI join support +---- +-- No. R-4-2-1 SEMI join +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 WHERE EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +'); + explain_filter +------------------------------------------------------------------------- + Merge Join (cost=xxx..xxx rows=100 width=xxx) + Merge Cond: (t1.c1 = t2.c1) + -> Index Scan using t1_i1 on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Sort (cost=xxx..xxx rows=100 width=xxx) + Sort Key: t2.c1 + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(6 rows) + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 WHERE EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +'); +LOG: pg_hint_plan: +used hint: +Rows(t1 t2 #1) +not used hint: +duplication hint: +error hint: + + explain_filter +------------------------------------------------------------------------- + Merge Join (cost=xxx..xxx rows=1 width=xxx) + Merge Cond: (t1.c1 = t2.c1) + -> Index Scan using t1_i1 on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Sort (cost=xxx..xxx rows=100 width=xxx) + Sort Key: t2.c1 + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(6 rows) + +-- No. R-4-2-2 ANTI join +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 WHERE NOT EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +'); + explain_filter +---------------------------------------------------------------- + Hash Anti Join (cost=xxx..xxx rows=900 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(5 rows) + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 WHERE NOT EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +'); +LOG: pg_hint_plan: +used hint: +Rows(t1 t2 #1) +not used hint: +duplication hint: +error hint: + + explain_filter +---------------------------------------------------------------- + Hash Anti Join (cost=xxx..xxx rows=1 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(5 rows) + +---- +---- No. R-4-3 negative tests +---- +-- No. R-4-3-1 hint references non-existent table +SELECT explain_filter(' +/*+Rows(t1 t99 #1)*/ +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1; +'); +LOG: pg_hint_plan: +used hint: +not used hint: +Rows(t1 t99 #1) +duplication hint: +error hint: + + explain_filter +---------------------------------------------------------------- + Hash Left Join (cost=xxx..xxx rows=1000 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(5 rows) + +-- No. R-4-3-2 hint references tables not in join +SELECT explain_filter(' +/*+Rows(t1 t3 #1)*/ +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1; +'); +LOG: pg_hint_plan: +used hint: +not used hint: +Rows(t1 t3 #1) +duplication hint: +error hint: + + explain_filter +---------------------------------------------------------------- + Hash Left Join (cost=xxx..xxx rows=1000 width=xxx) + Hash Cond: (t1.c1 = t2.c1) + -> Seq Scan on t1 (cost=xxx..xxx rows=1000 width=xxx) + -> Hash (cost=xxx..xxx rows=100 width=xxx) + -> Seq Scan on t2 (cost=xxx..xxx rows=100 width=xxx) +(5 rows) + diff --git a/make_join_rel.c b/make_join_rel.c index 9557041..08a1a80 100644 --- a/make_join_rel.c +++ b/make_join_rel.c @@ -130,6 +130,13 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) RowsHint *justforme = NULL; RowsHint *domultiply = NULL; RowsHint **rows_hints = (RowsHint **) get_current_hints(HINT_TYPE_ROWS); + Relids hint_joinrelids; + + /* + * joinrelids may include outer-join relids since PostgreSQL 16, + * so filter them out as hints can only handle base relations. + */ + hint_joinrelids = bms_intersect(joinrelids, root->all_baserels); /* Search for applicable rows hint for this join node */ for (i = 0; i < current_hint_state->num_hints[HINT_TYPE_ROWS]; i++) @@ -144,7 +151,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) rows_hint->base.state == HINT_STATE_ERROR) continue; - if (bms_equal(joinrelids, rows_hint->joinrelids)) + if (bms_equal(hint_joinrelids, rows_hint->joinrelids)) { /* * This joinrel is just the target of this rows_hint, so tweak @@ -154,7 +161,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) } else if (!(bms_is_subset(rows_hint->joinrelids, rel1->relids) || bms_is_subset(rows_hint->joinrelids, rel2->relids)) && - bms_is_subset(rows_hint->joinrelids, joinrelids) && + bms_is_subset(rows_hint->joinrelids, hint_joinrelids) && rows_hint->value_type == RVT_MULTI) { /* @@ -170,6 +177,8 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) } } + bms_free(hint_joinrelids); + if (justforme) { /* diff --git a/pg_hint_plan.c b/pg_hint_plan.c index 911ab8c..e6f76f4 100644 --- a/pg_hint_plan.c +++ b/pg_hint_plan.c @@ -4558,12 +4558,22 @@ add_paths_to_joinrel_wrapper(PlannerInfo *root, if (join_hint) { - if (bms_equal(join_hint->inner_joinrelids, innerrel->relids)) + Relids inner_relids; + + /* + * innerrel->relids may include outer-join relids since PostgreSQL + * 16, so filter them out for hint matching. + */ + inner_relids = bms_intersect(innerrel->relids, root->all_baserels); + + if (bms_equal(join_hint->inner_joinrelids, inner_relids)) set_join_config_options(join_hint->enforce_mask, false, current_hint_state->context); else set_join_config_options(DISABLE_ALL_JOIN, false, current_hint_state->context); + + bms_free(inner_relids); } if (memoize_hint) diff --git a/sql/ut-J.sql b/sql/ut-J.sql index 4c8b956..e54fa39 100644 --- a/sql/ut-J.sql +++ b/sql/ut-J.sql @@ -824,3 +824,25 @@ EXPLAIN (COSTS false) SELECT * FROM t1, t2, t3 WHERE t1.val = t2.val and t2.id = EXPLAIN (COSTS false) SELECT * FROM t1, t2, t3 WHERE t1.val = t2.val and t2.id = t3.id; -- doesn't work /*+ nomemoize(t1 t2 t3)*/ EXPLAIN (COSTS false) SELECT * FROM t1, t2, t3 WHERE t1.val = t2.val and t2.id = t3.id; + +---- +---- No. J-4-1 outer join support +---- + +-- No. J-4-1-1 nested outer join as inner side of join +EXPLAIN (COSTS false) SELECT * FROM s1.t1 JOIN (s1.t2 LEFT JOIN s1.t3 ON t2.c1 = t3.c1) ON t1.c1 = t2.c1; + +/*+Leading((t1 (t2 t3))) NestLoop(t1 t2 t3)*/ +EXPLAIN (COSTS false) SELECT * FROM s1.t1 JOIN (s1.t2 LEFT JOIN s1.t3 ON t2.c1 = t3.c1) ON t1.c1 = t2.c1; + +-- No. J-4-1-2 SEMI join +EXPLAIN (COSTS false) SELECT * FROM s1.t1 WHERE EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); + +/*+HashJoin(t1 t2)*/ +EXPLAIN (COSTS false) SELECT * FROM s1.t1 WHERE EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); + +-- No. J-4-1-3 ANTI join +EXPLAIN (COSTS false) SELECT * FROM s1.t1 WHERE NOT EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); + +/*+NestLoop(t1 t2)*/ +EXPLAIN (COSTS false) SELECT * FROM s1.t1 WHERE NOT EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); diff --git a/sql/ut-R.sql b/sql/ut-R.sql index 2b34592..5de1c15 100644 --- a/sql/ut-R.sql +++ b/sql/ut-R.sql @@ -1245,3 +1245,89 @@ SELECT explain_filter(' /*+Rows(t1 t2 +1)*/ EXPLAIN SELECT * FROM s1.t1, s1.t2 WHERE t1.c1 = t2.c1; '); + +SET client_min_messages TO LOG; + +---- +---- No. R-4-1 outer join support +---- + +-- No. R-4-1-1 LEFT JOIN +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1; +'); + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1; +'); + +-- No. R-4-1-2 RIGHT JOIN +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 RIGHT JOIN s1.t2 ON t1.c1 = t2.c1; +'); + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 RIGHT JOIN s1.t2 ON t1.c1 = t2.c1; +'); + +-- No. R-4-1-3 FULL JOIN +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 FULL JOIN s1.t2 ON t1.c1 = t2.c1; +'); + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 FULL JOIN s1.t2 ON t1.c1 = t2.c1; +'); + +-- No. R-4-1-4 nested outer joins +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1 LEFT JOIN s1.t3 ON t2.c1 = t3.c1; +'); + +SELECT explain_filter(' +/*+Rows(t2 t3 #1)*/ +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1 LEFT JOIN s1.t3 ON t2.c1 = t3.c1; +'); + +---- +---- No. R-4-2 SEMI/ANTI join support +---- + +-- No. R-4-2-1 SEMI join +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 WHERE EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +'); + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 WHERE EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +'); + +-- No. R-4-2-2 ANTI join +SELECT explain_filter(' +EXPLAIN SELECT * FROM s1.t1 WHERE NOT EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +'); + +SELECT explain_filter(' +/*+Rows(t1 t2 #1)*/ +EXPLAIN SELECT * FROM s1.t1 WHERE NOT EXISTS (SELECT 1 FROM s1.t2 WHERE t1.c1 = t2.c1); +'); + +---- +---- No. R-4-3 negative tests +---- + +-- No. R-4-3-1 hint references non-existent table +SELECT explain_filter(' +/*+Rows(t1 t99 #1)*/ +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1; +'); + +-- No. R-4-3-2 hint references tables not in join +SELECT explain_filter(' +/*+Rows(t1 t3 #1)*/ +EXPLAIN SELECT * FROM s1.t1 LEFT JOIN s1.t2 ON t1.c1 = t2.c1; +'); diff --git a/update_copied_funcs.pl b/update_copied_funcs.pl index a805285..445a1fc 100755 --- a/update_copied_funcs.pl +++ b/update_copied_funcs.pl @@ -244,7 +244,7 @@ sub patch_make_join_rel index 6e601a6c86e6..23f06be4e6d4 100644 --- b/make_join_rel.c +++ a/make_join_rel.c -@@ -123,6 +123,85 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) +@@ -123,6 +123,94 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) sjinfo, pushed_down_joins, &restrictlist); @@ -255,6 +255,13 @@ sub patch_make_join_rel + RowsHint *justforme = NULL; + RowsHint *domultiply = NULL; + RowsHint **rows_hints = (RowsHint **) get_current_hints(HINT_TYPE_ROWS); ++ Relids hint_joinrelids; ++ ++ /* ++ * joinrelids may include outer-join relids since PostgreSQL 16, ++ * so filter them out as hints can only handle base relations. ++ */ ++ hint_joinrelids = bms_intersect(joinrelids, root->all_baserels); + + /* Search for applicable rows hint for this join node */ + for (i = 0; i < current_hint_state->num_hints[HINT_TYPE_ROWS]; i++) @@ -269,7 +276,7 @@ sub patch_make_join_rel + rows_hint->base.state == HINT_STATE_ERROR) + continue; + -+ if (bms_equal(joinrelids, rows_hint->joinrelids)) ++ if (bms_equal(hint_joinrelids, rows_hint->joinrelids)) + { + /* + * This joinrel is just the target of this rows_hint, so tweak @@ -279,7 +286,7 @@ sub patch_make_join_rel + } + else if (!(bms_is_subset(rows_hint->joinrelids, rel1->relids) || + bms_is_subset(rows_hint->joinrelids, rel2->relids)) && -+ bms_is_subset(rows_hint->joinrelids, joinrelids) && ++ bms_is_subset(rows_hint->joinrelids, hint_joinrelids) && + rows_hint->value_type == RVT_MULTI) + { + /* @@ -295,6 +302,8 @@ sub patch_make_join_rel + } + } + ++ bms_free(hint_joinrelids); ++ + if (justforme) + { + /*