Skip to content

Commit 2b01996

Browse files
committed
sql: include unoptimized right side into apply join EXPLAIN
The apply join operator works by creating an optimized right side plan for each input row. Previously, this right side was completely omitted from the EXPLAIN outputs. This commit makes things a bit better by including the stringified form of the unoptimized right side plan (similar to the one that can be viewed with `EXPLAIN (OPT)`) in all EXPLAIN variants. In EXPLAIN this looks ok but in EXPLAIN ANALYZE, since the new addition is not annotated with execution statistics, it looks a bit out of place, but I think it's still better than nothing - at least it shows that there is something else going on within apply join that was previously completely hidden. Release note: None
1 parent dde2221 commit 2b01996

File tree

11 files changed

+186
-37
lines changed

11 files changed

+186
-37
lines changed

pkg/sql/distsql_spec_exec_factory.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ func (e *distSQLSpecExecFactory) ConstructApplyJoin(
510510
rightColumns colinfo.ResultColumns,
511511
onCond tree.TypedExpr,
512512
planRightSideFn exec.ApplyJoinPlanRightSideFn,
513+
rightSideForExplainFn exec.ApplyJoinRightSideForExplainFn,
513514
) (exec.Node, error) {
514515
return nil, unimplemented.NewWithIssue(47473, "experimental opt-driven distsql planning: apply join")
515516
}

pkg/sql/opt/exec/execbuilder/relational.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,16 @@ func (b *Builder) buildApplyJoin(join memo.RelExpr) (_ execPlan, outputCols colO
13491349
return plan, nil
13501350
}
13511351

