github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/dprocedures/dolt_conflicts_resolve.go (about) 1 // Copyright 2022 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 dprocedures 16 17 import ( 18 "errors" 19 "fmt" 20 "io" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 24 "github.com/dolthub/dolt/go/cmd/dolt/cli" 25 "github.com/dolthub/dolt/go/libraries/doltcore/branch_control" 26 "github.com/dolthub/dolt/go/libraries/doltcore/conflict" 27 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 28 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable" 29 "github.com/dolthub/dolt/go/libraries/doltcore/merge" 30 "github.com/dolthub/dolt/go/libraries/doltcore/row" 31 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 32 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" 33 "github.com/dolthub/dolt/go/libraries/doltcore/table" 34 "github.com/dolthub/dolt/go/libraries/doltcore/table/editor" 35 "github.com/dolthub/dolt/go/libraries/utils/set" 36 "github.com/dolthub/dolt/go/store/hash" 37 "github.com/dolthub/dolt/go/store/prolly" 38 "github.com/dolthub/dolt/go/store/prolly/tree" 39 "github.com/dolthub/dolt/go/store/types" 40 "github.com/dolthub/dolt/go/store/val" 41 ) 42 43 var ErrConfSchIncompatible = errors.New("the conflict schema's columns are not equal to the current schema's columns, please resolve manually") 44 45 // doltConflictsResolve is the stored procedure version for the CLI command `dolt conflict resolve`. 46 func doltConflictsResolve(ctx *sql.Context, args ...string) (sql.RowIter, error) { 47 res, err := DoDoltConflictsResolve(ctx, args) 48 if err != nil { 49 return nil, err 50 } 51 return rowToIter(res), nil 52 } 53 54 // DoltConflictsCatFunc runs a `dolt commit` in the SQL context, committing staged changes to head. 55 // Deprecated: please use the version in the dprocedures package 56 type DoltConflictsCatFunc struct { 57 children []sql.Expression 58 } 59 60 func getProllyRowMaps(ctx *sql.Context, vrw types.ValueReadWriter, ns tree.NodeStore, hash hash.Hash, tblName string) (prolly.Map, error) { 61 rootVal, err := doltdb.LoadRootValueFromRootIshAddr(ctx, vrw, ns, hash) 62 tbl, ok, err := rootVal.GetTable(ctx, doltdb.TableName{Name: tblName}) 63 if err != nil { 64 return prolly.Map{}, err 65 } 66 if !ok { 67 return prolly.Map{}, doltdb.ErrTableNotFound 68 } 69 70 idx, err := tbl.GetRowData(ctx) 71 if err != nil { 72 return prolly.Map{}, err 73 } 74 75 return durable.ProllyMapFromIndex(idx), nil 76 } 77 78 func resolveProllyConflicts(ctx *sql.Context, tbl *doltdb.Table, tblName string, sch schema.Schema) (*doltdb.Table, error) { 79 var err error 80 artifactIdx, err := tbl.GetArtifacts(ctx) 81 if err != nil { 82 return nil, err 83 } 84 85 artifactMap := durable.ProllyMapFromArtifactIndex(artifactIdx) 86 iter, err := artifactMap.IterAllConflicts(ctx) 87 if err != nil { 88 return nil, err 89 } 90 91 // get mutable prolly map 92 ourIdx, err := tbl.GetRowData(ctx) 93 if err != nil { 94 return nil, err 95 } 96 ourMap := durable.ProllyMapFromIndex(ourIdx) 97 mutMap := ourMap.Mutate() 98 99 // get mutable secondary indexes 100 idxSet, err := tbl.GetIndexSet(ctx) 101 if err != nil { 102 return nil, err 103 } 104 mutIdxs, err := merge.GetMutableSecondaryIdxs(ctx, sch, tblName, idxSet) 105 if err != nil { 106 return nil, err 107 } 108 109 var theirRoot hash.Hash 110 var theirMap prolly.Map 111 for { 112 cnfArt, err := iter.Next(ctx) 113 if err == io.EOF { 114 break 115 } 116 if err != nil { 117 return nil, err 118 } 119 120 // reload if their root hash changes 121 if theirRoot != cnfArt.TheirRootIsh { 122 theirMap, err = getProllyRowMaps(ctx, tbl.ValueReadWriter(), tbl.NodeStore(), cnfArt.TheirRootIsh, tblName) 123 if err != nil { 124 return nil, err 125 } 126 theirRoot = cnfArt.TheirRootIsh 127 } 128 129 // get row data 130 var ourRow, theirRow val.Tuple 131 err = ourMap.Get(ctx, cnfArt.Key, func(_, v val.Tuple) error { 132 ourRow = v 133 return nil 134 }) 135 if err != nil { 136 return nil, err 137 } 138 err = theirMap.Get(ctx, cnfArt.Key, func(_, v val.Tuple) error { 139 theirRow = v 140 return nil 141 }) 142 if err != nil { 143 return nil, err 144 } 145 146 // update row data 147 if len(theirRow) == 0 { 148 err = mutMap.Delete(ctx, cnfArt.Key) 149 } else { 150 err = mutMap.Put(ctx, cnfArt.Key, theirRow) 151 } 152 if err != nil { 153 return nil, err 154 } 155 156 // update secondary indexes 157 for _, mutIdx := range mutIdxs { 158 if len(ourRow) == 0 { 159 err = mutIdx.InsertEntry(ctx, cnfArt.Key, theirRow) 160 } else if len(theirRow) == 0 { 161 err = mutIdx.DeleteEntry(ctx, cnfArt.Key, ourRow) 162 } else { 163 err = mutIdx.UpdateEntry(ctx, cnfArt.Key, ourRow, theirRow) 164 } 165 if err != nil { 166 return nil, err 167 } 168 } 169 } 170 171 // Update table 172 newMap, err := mutMap.Map(ctx) 173 if err != nil { 174 return nil, err 175 } 176 newIdx := durable.IndexFromProllyMap(newMap) 177 newTbl, err := tbl.UpdateRows(ctx, newIdx) 178 if err != nil { 179 return nil, err 180 } 181 182 // Apply index set changes 183 for _, mutIdx := range mutIdxs { 184 m, err := mutIdx.Map(ctx) 185 if err != nil { 186 return nil, err 187 } 188 idxSet, err = idxSet.PutIndex(ctx, mutIdx.Name, durable.IndexFromProllyMap(m)) 189 if err != nil { 190 return nil, err 191 } 192 } 193 newTbl, err = newTbl.SetIndexSet(ctx, idxSet) 194 if err != nil { 195 return nil, err 196 } 197 198 return newTbl, nil 199 } 200 201 func resolvePkConflicts(ctx *sql.Context, opts editor.Options, tbl *doltdb.Table, tblName string, sch schema.Schema, conflicts types.Map) (*doltdb.Table, error) { 202 // Create table editor 203 tblEditor, err := editor.NewTableEditor(ctx, tbl, sch, tblName, opts) 204 if err != nil { 205 return nil, err 206 } 207 208 err = conflicts.Iter(ctx, func(key, val types.Value) (stop bool, err error) { 209 k := key.(types.Tuple) 210 cnf, err := conflict.ConflictFromTuple(val.(types.Tuple)) 211 if err != nil { 212 return true, err 213 } 214 215 // row was removed 216 if types.IsNull(cnf.MergeValue) { 217 baseRow, err := row.FromNoms(sch, k, cnf.Base.(types.Tuple)) 218 if err != nil { 219 return true, err 220 } 221 err = tblEditor.DeleteRow(ctx, baseRow) 222 if err != nil { 223 return true, err 224 } 225 return false, nil 226 } 227 228 newRow, err := row.FromNoms(sch, k, cnf.MergeValue.(types.Tuple)) 229 if err != nil { 230 return true, err 231 } 232 233 if isValid, err := row.IsValid(newRow, sch); err != nil { 234 return true, err 235 } else if !isValid { 236 return true, table.NewBadRow(newRow, "error resolving conflicts", fmt.Sprintf("row with primary key %v in table %s does not match constraints or types of the table's schema.", key, tblName)) 237 } 238 239 // row was added 240 if types.IsNull(cnf.Value) { 241 err = tblEditor.InsertRow(ctx, newRow, nil) 242 if err != nil { 243 return true, err 244 } 245 return false, nil 246 } 247 248 // row was modified 249 oldRow, err := row.FromNoms(sch, k, cnf.Value.(types.Tuple)) 250 if err != nil { 251 return true, err 252 } 253 err = tblEditor.UpdateRow(ctx, oldRow, newRow, nil) 254 if err != nil { 255 return true, err 256 } 257 return false, nil 258 }) 259 if err != nil { 260 return nil, err 261 } 262 return tblEditor.Table(ctx) 263 } 264 265 func resolveKeylessConflicts(ctx *sql.Context, tbl *doltdb.Table, conflicts types.Map) (*doltdb.Table, error) { 266 rowData, err := tbl.GetNomsRowData(ctx) 267 if err != nil { 268 return nil, err 269 } 270 271 mapEditor := rowData.Edit() 272 err = conflicts.Iter(ctx, func(key, value types.Value) (stop bool, err error) { 273 cnf, err := conflict.ConflictFromTuple(value.(types.Tuple)) 274 if err != nil { 275 return true, err 276 } 277 278 if types.IsNull(cnf.MergeValue) { 279 mapEditor.Remove(key) 280 } else { 281 mapEditor.Set(key, cnf.MergeValue) 282 } 283 284 return false, nil 285 }) 286 if err != nil { 287 return nil, err 288 } 289 290 rowData, err = mapEditor.Map(ctx) 291 if err != nil { 292 return nil, err 293 } 294 295 return tbl.UpdateNomsRows(ctx, rowData) 296 } 297 298 func resolveNomsConflicts(ctx *sql.Context, opts editor.Options, tbl *doltdb.Table, tblName string, sch schema.Schema) (*doltdb.Table, error) { 299 // Get conflicts 300 _, confIdx, err := tbl.GetConflicts(ctx) 301 if err != nil { 302 return nil, err 303 } 304 conflicts := durable.NomsMapFromConflictIndex(confIdx) 305 306 if schema.IsKeyless(sch) { 307 return resolveKeylessConflicts(ctx, tbl, conflicts) 308 } 309 310 return resolvePkConflicts(ctx, opts, tbl, tblName, sch, conflicts) 311 } 312 313 func validateConstraintViolations(ctx *sql.Context, before, after doltdb.RootValue, table string) error { 314 tables, err := after.GetTableNames(ctx, doltdb.DefaultSchemaName) 315 if err != nil { 316 return err 317 } 318 319 violators, err := merge.GetForeignKeyViolatedTables(ctx, after, before, set.NewStrSet(tables)) 320 if err != nil { 321 return err 322 } 323 if violators.Size() > 0 { 324 return fmt.Errorf("resolving conflicts for table %s created foreign key violations", table) 325 } 326 327 return nil 328 } 329 330 func clearTableAndUpdateRoot(ctx *sql.Context, root doltdb.RootValue, tbl *doltdb.Table, tblName string) (doltdb.RootValue, error) { 331 newTbl, err := tbl.ClearConflicts(ctx) 332 if err != nil { 333 return nil, err 334 } 335 newRoot, err := root.PutTable(ctx, doltdb.TableName{Name: tblName}, newTbl) 336 if err != nil { 337 return nil, err 338 } 339 return newRoot, nil 340 } 341 342 func ResolveSchemaConflicts(ctx *sql.Context, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, resolveOurs bool, tables []string) (*doltdb.WorkingSet, error) { 343 if !ws.MergeActive() { 344 return ws, nil // no schema conflicts 345 } 346 347 // TODO: There's an issue with using `dolt conflicts resolve` for schema conflicts, since having 348 // schema conflicts reported means that we haven't yet merged the table data. In some case, 349 // such as when there have ONLY been schema changes and no data changes that need to be 350 // merged, it is safe to use `dolt conflicts resolve`, but there are many other cases where the 351 // data changes would not be merged and could surprise customers. So, we are being cautious to 352 // prevent auto-resolution of schema changes with `dolt conflicts resolve` until we have a fix 353 // for resolving schema changes AND merging data (including dealing with any data conflicts). 354 // For more details, see: https://github.com/dolthub/dolt/issues/6616 355 if ws.MergeState().HasSchemaConflicts() { 356 return nil, fmt.Errorf("Unable to automatically resolve schema conflicts since data changes may " + 357 "not have been fully merged yet. " + 358 "To continue, abort this merge (dolt merge --abort) then apply ALTER TABLE statements to one " + 359 "side of this merge to get the two schemas in sync with the desired schema, then rerun the merge. " + 360 "To track resolution of this limitation, follow https://github.com/dolthub/dolt/issues/6616") 361 } 362 363 tblSet := set.NewStrSet(tables) 364 updates := make(map[string]*doltdb.Table) 365 err := ws.MergeState().IterSchemaConflicts(ctx, ddb, func(table string, conflict doltdb.SchemaConflict) error { 366 if !tblSet.Contains(table) { 367 return nil 368 } 369 ours, theirs := conflict.GetConflictingTables() 370 if resolveOurs { 371 updates[table] = ours 372 } else { 373 updates[table] = theirs 374 } 375 return nil 376 }) 377 if err != nil { 378 return nil, err 379 } 380 381 var merged []string 382 root := ws.WorkingRoot() 383 for name, tbl := range updates { 384 if root, err = root.PutTable(ctx, doltdb.TableName{Name: name}, tbl); err != nil { 385 return nil, err 386 } 387 merged = append(merged, name) 388 } 389 390 // clear resolved schema conflicts 391 var unmerged []string 392 for _, tbl := range ws.MergeState().TablesWithSchemaConflicts() { 393 if tblSet.Contains(tbl) { 394 continue 395 } 396 unmerged = append(unmerged, tbl) 397 } 398 399 return ws.WithWorkingRoot(root).WithUnmergableTables(unmerged).WithMergedTables(merged), nil 400 } 401 402 func ResolveDataConflicts(ctx *sql.Context, dSess *dsess.DoltSession, root doltdb.RootValue, dbName string, ours bool, tblNames []string) error { 403 for _, tblName := range tblNames { 404 tbl, ok, err := root.GetTable(ctx, doltdb.TableName{Name: tblName}) 405 if err != nil { 406 return err 407 } 408 if !ok { 409 return doltdb.ErrTableNotFound 410 } 411 412 if has, err := tbl.HasConflicts(ctx); err != nil { 413 return err 414 } else if !has { 415 continue 416 } 417 418 sch, err := tbl.GetSchema(ctx) 419 if err != nil { 420 return err 421 } 422 _, ourSch, theirSch, err := tbl.GetConflictSchemas(ctx, tblName) 423 if err != nil { 424 return err 425 } 426 427 if ours && !schema.ColCollsAreEqual(sch.GetAllCols(), ourSch.GetAllCols()) { 428 return ErrConfSchIncompatible 429 } else if !ours && !schema.ColCollsAreEqual(sch.GetAllCols(), theirSch.GetAllCols()) { 430 return ErrConfSchIncompatible 431 } 432 433 if !ours { 434 if tbl.Format() == types.Format_DOLT { 435 tbl, err = resolveProllyConflicts(ctx, tbl, tblName, sch) 436 } else { 437 state, _, err := dSess.LookupDbState(ctx, dbName) 438 if err != nil { 439 return err 440 } 441 var opts editor.Options 442 if ws := state.WriteSession(); ws != nil { 443 opts = ws.GetOptions() 444 } 445 tbl, err = resolveNomsConflicts(ctx, opts, tbl, tblName, sch) 446 } 447 if err != nil { 448 return err 449 } 450 } 451 452 newRoot, err := clearTableAndUpdateRoot(ctx, root, tbl, tblName) 453 if err != nil { 454 return err 455 } 456 457 err = validateConstraintViolations(ctx, root, newRoot, tblName) 458 if err != nil { 459 return err 460 } 461 462 root = newRoot 463 } 464 return dSess.SetWorkingRoot(ctx, dbName, root) 465 } 466 467 func DoDoltConflictsResolve(ctx *sql.Context, args []string) (int, error) { 468 if err := branch_control.CheckAccess(ctx, branch_control.Permissions_Write); err != nil { 469 return 1, err 470 } 471 dbName := ctx.GetCurrentDatabase() 472 473 apr, err := cli.CreateConflictsResolveArgParser().Parse(args) 474 if err != nil { 475 return 1, err 476 } 477 478 dSess := dsess.DSessFromSess(ctx.Session) 479 ws, err := dSess.WorkingSet(ctx, dbName) 480 if err != nil { 481 return 0, err 482 } 483 484 ddb, _ := dSess.GetDoltDB(ctx, dbName) 485 if err != nil { 486 return 0, err 487 } 488 489 ours := apr.Contains(cli.OursFlag) 490 theirs := apr.Contains(cli.TheirsFlag) 491 if ours && theirs { 492 return 1, fmt.Errorf("specify only either --ours or --theirs") 493 } else if !ours && !theirs { 494 return 1, fmt.Errorf("--ours or --theirs must be supplied") 495 } 496 497 if apr.NArg() == 0 { 498 return 1, fmt.Errorf("specify at least one table to resolve conflicts") 499 } 500 501 // get all tables in conflict 502 tbls := apr.Args 503 if len(tbls) == 1 && tbls[0] == "." { 504 all, err := ws.WorkingRoot().GetTableNames(ctx, doltdb.DefaultSchemaName) 505 if err != nil { 506 return 1, nil 507 } 508 tbls = all 509 } 510 511 ws, err = ResolveSchemaConflicts(ctx, ddb, ws, ours, tbls) 512 if err != nil { 513 return 1, err 514 } 515 516 err = ResolveDataConflicts(ctx, dSess, ws.WorkingRoot(), dbName, ours, tbls) 517 if err != nil { 518 return 1, err 519 } 520 521 return 0, nil 522 }