github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/rowexec/interleaved_reader_joiner_test.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package rowexec
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"strconv"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/base"
    20  	"github.com/cockroachdb/cockroach/pkg/keys"
    21  	"github.com/cockroachdb/cockroach/pkg/kv"
    22  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/execinfra"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    27  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    28  	"github.com/cockroachdb/cockroach/pkg/testutils/distsqlutils"
    29  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    30  	"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
    31  	"github.com/cockroachdb/cockroach/pkg/util/encoding"
    32  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    33  	"github.com/cockroachdb/cockroach/pkg/util/tracing"
    34  )
    35  
    36  // min and max are inclusive bounds on the root table's ID.
    37  // If min and/or max is -1, then no bound is used for that endpoint.
    38  func makeSpanWithRootBound(desc *sqlbase.TableDescriptor, min int, max int) roachpb.Span {
    39  	keyPrefix := sqlbase.MakeIndexKeyPrefix(keys.SystemSQLCodec, desc, desc.PrimaryIndex.ID)
    40  
    41  	startKey := roachpb.Key(append([]byte(nil), keyPrefix...))
    42  	if min != -1 {
    43  		startKey = encoding.EncodeUvarintAscending(startKey, uint64(min))
    44  	}
    45  	endKey := roachpb.Key(append([]byte(nil), keyPrefix...))
    46  	if max != -1 {
    47  		endKey = encoding.EncodeUvarintAscending(endKey, uint64(max))
    48  	}
    49  	// endKey needs to be exclusive.
    50  	endKey = endKey.PrefixEnd()
    51  
    52  	return roachpb.Span{Key: startKey, EndKey: endKey}
    53  }
    54  
    55  func TestInterleavedReaderJoiner(t *testing.T) {
    56  	defer leaktest.AfterTest(t)()
    57  	ctx := context.Background()
    58  
    59  	s, sqlDB, kvDB := serverutils.StartServer(t, base.TestServerArgs{})
    60  	defer s.Stopper().Stop(ctx)
    61  
    62  	aFn := func(row int) tree.Datum {
    63  		return tree.NewDInt(tree.DInt(row % 10))
    64  	}
    65  
    66  	bFn := func(row int) tree.Datum {
    67  		return tree.NewDInt(tree.DInt(row / 10))
    68  	}
    69  
    70  	sqlutils.CreateTable(t, sqlDB, "parent",
    71  		"id INT PRIMARY KEY, a INT",
    72  		30,
    73  		sqlutils.ToRowFn(sqlutils.RowIdxFn, aFn),
    74  	)
    75  	// Child table has 3 rows for the first two parent rows and 2 rows for every
    76  	// other parent row.
    77  	sqlutils.CreateTableInterleaved(t, sqlDB, "child1",
    78  		"pid INT, id INT, b INT, PRIMARY KEY (pid, id)",
    79  		"parent (pid)",
    80  		62,
    81  		sqlutils.ToRowFn(sqlutils.RowModuloShiftedFn(30), sqlutils.RowIdxFn, bFn),
    82  	)
    83  	// Create another table also interleaved into parent with 1 or 0 rows
    84  	// interleaved into each parent row.
    85  	sqlutils.CreateTableInterleaved(t, sqlDB, "child2",
    86  		"pid INT, id INT, s STRING, PRIMARY KEY (pid, id), INDEX (s)",
    87  		"parent (pid)",
    88  		15,
    89  		sqlutils.ToRowFn(sqlutils.RowModuloShiftedFn(30), sqlutils.RowIdxFn, sqlutils.RowEnglishFn),
    90  	)
    91  	r := sqlutils.MakeSQLRunner(sqlDB)
    92  	// Insert additional rows into child1 not interleaved in parent.
    93  	r.Exec(t, fmt.Sprintf(`INSERT INTO %s.child1 VALUES
    94  	(-1, -1, 0),
    95  	(0, 0, 0),
    96  	(63, 63, 6),
    97  	(70, 70, 7)
    98  	`, sqlutils.TestDB))
    99  
   100  	// Create child table that do not correspond to a parent row to
   101  	// exercise OUTER joins.
   102  	sqlutils.CreateTableInterleaved(t, sqlDB, "child3",
   103  		"pid INT, id INT, s STRING, PRIMARY KEY (pid, id), INDEX (s)",
   104  		"parent (pid)",
   105  		0,
   106  		sqlutils.ToRowFn(),
   107  	)
   108  	r.Exec(t, fmt.Sprintf(`INSERT INTO %s.child3 VALUES
   109  	(-1, -1, '-1'),
   110  	(-1, -101, '-101'),
   111  	(0, 0, '0'),
   112  	(1, 1, '1'),
   113  	(3, 3, '3'),
   114  	(3, 103, '103'),
   115  	(5, 5, '5'),
   116  	(31, 31, '31'),
   117  	(31, 131, '131'),
   118  	(32, 32, '32')
   119  	`, sqlutils.TestDB))
   120  
   121  	pd := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "parent")
   122  	cd1 := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "child1")
   123  	cd2 := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "child2")
   124  	cd3 := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "child3")
   125  
   126  	// InterleavedReaderJoiner specs for each parent-child combination used
   127  	// throughout the test cases.
   128  	// They are copied and/or modified via closures for each test case.
   129  	// For example, pdCd1Spec is the spec for full table INNER JOIN between
   130  	// parent and child1.
   131  	pdCd1Spec := execinfrapb.InterleavedReaderJoinerSpec{
   132  		Tables: []execinfrapb.InterleavedReaderJoinerSpec_Table{
   133  			{
   134  				Desc:     *pd,
   135  				Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_ASC}}},
   136  				Spans:    []execinfrapb.TableReaderSpan{{Span: pd.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   137  			},
   138  			{
   139  				Desc:     *cd1,
   140  				Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_ASC}}},
   141  				Spans:    []execinfrapb.TableReaderSpan{{Span: cd1.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   142  			},
   143  		},
   144  		Type: sqlbase.InnerJoin,
   145  	}
   146  
   147  	copySpec := func(spec execinfrapb.InterleavedReaderJoinerSpec) execinfrapb.InterleavedReaderJoinerSpec {
   148  		spec.Tables = append([]execinfrapb.InterleavedReaderJoinerSpec_Table(nil), spec.Tables...)
   149  		return spec
   150  	}
   151  
   152  	pdCd2Spec := copySpec(pdCd1Spec)
   153  	pdCd2Spec.Tables[1].Desc = *cd2
   154  	pdCd2Spec.Tables[1].Spans = []execinfrapb.TableReaderSpan{{Span: cd2.PrimaryIndexSpan(keys.SystemSQLCodec)}}
   155  	pdCd3Spec := copySpec(pdCd1Spec)
   156  	pdCd3Spec.Tables[1].Desc = *cd3
   157  	pdCd3Spec.Tables[1].Spans = []execinfrapb.TableReaderSpan{{Span: cd3.PrimaryIndexSpan(keys.SystemSQLCodec)}}
   158  
   159  	testCases := []struct {
   160  		spec     execinfrapb.InterleavedReaderJoinerSpec
   161  		post     execinfrapb.PostProcessSpec
   162  		expected string
   163  	}{
   164  		// Simple join with a post process filter and projection.
   165  		{
   166  			spec: pdCd1Spec,
   167  			post: execinfrapb.PostProcessSpec{
   168  				Filter:     execinfrapb.Expression{Expr: "@1 <= 4 OR @3 <= 4 OR @3 > 30"},
   169  				Projection: true,
   170  				// pd.pid1, cid1, cid2
   171  				OutputColumns: []uint32{0, 3, 4},
   172  			},
   173  			expected: "[[1 1 0] [1 31 3] [1 61 6] [2 2 0] [2 32 3] [2 62 6] [3 3 0] [3 33 3] [4 4 0] [4 34 3]]",
   174  		},
   175  
   176  		// Swapped parent-child tables.
   177  		{
   178  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   179  				spec := copySpec(pdCd1Spec)
   180  				spec.Tables[0], spec.Tables[1] = spec.Tables[1], spec.Tables[0]
   181  				return spec
   182  			}(),
   183  			post: execinfrapb.PostProcessSpec{
   184  				Filter:     execinfrapb.Expression{Expr: "@1 <= 4 OR @1 > 30"},
   185  				Projection: true,
   186  				// pd.pid1, cid1, cid2
   187  				OutputColumns: []uint32{4, 1, 2},
   188  			},
   189  			expected: "[[1 1 0] [1 31 3] [1 61 6] [2 2 0] [2 32 3] [2 62 6] [3 3 0] [3 33 3] [4 4 0] [4 34 3]]",
   190  		},
   191  
   192  		// Not specifying spans on either table should return no joined rows.
   193  		{
   194  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   195  				spec := copySpec(pdCd1Spec)
   196  				// No spans specified for cd1 should return no rows.
   197  				spec.Tables[1].Spans = nil
   198  				return spec
   199  			}(),
   200  			expected: "[]",
   201  		},
   202  
   203  		{
   204  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   205  				spec := copySpec(pdCd1Spec)
   206  				// No spans specified for pd should return no rows.
   207  				spec.Tables[0].Spans = nil
   208  				return spec
   209  			}(),
   210  			expected: "[]",
   211  		},
   212  
   213  		// Intermediate filters that are logically disjoint returns no
   214  		// joined rows.
   215  		{
   216  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   217  				spec := copySpec(pdCd1Spec)
   218  				spec.Tables[0].Post = execinfrapb.PostProcessSpec{
   219  					Filter: execinfrapb.Expression{Expr: "@1 < 4"},
   220  				}
   221  				spec.Tables[1].Post = execinfrapb.PostProcessSpec{
   222  					Filter: execinfrapb.Expression{Expr: "@1 > 4"},
   223  				}
   224  				return spec
   225  			}(),
   226  			expected: "[]",
   227  		},
   228  
   229  		// Intermediate filters restrict range of joined rows.
   230  		{
   231  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   232  				spec := copySpec(pdCd2Spec)
   233  				spec.Tables[0].Post = execinfrapb.PostProcessSpec{
   234  					Filter: execinfrapb.Expression{Expr: "@1 <= 18"},
   235  				}
   236  				spec.Tables[1].Post = execinfrapb.PostProcessSpec{
   237  					Filter: execinfrapb.Expression{Expr: "@1 >= 12"},
   238  				}
   239  				return spec
   240  			}(),
   241  			post: execinfrapb.PostProcessSpec{
   242  				Projection: true,
   243  				// id column of parent and child table.
   244  				OutputColumns: []uint32{0, 3, 4},
   245  			},
   246  			expected: "[[12 12 'one-two'] [13 13 'one-three'] [14 14 'one-four'] [15 15 'one-five']]",
   247  		},
   248  
   249  		// Filters that are converted to spans with index constraints.
   250  		{
   251  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   252  				spec := copySpec(pdCd2Spec)
   253  				// Filter on id <= 18.
   254  				spec.Tables[0].Spans = []execinfrapb.TableReaderSpan{{Span: makeSpanWithRootBound(pd, -1, 18)}}
   255  				// Filter on pid >= 12.
   256  				spec.Tables[1].Spans = []execinfrapb.TableReaderSpan{{Span: makeSpanWithRootBound(pd, 12, -1)}}
   257  				return spec
   258  			}(),
   259  			post: execinfrapb.PostProcessSpec{
   260  				Projection: true,
   261  				// id column of parent and child table.
   262  				OutputColumns: []uint32{0, 3, 4},
   263  			},
   264  			expected: "[[12 12 'one-two'] [13 13 'one-three'] [14 14 'one-four'] [15 15 'one-five']]",
   265  		},
   266  
   267  		// Check that even though InterleavedReaderJoiner scans all
   268  		// children rows, only those that fit the filter on cd2Spans
   269  		// (pid >= 12) are not ignored and ultimately joined.
   270  		{
   271  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   272  				spec := copySpec(pdCd2Spec)
   273  				// Filter on pid >= 12.
   274  				spec.Tables[1].Spans = []execinfrapb.TableReaderSpan{{Span: makeSpanWithRootBound(pd, 12, -1)}}
   275  				return spec
   276  			}(),
   277  			post: execinfrapb.PostProcessSpec{
   278  				// Filter on id <= 18.
   279  				Filter:     execinfrapb.Expression{Expr: "@1 <= 18"},
   280  				Projection: true,
   281  				// id column of parent and child table.
   282  				OutputColumns: []uint32{0, 3, 4},
   283  			},
   284  			expected: "[[12 12 'one-two'] [13 13 'one-three'] [14 14 'one-four'] [15 15 'one-five']]",
   285  		},
   286  
   287  		// Intermediate projections.
   288  		{
   289  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   290  				spec := copySpec(pdCd2Spec)
   291  				spec.Tables[0].Post = execinfrapb.PostProcessSpec{
   292  					Filter:        execinfrapb.Expression{Expr: "@1 <= 18"},
   293  					Projection:    true,
   294  					OutputColumns: []uint32{0},
   295  				}
   296  				spec.Tables[1].Post = execinfrapb.PostProcessSpec{
   297  					Filter:     execinfrapb.Expression{Expr: "@1 >= 12"},
   298  					Projection: true,
   299  					// Skip the primary ID of child2.
   300  					OutputColumns: []uint32{0, 2},
   301  				}
   302  				return spec
   303  			}(),
   304  			expected: "[[12 12 'one-two'] [13 13 'one-three'] [14 14 'one-four'] [15 15 'one-five']]",
   305  		},
   306  
   307  		// Postprocess limit.
   308  		{
   309  			spec: pdCd1Spec,
   310  			post: execinfrapb.PostProcessSpec{
   311  				Limit: 5,
   312  			},
   313  			expected: "[[1 1 1 1 0] [1 1 1 31 3] [1 1 1 61 6] [2 2 2 2 0] [2 2 2 32 3]]",
   314  		},
   315  
   316  		// With an OnExpr.
   317  		{
   318  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   319  				spec := pdCd1Spec
   320  				spec.OnExpr = execinfrapb.Expression{Expr: "@4 >= 60"}
   321  				return spec
   322  			}(),
   323  			expected: "[[1 1 1 61 6] [2 2 2 62 6] [30 0 30 60 6]]",
   324  		},
   325  
   326  		// FULL OUTER joins.
   327  		{
   328  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   329  				spec := pdCd3Spec
   330  				spec.Type = sqlbase.FullOuterJoin
   331  				return spec
   332  			}(),
   333  			post: execinfrapb.PostProcessSpec{
   334  				Filter: execinfrapb.Expression{Expr: "@1 <= 7 OR @1 IS NOT DISTINCT FROM NULL"},
   335  			},
   336  			expected: `[[NULL NULL -1 -101 '-101'] [NULL NULL -1 -1 '-1'] [NULL NULL 0 0 '0'] [1 1 1 1 '1'] [2 2 NULL NULL NULL] [3 3 3 3 '3'] [3 3 3 103 '103'] [4 4 NULL NULL NULL] [5 5 5 5 '5'] [6 6 NULL NULL NULL] [7 7 NULL NULL NULL] [NULL NULL 31 31 '31'] [NULL NULL 31 131 '131'] [NULL NULL 32 32 '32']]`,
   337  		},
   338  
   339  		{
   340  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   341  				spec := copySpec(pdCd3Spec)
   342  				spec.Tables[0], spec.Tables[1] = spec.Tables[1], spec.Tables[0]
   343  				spec.Type = sqlbase.FullOuterJoin
   344  				return spec
   345  			}(),
   346  			post: execinfrapb.PostProcessSpec{
   347  				Filter: execinfrapb.Expression{Expr: "@4 <= 7 OR @4 IS NOT DISTINCT FROM NULL"},
   348  			},
   349  			expected: `[[-1 -101 '-101' NULL NULL] [-1 -1 '-1' NULL NULL] [0 0 '0' NULL NULL] [1 1 '1' 1 1] [NULL NULL NULL 2 2] [3 3 '3' 3 3] [3 103 '103' 3 3] [NULL NULL NULL 4 4] [5 5 '5' 5 5] [NULL NULL NULL 6 6] [NULL NULL NULL 7 7] [31 31 '31' NULL NULL] [31 131 '131' NULL NULL] [32 32 '32' NULL NULL]]`,
   350  		},
   351  
   352  		// LEFT OUTER joins.
   353  		{
   354  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   355  				spec := pdCd3Spec
   356  				spec.Type = sqlbase.LeftOuterJoin
   357  				return spec
   358  			}(),
   359  			post: execinfrapb.PostProcessSpec{
   360  				Filter: execinfrapb.Expression{Expr: "@1 <= 7 OR @1 IS NOT DISTINCT FROM NULL"},
   361  			},
   362  			expected: `[[1 1 1 1 '1'] [2 2 NULL NULL NULL] [3 3 3 3 '3'] [3 3 3 103 '103'] [4 4 NULL NULL NULL] [5 5 5 5 '5'] [6 6 NULL NULL NULL] [7 7 NULL NULL NULL]]`,
   363  		},
   364  		{
   365  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   366  				spec := copySpec(pdCd3Spec)
   367  				spec.Tables[0], spec.Tables[1] = spec.Tables[1], spec.Tables[0]
   368  				spec.Type = sqlbase.LeftOuterJoin
   369  				return spec
   370  			}(),
   371  			expected: `[[-1 -101 '-101' NULL NULL] [-1 -1 '-1' NULL NULL] [0 0 '0' NULL NULL] [1 1 '1' 1 1] [3 3 '3' 3 3] [3 103 '103' 3 3] [5 5 '5' 5 5] [31 31 '31' NULL NULL] [31 131 '131' NULL NULL] [32 32 '32' NULL NULL]]`,
   372  		},
   373  
   374  		// RIGHT OUTER joins.
   375  		{
   376  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   377  				spec := pdCd3Spec
   378  				spec.Type = sqlbase.RightOuterJoin
   379  				return spec
   380  			}(),
   381  			expected: `[[NULL NULL -1 -101 '-101'] [NULL NULL -1 -1 '-1'] [NULL NULL 0 0 '0'] [1 1 1 1 '1'] [3 3 3 3 '3'] [3 3 3 103 '103'] [5 5 5 5 '5'] [NULL NULL 31 31 '31'] [NULL NULL 31 131 '131'] [NULL NULL 32 32 '32']]`,
   382  		},
   383  		{
   384  			spec: func() execinfrapb.InterleavedReaderJoinerSpec {
   385  				spec := copySpec(pdCd3Spec)
   386  				spec.Tables[0], spec.Tables[1] = spec.Tables[1], spec.Tables[0]
   387  				spec.Type = sqlbase.RightOuterJoin
   388  				return spec
   389  			}(),
   390  			post: execinfrapb.PostProcessSpec{
   391  				Filter: execinfrapb.Expression{Expr: "@4 <= 7 OR @4 IS NOT DISTINCT FROM NULL"},
   392  			},
   393  			expected: `[[1 1 '1' 1 1] [NULL NULL NULL 2 2] [3 3 '3' 3 3] [3 103 '103' 3 3] [NULL NULL NULL 4 4] [5 5 '5' 5 5] [NULL NULL NULL 6 6] [NULL NULL NULL 7 7]]`,
   394  		},
   395  	}
   396  
   397  	for i, tc := range testCases {
   398  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   399  			evalCtx := tree.MakeTestingEvalContext(s.ClusterSettings())
   400  			defer evalCtx.Stop(ctx)
   401  			flowCtx := execinfra.FlowCtx{
   402  				EvalCtx: &evalCtx,
   403  				Cfg:     &execinfra.ServerConfig{Settings: s.ClusterSettings()},
   404  				// Run in a RootTxn so that there's no txn metadata produced.
   405  				Txn:    kv.NewTxn(ctx, s.DB(), s.NodeID()),
   406  				NodeID: evalCtx.NodeID,
   407  			}
   408  
   409  			out := &distsqlutils.RowBuffer{}
   410  			irj, err := newInterleavedReaderJoiner(&flowCtx, 0 /* processorID */, &tc.spec, &tc.post, out)
   411  			if err != nil {
   412  				t.Fatal(err)
   413  			}
   414  			irj.Run(ctx)
   415  			if !out.ProducerClosed() {
   416  				t.Fatalf("output RowReceiver not closed")
   417  			}
   418  
   419  			var res sqlbase.EncDatumRows
   420  			for {
   421  				row, meta := out.Next()
   422  				if meta != nil {
   423  					t.Fatalf("unexpected metadata: %v", meta)
   424  				}
   425  				if row == nil {
   426  					break
   427  				}
   428  				res = append(res, row.Copy())
   429  			}
   430  
   431  			if result := res.String(irj.OutputTypes()); result != tc.expected {
   432  				t.Errorf("invalid results.\ngot: %s\nexpected: %s'", result, tc.expected)
   433  			}
   434  		})
   435  	}
   436  }
   437  
   438  func TestInterleavedReaderJoinerErrors(t *testing.T) {
   439  	defer leaktest.AfterTest(t)()
   440  	ctx := context.Background()
   441  
   442  	s, sqlDB, kvDB := serverutils.StartServer(t, base.TestServerArgs{})
   443  	defer s.Stopper().Stop(ctx)
   444  
   445  	sqlutils.CreateTable(t, sqlDB, "parent",
   446  		"id INT PRIMARY KEY",
   447  		0,
   448  		sqlutils.ToRowFn(sqlutils.RowIdxFn),
   449  	)
   450  
   451  	sqlutils.CreateTableInterleaved(t, sqlDB, "child",
   452  		"pid INT, id INT, PRIMARY KEY (pid, id)",
   453  		"parent (pid)",
   454  		0,
   455  		sqlutils.ToRowFn(sqlutils.RowModuloShiftedFn(0), sqlutils.RowIdxFn),
   456  	)
   457  
   458  	sqlutils.CreateTableInterleaved(t, sqlDB, "grandchild",
   459  		"pid INT, cid INT, id INT, PRIMARY KEY (pid, cid, id)",
   460  		"child (pid, cid)",
   461  		0,
   462  		sqlutils.ToRowFn(sqlutils.RowModuloShiftedFn(0, 0), sqlutils.RowModuloShiftedFn(0), sqlutils.RowIdxFn),
   463  	)
   464  
   465  	pd := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "parent")
   466  	cd := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "child")
   467  	gcd := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "grandchild")
   468  
   469  	testCases := []struct {
   470  		spec     execinfrapb.InterleavedReaderJoinerSpec
   471  		post     execinfrapb.PostProcessSpec
   472  		expected string
   473  	}{
   474  		{
   475  			spec: execinfrapb.InterleavedReaderJoinerSpec{
   476  				Tables: []execinfrapb.InterleavedReaderJoinerSpec_Table{
   477  					{
   478  						Desc:     *pd,
   479  						Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_ASC}}},
   480  						Spans:    []execinfrapb.TableReaderSpan{{Span: pd.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   481  					},
   482  					{
   483  						Desc:     *cd,
   484  						Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_DESC}}},
   485  						Spans:    []execinfrapb.TableReaderSpan{{Span: cd.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   486  					},
   487  				},
   488  				Type: sqlbase.InnerJoin,
   489  			},
   490  			expected: "internal error: unmatched column orderings",
   491  		},
   492  
   493  		{
   494  			spec: execinfrapb.InterleavedReaderJoinerSpec{
   495  				Tables: []execinfrapb.InterleavedReaderJoinerSpec_Table{
   496  					{
   497  						Desc:     *pd,
   498  						Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_ASC}}},
   499  						Spans:    []execinfrapb.TableReaderSpan{{Span: pd.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   500  					},
   501  				},
   502  				Type: sqlbase.InnerJoin,
   503  			},
   504  			expected: "internal error: interleavedReaderJoiner only reads from two tables in an interleaved hierarchy",
   505  		},
   506  
   507  		{
   508  			spec: execinfrapb.InterleavedReaderJoinerSpec{
   509  				Tables: []execinfrapb.InterleavedReaderJoinerSpec_Table{
   510  					{
   511  						Desc:     *cd,
   512  						Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_ASC}}},
   513  						Spans:    []execinfrapb.TableReaderSpan{{Span: pd.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   514  					},
   515  					{
   516  						Desc:     *gcd,
   517  						Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_ASC}}},
   518  						Spans:    []execinfrapb.TableReaderSpan{{Span: gcd.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   519  					},
   520  				},
   521  				Type: sqlbase.InnerJoin,
   522  			},
   523  			expected: "internal error: interleavedReaderJoiner only supports joins on the entire interleaved prefix",
   524  		},
   525  	}
   526  
   527  	for i, tc := range testCases {
   528  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   529  			evalCtx := tree.MakeTestingEvalContext(s.ClusterSettings())
   530  			defer evalCtx.Stop(ctx)
   531  			flowCtx := execinfra.FlowCtx{
   532  				EvalCtx: &evalCtx,
   533  				Cfg:     &execinfra.ServerConfig{Settings: s.ClusterSettings()},
   534  				// Run in a RootTxn so that there's no txn metadata produced.
   535  				Txn:    kv.NewTxn(ctx, s.DB(), s.NodeID()),
   536  				NodeID: evalCtx.NodeID,
   537  			}
   538  
   539  			out := &distsqlutils.RowBuffer{}
   540  			_, err := newInterleavedReaderJoiner(&flowCtx, 0 /* processorID */, &tc.spec, &tc.post, out)
   541  			if err == nil {
   542  				t.Fatalf("expected an error")
   543  			}
   544  
   545  			// We flatten the error here since the test asserts that the
   546  			// "internal error: " prefix was added.
   547  			pgErr := pgerror.Flatten(err)
   548  			if actual := pgErr.Error(); actual != tc.expected {
   549  				t.Errorf("expected error: %s, actual: %s", tc.expected, actual)
   550  			}
   551  		})
   552  	}
   553  }
   554  
   555  func TestInterleavedReaderJoinerTrailingMetadata(t *testing.T) {
   556  	defer leaktest.AfterTest(t)()
   557  	ctx := context.Background()
   558  
   559  	s, sqlDB, kvDB := serverutils.StartServer(t, base.TestServerArgs{})
   560  	defer s.Stopper().Stop(ctx)
   561  
   562  	sqlutils.CreateTable(t, sqlDB, "parent",
   563  		"id INT PRIMARY KEY",
   564  		0, // numRows
   565  		func(row int) []tree.Datum { return nil },
   566  	)
   567  
   568  	sqlutils.CreateTableInterleaved(t, sqlDB, "child",
   569  		"pid INT, id INT, PRIMARY KEY (pid, id)",
   570  		"parent (pid)",
   571  		0,
   572  		func(row int) []tree.Datum { return nil },
   573  	)
   574  
   575  	pd := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "parent")
   576  	cd := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, sqlutils.TestDB, "child")
   577  
   578  	evalCtx := tree.MakeTestingEvalContext(s.ClusterSettings())
   579  	defer evalCtx.Stop(ctx)
   580  
   581  	// Run the flow in a snowball trace so that we can test for tracing info.
   582  	tracer := tracing.NewTracer()
   583  	ctx, sp := tracing.StartSnowballTrace(ctx, tracer, "test flow ctx")
   584  	defer sp.Finish()
   585  
   586  	rootTxn := kv.NewTxn(ctx, s.DB(), s.NodeID())
   587  	leafInputState := rootTxn.GetLeafTxnInputState(ctx)
   588  	leafTxn := kv.NewLeafTxn(ctx, s.DB(), s.NodeID(), &leafInputState)
   589  
   590  	flowCtx := execinfra.FlowCtx{
   591  		EvalCtx: &evalCtx,
   592  		Cfg:     &execinfra.ServerConfig{Settings: s.ClusterSettings()},
   593  		// Run in a LeafTxn so that txn metadata is produced.
   594  		Txn:    leafTxn,
   595  		NodeID: evalCtx.NodeID,
   596  	}
   597  
   598  	innerJoinSpec := execinfrapb.InterleavedReaderJoinerSpec{
   599  		Tables: []execinfrapb.InterleavedReaderJoinerSpec_Table{
   600  			{
   601  				Desc:     *pd,
   602  				Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_ASC}}},
   603  				Spans:    []execinfrapb.TableReaderSpan{{Span: pd.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   604  			},
   605  			{
   606  				Desc:     *cd,
   607  				Ordering: execinfrapb.Ordering{Columns: []execinfrapb.Ordering_Column{{ColIdx: 0, Direction: execinfrapb.Ordering_Column_ASC}}},
   608  				Spans:    []execinfrapb.TableReaderSpan{{Span: cd.PrimaryIndexSpan(keys.SystemSQLCodec)}},
   609  			},
   610  		},
   611  		Type: sqlbase.InnerJoin,
   612  	}
   613  
   614  	out := &distsqlutils.RowBuffer{}
   615  	irj, err := newInterleavedReaderJoiner(&flowCtx, 0 /* processorID */, &innerJoinSpec, &execinfrapb.PostProcessSpec{}, out)
   616  	if err != nil {
   617  		t.Fatal(err)
   618  	}
   619  	irj.Run(ctx)
   620  	if !out.ProducerClosed() {
   621  		t.Fatalf("output RowReceiver not closed")
   622  	}
   623  
   624  	// Check for trailing metadata.
   625  	var traceSeen, txnFinalStateSeen bool
   626  	for {
   627  		row, meta := out.Next()
   628  		if row != nil {
   629  			t.Fatalf("row was pushed unexpectedly: %s", row.String(sqlbase.OneIntCol))
   630  		}
   631  		if meta == nil {
   632  			break
   633  		}
   634  		if meta.TraceData != nil {
   635  			traceSeen = true
   636  		}
   637  		if meta.LeafTxnFinalState != nil {
   638  			txnFinalStateSeen = true
   639  		}
   640  	}
   641  	if !traceSeen {
   642  		t.Fatal("missing tracing trailing metadata")
   643  	}
   644  	if !txnFinalStateSeen {
   645  		t.Fatal("missing txn final state")
   646  	}
   647  }