1352+
// Build the stringified representation of the unoptimized right side for
1353+
// EXPLAIN purposes on demand.
1354+
rightSideForExplainFn := func(redactableValues bool) string {
1355+
f := memo.MakeExprFmtCtx(
1356+
b.ctx, memo.ExprFmtHideAll, redactableValues, b.mem, b.catalog,
1357+
)
1358+
f.FormatExpr(rightExpr)
1359+
return f.Buffer.String()
1360+
}
1361+
13521362
// The right plan will always produce the columns in the presentation, in
13531363
// the same order. This map is only used for the lifetime of this function,
13541364
// so free the map afterward.
@@ -1391,6 +1401,7 @@ func (b *Builder) buildApplyJoin(join memo.RelExpr) (_ execPlan, outputCols colO
13911401
b.presentationToResultColumns(rightRequiredProps.Presentation),
13921402
onExpr,
13931403
planRightSideFn,
1404+
rightSideForExplainFn,
13941405
)
13951406
if err != nil {
13961407
return execPlan{}, colOrdMap{}, err

pkg/sql/opt/exec/execbuilder/testdata/explain_redact

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,10 +2616,24 @@ vectorized: true
26162616
26172617
└── • apply join (left outer)
26182618
2619-
└── • scan
2620-
missing stats
2621-
table: f@f_pkey
2622-
spans: FULL SCAN
2619+
├── • scan
2620+
│ missing stats
2621+
│ table: f@f_pkey
2622+
│ spans: FULL SCAN
2623+
2624+
└── • inner loop (unoptimized)
2625+
2626+
└── • distinct-on
2627+
└── project
2628+
├── select
2629+
│ ├── scan bc
2630+
│ │ └── computed column expressions
2631+
│ │ └── c
2632+
│ │ └── b * ‹×›
2633+
│ └── filters
2634+
│ └── (b * f) < ‹×›
2635+
└── projections
2636+
└── (f + (b * ‹×›)) + ‹×›
26232637

26242638
query T
26252639
EXPLAIN (VERBOSE, REDACT) SELECT f, g FROM f, LATERAL (SELECT count(DISTINCT c + f + 1) * 2 AS g FROM bc WHERE b * f < 10)
@@ -2643,11 +2657,27 @@ vectorized: true
26432657
│ columns: (f, rowid, column13)
26442658
│ estimated row count: 333,333 (missing stats)
26452659
2646-
└── • scan
2647-
columns: (f, rowid)
2648-
estimated row count: 1,000 (missing stats)
2649-
table: f@f_pkey
2650-
spans: FULL SCAN
2660+
├── • scan
2661+
│ columns: (f, rowid)
2662+
│ estimated row count: 1,000 (missing stats)
2663+
│ table: f@f_pkey
2664+
│ spans: FULL SCAN
2665+
2666+
└── • inner loop (unoptimized)
2667+
│ columns: ()
2668+
2669+
└── • distinct-on
2670+
columns: ()
2671+
└── project
2672+
├── select
2673+
│ ├── scan bc
2674+
│ │ └── computed column expressions
2675+
│ │ └── c
2676+
│ │ └── b * ‹×›
2677+
│ └── filters
2678+
│ └── (b * f) < ‹×›
2679+
└── projections
2680+
└── (f + (b * ‹×›)) + ‹×›
26512681

26522682
query T
26532683
EXPLAIN (OPT, REDACT) SELECT f, g FROM f, LATERAL (SELECT count(DISTINCT c + f + 1) * 2 AS g FROM bc WHERE b * f < 10)
@@ -3022,10 +3052,23 @@ vectorized: true
30223052
• apply join (anti)
30233053
│ pred: (a <= "?column?") IS NOT ‹×›
30243054
3025-
└── • scan
3026-
missing stats
3027-
table: a@a_pkey
3028-
spans: FULL SCAN
3055+
├── • scan
3056+
│ missing stats
3057+
│ table: a@a_pkey
3058+
│ spans: FULL SCAN
3059+
3060+
└── • inner loop (unoptimized)
3061+
3062+
└── • project
3063+
├── select
3064+
│ ├── scan bc
3065+
│ │ └── computed column expressions
3066+
│ │ └── c
3067+
│ │ └── b * ‹×›
3068+
│ └── filters
3069+
│ └── b > (a::FLOAT8 * ‹×›)
3070+
└── projections
3071+
└── (b * ‹×›)::INT8 + ‹×›
30293072

30303073
query T
30313074
EXPLAIN (VERBOSE, REDACT) SELECT * FROM a WHERE a > ALL (SELECT c::int + 2 FROM bc WHERE b > a::float * 3)
@@ -3038,11 +3081,26 @@ vectorized: true
30383081
│ estimated row count: 667 (missing stats)
30393082
│ pred: (a <= "?column?") IS NOT ‹×›
30403083
3041-
└── • scan
3042-
columns: (a)
3043-
estimated row count: 1,000 (missing stats)
3044-
table: a@a_pkey
3045-
spans: FULL SCAN
3084+
├── • scan
3085+
│ columns: (a)
3086+
│ estimated row count: 1,000 (missing stats)
3087+
│ table: a@a_pkey
3088+
│ spans: FULL SCAN
3089+
3090+
└── • inner loop (unoptimized)
3091+
│ columns: ()
3092+
3093+
└── • project
3094+
columns: ()
3095+
├── select
3096+
│ ├── scan bc
3097+
│ │ └── computed column expressions
3098+
│ │ └── c
3099+
│ │ └── b * ‹×›
3100+
│ └── filters
3101+
│ └── b > (a::FLOAT8 * ‹×›)
3102+
└── projections
3103+
└── (b * ‹×›)::INT8 + ‹×›
30463104

30473105
query T
30483106
EXPLAIN (OPT, REDACT) SELECT * FROM a WHERE a > ALL (SELECT c::int + 2 FROM bc WHERE b > a::float * 3)

pkg/sql/opt/exec/execbuilder/testdata/srfs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,19 @@ vectorized: true
303303
│ columns: (x, y, unnest)
304304
│ estimated row count: 2,000 (missing stats)
305305
306-
└── • scan
307-
columns: (x, y)
308-
estimated row count: 1,000 (missing stats)
309-
table: xy@xy_pkey
310-
spans: FULL SCAN
306+
├── • scan
307+
│ columns: (x, y)
308+
│ estimated row count: 1,000 (missing stats)
309+
│ table: xy@xy_pkey
310+
│ spans: FULL SCAN
311+
312+
└── • inner loop (unoptimized)
313+
│ columns: ()
314+
315+
└── • values
316+
columns: ()
317+
├── (xy.x,)
318+
└── (y,)
311319

312320
# Regression test for #24676.
313321
statement ok

pkg/sql/opt/exec/execbuilder/testdata/subquery

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -437,11 +437,19 @@ vectorized: true
437437
│ estimated row count: 2 (missing stats)
438438
│ pred: column1 = a
439439
440-
└── • scan
441-
columns: (a, b, c)
442-
estimated row count: 1,000 (missing stats)
443-
table: abc@abc_pkey
444-
spans: FULL SCAN
440+
├── • scan
441+
│ columns: (a, b, c)
442+
│ estimated row count: 1,000 (missing stats)
443+
│ table: abc@abc_pkey
444+
│ spans: FULL SCAN
445+
446+
└── • inner loop (unoptimized)
447+
│ columns: ()
448+
449+
└── • values
450+
columns: ()
451+
├── (a,)
452+
└── (b,)
445453

446454
statement ok
447455
CREATE TABLE corr (

pkg/sql/opt/exec/execbuilder/testdata/with

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,20 @@ vectorized: true
213213
214214
└── • apply join (left outer)
215215
216-
└── • scan
217-
missing stats
218-
table: y@y_pkey
219-
spans: FULL SCAN
216+
├── • scan
217+
│ missing stats
218+
│ table: y@y_pkey
219+
│ spans: FULL SCAN
220+
221+
└── • inner loop (unoptimized)
222+
223+
└── • with &1 (foo)
224+
├── materialized
225+
├── select
226+
│ ├── scan x
227+
│ └── filters
228+
│ └── x.a = y.a
229+
└── with-scan &1 (foo)
220230

221231
query T
222232
EXPLAIN
@@ -229,8 +239,18 @@ vectorized: true
229239
·
230240
• apply join
231241
232-
└── • values
233-
size: 1 column, 3 rows
242+
├── • values
243+
│ size: 1 column, 3 rows
244+
245+
└── • inner loop (unoptimized)
246+
247+
└── • with &1 (foo)
248+
├── materialized
249+
├── select
250+
│ ├── scan y
251+
│ └── filters
252+
│ └── y.a <= column1
253+
└── with-scan &1 (foo)
234254

235255
# Regression tests for #93370. Do not convert a non-recursive CTE
236256
# that uses UNION ALL and WITH RECURSIVE to UNION.

pkg/sql/opt/exec/explain/emit.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func emitInternal(
9999
return err
100100
}
101101
}
102+
e.emitNodeFootnotes(n)
102103
ob.LeaveNode()
103104
return nil
104105
}
@@ -1269,6 +1270,35 @@ func (e *emitter) emitNodeAttributes(ctx context.Context, evalCtx *eval.Context,
12691270
return nil
12701271
}
12711272

