github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/index/dolt_map_iter.go (about) 1 // Copyright 2020 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package index 16 17 import ( 18 "context" 19 "errors" 20 "io" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 24 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 25 "github.com/dolthub/dolt/go/store/types" 26 ) 27 28 func maxU64(x, y uint64) uint64 { 29 if x > y { 30 return x 31 } 32 33 return y 34 } 35 36 // KVToSqlRowConverter takes noms types.Value key value pairs and converts them directly to a sql.Row. It 37 // can be configured to only process a portion of the columns and map columns to desired output columns. 38 type KVToSqlRowConverter struct { 39 nbf *types.NomsBinFormat 40 cols []schema.Column 41 tagToSqlColIdx map[uint64]int 42 // rowSize is the number of columns in the output row. This may be bigger than the number of columns being converted, 43 // but not less. When rowSize is bigger than the number of columns being processed that means that some of the columns 44 // in the output row will be filled with nils 45 rowSize int 46 valsFromKey int 47 valsFromVal int 48 maxValTag uint64 49 } 50 51 func NewKVToSqlRowConverter(nbf *types.NomsBinFormat, tagToSqlColIdx map[uint64]int, cols []schema.Column, rowSize int) *KVToSqlRowConverter { 52 valsFromKey, valsFromVal, maxValTag := getValLocations(tagToSqlColIdx, cols) 53 54 return &KVToSqlRowConverter{ 55 nbf: nbf, 56 cols: cols, 57 tagToSqlColIdx: tagToSqlColIdx, 58 rowSize: rowSize, 59 valsFromKey: valsFromKey, 60 valsFromVal: valsFromVal, 61 maxValTag: maxValTag, 62 } 63 } 64 65 // get counts of where the values we want converted come from so we can skip entire tuples at times. 66 func getValLocations(tagToSqlColIdx map[uint64]int, cols []schema.Column) (int, int, uint64) { 67 var fromKey int 68 var fromVal int 69 var maxValTag uint64 70 for _, col := range cols { 71 if _, ok := tagToSqlColIdx[col.Tag]; ok { 72 if col.IsPartOfPK { 73 fromKey++ 74 } else { 75 fromVal++ 76 maxValTag = maxU64(maxValTag, col.Tag) 77 } 78 } 79 } 80 81 return fromKey, fromVal, maxValTag 82 } 83 84 // NewKVToSqlRowConverterForCols returns a KVToSqlConverter instance based on the list of columns passed in 85 func NewKVToSqlRowConverterForCols(nbf *types.NomsBinFormat, sch schema.Schema, columns []uint64) *KVToSqlRowConverter { 86 allCols := sch.GetAllCols().GetColumns() 87 tagToSqlColIdx := make(map[uint64]int) 88 var outCols []schema.Column 89 if len(columns) > 0 { 90 outCols = make([]schema.Column, len(columns)) 91 for i, tag := range columns { 92 schIdx := sch.GetAllCols().TagToIdx[tag] 93 outCols[i] = allCols[schIdx] 94 tagToSqlColIdx[tag] = i 95 } 96 } else { 97 outCols = allCols 98 for i, col := range allCols { 99 tagToSqlColIdx[col.Tag] = i 100 } 101 } 102 103 return NewKVToSqlRowConverter(nbf, tagToSqlColIdx, outCols, len(outCols)) 104 } 105 106 // ConvertKVToSqlRow returns a sql.Row generated from the key and value provided. 107 func (conv *KVToSqlRowConverter) ConvertKVToSqlRow(k, v types.Value) (sql.Row, error) { 108 keyTup, ok := k.(types.Tuple) 109 110 if !ok { 111 return nil, errors.New("invalid key is not a tuple") 112 } 113 114 var valTup types.Tuple 115 if !types.IsNull(v) { 116 valTup, ok = v.(types.Tuple) 117 118 if !ok { 119 return nil, errors.New("invalid value is not a tuple") 120 } 121 } else { 122 valTup = types.EmptyTuple(conv.nbf) 123 } 124 125 return conv.ConvertKVTuplesToSqlRow(keyTup, valTup) 126 } 127 128 // ConvertKVTuplesToSqlRow returns a sql.Row generated from the key and value provided. 129 func (conv *KVToSqlRowConverter) ConvertKVTuplesToSqlRow(k, v types.Tuple) (sql.Row, error) { 130 tupItr := types.TupleItrPool.Get().(*types.TupleIterator) 131 defer types.TupleItrPool.Put(tupItr) 132 133 cols := make([]interface{}, conv.rowSize) 134 if conv.valsFromKey > 0 { 135 // keys are not in sorted order so cannot use max tag to early exit 136 err := conv.processTuple(cols, conv.valsFromKey, 0xFFFFFFFFFFFFFFFF, k, tupItr) 137 138 if err != nil { 139 return nil, err 140 } 141 } 142 143 if conv.valsFromVal > 0 { 144 err := conv.processTuple(cols, conv.valsFromVal, conv.maxValTag, v, tupItr) 145 146 if err != nil { 147 return nil, err 148 } 149 } 150 151 return cols, nil 152 } 153 154 func (conv *KVToSqlRowConverter) processTuple(cols []interface{}, valsToFill int, maxTag uint64, tup types.Tuple, tupItr *types.TupleIterator) error { 155 err := tupItr.InitForTuple(tup) 156 157 if err != nil { 158 return err 159 } 160 161 nbf := tup.Format() 162 primReader, numPrimitives := tupItr.CodecReader() 163 164 filled := 0 165 for pos := uint64(0); pos+1 < numPrimitives; pos += 2 { 166 if filled >= valsToFill { 167 break 168 } 169 170 tagKind := primReader.ReadKind() 171 172 if tagKind != types.UintKind { 173 return errors.New("Encountered unexpected kind while attempting to read tag") 174 } 175 176 tag64 := primReader.ReadUint() 177 178 if tag64 > maxTag && tag64 != schema.KeylessRowCardinalityTag && tag64 != schema.KeylessRowIdTag { 179 break 180 } 181 182 if sqlColIdx, ok := conv.tagToSqlColIdx[tag64]; !ok { 183 err = primReader.SkipValue(nbf) 184 185 if err != nil { 186 return err 187 } 188 } else { 189 cols[sqlColIdx], err = conv.cols[sqlColIdx].TypeInfo.ReadFrom(nbf, primReader) 190 191 if err != nil { 192 return err 193 } 194 195 filled++ 196 } 197 } 198 199 return nil 200 } 201 202 // KVGetFunc defines a function that returns a Key Value pair 203 type KVGetFunc func(ctx context.Context) (types.Tuple, types.Tuple, error) 204 205 func GetGetFuncForMapIter(nbf *types.NomsBinFormat, mapItr types.MapIterator) func(ctx context.Context) (types.Tuple, types.Tuple, error) { 206 return func(ctx context.Context) (types.Tuple, types.Tuple, error) { 207 k, v, err := mapItr.Next(ctx) 208 209 if err != nil { 210 return types.Tuple{}, types.Tuple{}, err 211 } else if k == nil { 212 return types.Tuple{}, types.Tuple{}, io.EOF 213 } 214 215 valTup, ok := v.(types.Tuple) 216 if !ok { 217 valTup = types.EmptyTuple(nbf) 218 } 219 220 return k.(types.Tuple), valTup, nil 221 } 222 } 223 224 // DoltMapIter uses a types.MapIterator to iterate over a types.Map and returns sql.Row instances that it reads and 225 // converts 226 type DoltMapIter struct { 227 kvGet KVGetFunc 228 closeKVGetter func() error 229 conv *KVToSqlRowConverter 230 } 231 232 // NewDoltMapIter returns a new DoltMapIter 233 func NewDoltMapIter(keyValGet KVGetFunc, closeKVGetter func() error, conv *KVToSqlRowConverter) *DoltMapIter { 234 return &DoltMapIter{ 235 kvGet: keyValGet, 236 closeKVGetter: closeKVGetter, 237 conv: conv, 238 } 239 } 240 241 // Next returns the next sql.Row until all rows are returned at which point (nil, io.EOF) is returned. 242 func (dmi *DoltMapIter) Next(ctx *sql.Context) (sql.Row, error) { 243 k, v, err := dmi.kvGet(ctx) 244 if err != nil { 245 return nil, err 246 } 247 248 return dmi.conv.ConvertKVTuplesToSqlRow(k, v) 249 } 250 251 func (dmi *DoltMapIter) Close(*sql.Context) error { 252 if dmi.closeKVGetter != nil { 253 return dmi.closeKVGetter() 254 } 255 256 return nil 257 }