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  }