1273+
// emitNodeFootnotes populates the Node's information after having recursed into
1274+
// the Node's children.
1275+
func (e *emitter) emitNodeFootnotes(n *Node) {
1276+
switch n.op {
1277+
case applyJoinOp:
1278+
a := n.args.(*applyJoinArgs)
1279+
if a.RightSideForExplainFn != nil {
1280+
e.ob.EnterNode("inner loop (unoptimized)" /* name */, nil /* columns */, nil /* ordering */)
1281+
defer e.ob.LeaveNode()
1282+
// RightSideForExplainFn can produce multiple lines that correspond
1283+
// to the unoptimized right side plan.
1284+
lines := strings.Split(a.RightSideForExplainFn(e.ob.flags.RedactValues), "\n")
1285+
var enteredNode bool
1286+
for _, l := range lines {
1287+
if len(l) == 0 {
1288+
continue
1289+
}
1290+
if !enteredNode {
1291+
e.ob.EnterNode(l /* name */, nil /* columns */, nil /* ordering */)
1292+
defer e.ob.LeaveNode() //nolint:deferloop
1293+
enteredNode = true
1294+
} else {
1295+
e.ob.Attr(l, "")
1296+
}
1297+
}
1298+
}
1299+
}
1300+
}
1301+
12721302
func (e *emitter) emitTableAndIndex(field string, table cat.Table, index cat.Index, suffix string) {
12731303
partial := ""
12741304
if _, isPartial := index.Predicate(); isPartial {

pkg/sql/opt/exec/explain/testdata/gists

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,15 @@ explain(shape):
132132
• apply join (semi)
133133
│ pred: column1 = a
134134
135-
└── • scan
136-
table: abc@abc_pkey
137-
spans: FULL SCAN
135+
├── • scan
136+
│ table: abc@abc_pkey
137+
│ spans: FULL SCAN
138+
139+
└── • inner loop (unoptimized)
140+
141+
└── • values
142+
├── (a,)
143+
└── (b,)
138144
explain(gist):
139145
• apply join (semi)
140146

pkg/sql/opt/exec/factory.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,11 @@ type RecursiveCTEIterationFn func(ctx context.Context, ef Factory, bufferRef Nod
292292
// rightColumns passed to ConstructApplyJoin (in order).
293293
type ApplyJoinPlanRightSideFn func(ctx context.Context, ef Factory, leftRow tree.Datums) (Plan, error)
294294

295+
// ApplyJoinRightSideForExplainFn is a function that lazily populates the
296+
// stringified version of the unoptimized right-hand side plan, for EXPLAIN
297+
// purposes.
298+
type ApplyJoinRightSideForExplainFn func(redactableValues bool) string
299+
295300
// PostQuery describes a cascading query or an AFTER trigger action. The query
296301
// uses a node created by ConstructBuffer as an input; it should only be
297302
// triggered if this buffer is not empty.

pkg/sql/opt/exec/factory.opt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ define ApplyJoin {
8282
RightColumns colinfo.ResultColumns
8383
OnCond tree.TypedExpr
8484
PlanRightSideFn exec.ApplyJoinPlanRightSideFn
85+
RightSideForExplainFn exec.ApplyJoinRightSideForExplainFn
8586
}
8687

8788
# HashJoin runs a hash-join between the results of two input nodes.

0 commit comments

Comments
 (0)