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 }