github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/xform/index_scan_builder.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 xform 12 13 import ( 14 "github.com/cockroachdb/cockroach/pkg/sql/opt" 15 "github.com/cockroachdb/cockroach/pkg/sql/opt/cat" 16 "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" 17 "github.com/cockroachdb/cockroach/pkg/sql/opt/norm" 18 "github.com/cockroachdb/errors" 19 ) 20 21 // indexScanBuilder composes a constrained, limited scan over a table index. 22 // Any filters are created as close to the scan as possible, and index joins can 23 // be used to scan a non-covering index. For example, in order to construct: 24 // 25 // (IndexJoin 26 // (Select (Scan $scanPrivate) $filters) 27 // $indexJoinPrivate 28 // ) 29 // 30 // make the following calls: 31 // 32 // var sb indexScanBuilder 33 // sb.init(c, tabID) 34 // sb.setScan(scanPrivate) 35 // sb.addSelect(filters) 36 // sb.addIndexJoin(cols) 37 // expr := sb.build() 38 // 39 type indexScanBuilder struct { 40 c *CustomFuncs 41 f *norm.Factory 42 mem *memo.Memo 43 tabID opt.TableID 44 pkCols opt.ColSet 45 scanPrivate memo.ScanPrivate 46 innerFilters memo.FiltersExpr 47 outerFilters memo.FiltersExpr 48 indexJoinPrivate memo.IndexJoinPrivate 49 } 50 51 func (b *indexScanBuilder) init(c *CustomFuncs, tabID opt.TableID) { 52 b.c = c 53 b.f = c.e.f 54 b.mem = c.e.mem 55 b.tabID = tabID 56 } 57 58 // primaryKeyCols returns the columns from the scanned table's primary index. 59 func (b *indexScanBuilder) primaryKeyCols() opt.ColSet { 60 // Ensure that pkCols set is initialized with the primary index columns. 61 if b.pkCols.Empty() { 62 primaryIndex := b.c.e.mem.Metadata().Table(b.tabID).Index(cat.PrimaryIndex) 63 for i, cnt := 0, primaryIndex.KeyColumnCount(); i < cnt; i++ { 64 b.pkCols.Add(b.tabID.ColumnID(primaryIndex.Column(i).Ordinal)) 65 } 66 } 67 return b.pkCols 68 } 69 70 // setScan constructs a standalone Scan expression. As a side effect, it clears 71 // any expressions added during previous invocations of the builder. setScan 72 // makes a copy of scanPrivate so that it doesn't escape. 73 func (b *indexScanBuilder) setScan(scanPrivate *memo.ScanPrivate) { 74 b.scanPrivate = *scanPrivate 75 b.innerFilters = nil 76 b.outerFilters = nil 77 b.indexJoinPrivate = memo.IndexJoinPrivate{} 78 } 79 80 // addSelect wraps the input expression with a Select expression having the 81 // given filter. 82 func (b *indexScanBuilder) addSelect(filters memo.FiltersExpr) { 83 if len(filters) != 0 { 84 if b.indexJoinPrivate.Table == 0 { 85 if b.innerFilters != nil { 86 panic(errors.AssertionFailedf("cannot call addSelect methods twice before index join is added")) 87 } 88 b.innerFilters = filters 89 } else { 90 if b.outerFilters != nil { 91 panic(errors.AssertionFailedf("cannot call addSelect methods twice after index join is added")) 92 } 93 b.outerFilters = filters 94 } 95 } 96 } 97 98 // addSelectAfterSplit first splits the given filter into two parts: a filter 99 // that only involves columns in the given set, and a remaining filter that 100 // includes everything else. The filter that is bound by the columns becomes a 101 // Select expression that wraps the input expression, and the remaining filter 102 // is returned (or 0 if there is no remaining filter). 103 func (b *indexScanBuilder) addSelectAfterSplit( 104 filters memo.FiltersExpr, cols opt.ColSet, 105 ) (remainingFilters memo.FiltersExpr) { 106 if len(filters) == 0 { 107 return nil 108 } 109 110 if b.c.FiltersBoundBy(filters, cols) { 111 // Filter is fully bound by the cols, so add entire filter. 112 b.addSelect(filters) 113 return nil 114 } 115 116 // Try to split filter. 117 boundConditions := b.c.ExtractBoundConditions(filters, cols) 118 if len(boundConditions) == 0 { 119 // None of the filter conjuncts can be bound by the cols, so no expression 120 // can be added. 121 return filters 122 } 123 124 // Add conditions which are fully bound by the cols and return the rest. 125 b.addSelect(boundConditions) 126 return b.c.ExtractUnboundConditions(filters, cols) 127 } 128 129 // addIndexJoin wraps the input expression with an IndexJoin expression that 130 // produces the given set of columns by lookup in the primary index. 131 func (b *indexScanBuilder) addIndexJoin(cols opt.ColSet) { 132 if b.indexJoinPrivate.Table != 0 { 133 panic(errors.AssertionFailedf("cannot call addIndexJoin twice")) 134 } 135 if b.outerFilters != nil { 136 panic(errors.AssertionFailedf("cannot add index join after an outer filter has been added")) 137 } 138 b.indexJoinPrivate = memo.IndexJoinPrivate{ 139 Table: b.tabID, 140 Cols: cols, 141 } 142 } 143 144 // build constructs the final memo expression by composing together the various 145 // expressions that were specified by previous calls to various add methods. 146 func (b *indexScanBuilder) build(grp memo.RelExpr) { 147 // 1. Only scan. 148 if len(b.innerFilters) == 0 && b.indexJoinPrivate.Table == 0 { 149 b.mem.AddScanToGroup(&memo.ScanExpr{ScanPrivate: b.scanPrivate}, grp) 150 return 151 } 152 153 // 2. Wrap scan in inner filter if it was added. 154 input := b.f.ConstructScan(&b.scanPrivate) 155 if len(b.innerFilters) != 0 { 156 if b.indexJoinPrivate.Table == 0 { 157 b.mem.AddSelectToGroup(&memo.SelectExpr{Input: input, Filters: b.innerFilters}, grp) 158 return 159 } 160 161 input = b.f.ConstructSelect(input, b.innerFilters) 162 } 163 164 // 3. Wrap input in index join if it was added. 165 if b.indexJoinPrivate.Table != 0 { 166 if len(b.outerFilters) == 0 { 167 indexJoin := &memo.IndexJoinExpr{Input: input, IndexJoinPrivate: b.indexJoinPrivate} 168 b.mem.AddIndexJoinToGroup(indexJoin, grp) 169 return 170 } 171 172 input = b.f.ConstructIndexJoin(input, &b.indexJoinPrivate) 173 } 174 175 // 4. Wrap input in outer filter (which must exist at this point). 176 if len(b.outerFilters) == 0 { 177 // indexJoinDef == 0: outerFilters == 0 handled by #1 and #2 above. 178 // indexJoinDef != 0: outerFilters == 0 handled by #3 above. 179 panic(errors.AssertionFailedf("outer filter cannot be 0 at this point")) 180 } 181 b.mem.AddSelectToGroup(&memo.SelectExpr{Input: input, Filters: b.outerFilters}, grp) 182 }