пн, 16 дек. 2024 г. в 12:52, Andrei Lepikhov <lepi...@gmail.com>:
> On 12/8/24 06:13, Tom Lane wrote: > > Andres Freund <and...@anarazel.de> writes: > >> On 2024-12-07 17:06:52 -0500, Tom Lane wrote: > >>> One could imagine that we split up the join filter conditions into > >>> "depends on RHS" and "doesn't depend on RHS" subsets, and make the > >>> nestloop plan node evaluate the latter set only once per LHS row, > >>> and then skip the inner-side scan when that condition fails. > > > >> As I wrote in my other email, I'm also somewhat dubious it's worth > having > >> explicit code for this in nodeNestloop.c. > > > > Yeah. Your idea of pushing the "doesn't depend on RHS" subset into > > a one-time Result filter atop the RHS is interesting though. Then we > > don't need any new executor machinery, but we pay for that with a more > > complex planner patch. Not sure how hard that would be. > Well, I have been working on this topic for some time. Let me share some > experiences and thoughts. They may be helpful. > I had a user request on this feature, a kind of 'semi-pushdown' clause. > As production people said, it is quite typical in sales systems to have > a table of products and additional tables describing product categories. > Something like that: > > CREATE TABLE products ( > id int PRIMARY KEY, payload text DEFAULT 'product', > price real DEFAULT random(), type text); > CREATE TABLE vehicles (id int PRIMARY KEY, maxspeed int DEFAULT 250); > CREATE TABLE phones (id int PRIMARY KEY, screensize real DEFAULT 6.1); > > INSERT INTO products (id,type) > SELECT x,'v' FROM generate_series(1,5E4) AS x; > INSERT INTO products (id,type) > SELECT x,'p' FROM generate_series(1+5E4,1E5) AS x; > INSERT INTO vehicles (id) SELECT x FROM generate_series(1,5E4) AS x; > INSERT INTO phones (id) SELECT x FROM generate_series(1+5E4,1E5) AS x; > VACUUM ANALYZE products, vehicles, phones; > > They usually need to get top-sales products from different categories > querying database with something like that: > > EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF) > SELECT * FROM products p > LEFT JOIN phones ph ON (p.id = ph.id) > LEFT JOIN vehicles v ON (p.id = v.id) > WHERE p.price > 0.9; > > Users just want to optimise such queries and get description of > different products within a single query. The query they wish to execute > looks like this: > > EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF) > SELECT * FROM products p > LEFT JOIN phones ph ON (p.id = ph.id AND p.type = 'p') > LEFT JOIN vehicles v ON (p.id = v.id AND p.type = 'v') > WHERE p.price > 0.9; > > The request was: why not check the RHS-only clause inside a join node > and save cycles? > > To determine how difficult it could be, I wrote a prototype (very raw), > which shows how it works in action and how many subsystems we have to > touch. > Also, I wonder why you think it could work with NestLoop only. I think > avoiding touching a hash table and an index under MergeJoin can also be > beneficial. > > The patch and reproduction script with resulting EXPLAINs are in the > attachment. Petr Petrov is doing further work. He may provide additional > details, if any. > > > -- > regards, Andrei Lepikhov Hello! I would like to describe the main idea: 1) We would like to speed up Nested Loop Left Join execution since getting the next inner tuple could be expensive. Also, not all outer tuples should be matched. For example, we have 200k rows and inner tuples are extracted only for 100 of them. That could allow us to execute Nested Loop Left Join faster. 2) When we analyze RestrictInfo's clauses it is possible to detect whether it's sufficient to use outer tuple data to calculate them. If it is the case, then we save them in rhs_joinrinfo. In the current patch version that was done in create_nestloop_path(). Then in make_nestloop() rhs_joinqual clauses were removed from joinqual. Then execute rhs_joinqual in ExecNestLoop() before the next inner tuple extraction. If the result of their evaluation is false then we fill inner tuple's fields with nulls and return the result without visiting the inner relation. 3) I also propose to add a new type of filter: Outer Tuple Filter. It's a filter which is evaluated after getting the outer tuple but before extracting the inner tuple. Join filter is evaluated after grabbing the inner tuple, that's why I suggest to differentiate them. To calculate unmatched tuples by Outer Tuple Filter an additional counter was added to Instrumentation struct: int nunmatched. You can find the execution plan in reproduction.sql The patch was based on commit 3191eccd8a9bff1715f2e4fab86d2932a556185e At least, make check-world has passed. That's my first experience in making patches, so apologize if I misunderstand or explain something poorly. Looking forward to your questions and feedback. --- Best regards, Peter Petrov
reproduction.sql
Description: application/sql
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bf322198a20..fd145421a5b 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -2407,7 +2407,7 @@ SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 O --------------------------------------------------------------------------------------------------------------------------- Nested Loop Left Join Output: (13), ft2.c1 - Join Filter: (13 = ft2.c1) + Outer Tuple Filter: (13 = ft2.c1) -> Foreign Scan on public.ft2 Output: ft2.c1 Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index a201ed30824..3f6c3dddc41 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -2302,6 +2302,12 @@ ExplainNode(PlanState *planstate, List *ancestors, } break; case T_NestLoop: + show_upper_qual(((NestLoop *) plan)->join.rhs_joinqual, + "Outer Tuple Filter", planstate, ancestors, es); + if (((NestLoop *) plan)->join.rhs_joinqual) + show_instrumentation_count("Rows Unmatched by Outer Tuple Filter", 3, + planstate, es); + show_upper_qual(((NestLoop *) plan)->join.joinqual, "Join Filter", planstate, ancestors, es); if (((NestLoop *) plan)->join.joinqual) @@ -3957,6 +3963,8 @@ show_instrumentation_count(const char *qlabel, int which, if (which == 2) nfiltered = planstate->instrument->nfiltered2; + else if (which == 3) + nfiltered = planstate->instrument->nunmatched; else nfiltered = planstate->instrument->nfiltered1; nloops = planstate->instrument->nloops; diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c index 7f4bf6c4dbb..4a3d7fa0dd4 100644 --- a/src/backend/executor/nodeNestloop.c +++ b/src/backend/executor/nodeNestloop.c @@ -66,6 +66,7 @@ ExecNestLoop(PlanState *pstate) TupleTableSlot *outerTupleSlot; TupleTableSlot *innerTupleSlot; ExprState *joinqual; + ExprState *rhs_joinqual; ExprState *otherqual; ExprContext *econtext; ListCell *lc; @@ -79,6 +80,7 @@ ExecNestLoop(PlanState *pstate) nl = (NestLoop *) node->js.ps.plan; joinqual = node->js.joinqual; + rhs_joinqual = node->js.rhs_joinqual; otherqual = node->js.ps.qual; outerPlan = outerPlanState(node); innerPlan = innerPlanState(node); @@ -152,12 +154,23 @@ ExecNestLoop(PlanState *pstate) } /* - * we have an outerTuple, try to get the next inner tuple. + * we have an outerTuple, try to execute quals related to it. If the + * result is false then we won't get the next inner tuple. Otherwise, + * extract it. */ - ENL1_printf("getting new inner tuple"); + ENL1_printf("executing quals related to outer tuple if any"); - innerTupleSlot = ExecProcNode(innerPlan); - econtext->ecxt_innertuple = innerTupleSlot; + if (rhs_joinqual && !ExecQual(rhs_joinqual, econtext)) + { + InstrCountUnmatched(node, 1); + innerTupleSlot = NULL; + } + else + { + ENL1_printf("getting new inner tuple"); + innerTupleSlot = ExecProcNode(innerPlan); + econtext->ecxt_innertuple = innerTupleSlot; + } if (TupIsNull(innerTupleSlot)) { @@ -314,6 +327,8 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags) nlstate->js.jointype = node->join.jointype; nlstate->js.joinqual = ExecInitQual(node->join.joinqual, (PlanState *) nlstate); + nlstate->js.rhs_joinqual = + ExecInitQual(node->join.rhs_joinqual, (PlanState *) nlstate); /* * detect whether we need only consider the first matching inner tuple diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 178c572b021..51a1a29fd2b 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -232,7 +232,7 @@ static RecursiveUnion *make_recursive_union(List *tlist, static BitmapAnd *make_bitmap_and(List *bitmapplans); static BitmapOr *make_bitmap_or(List *bitmapplans); static NestLoop *make_nestloop(List *tlist, - List *joinclauses, List *otherclauses, List *nestParams, + List *joinclauses, List *rhs_joinclauses, List *otherclauses, List *nestParams, Plan *lefttree, Plan *righttree, JoinType jointype, bool inner_unique); static HashJoin *make_hashjoin(List *tlist, @@ -4352,7 +4352,9 @@ create_nestloop_plan(PlannerInfo *root, Plan *inner_plan; List *tlist = build_path_tlist(root, &best_path->jpath.path); List *joinrestrictclauses = best_path->jpath.joinrestrictinfo; + List *rhs_joinclauses = NIL; List *joinclauses; + List *rhs_otherclauses = NIL; List *otherclauses; Relids outerrelids; List *nestParams; @@ -4397,6 +4399,10 @@ create_nestloop_plan(PlannerInfo *root, extract_actual_join_clauses(joinrestrictclauses, best_path->jpath.path.parent->relids, &joinclauses, &otherclauses); + + extract_actual_join_clauses(best_path->jpath.rhs_joinrinfo, + best_path->jpath.path.parent->relids, + &rhs_joinclauses, &rhs_otherclauses); } else { @@ -4423,6 +4429,7 @@ create_nestloop_plan(PlannerInfo *root, join_plan = make_nestloop(tlist, joinclauses, + rhs_joinclauses, otherclauses, nestParams, outer_plan, @@ -6019,6 +6026,7 @@ make_bitmap_or(List *bitmapplans) static NestLoop * make_nestloop(List *tlist, List *joinclauses, + List *rhs_joinclauses, List *otherclauses, List *nestParams, Plan *lefttree, @@ -6036,6 +6044,24 @@ make_nestloop(List *tlist, node->join.jointype = jointype; node->join.inner_unique = inner_unique; node->join.joinqual = joinclauses; + + { + ListCell *lc; + + /* Remove quals from joinqual which belongs to outer relation */ + foreach(lc, node->join.joinqual) + { + Node *qual = (Node *) lfirst(lc); + + if (!list_member(rhs_joinclauses, qual)) + continue; + + node->join.joinqual = foreach_delete_current(node->join.joinqual, lc); + } + + } + + node->join.rhs_joinqual = rhs_joinclauses; node->nestParams = nestParams; return node; diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6d23df108da..2499dbfa94b 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -2292,6 +2292,16 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset) NRM_EQUAL, NUM_EXEC_QUAL((Plan *) join)); + /* Process rhs_joinqual as well */ + join->rhs_joinqual = fix_join_expr(root, + join->rhs_joinqual, + outer_itlist, + inner_itlist, + (Index) 0, + rtoffset, + NRM_EQUAL, + NUM_EXEC_QUAL((Plan *) join)); + /* Now do join-type-specific stuff */ if (IsA(join, NestLoop)) { diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index fc97bf6ee26..bfd5bd3df59 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -2603,6 +2603,14 @@ create_nestloop_path(PlannerInfo *root, pathnode->jpath.innerjoinpath = inner_path; pathnode->jpath.joinrestrictinfo = restrict_clauses; + /* If clause_relids belong to outerpath then add it to rhs_joinrinfo list */ + foreach_node(RestrictInfo, rinfo, restrict_clauses) + { + if (bms_is_subset(rinfo->clause_relids, outer_path->parent->relids)) + pathnode->jpath.rhs_joinrinfo = + lappend(pathnode->jpath.rhs_joinrinfo, rinfo); + } + final_cost_nestloop(root, pathnode, workspace, extra); return pathnode; diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h index bfd7b6d8445..c79f67f4d08 100644 --- a/src/include/executor/instrument.h +++ b/src/include/executor/instrument.h @@ -88,6 +88,8 @@ typedef struct Instrumentation double nloops; /* # of run cycles for this node */ double nfiltered1; /* # of tuples removed by scanqual or joinqual */ double nfiltered2; /* # of tuples removed by "other" quals */ + double nunmatched; /* # of tuples removed by quals related to + * only outer tuples */ BufferUsage bufusage; /* total buffer usage */ WalUsage walusage; /* total WAL usage */ } Instrumentation; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 7f71b7625df..815f7b3fd5b 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1237,6 +1237,11 @@ typedef struct PlanState if (((PlanState *)(node))->instrument) \ ((PlanState *)(node))->instrument->nfiltered2 += (delta); \ } while(0) +#define InstrCountUnmatched(node, delta) \ + do { \ + if (((PlanState *)(node))->instrument) \ + ((PlanState *)(node))->instrument->nunmatched += (delta); \ + } while(0) /* * EPQState is state for executing an EvalPlanQual recheck on a candidate @@ -2124,6 +2129,8 @@ typedef struct JoinState bool single_match; /* True if we should skip to next outer tuple * after finding one inner match */ ExprState *joinqual; /* JOIN quals (in addition to ps.qual) */ + ExprState *rhs_joinqual; /* JOIN quals which can be executed using only + * outer tuple */ } JoinState; /* ---------------- diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 0759e00e96d..dd84cd96815 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -2087,6 +2087,7 @@ typedef struct JoinPath Path *innerjoinpath; /* path for the inner side of the join */ List *joinrestrictinfo; /* RestrictInfos to apply to join */ + List *rhs_joinrinfo; /* Outer-side join clauses (filters) */ /* * See the notes for RelOptInfo and ParamPathInfo to understand why diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 52f29bcdb69..8f8030803a5 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -792,6 +792,8 @@ typedef struct Join JoinType jointype; bool inner_unique; List *joinqual; /* JOIN quals (in addition to plan.qual) */ + List *rhs_joinqual; /* JOIN quals which can be executed by using + * only outer tuple */ } Join; /* ---------------- diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 1904eb65bb9..b1ca1608666 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -2189,7 +2189,7 @@ SELECT count(*) FROM tenk1 LEFT JOIN tenk2 ON ------------------------------------------------------------------------------------ Aggregate -> Nested Loop Left Join - Join Filter: (tenk1.hundred = 42) + Outer Tuple Filter: (tenk1.hundred = 42) -> Index Only Scan using tenk1_hundred on tenk1 -> Memoize Cache Key: tenk1.hundred diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 55d44a9bcee..1b7d495abf9 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -2430,7 +2430,7 @@ where t4.f1 is null; -> Seq Scan on int4_tbl t2 -> Materialize -> Nested Loop Left Join - Join Filter: (t3.f1 > 1) + Outer Tuple Filter: (t3.f1 > 1) -> Seq Scan on int4_tbl t3 Filter: (f1 > 0) -> Materialize @@ -2458,9 +2458,9 @@ from int4_tbl t1 left join int4_tbl t2 on true -> Seq Scan on int4_tbl t1 -> Materialize -> Nested Loop Left Join - Join Filter: (t3.f1 > 0) + Outer Tuple Filter: (t3.f1 > 0) -> Nested Loop Left Join - Join Filter: (t2.f1 > 0) + Outer Tuple Filter: (t2.f1 > 0) -> Seq Scan on int4_tbl t2 -> Materialize -> Seq Scan on int4_tbl t3 @@ -2523,7 +2523,7 @@ select * from int4_tbl t1 QUERY PLAN ------------------------------------------------- Nested Loop Left Join - Join Filter: (t2.f1 = t3.f1) + Outer Tuple Filter: (t2.f1 = t3.f1) -> Nested Loop Left Join -> Nested Loop Left Join -> Seq Scan on int4_tbl t1 @@ -2561,12 +2561,12 @@ select * from int4_tbl t1 left join (int4_tbl t2 left join int4_tbl t3 on t2.f1 > 0) on t2.f1 > 1 left join int4_tbl t4 on t2.f1 > 2 and t3.f1 > 3 where t1.f1 = coalesce(t2.f1, 1); - QUERY PLAN ----------------------------------------------------- + QUERY PLAN +----------------------------------------------------- Nested Loop Left Join - Join Filter: ((t2.f1 > 2) AND (t3.f1 > 3)) + Outer Tuple Filter: ((t2.f1 > 2) AND (t3.f1 > 3)) -> Nested Loop Left Join - Join Filter: (t2.f1 > 0) + Outer Tuple Filter: (t2.f1 > 0) -> Nested Loop Left Join Filter: (t1.f1 = COALESCE(t2.f1, 1)) -> Seq Scan on int4_tbl t1 @@ -2590,9 +2590,9 @@ select * from int4_tbl t1 Hash Right Join Hash Cond: (t2.f1 = t1.f1) -> Nested Loop Left Join - Join Filter: (t2.f1 > 1) + Outer Tuple Filter: (t2.f1 > 1) -> Nested Loop Left Join - Join Filter: (t2.f1 > 0) + Outer Tuple Filter: (t2.f1 > 0) Filter: (t3.f1 IS NULL) -> Seq Scan on int4_tbl t2 -> Materialize @@ -2612,11 +2612,11 @@ select * from int4_tbl t1 QUERY PLAN ----------------------------------------------------------------- Nested Loop Left Join - Join Filter: (t2.f1 > 1) + Outer Tuple Filter: (t2.f1 > 1) -> Hash Right Join Hash Cond: (t2.f1 = t1.f1) -> Nested Loop Left Join - Join Filter: (t2.f1 > 0) + Outer Tuple Filter: (t2.f1 > 0) Filter: (t2.f1 <> COALESCE(t3.f1, '-1'::integer)) -> Seq Scan on int4_tbl t2 -> Materialize @@ -2662,7 +2662,7 @@ on t2.q2 = 123; -> Nested Loop Left Join Join Filter: (t2.q1 = t5.q1) -> Nested Loop Left Join - Join Filter: false + Outer Tuple Filter: false -> Seq Scan on int8_tbl t2 Filter: (q2 = 123) -> Result @@ -2676,13 +2676,13 @@ select * from int8_tbl t1 left join lateral (select * from int8_tbl t3 where t3.q1 = t2.q1 offset 0) s on t2.q1 = 1; - QUERY PLAN -------------------------------------------- + QUERY PLAN +----------------------------------------------- Nested Loop Left Join -> Seq Scan on int8_tbl t1 -> Materialize -> Nested Loop Left Join - Join Filter: (t2.q1 = 1) + Outer Tuple Filter: (t2.q1 = 1) -> Seq Scan on int8_tbl t2 -> Seq Scan on int8_tbl t3 Filter: (q1 = t2.q1) @@ -2700,7 +2700,7 @@ select * from int8_tbl t1 -> Seq Scan on int8_tbl t1 -> Materialize -> Nested Loop Left Join - Join Filter: (t2.q1 = 1) + Outer Tuple Filter: (t2.q1 = 1) -> Seq Scan on int8_tbl t2 -> Function Scan on generate_series (7 rows) @@ -2711,13 +2711,13 @@ select * from int8_tbl t1 left join lateral (select t2.q1 from int8_tbl t3) s on t2.q1 = 1; - QUERY PLAN -------------------------------------------- + QUERY PLAN +----------------------------------------------- Nested Loop Left Join -> Seq Scan on int8_tbl t1 -> Materialize -> Nested Loop Left Join - Join Filter: (t2.q1 = 1) + Outer Tuple Filter: (t2.q1 = 1) -> Seq Scan on int8_tbl t2 -> Seq Scan on int8_tbl t3 (7 rows) @@ -2728,13 +2728,13 @@ select * from onek t1 left join lateral (select * from onek t3 where t3.two = t2.two offset 0) s on t2.unique1 = 1; - QUERY PLAN --------------------------------------------------- + QUERY PLAN +---------------------------------------------------- Nested Loop Left Join -> Seq Scan on onek t1 -> Materialize -> Nested Loop Left Join - Join Filter: (t2.unique1 = 1) + Outer Tuple Filter: (t2.unique1 = 1) -> Seq Scan on onek t2 -> Memoize Cache Key: t2.two @@ -4196,7 +4196,7 @@ select unique1, x from tenk1 left join f_immutable_int4(1) x on unique1 = x; QUERY PLAN ---------------------------------------------------- Nested Loop Left Join - Join Filter: (tenk1.unique1 = 1) + Outer Tuple Filter: (tenk1.unique1 = 1) -> Index Only Scan using tenk1_unique1 on tenk1 -> Materialize -> Result @@ -4253,7 +4253,7 @@ where nt3.id = 1 and ss2.b3; -> Index Scan using nt3_pkey on nt3 Index Cond: (id = 1) -> Nested Loop Left Join - Join Filter: (0 = nt2.nt1_id) + Outer Tuple Filter: (0 = nt2.nt1_id) -> Index Scan using nt2_pkey on nt2 Index Cond: (id = nt3.nt2_id) -> Result @@ -4496,7 +4496,7 @@ select count(*) from ------------------------------------------------------------------------- Aggregate -> Nested Loop Left Join - Join Filter: (a.unique2 = b.unique1) + Outer Tuple Filter: (a.unique2 = b.unique1) -> Nested Loop -> Nested Loop -> Seq Scan on int4_tbl @@ -4533,7 +4533,7 @@ select b.unique1 from -> Nested Loop Left Join -> Seq Scan on int4_tbl i2 -> Nested Loop Left Join - Join Filter: (b.unique1 = 42) + Outer Tuple Filter: (b.unique1 = 42) -> Nested Loop -> Nested Loop -> Seq Scan on int4_tbl i1 @@ -4848,7 +4848,7 @@ select t1.* from Hash Cond: (i8.q2 = i4.f1) -> Nested Loop Left Join Output: t1.f1, i8.q2 - Join Filter: (t1.f1 = '***'::text) + Outer Tuple Filter: (t1.f1 = '***'::text) -> Seq Scan on public.text_tbl t1 Output: t1.f1 -> Materialize @@ -4909,7 +4909,7 @@ select t1.* from Hash Cond: (i8.q2 = i4.f1) -> Nested Loop Left Join Output: t1.f1, i8.q2 - Join Filter: (t1.f1 = '***'::text) + Outer Tuple Filter: (t1.f1 = '***'::text) -> Seq Scan on public.text_tbl t1 Output: t1.f1 -> Materialize @@ -4975,7 +4975,7 @@ select t1.* from Hash Cond: (i8.q2 = i4.f1) -> Nested Loop Left Join Output: t1.f1, i8.q2 - Join Filter: (t1.f1 = '***'::text) + Outer Tuple Filter: (t1.f1 = '***'::text) -> Seq Scan on public.text_tbl t1 Output: t1.f1 -> Materialize @@ -5084,7 +5084,7 @@ left join QUERY PLAN -------------------------------- Nested Loop Left Join - Join Filter: false + Outer Tuple Filter: false -> Result -> Result One-Time Filter: false @@ -5100,16 +5100,16 @@ select 1 from join int4_tbl i42 on ss1.a is null or i8.q1 <> i8.q2 right join (select 2 as b) ss2 on ss2.b < i4.f1; - QUERY PLAN ------------------------------------------------------------ + QUERY PLAN +------------------------------------------------------------- Nested Loop Left Join -> Result -> Nested Loop -> Nested Loop Left Join - Join Filter: NULL::boolean + Outer Tuple Filter: NULL::boolean Filter: (((1) IS NULL) OR (i8.q1 <> i8.q2)) -> Nested Loop Left Join - Join Filter: (i4.f1 IS NOT NULL) + Outer Tuple Filter: (i4.f1 IS NOT NULL) -> Seq Scan on int4_tbl i4 Filter: (2 < f1) -> Materialize @@ -5720,7 +5720,7 @@ from int4_tbl as t1 QUERY PLAN -------------------------------- Nested Loop Left Join - Join Filter: false + Outer Tuple Filter: false -> Seq Scan on int4_tbl t1 -> Result One-Time Filter: false @@ -5742,7 +5742,7 @@ from int4_tbl as t1 QUERY PLAN -------------------------------- Nested Loop Left Join - Join Filter: false + Outer Tuple Filter: false -> Seq Scan on int4_tbl t1 -> Result One-Time Filter: false @@ -5775,7 +5775,7 @@ from int8_tbl t1 QUERY PLAN ------------------------------------------------- Nested Loop Left Join - Join Filter: (t2.q2 < t3.unique2) + Outer Tuple Filter: (t2.q2 < t3.unique2) -> Nested Loop Left Join Join Filter: (t2.q1 > t3.unique1) -> Hash Left Join @@ -5915,7 +5915,7 @@ select 1 from a t1 -> Seq Scan on a t1 -> Materialize -> Nested Loop Left Join - Join Filter: (t2.id = 1) + Outer Tuple Filter: (t2.id = 1) -> Index Only Scan using a_pkey on a t2 Index Cond: (id = 1) -> Seq Scan on a t3 @@ -6122,7 +6122,7 @@ SELECT q2 FROM ------------------------------------------------------ Nested Loop Left Join Output: q2 - Join Filter: NULL::boolean + Outer Tuple Filter: NULL::boolean Filter: (('constant'::text) >= ('constant'::text)) -> Seq Scan on public.int4_tbl Output: int4_tbl.f1 @@ -6144,7 +6144,7 @@ FROM int4_tbl -> Seq Scan on int4_tbl -> Materialize -> Nested Loop Left Join - Join Filter: NULL::boolean + Outer Tuple Filter: NULL::boolean Filter: ((tenk1.unique1 = (42)) OR (tenk1.unique2 = (42))) -> Seq Scan on tenk1 -> Result @@ -7242,7 +7242,7 @@ select * from -------------------------------- Nested Loop Left Join Output: 0, (1), ((1)) - Join Filter: false + Outer Tuple Filter: false -> Result Output: 1 -> Result @@ -7361,7 +7361,7 @@ select * from int8_tbl i8 left join lateral -------------------------------------- Nested Loop Left Join Output: i8.q1, i8.q2, f1, (i8.q2) - Join Filter: false + Outer Tuple Filter: false -> Seq Scan on public.int8_tbl i8 Output: i8.q1, i8.q2 -> Result diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out index 965a3a76161..30b169bcf65 100644 --- a/src/test/regress/expected/predicate.out +++ b/src/test/regress/expected/predicate.out @@ -119,9 +119,9 @@ SELECT * FROM pred_tab t1 QUERY PLAN ------------------------------------------- Nested Loop Left Join - Join Filter: (t2.a IS NOT NULL) + Outer Tuple Filter: (t2.a IS NOT NULL) -> Nested Loop Left Join - Join Filter: (t1.a = 1) + Outer Tuple Filter: (t1.a = 1) -> Seq Scan on pred_tab t1 -> Materialize -> Seq Scan on pred_tab t2 @@ -135,13 +135,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 LEFT JOIN pred_tab t2 ON TRUE LEFT JOIN pred_tab t3 ON t2.a IS NULL AND t2.b = 1; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +---------------------------------------------------------- Nested Loop Left Join -> Seq Scan on pred_tab t1 -> Materialize -> Nested Loop Left Join - Join Filter: (false AND (t2.b = 1)) + Outer Tuple Filter: (false AND (t2.b = 1)) -> Seq Scan on pred_tab t2 -> Result One-Time Filter: false @@ -156,9 +156,9 @@ SELECT * FROM pred_tab t1 QUERY PLAN ------------------------------------------- Nested Loop Left Join - Join Filter: (t2.a IS NULL) + Outer Tuple Filter: (t2.a IS NULL) -> Nested Loop Left Join - Join Filter: (t1.a = 1) + Outer Tuple Filter: (t1.a = 1) -> Seq Scan on pred_tab t1 -> Materialize -> Seq Scan on pred_tab t2 @@ -191,12 +191,12 @@ EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 LEFT JOIN pred_tab t2 ON t1.a = 1 LEFT JOIN pred_tab t3 ON t2.a IS NOT NULL OR t2.b = 1; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +---------------------------------------------------------- Nested Loop Left Join - Join Filter: ((t2.a IS NOT NULL) OR (t2.b = 1)) + Outer Tuple Filter: ((t2.a IS NOT NULL) OR (t2.b = 1)) -> Nested Loop Left Join - Join Filter: (t1.a = 1) + Outer Tuple Filter: (t1.a = 1) -> Seq Scan on pred_tab t1 -> Materialize -> Seq Scan on pred_tab t2 @@ -210,13 +210,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 LEFT JOIN pred_tab t2 ON TRUE LEFT JOIN pred_tab t3 ON (t2.a IS NULL OR t2.c IS NULL) AND t2.b = 1; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +---------------------------------------------------------- Nested Loop Left Join -> Seq Scan on pred_tab t1 -> Materialize -> Nested Loop Left Join - Join Filter: (false AND (t2.b = 1)) + Outer Tuple Filter: (false AND (t2.b = 1)) -> Seq Scan on pred_tab t2 -> Result One-Time Filter: false @@ -228,12 +228,12 @@ EXPLAIN (COSTS OFF) SELECT * FROM pred_tab t1 LEFT JOIN pred_tab t2 ON t1.a = 1 LEFT JOIN pred_tab t3 ON t2.a IS NULL OR t2.c IS NULL; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +---------------------------------------------------------- Nested Loop Left Join - Join Filter: ((t2.a IS NULL) OR (t2.c IS NULL)) + Outer Tuple Filter: ((t2.a IS NULL) OR (t2.c IS NULL)) -> Nested Loop Left Join - Join Filter: (t1.a = 1) + Outer Tuple Filter: (t1.a = 1) -> Seq Scan on pred_tab t1 -> Materialize -> Seq Scan on pred_tab t2 @@ -301,13 +301,13 @@ SELECT 1 FROM pred_tab t1 (pred_tab t2 LEFT JOIN pred_tab t3 ON t2.a = t3.a) ON TRUE LEFT JOIN pred_tab t4 ON t1.a IS NULL AND t1.b = 1 RIGHT JOIN pred_tab t5 ON t1.b = t5.b; - QUERY PLAN ---------------------------------------------------- + QUERY PLAN +---------------------------------------------------------- Hash Right Join Hash Cond: (t1.b = t5.b) -> Nested Loop Left Join -> Nested Loop Left Join - Join Filter: (false AND (t1.b = 1)) + Outer Tuple Filter: (false AND (t1.b = 1)) -> Seq Scan on pred_tab t1 -> Result One-Time Filter: false