github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/ordering/scan_test.go (about) 1 // Copyright 2018 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 ordering 12 13 import ( 14 "fmt" 15 "testing" 16 17 "github.com/cockroachdb/cockroach/pkg/sql/opt" 18 "github.com/cockroachdb/cockroach/pkg/sql/opt/cat" 19 "github.com/cockroachdb/cockroach/pkg/sql/opt/constraint" 20 "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" 21 "github.com/cockroachdb/cockroach/pkg/sql/opt/norm" 22 "github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical" 23 "github.com/cockroachdb/cockroach/pkg/sql/opt/testutils/testcat" 24 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 25 ) 26 27 func TestScan(t *testing.T) { 28 tc := testcat.New() 29 if _, err := tc.ExecuteDDL( 30 "CREATE TABLE t (c1 INT, c2 INT, c3 INT, c4 INT, PRIMARY KEY(c1, c2), INDEX(c3 DESC, c4))", 31 ); err != nil { 32 t.Fatal(err) 33 } 34 evalCtx := tree.NewTestingEvalContext(nil /* st */) 35 var f norm.Factory 36 f.Init(evalCtx, tc) 37 md := f.Metadata() 38 tn := tree.NewUnqualifiedTableName("t") 39 tab := md.AddTable(tc.Table(tn), tn) 40 41 if c1 := tab.ColumnID(0); c1 != 1 { 42 t.Fatalf("unexpected ID for column c1: %d\n", c1) 43 } 44 45 // Make a constraint for the index. 46 var columns constraint.Columns 47 columns.Init([]opt.OrderingColumn{-3, +4, +1, +2}) 48 var c constraint.Constraint 49 keyCtx := constraint.MakeKeyContext(&columns, evalCtx) 50 var span constraint.Span 51 span.Init( 52 constraint.MakeCompositeKey(tree.NewDInt(1), tree.NewDInt(10)), 53 constraint.IncludeBoundary, 54 constraint.MakeCompositeKey(tree.NewDInt(1), tree.NewDInt(20)), 55 constraint.IncludeBoundary, 56 ) 57 c.InitSingleSpan(&keyCtx, &span) 58 59 // We have groups of test cases for various ScanPrivates. 60 type testCase struct { 61 req string // required ordering 62 exp string // "no", "fwd", or "rev" 63 prov string // provided ordering 64 } 65 66 type testGroup struct { 67 p memo.ScanPrivate 68 cases []testCase 69 } 70 71 tests := []testGroup{ 72 { // group 1: primary index scan. 73 p: memo.ScanPrivate{ 74 Table: tab, 75 Index: cat.PrimaryIndex, 76 Cols: opt.MakeColSet(1, 2, 3, 4), 77 }, 78 cases: []testCase{ 79 {req: "", exp: "fwd", prov: ""}, // case 1 80 {req: "+1", exp: "fwd", prov: "+1"}, // case 2 81 {req: "-1", exp: "rev", prov: "-1"}, // case 3 82 {req: "+2", exp: "no"}, // case 4 83 {req: "+1,+2", exp: "fwd", prov: "+1,+2"}, // case 5 84 {req: "-1,-2", exp: "rev", prov: "-1,-2"}, // case 6 85 {req: "+1,-2", exp: "no", prov: "+1,-2"}, // case 7 86 {req: "+(1|2)", exp: "fwd", prov: "+1"}, // case 8 87 {req: "+2 opt(1)", exp: "fwd", prov: "+1,+2"}, // case 9 88 {req: "-2 opt(1)", exp: "rev", prov: "-1,-2"}, // case 10 89 }, 90 }, 91 { // group 2: secondary index scan. 92 p: memo.ScanPrivate{ 93 Table: tab, 94 Index: 1, 95 Cols: opt.MakeColSet(1, 2, 3, 4), 96 }, 97 cases: []testCase{ 98 {req: "", exp: "fwd", prov: ""}, // case 1 99 {req: "-3", exp: "fwd", prov: "-3"}, // case 2 100 {req: "+3", exp: "rev", prov: "+3"}, // case 3 101 {req: "-3,+4", exp: "fwd", prov: "-3,+4"}, // case 4 102 {req: "+3,-4", exp: "rev", prov: "+3,-4"}, // case 5 103 {req: "+3,+4", exp: "no"}, // case 6 104 {req: "-3,+4,+1", exp: "fwd", prov: "-3,+4,+1"}, // case 7 105 {req: "-3,+4,+1,+2", exp: "fwd", prov: "-3,+4,+1,+2"}, // case 8 106 {req: "-3,+2 opt(1,4)", exp: "fwd", prov: "-3,+4,+1,+2"}, // case 9 107 {req: "+3,-2 opt(1,4)", exp: "rev", prov: "+3,-4,-1,-2"}, // case 10 108 }, 109 }, 110 { // group 3: scan with limit (forces forward scan). 111 p: memo.ScanPrivate{ 112 Table: tab, 113 Index: cat.PrimaryIndex, 114 Cols: opt.MakeColSet(1, 2, 3, 4), 115 HardLimit: +10, 116 }, 117 cases: []testCase{ 118 {req: "", exp: "fwd", prov: ""}, // case 1 119 {req: "+1", exp: "fwd", prov: "+1"}, // case 2 120 {req: "-1", exp: "no"}, // case 3 121 {req: "+2", exp: "no"}, // case 4 122 {req: "+1,+2", exp: "fwd", prov: "+1,+2"}, // case 5 123 {req: "-1,-2", exp: "no"}, // case 6 124 {req: "+1,-2", exp: "no"}, // case 7 125 {req: "+(1|2)", exp: "fwd", prov: "+1"}, // case 8 126 {req: "+2 opt(1)", exp: "fwd", prov: "+1,+2"}, // case 9 127 {req: "-2 opt(1)", exp: "no"}, // case 10 128 }, 129 }, 130 { // group 4: scan with reverse limit. 131 p: memo.ScanPrivate{ 132 Table: tab, 133 Index: cat.PrimaryIndex, 134 Cols: opt.MakeColSet(1, 2, 3, 4), 135 HardLimit: -10, 136 }, 137 cases: []testCase{ 138 {req: "", exp: "rev", prov: ""}, // case 1 139 {req: "+1", exp: "no"}, // case 2 140 {req: "-1", exp: "rev", prov: "-1"}, // case 3 141 {req: "+2", exp: "no"}, // case 4 142 {req: "+1,+2", exp: "no"}, // case 5 143 {req: "-1,-2", exp: "rev", prov: "-1,-2"}, // case 6 144 {req: "+1,-2", exp: "no"}, // case 7 145 {req: "+(1|2)", exp: "no"}, // case 8 146 {req: "+2 opt(1)", exp: "no"}, // case 9 147 {req: "-2 opt(1)", exp: "rev", prov: "-1,-2"}, // case 10 148 }, 149 }, 150 { // group 5: scan with constraint. 151 p: memo.ScanPrivate{ 152 Table: tab, 153 Index: 1, 154 Cols: opt.MakeColSet(1, 2, 3, 4), 155 Constraint: &c, 156 }, 157 cases: []testCase{ 158 {req: "-3", exp: "fwd", prov: ""}, // case 1 159 {req: "-3,+4", exp: "fwd", prov: "+4"}, // case 2 160 {req: "-1 opt(3,4)", exp: "rev", prov: "-4,-1"}, // case 3 161 {req: "+1 opt(3)", exp: "no"}, // case 4 162 {req: "-(1|2) opt(3,4)", exp: "rev", prov: "-4,-1"}, // case 5 163 }, 164 }, 165 } 166 167 for gIdx, g := range tests { 168 t.Run(fmt.Sprintf("group%d", gIdx+1), func(t *testing.T) { 169 for tcIdx, tc := range g.cases { 170 t.Run(fmt.Sprintf("case%d", tcIdx+1), func(t *testing.T) { 171 req := physical.ParseOrderingChoice(tc.req) 172 ok, rev := ScanPrivateCanProvide(md, &g.p, &req) 173 res := "no" 174 if ok { 175 if rev { 176 res = "rev" 177 } else { 178 res = "fwd" 179 } 180 } 181 if res != tc.exp { 182 t.Errorf("expected %s, got %s", tc.exp, res) 183 } 184 if ok { 185 scan := f.ConstructScan(&g.p) 186 prov := scanBuildProvided(scan, &req).String() 187 if prov != tc.prov { 188 t.Errorf("expected provided '%s', got '%s'", tc.prov, prov) 189 } 190 } 191 }) 192 } 193 }) 194 } 195 }