From db440802d84a96a98d0fb30672232e81e10c4598 Mon Sep 17 00:00:00 2001 From: Andrey Lepikhov Date: Tue, 24 Aug 2021 10:59:00 +0500 Subject: [PATCH] Improve EXPLAIN presentation --- src/backend/utils/adt/ruleutils.c | 101 +++++++++++------- src/test/regress/expected/insert_conflict.out | 2 +- src/test/regress/expected/join.out | 2 +- src/test/regress/expected/rowsecurity.out | 6 +- src/test/regress/expected/select_parallel.out | 12 +-- src/test/regress/expected/subselect.out | 40 +++---- src/test/regress/expected/updatable_views.out | 24 ++--- 7 files changed, 106 insertions(+), 81 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 4df8cc5abf..1c25769633 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -455,7 +455,10 @@ static void get_const_expr(Const *constval, deparse_context *context, int showtype); static void get_const_collation(Const *constval, deparse_context *context); static void simple_quote_literal(StringInfo buf, const char *val); +static void get_subplan_expr(SubPlan *subplan, deparse_context *context); static void get_sublink_expr(SubLink *sublink, deparse_context *context); +static bool get_subselect_expr(SubLinkType subLinkType, Node *testexpr, + deparse_context *context); static void get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit); static void get_from_clause(Query *query, const char *prefix, @@ -8536,20 +8539,7 @@ get_rule_expr(Node *node, deparse_context *context, break; case T_SubPlan: - { - SubPlan *subplan = (SubPlan *) node; - - /* - * We cannot see an already-planned subplan in rule deparsing, - * only while EXPLAINing a query plan. We don't try to - * reconstruct the original SQL, just reference the subplan - * that appears elsewhere in EXPLAIN's result. - */ - if (subplan->useHashTable) - appendStringInfo(buf, "(hashed %s)", subplan->plan_name); - else - appendStringInfo(buf, "(%s)", subplan->plan_name); - } + get_subplan_expr((SubPlan *) node, context); break; case T_AlternativeSubPlan: @@ -10358,6 +10348,31 @@ simple_quote_literal(StringInfo buf, const char *val) } +static void +get_subplan_expr(SubPlan *subplan, deparse_context *context) +{ + StringInfo buf = context->buf; + + if (subplan->testexpr) + appendStringInfoChar(buf, '('); + + (void) get_subselect_expr(subplan->subLinkType, subplan->testexpr, context); + + /* + * We cannot see an already-planned subplan in rule deparsing, + * only while EXPLAINing a query plan. We don't try to + * reconstruct the original SQL, just reference the subplan + * that appears elsewhere in EXPLAIN's result. + */ + if (subplan->useHashTable) + appendStringInfo(buf, "(hashed %s)", subplan->plan_name); + else + appendStringInfo(buf, "(%s)", subplan->plan_name); + + if (subplan->testexpr) + appendStringInfoChar(buf, ')'); +} + /* ---------- * get_sublink_expr - Parse back a sublink * ---------- @@ -10367,14 +10382,35 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) { StringInfo buf = context->buf; Query *query = (Query *) (sublink->subselect); - char *opname = NULL; - bool need_paren; + bool need_paren = true; if (sublink->subLinkType == ARRAY_SUBLINK) appendStringInfoString(buf, "ARRAY("); else appendStringInfoChar(buf, '('); + need_paren = get_subselect_expr(sublink->subLinkType, sublink->testexpr, context); + + if (need_paren) + appendStringInfoChar(buf, '('); + + get_query_def(query, buf, context->namespaces, NULL, + context->prettyFlags, context->wrapColumn, + context->indentLevel); + + if (need_paren) + appendStringInfoString(buf, "))"); + else if (query) + appendStringInfoChar(buf, ')'); +} + +static bool +get_subselect_expr(SubLinkType subLinkType, Node *testexpr, deparse_context *context) +{ + StringInfo buf = context->buf; + char *opname = NULL; + bool need_paren; + /* * Note that we print the name of only the first operator, when there are * multiple combining operators. This is an approximation that could go @@ -10382,19 +10418,19 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) * operators, etc) but there is not a whole lot we can do about it, since * the syntax allows only one operator to be shown. */ - if (sublink->testexpr) + if (testexpr) { - if (IsA(sublink->testexpr, OpExpr)) + if (IsA(testexpr, OpExpr)) { /* single combining operator */ - OpExpr *opexpr = (OpExpr *) sublink->testexpr; + OpExpr *opexpr = (OpExpr *) testexpr; get_rule_expr(linitial(opexpr->args), context, true); opname = generate_operator_name(opexpr->opno, exprType(linitial(opexpr->args)), exprType(lsecond(opexpr->args))); } - else if (IsA(sublink->testexpr, BoolExpr)) + else if (IsA(testexpr, BoolExpr)) { /* multiple combining operators, = or <> cases */ char *sep; @@ -10402,7 +10438,7 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) appendStringInfoChar(buf, '('); sep = ""; - foreach(l, ((BoolExpr *) sublink->testexpr)->args) + foreach(l, ((BoolExpr *) testexpr)->args) { OpExpr *opexpr = lfirst_node(OpExpr, l); @@ -10416,10 +10452,10 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) } appendStringInfoChar(buf, ')'); } - else if (IsA(sublink->testexpr, RowCompareExpr)) + else if (IsA(testexpr, RowCompareExpr)) { /* multiple combining operators, < <= > >= cases */ - RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr; + RowCompareExpr *rcexpr = (RowCompareExpr *) testexpr; appendStringInfoChar(buf, '('); get_rule_expr((Node *) rcexpr->largs, context, true); @@ -10430,12 +10466,12 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) } else elog(ERROR, "unrecognized testexpr type: %d", - (int) nodeTag(sublink->testexpr)); + (int) nodeTag(testexpr)); } need_paren = true; - switch (sublink->subLinkType) + switch (subLinkType) { case EXISTS_SUBLINK: appendStringInfoString(buf, "EXISTS "); @@ -10465,21 +10501,10 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) case CTE_SUBLINK: /* shouldn't occur in a SubLink */ default: elog(ERROR, "unrecognized sublink type: %d", - (int) sublink->subLinkType); + (int) subLinkType); break; } - - if (need_paren) - appendStringInfoChar(buf, '('); - - get_query_def(query, buf, context->namespaces, NULL, - context->prettyFlags, context->wrapColumn, - context->indentLevel); - - if (need_paren) - appendStringInfoString(buf, "))"); - else - appendStringInfoChar(buf, ')'); + return need_paren; } diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out index 66d8633e3e..8ac7756925 100644 --- a/src/test/regress/expected/insert_conflict.out +++ b/src/test/regress/expected/insert_conflict.out @@ -50,7 +50,7 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con Insert on insertconflicttest Conflict Resolution: UPDATE Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key - Conflict Filter: (SubPlan 1) + Conflict Filter: EXISTS (SubPlan 1) -> Result SubPlan 1 -> Index Only Scan using both_index_expr_key on insertconflicttest ii diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index f3589d0dbb..bfc50f7a75 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5899,7 +5899,7 @@ lateral (select * from int8_tbl t1, Filter: (t1.q1 = ss2.q2) -> Seq Scan on public.int8_tbl t2 Output: t2.q1, t2.q2 - Filter: (SubPlan 3) + Filter: (t2.q1 IN (SubPlan 3)) SubPlan 3 -> Result Output: t3.q2 diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 89397e41f0..1e34c41480 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -1440,7 +1440,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b); QUERY PLAN ----------------------------------------------------------- Seq Scan on s1 - Filter: ((hashed SubPlan 1) AND f_leak(b)) + Filter: ((a IN (hashed SubPlan 1)) AND f_leak(b)) SubPlan 1 -> Seq Scan on s2 Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text)) @@ -1462,7 +1462,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b); QUERY PLAN ----------------------------------------------------------- Seq Scan on s1 - Filter: ((hashed SubPlan 1) AND f_leak(b)) + Filter: ((a IN (hashed SubPlan 1)) AND f_leak(b)) SubPlan 1 -> Seq Scan on s2 Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) @@ -1484,7 +1484,7 @@ EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like SubPlan 2 -> Limit -> Seq Scan on s1 - Filter: (hashed SubPlan 1) + Filter: (a IN (hashed SubPlan 1)) SubPlan 1 -> Seq Scan on s2 s2_1 Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index 4ea1aa7dfd..bd77033882 100644 --- a/src/test/regress/expected/select_parallel.out +++ b/src/test/regress/expected/select_parallel.out @@ -291,14 +291,14 @@ alter table tenk2 set (parallel_workers = 0); explain (costs off) select count(*) from tenk1 where (two, four) not in (select hundred, thousand from tenk2 where thousand > 100); - QUERY PLAN ------------------------------------------------------- + QUERY PLAN +----------------------------------------------------------------------- Finalize Aggregate -> Gather Workers Planned: 4 -> Partial Aggregate -> Parallel Seq Scan on tenk1 - Filter: (NOT (hashed SubPlan 1)) + Filter: (NOT ((two, four) IN (hashed SubPlan 1))) SubPlan 1 -> Seq Scan on tenk2 Filter: (thousand > 100) @@ -315,10 +315,10 @@ select count(*) from tenk1 where (two, four) not in explain (costs off) select * from tenk1 where (unique1 + random())::integer not in (select ten from tenk2); - QUERY PLAN ------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------- Seq Scan on tenk1 - Filter: (NOT (hashed SubPlan 1)) + Filter: (NOT ((((unique1)::double precision + random()))::integer IN (hashed SubPlan 1))) SubPlan 1 -> Seq Scan on tenk2 (4 rows) diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 30615dd6bc..2bc2f0ba56 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -258,7 +258,7 @@ select 1 = all (select (select 1)); QUERY PLAN ----------------------------------- Result - Output: (SubPlan 2) + Output: (1 = ALL (SubPlan 2)) SubPlan 2 -> Materialize Output: ($0) @@ -308,7 +308,7 @@ select * from int4_tbl o where exists QUERY PLAN -------------------------------------- Seq Scan on int4_tbl o - Filter: (SubPlan 1) + Filter: EXISTS (SubPlan 1) SubPlan 1 -> Limit -> Seq Scan on int4_tbl i @@ -771,10 +771,10 @@ select * from outer_text where (f1, f2) not in (select * from inner_text); -- explain (verbose, costs off) select 'foo'::text in (select 'bar'::name union all select 'bar'::name); - QUERY PLAN -------------------------------------- + QUERY PLAN +----------------------------------------------- Result - Output: (hashed SubPlan 1) + Output: ('foo'::text IN (hashed SubPlan 1)) SubPlan 1 -> Append -> Result @@ -815,10 +815,10 @@ language sql as 'select $1::text = $2'; create operator = (procedure=bogus_int8_text_eq, leftarg=int8, rightarg=text); explain (costs off) select * from int8_tbl where q1 in (select c1 from inner_text); - QUERY PLAN --------------------------------- + QUERY PLAN +---------------------------------------------- Seq Scan on int8_tbl - Filter: (hashed SubPlan 1) + Filter: ((q1)::text IN (hashed SubPlan 1)) SubPlan 1 -> Seq Scan on inner_text (4 rows) @@ -836,10 +836,10 @@ create or replace function bogus_int8_text_eq(int8, text) returns boolean language sql as 'select $1::text = $2 and $1::text = $2'; explain (costs off) select * from int8_tbl where q1 in (select c1 from inner_text); - QUERY PLAN --------------------------------- + QUERY PLAN +------------------------------------------------------------ Seq Scan on int8_tbl - Filter: (hashed SubPlan 1) + Filter: (((q1)::text, (q1)::text) IN (hashed SubPlan 1)) SubPlan 1 -> Seq Scan on inner_text (4 rows) @@ -860,7 +860,7 @@ select * from int8_tbl where q1 in (select c1 from inner_text); QUERY PLAN -------------------------------------- Seq Scan on int8_tbl - Filter: (SubPlan 1) + Filter: ($0 IN (SubPlan 1)) SubPlan 1 -> Materialize -> Seq Scan on inner_text @@ -880,11 +880,11 @@ rollback; -- to get rid of the bogus operator explain (costs off) select count(*) from tenk1 t where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); - QUERY PLAN --------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------- Aggregate -> Seq Scan on tenk1 t - Filter: ((hashed SubPlan 2) OR (ten < 0)) + Filter: ((unique2 IN (hashed SubPlan 2)) OR (ten < 0)) SubPlan 2 -> Index Only Scan using tenk1_unique1 on tenk1 k (5 rows) @@ -905,7 +905,7 @@ where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) Aggregate -> Bitmap Heap Scan on tenk1 t Recheck Cond: (thousand = 1) - Filter: ((SubPlan 1) OR (ten < 0)) + Filter: (EXISTS (SubPlan 1) OR (ten < 0)) -> Bitmap Index Scan on tenk1_thous_tenthous Index Cond: (thousand = 1) SubPlan 1 @@ -1011,14 +1011,14 @@ where o.ten = 0; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate - Output: sum((((hashed SubPlan 1)))::integer) + Output: sum((((i.ten IN (hashed SubPlan 1))))::integer) -> Nested Loop - Output: ((hashed SubPlan 1)) + Output: ((i.ten IN (hashed SubPlan 1))) -> Seq Scan on public.onek o Output: o.unique1, o.unique2, o.two, o.four, o.ten, o.twenty, o.hundred, o.thousand, o.twothousand, o.fivethous, o.tenthous, o.odd, o.even, o.stringu1, o.stringu2, o.string4 Filter: (o.ten = 0) -> Index Scan using onek_unique1 on public.onek i - Output: (hashed SubPlan 1), random() + Output: (i.ten IN (hashed SubPlan 1)), random() Index Cond: (i.unique1 = o.unique1) SubPlan 1 -> Seq Scan on public.int4_tbl @@ -1213,7 +1213,7 @@ select * from int4_tbl where --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Semi Join Output: int4_tbl.f1 - Join Filter: (CASE WHEN (hashed SubPlan 1) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten) + Join Filter: (CASE WHEN (int4_tbl.f1 IN (hashed SubPlan 1)) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten) -> Seq Scan on public.int4_tbl Output: int4_tbl.f1 -> Seq Scan on public.tenk1 b diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index cdff914b93..b4e91573c7 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -2327,8 +2327,8 @@ SELECT * FROM v1 WHERE a=8; EXPLAIN (VERBOSE, COSTS OFF) UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; - QUERY PLAN ------------------------------------------------------------------------------------------------------ + QUERY PLAN +------------------------------------------------------------------------------------------------------------ Update on public.t1 Update on public.t1 t1_1 Update on public.t11 t1_2 @@ -2340,7 +2340,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; -> Index Scan using t1_a_idx on public.t1 t1_1 Output: t1_1.tableoid, t1_1.ctid Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7)) - Filter: ((t1_1.a <> 6) AND (SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a)) + Filter: ((t1_1.a <> 6) AND EXISTS (SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a)) SubPlan 1 -> Append -> Seq Scan on public.t12 t12_1 @@ -2350,15 +2350,15 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; -> Index Scan using t11_a_idx on public.t11 t1_2 Output: t1_2.tableoid, t1_2.ctid Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7)) - Filter: ((t1_2.a <> 6) AND (SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a)) + Filter: ((t1_2.a <> 6) AND EXISTS (SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a)) -> Index Scan using t12_a_idx on public.t12 t1_3 Output: t1_3.tableoid, t1_3.ctid Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7)) - Filter: ((t1_3.a <> 6) AND (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a)) + Filter: ((t1_3.a <> 6) AND EXISTS (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a)) -> Index Scan using t111_a_idx on public.t111 t1_4 Output: t1_4.tableoid, t1_4.ctid Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7)) - Filter: ((t1_4.a <> 6) AND (SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a)) + Filter: ((t1_4.a <> 6) AND EXISTS (SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a)) (30 rows) UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; @@ -2374,8 +2374,8 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100 EXPLAIN (VERBOSE, COSTS OFF) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; - QUERY PLAN ------------------------------------------------------------------------------------ + QUERY PLAN +------------------------------------------------------------------------------------------ Update on public.t1 Update on public.t1 t1_1 Update on public.t11 t1_2 @@ -2387,7 +2387,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; -> Index Scan using t1_a_idx on public.t1 t1_1 Output: t1_1.a, t1_1.tableoid, t1_1.ctid Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8)) - Filter: ((SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a)) + Filter: (EXISTS (SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a)) SubPlan 1 -> Append -> Seq Scan on public.t12 t12_1 @@ -2397,15 +2397,15 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; -> Index Scan using t11_a_idx on public.t11 t1_2 Output: t1_2.a, t1_2.tableoid, t1_2.ctid Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8)) - Filter: ((SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a)) + Filter: (EXISTS (SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a)) -> Index Scan using t12_a_idx on public.t12 t1_3 Output: t1_3.a, t1_3.tableoid, t1_3.ctid Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8)) - Filter: ((SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a)) + Filter: (EXISTS (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a)) -> Index Scan using t111_a_idx on public.t111 t1_4 Output: t1_4.a, t1_4.tableoid, t1_4.ctid Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8)) - Filter: ((SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a)) + Filter: (EXISTS (SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a)) (30 rows) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; -- 2.25.1