github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/physicalplan/physical_plan_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  // This file defines structures and basic functionality that is useful when
    12  // building distsql plans. It does not contain the actual physical planning
    13  // code.
    14  
    15  package physicalplan
    16  
    17  import (
    18  	"reflect"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    26  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    27  )
    28  
    29  func TestProjectionAndRendering(t *testing.T) {
    30  	defer leaktest.AfterTest(t)()
    31  
    32  	// We don't care about actual types, so we use ColumnType.Locale to store an
    33  	// arbitrary string.
    34  	strToType := func(s string) *types.T {
    35  		return types.MakeCollatedString(types.String, s)
    36  	}
    37  
    38  	// For each test case we set up processors with a certain post-process spec,
    39  	// run a function that adds a projection or a rendering, and verify the output
    40  	// post-process spec (as well as ResultTypes, Ordering).
    41  	testCases := []struct {
    42  		// post-process spec of the last stage in the plan.
    43  		post execinfrapb.PostProcessSpec
    44  		// Comma-separated list of result "types".
    45  		resultTypes string
    46  		// ordering in a string like "0,1,-2" (negative values = descending). Can't
    47  		// express descending on column 0, deal with it.
    48  		ordering string
    49  
    50  		// function that applies a projection or rendering.
    51  		action func(p *PhysicalPlan)
    52  
    53  		// expected post-process spec of the last stage in the resulting plan.
    54  		expPost execinfrapb.PostProcessSpec
    55  		// expected result types, same format and strings as resultTypes.
    56  		expResultTypes string
    57  		// expected ordeering, same format as ordering.
    58  		expOrdering string
    59  	}{
    60  		{
    61  			// Simple projection.
    62  			post:        execinfrapb.PostProcessSpec{},
    63  			resultTypes: "A,B,C,D",
    64  
    65  			action: func(p *PhysicalPlan) {
    66  				p.AddProjection([]uint32{1, 3, 2})
    67  			},
    68  
    69  			expPost: execinfrapb.PostProcessSpec{
    70  				Projection:    true,
    71  				OutputColumns: []uint32{1, 3, 2},
    72  			},
    73  			expResultTypes: "B,D,C",
    74  		},
    75  
    76  		{
    77  			// Projection with ordering.
    78  			post:        execinfrapb.PostProcessSpec{},
    79  			resultTypes: "A,B,C,D",
    80  			ordering:    "2",
    81  
    82  			action: func(p *PhysicalPlan) {
    83  				p.AddProjection([]uint32{2})
    84  			},
    85  
    86  			expPost: execinfrapb.PostProcessSpec{
    87  				Projection:    true,
    88  				OutputColumns: []uint32{2},
    89  			},
    90  			expResultTypes: "C",
    91  			expOrdering:    "0",
    92  		},
    93  
    94  		{
    95  			// Projection with ordering that refers to non-projected column.
    96  			post:        execinfrapb.PostProcessSpec{},
    97  			resultTypes: "A,B,C,D",
    98  			ordering:    "2,-1,3",
    99  
   100  			action: func(p *PhysicalPlan) {
   101  				p.AddProjection([]uint32{2, 3})
   102  			},
   103  
   104  			expPost: execinfrapb.PostProcessSpec{
   105  				Projection:    true,
   106  				OutputColumns: []uint32{2, 3, 1},
   107  			},
   108  			expResultTypes: "C,D,B",
   109  			expOrdering:    "0,-2,1",
   110  		},
   111  
   112  		{
   113  			// Projection after projection.
   114  			post: execinfrapb.PostProcessSpec{
   115  				Projection:    true,
   116  				OutputColumns: []uint32{5, 6, 7, 8},
   117  			},
   118  			resultTypes: "A,B,C,D",
   119  			ordering:    "3",
   120  
   121  			action: func(p *PhysicalPlan) {
   122  				p.AddProjection([]uint32{3, 1})
   123  			},
   124  
   125  			expPost: execinfrapb.PostProcessSpec{
   126  				Projection:    true,
   127  				OutputColumns: []uint32{8, 6},
   128  			},
   129  			expResultTypes: "D,B",
   130  			expOrdering:    "0",
   131  		},
   132  
   133  		{
   134  			// Projection after projection; ordering refers to non-projected column.
   135  			post: execinfrapb.PostProcessSpec{
   136  				Projection:    true,
   137  				OutputColumns: []uint32{5, 6, 7, 8},
   138  			},
   139  			resultTypes: "A,B,C,D",
   140  			ordering:    "0,3",
   141  
   142  			action: func(p *PhysicalPlan) {
   143  				p.AddProjection([]uint32{3, 1})
   144  			},
   145  
   146  			expPost: execinfrapb.PostProcessSpec{
   147  				Projection:    true,
   148  				OutputColumns: []uint32{8, 6, 5},
   149  			},
   150  			expResultTypes: "D,B,A",
   151  			expOrdering:    "2,0",
   152  		},
   153  
   154  		{
   155  			// Projection after rendering.
   156  			post: execinfrapb.PostProcessSpec{
   157  				RenderExprs: []execinfrapb.Expression{{Expr: "@5"}, {Expr: "@1 + @2"}, {Expr: "@6"}},
   158  			},
   159  			resultTypes: "A,B,C",
   160  			ordering:    "2",
   161  
   162  			action: func(p *PhysicalPlan) {
   163  				p.AddProjection([]uint32{2, 0})
   164  			},
   165  
   166  			expPost: execinfrapb.PostProcessSpec{
   167  				RenderExprs: []execinfrapb.Expression{{Expr: "@6"}, {Expr: "@5"}},
   168  			},
   169  			expResultTypes: "C,A",
   170  			expOrdering:    "0",
   171  		},
   172  
   173  		{
   174  			// Projection after rendering; ordering refers to non-projected column.
   175  			post: execinfrapb.PostProcessSpec{
   176  				RenderExprs: []execinfrapb.Expression{{Expr: "@5"}, {Expr: "@1 + @2"}, {Expr: "@6"}},
   177  			},
   178  			resultTypes: "A,B,C",
   179  			ordering:    "2,-1",
   180  
   181  			action: func(p *PhysicalPlan) {
   182  				p.AddProjection([]uint32{2})
   183  			},
   184  
   185  			expPost: execinfrapb.PostProcessSpec{
   186  				RenderExprs: []execinfrapb.Expression{{Expr: "@6"}, {Expr: "@1 + @2"}},
   187  			},
   188  			expResultTypes: "C,B",
   189  			expOrdering:    "0,-1",
   190  		},
   191  
   192  		{
   193  			// Identity rendering.
   194  			post:        execinfrapb.PostProcessSpec{},
   195  			resultTypes: "A,B,C,D",
   196  
   197  			action: func(p *PhysicalPlan) {
   198  				if err := p.AddRendering(
   199  					[]tree.TypedExpr{
   200  						&tree.IndexedVar{Idx: 10},
   201  						&tree.IndexedVar{Idx: 11},
   202  						&tree.IndexedVar{Idx: 12},
   203  						&tree.IndexedVar{Idx: 13},
   204  					},
   205  					fakeExprContext{},
   206  					[]int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3},
   207  					[]*types.T{strToType("A"), strToType("B"), strToType("C"), strToType("D")},
   208  				); err != nil {
   209  					t.Fatal(err)
   210  				}
   211  			},
   212  
   213  			expPost:        execinfrapb.PostProcessSpec{},
   214  			expResultTypes: "A,B,C,D",
   215  		},
   216  
   217  		{
   218  			// Rendering that becomes projection.
   219  			post:        execinfrapb.PostProcessSpec{},
   220  			resultTypes: "A,B,C,D",
   221  
   222  			action: func(p *PhysicalPlan) {
   223  				if err := p.AddRendering(
   224  					[]tree.TypedExpr{
   225  						&tree.IndexedVar{Idx: 11},
   226  						&tree.IndexedVar{Idx: 13},
   227  						&tree.IndexedVar{Idx: 12},
   228  					},
   229  					fakeExprContext{},
   230  					[]int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3},
   231  					[]*types.T{strToType("B"), strToType("D"), strToType("C")},
   232  				); err != nil {
   233  					t.Fatal(err)
   234  				}
   235  
   236  			},
   237  
   238  			expPost: execinfrapb.PostProcessSpec{
   239  				Projection:    true,
   240  				OutputColumns: []uint32{1, 3, 2},
   241  			},
   242  			expResultTypes: "B,D,C",
   243  		},
   244  
   245  		{
   246  			// Rendering with ordering that refers to non-projected column.
   247  			post:        execinfrapb.PostProcessSpec{},
   248  			resultTypes: "A,B,C,D",
   249  			ordering:    "3",
   250  
   251  			action: func(p *PhysicalPlan) {
   252  				if err := p.AddRendering(
   253  					[]tree.TypedExpr{
   254  						&tree.BinaryExpr{
   255  							Operator: tree.Plus,
   256  							Left:     &tree.IndexedVar{Idx: 1},
   257  							Right:    &tree.IndexedVar{Idx: 2},
   258  						},
   259  					},
   260  					fakeExprContext{},
   261  					[]int{0, 1, 2},
   262  					[]*types.T{strToType("X")},
   263  				); err != nil {
   264  					t.Fatal(err)
   265  				}
   266  			},
   267  
   268  			expPost: execinfrapb.PostProcessSpec{
   269  				RenderExprs: []execinfrapb.Expression{{Expr: "@2 + @3"}, {Expr: "@4"}},
   270  			},
   271  			expResultTypes: "X,D",
   272  			expOrdering:    "1",
   273  		},
   274  		{
   275  			// Rendering with ordering that refers to non-projected column after
   276  			// projection.
   277  			post: execinfrapb.PostProcessSpec{
   278  				Projection:    true,
   279  				OutputColumns: []uint32{5, 6, 7, 8},
   280  			},
   281  			resultTypes: "A,B,C,D",
   282  			ordering:    "0,-3",
   283  
   284  			action: func(p *PhysicalPlan) {
   285  				if err := p.AddRendering(
   286  					[]tree.TypedExpr{
   287  						&tree.BinaryExpr{
   288  							Operator: tree.Plus,
   289  							Left:     &tree.IndexedVar{Idx: 11},
   290  							Right:    &tree.IndexedVar{Idx: 12},
   291  						},
   292  						&tree.IndexedVar{Idx: 10},
   293  					},
   294  					fakeExprContext{},
   295  					[]int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2},
   296  					[]*types.T{strToType("X"), strToType("A")},
   297  				); err != nil {
   298  					t.Fatal(err)
   299  				}
   300  			},
   301  
   302  			expPost: execinfrapb.PostProcessSpec{
   303  				RenderExprs: []execinfrapb.Expression{{Expr: "@7 + @8"}, {Expr: "@6"}, {Expr: "@9"}},
   304  			},
   305  			expResultTypes: "X,A,D",
   306  			expOrdering:    "1,-2",
   307  		},
   308  	}
   309  
   310  	for testIdx, tc := range testCases {
   311  		p := PhysicalPlan{
   312  			Processors: []Processor{
   313  				{Spec: execinfrapb.ProcessorSpec{Post: tc.post}},
   314  				{Spec: execinfrapb.ProcessorSpec{Post: tc.post}},
   315  			},
   316  			ResultRouters: []ProcessorIdx{0, 1},
   317  		}
   318  
   319  		if tc.ordering != "" {
   320  			for _, s := range strings.Split(tc.ordering, ",") {
   321  				var o execinfrapb.Ordering_Column
   322  				col, _ := strconv.Atoi(s)
   323  				if col >= 0 {
   324  					o.ColIdx = uint32(col)
   325  					o.Direction = execinfrapb.Ordering_Column_ASC
   326  				} else {
   327  					o.ColIdx = uint32(-col)
   328  					o.Direction = execinfrapb.Ordering_Column_DESC
   329  				}
   330  				p.MergeOrdering.Columns = append(p.MergeOrdering.Columns, o)
   331  			}
   332  		}
   333  
   334  		for _, s := range strings.Split(tc.resultTypes, ",") {
   335  			p.ResultTypes = append(p.ResultTypes, strToType(s))
   336  		}
   337  
   338  		tc.action(&p)
   339  
   340  		if post := p.GetLastStagePost(); !reflect.DeepEqual(post, tc.expPost) {
   341  			t.Errorf("%d: incorrect post:\n%s\nexpected:\n%s", testIdx, &post, &tc.expPost)
   342  		}
   343  		var resTypes []string
   344  		for _, t := range p.ResultTypes {
   345  			resTypes = append(resTypes, t.Locale())
   346  		}
   347  		if r := strings.Join(resTypes, ","); r != tc.expResultTypes {
   348  			t.Errorf("%d: incorrect result types: %s expected %s", testIdx, r, tc.expResultTypes)
   349  		}
   350  
   351  		var ord []string
   352  		for _, c := range p.MergeOrdering.Columns {
   353  			i := int(c.ColIdx)
   354  			if c.Direction == execinfrapb.Ordering_Column_DESC {
   355  				i = -i
   356  			}
   357  			ord = append(ord, strconv.Itoa(i))
   358  		}
   359  		if o := strings.Join(ord, ","); o != tc.expOrdering {
   360  			t.Errorf("%d: incorrect ordering: '%s' expected '%s'", testIdx, o, tc.expOrdering)
   361  		}
   362  	}
   363  }
   364  
   365  func TestMergeResultTypes(t *testing.T) {
   366  	defer leaktest.AfterTest(t)()
   367  
   368  	empty := []*types.T{}
   369  	null := []*types.T{types.Unknown}
   370  	typeInt := []*types.T{types.Int}
   371  
   372  	testData := []struct {
   373  		name     string
   374  		left     []*types.T
   375  		right    []*types.T
   376  		expected *[]*types.T
   377  		err      bool
   378  	}{
   379  		{"both empty", empty, empty, &empty, false},
   380  		{"left empty", empty, typeInt, nil, true},
   381  		{"right empty", typeInt, empty, nil, true},
   382  		{"both null", null, null, &null, false},
   383  		{"left null", null, typeInt, &typeInt, false},
   384  		{"right null", typeInt, null, &typeInt, false},
   385  		{"both int", typeInt, typeInt, &typeInt, false},
   386  	}
   387  	for _, td := range testData {
   388  		t.Run(td.name, func(t *testing.T) {
   389  			result, err := MergeResultTypes(td.left, td.right)
   390  			if td.err {
   391  				if err == nil {
   392  					t.Fatalf("expected error, got %+v", result)
   393  				}
   394  				return
   395  			}
   396  			if err != nil {
   397  				t.Fatalf("unexpected error: %s", err)
   398  			}
   399  			if !reflect.DeepEqual(*td.expected, result) {
   400  				t.Fatalf("expected %+v, got %+v", *td.expected, result)
   401  			}
   402  		})
   403  	}
   404  }