github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/merge/conflict_reader.go (about) 1 // Copyright 2019 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 merge 16 17 import ( 18 "context" 19 "errors" 20 "io" 21 22 "github.com/dolthub/dolt/go/libraries/doltcore/conflict" 23 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable" 24 25 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 26 "github.com/dolthub/dolt/go/libraries/doltcore/row" 27 "github.com/dolthub/dolt/go/libraries/doltcore/rowconv" 28 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 29 "github.com/dolthub/dolt/go/store/types" 30 ) 31 32 const ( 33 oursStr = "our" 34 theirsStr = "their" 35 baseStr = "base" 36 ) 37 38 const ( 39 ConflictDiffTypeAdded = "added" 40 ConflictDiffTypeModified = "modified" 41 ConflictDiffTypeRemoved = "removed" 42 ) 43 44 // ConflictReader is a class providing a NextConflict function which can be used in a pipeline as a pipeline.SourceFunc, 45 // or it can be used to read each conflict 46 type ConflictReader struct { 47 confItr types.MapIterator 48 joiner *rowconv.Joiner 49 sch schema.Schema 50 nbf *types.NomsBinFormat 51 keyless bool 52 } 53 54 // NewConflictReader returns a new conflict reader for a given table 55 func NewConflictReader(ctx context.Context, tbl *doltdb.Table, tblName string) (*ConflictReader, error) { 56 base, sch, mergeSch, err := tbl.GetConflictSchemas(ctx, tblName) // tblName unused by old storage format 57 if err != nil { 58 return nil, err 59 } 60 if base == nil || sch == nil || mergeSch == nil { 61 base, err = tbl.GetSchema(ctx) 62 sch, mergeSch = base, base 63 } 64 if err != nil { 65 return nil, err 66 } 67 68 joiner, err := rowconv.NewJoiner( 69 []rowconv.NamedSchema{ 70 {Name: baseStr, Sch: base}, 71 {Name: oursStr, Sch: sch}, 72 {Name: theirsStr, Sch: mergeSch}, 73 }, map[string]rowconv.ColNamingFunc{ 74 baseStr: func(colName string) string { return baseStr + "_" + colName }, 75 oursStr: func(colName string) string { return oursStr + "_" + colName }, 76 theirsStr: func(colName string) string { return theirsStr + "_" + colName }, 77 }, 78 ) 79 if err != nil { 80 return nil, err 81 } 82 readerSch := joiner.GetSchema() 83 readerSch, err = readerSch.AddColumn(schema.NewColumn("our_diff_type", schema.DoltConflictsOurDiffTypeTag, types.StringKind, false), nil) 84 if err != nil { 85 return nil, err 86 } 87 readerSch, err = readerSch.AddColumn(schema.NewColumn("their_diff_type", schema.DoltConflictsTheirDiffTypeTag, types.StringKind, false), nil) 88 if err != nil { 89 return nil, err 90 } 91 92 var keyless bool 93 if schema.IsKeyless(sch) { 94 keyless = true 95 readerSch, err = readerSch.AddColumn( 96 schema.NewColumn( 97 "base_cardinality", 98 schema.DoltConflictsBaseCardinalityTag, 99 types.UintKind, 100 false), 101 nil) 102 if err != nil { 103 return nil, err 104 } 105 readerSch, err = readerSch.AddColumn( 106 schema.NewColumn( 107 "our_cardinality", 108 schema.DoltConflictsOurCardinalityTag, 109 types.UintKind, 110 false), 111 nil) 112 if err != nil { 113 return nil, err 114 } 115 readerSch, err = readerSch.AddColumn( 116 schema.NewColumn( 117 "their_cardinality", 118 schema.DoltConflictsTheirCardinalityTag, 119 types.UintKind, 120 false), 121 nil) 122 if err != nil { 123 return nil, err 124 } 125 } 126 127 _, confIdx, err := tbl.GetConflicts(ctx) 128 if err != nil { 129 return nil, err 130 } 131 132 if confIdx.Format() == types.Format_DOLT { 133 panic("conflict reader not implemented for new storage format") 134 } 135 136 confData := durable.NomsMapFromConflictIndex(confIdx) 137 confItr, err := confData.Iterator(ctx) 138 if err != nil { 139 return nil, err 140 } 141 142 return &ConflictReader{ 143 confItr: confItr, 144 joiner: joiner, 145 sch: readerSch, 146 nbf: tbl.Format(), 147 keyless: keyless, 148 }, nil 149 } 150 151 // GetSchema gets the schema of the rows that this reader will return 152 func (cr *ConflictReader) GetSchema() schema.Schema { 153 return cr.sch 154 } 155 156 // GetJoiner returns the joiner used to join a row with its base, and merge versions 157 func (cr *ConflictReader) GetJoiner() *rowconv.Joiner { 158 return cr.joiner 159 } 160 161 // NextConflict can be called successively to retrieve the conflicts in a table. Once all conflicts have been returned 162 // io.EOF will be returned in the error field. This can be used in a pipeline, or to iterate through all the conflicts 163 // in a table. 164 func (cr *ConflictReader) NextConflict(ctx context.Context) (row.Row, error) { 165 key, value, err := cr.confItr.Next(ctx) 166 167 if err != nil { 168 return nil, err 169 } 170 171 if key == nil { 172 return nil, io.EOF 173 } 174 175 keyTpl := key.(types.Tuple) 176 conflict, err := conflict.ConflictFromTuple(value.(types.Tuple)) 177 if err != nil { 178 return nil, err 179 } 180 181 var joinedRow row.Row 182 if !cr.keyless { 183 joinedRow, err = cr.pkJoinedRow(keyTpl, conflict) 184 } else { 185 joinedRow, err = cr.keylessJoinedRow(keyTpl, conflict) 186 } 187 if err != nil { 188 return nil, err 189 } 190 191 ourDiffType := getDiffType(conflict.Base, conflict.Value) 192 theirDiffType := getDiffType(conflict.Base, conflict.MergeValue) 193 joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsOurDiffTypeTag, types.String(ourDiffType), cr.sch) 194 if err != nil { 195 return nil, err 196 } 197 joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsTheirDiffTypeTag, types.String(theirDiffType), cr.sch) 198 if err != nil { 199 return nil, err 200 } 201 202 return joinedRow, nil 203 } 204 205 func (cr *ConflictReader) pkJoinedRow(key types.Tuple, conflict conflict.Conflict) (row.Row, error) { 206 var err error 207 namedRows := make(map[string]row.Row) 208 209 if !types.IsNull(conflict.Base) { 210 namedRows[baseStr], err = row.FromNoms(cr.joiner.SchemaForName(baseStr), key, conflict.Base.(types.Tuple)) 211 if err != nil { 212 return nil, err 213 } 214 } 215 if !types.IsNull(conflict.Value) { 216 namedRows[oursStr], err = row.FromNoms(cr.joiner.SchemaForName(oursStr), key, conflict.Value.(types.Tuple)) 217 if err != nil { 218 return nil, err 219 } 220 } 221 if !types.IsNull(conflict.MergeValue) { 222 namedRows[theirsStr], err = row.FromNoms(cr.joiner.SchemaForName(theirsStr), key, conflict.MergeValue.(types.Tuple)) 223 if err != nil { 224 return nil, err 225 } 226 } 227 228 joinedRow, err := cr.joiner.Join(namedRows) 229 if err != nil { 230 return nil, err 231 } 232 233 return joinedRow, nil 234 } 235 236 func (cr *ConflictReader) keylessJoinedRow(key types.Tuple, conflict conflict.Conflict) (row.Row, error) { 237 var err error 238 namedRows := make(map[string]row.Row) 239 var baseCard, ourCard, theirCard uint64 240 241 if !types.IsNull(conflict.Base) { 242 namedRows[baseStr], baseCard, err = row.KeylessRowsFromTuples(key, conflict.Base.(types.Tuple)) 243 if err != nil { 244 return nil, err 245 } 246 } 247 if !types.IsNull(conflict.Value) { 248 namedRows[oursStr], ourCard, err = row.KeylessRowsFromTuples(key, conflict.Value.(types.Tuple)) 249 if err != nil { 250 return nil, err 251 } 252 } 253 if !types.IsNull(conflict.MergeValue) { 254 namedRows[theirsStr], theirCard, err = row.KeylessRowsFromTuples(key, conflict.MergeValue.(types.Tuple)) 255 if err != nil { 256 return nil, err 257 } 258 } 259 260 joinedRow, err := cr.joiner.Join(namedRows) 261 if err != nil { 262 return nil, err 263 } 264 joinedRow, err = setCardinalities(types.Uint(baseCard), types.Uint(ourCard), types.Uint(theirCard), joinedRow, cr.sch) 265 if err != nil { 266 return nil, err 267 } 268 269 return joinedRow, nil 270 } 271 272 func setCardinalities(base, ours, theirs types.Uint, joinedRow row.Row, sch schema.Schema) (row.Row, error) { 273 joinedRow, err := joinedRow.SetColVal(schema.DoltConflictsBaseCardinalityTag, base, sch) 274 if err != nil { 275 return nil, err 276 } 277 joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsOurCardinalityTag, ours, sch) 278 if err != nil { 279 return nil, err 280 } 281 joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsTheirCardinalityTag, theirs, sch) 282 if err != nil { 283 return nil, err 284 } 285 return joinedRow, nil 286 } 287 288 func getDiffType(base types.Value, other types.Value) string { 289 if types.IsNull(base) { 290 return ConflictDiffTypeAdded 291 } else if types.IsNull(other) { 292 return ConflictDiffTypeRemoved 293 } 294 295 return ConflictDiffTypeModified 296 } 297 298 // GetKeyForConflicts returns the pk for a conflict row 299 func (cr *ConflictReader) GetKeyForConflict(ctx context.Context, r row.Row) (types.Value, error) { 300 rows, err := cr.joiner.Split(r) 301 302 if err != nil { 303 return nil, err 304 } 305 306 for rowType, r := range rows { 307 key, err := r.NomsMapKey(cr.joiner.SchemaForName(rowType)).Value(ctx) 308 309 if err != nil { 310 return nil, err 311 } 312 313 return key, nil 314 } 315 316 return nil, errors.New("could not determine key") 317 } 318 319 // Close should release resources being held 320 func (cr *ConflictReader) Close() error { 321 return nil 322 }