go/{commands, doltcore/sqle}: added schema conflict resolution

This commit is contained in:
Andy Arthur
2023-04-24 07:33:39 -07:00
parent 87943872a5
commit dda28f6788
16 changed files with 166 additions and 52 deletions
+59 -10
View File
@@ -51,8 +51,7 @@ func AutoResolveAll(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolve
return err
}
tbls, err := root.TablesInConflict(ctx)
tbls, err := root.GetTableNames(ctx)
if err != nil {
return err
}
@@ -63,26 +62,77 @@ func AutoResolveAll(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolve
// AutoResolveTables resolves all conflicts in the given tables according to the
// given |strategy|.
func AutoResolveTables(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolveStrategy, tbls []string) error {
root, err := dEnv.WorkingRoot(ctx)
ws, err := dEnv.WorkingSet(ctx)
if err != nil {
return err
}
for _, tblName := range tbls {
err = ResolveTable(ctx, dEnv, root, tblName, strategy)
// schema conflicts
if ws.MergeActive() {
ws, err = ResolveSchemaConflicts(ctx, dEnv.DoltDB, ws, tbls, strategy)
if err != nil {
return err
}
if err = dEnv.UpdateWorkingSet(ctx, ws); err != nil {
return err
}
}
// data conflicts
for _, tblName := range tbls {
err = ResolveDataConflicts(ctx, dEnv, ws, tblName, strategy)
if err != nil {
return err
}
}
return nil
}
// ResolveTable resolves all conflicts in the given table according to the given
func ResolveSchemaConflicts(ctx context.Context, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, tables []string, strategy AutoResolveStrategy) (*doltdb.WorkingSet, error) {
tblSet := set.NewStrSet(tables)
updates := make(map[string]*doltdb.Table)
err := ws.MergeState().IterSchemaConflicts(ctx, ddb, func(table string, conflict doltdb.SchemaConflict) error {
if !tblSet.Contains(table) {
return nil
}
ours, theirs := conflict.GetConflictingTables()
switch strategy {
case AutoResolveStrategyOurs:
updates[table] = ours
case AutoResolveStrategyTheirs:
updates[table] = theirs
default:
panic("unhandled auto resolve strategy")
}
return nil
})
if err != nil {
return nil, err
}
root := ws.WorkingRoot()
for name, tbl := range updates {
if root, err = root.PutTable(ctx, name, tbl); err != nil {
return nil, err
}
}
// clear resolved schema conflicts
var unmerged []string
for _, tbl := range ws.MergeState().TablesWithSchemaConflicts() {
if tblSet.Contains(tbl) {
continue
}
unmerged = append(unmerged, tbl)
}
return ws.WithWorkingRoot(root).WithUnmergableTables(unmerged), nil
}
// ResolveDataConflicts resolves all conflicts in the given table according to the given
// |strategy|. It errors if the schema of the conflict version you are choosing
// differs from the current schema.
func ResolveTable(ctx context.Context, dEnv *env.DoltEnv, root *doltdb.RootValue, tblName string, strategy AutoResolveStrategy) (err error) {
tbl, ok, err := root.GetTable(ctx, tblName)
func ResolveDataConflicts(ctx context.Context, dEnv *env.DoltEnv, ws *doltdb.WorkingSet, tblName string, strategy AutoResolveStrategy) (err error) {
tbl, ok, err := ws.WorkingRoot().GetTable(ctx, tblName)
if err != nil {
return err
}
@@ -92,8 +142,7 @@ func ResolveTable(ctx context.Context, dEnv *env.DoltEnv, root *doltdb.RootValue
has, err := tbl.HasConflicts(ctx)
if err != nil {
return err
}
if !has {
} else if !has {
return nil
}
+1 -1
View File
@@ -198,7 +198,7 @@ func performCommit(ctx context.Context, commandStr string, args []string, dEnv *
mergeParentCommits = parentsHeadForAmend
}
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws.MergeActive(), mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws, mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
Message: msg,
Date: t,
AllowEmpty: apr.Contains(cli.AllowEmptyFlag) || apr.Contains(cli.AmendFlag),
+2 -2
View File
@@ -205,7 +205,7 @@ func getUnmergedTableCount(ctx context.Context, ws *doltdb.WorkingSet) (int, err
unmerged.Add(ws.MergeState().TablesWithSchemaConflicts()...)
}
conflicted, err := ws.WorkingRoot().TablesInConflict(ctx)
conflicted, err := ws.WorkingRoot().TablesWithDataConflicts(ctx)
if err != nil {
return 0, err
}
@@ -520,7 +520,7 @@ func executeNoFFMergeAndCommit(ctx context.Context, dEnv *env.DoltEnv, spec *mer
return tblToStats, err
}
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws.MergeActive(), mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws, mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
Message: msg,
Date: spec.Date,
AllowEmpty: spec.AllowEmpty,
+2 -2
View File
@@ -694,7 +694,7 @@ func (root *RootValue) getTableMap(ctx context.Context) (tableMap, error) {
return root.st.GetTablesMap(ctx, root.vrw, root.ns)
}
func (root *RootValue) TablesInConflict(ctx context.Context) ([]string, error) {
func (root *RootValue) TablesWithDataConflicts(ctx context.Context) ([]string, error) {
names, err := root.GetTableNames(ctx)
if err != nil {
return nil, err
@@ -747,7 +747,7 @@ func (root *RootValue) TablesWithConstraintViolations(ctx context.Context) ([]st
}
func (root *RootValue) HasConflicts(ctx context.Context) (bool, error) {
cnfTbls, err := root.TablesInConflict(ctx)
cnfTbls, err := root.TablesWithDataConflicts(ctx)
if err != nil {
return false, err
@@ -43,6 +43,11 @@ type SchemaConflict struct {
ToFks, FromFks []ForeignKey
ToParentSchemas map[string]schema.Schema
FromParentSchemas map[string]schema.Schema
toTbl, fromTbl *Table
}
func (sc SchemaConflict) GetConflictingTables() (ours, theirs *Table) {
return sc.toTbl, sc.fromTbl
}
// TodoWorkingSetMeta returns an incomplete WorkingSetMeta, suitable for methods that don't have the means to construct
@@ -248,7 +248,7 @@ func (mr *MultiRepoTestSetup) CommitWithWorkingSet(dbName string) *doltdb.Commit
if err != nil {
panic("couldn't get roots: " + err.Error())
}
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws.MergeActive(), mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws, mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
Message: "auto commit",
Date: t,
AllowEmpty: true,
+10 -3
View File
@@ -37,7 +37,7 @@ type CommitStagedProps struct {
func GetCommitStaged(
ctx context.Context,
roots doltdb.Roots,
mergeActive bool,
ws *doltdb.WorkingSet,
mergeParents []*doltdb.Commit,
db *doltdb.DoltDB,
props CommitStagedProps,
@@ -61,13 +61,13 @@ func GetCommitStaged(
}
isEmpty := len(staged) == 0
allowEmpty := mergeActive || props.AllowEmpty || props.Amend
allowEmpty := ws.MergeActive() || props.AllowEmpty || props.Amend
if isEmpty && !allowEmpty {
return nil, NothingStaged{notStaged}
}
if !props.Force {
inConflict, err := roots.Working.TablesInConflict(ctx)
inConflict, err := roots.Working.TablesWithDataConflicts(ctx)
if err != nil {
return nil, err
}
@@ -81,6 +81,13 @@ func GetCommitStaged(
if len(violatesConstraints) > 0 {
return nil, NewTblHasConstraintViolations(violatesConstraints)
}
if ws.MergeActive() {
schConflicts := ws.MergeState().TablesWithSchemaConflicts()
if len(schConflicts) > 0 {
return nil, NewTblSchemaConflictError(schConflicts)
}
}
}
if !props.Force {
+9 -4
View File
@@ -23,10 +23,11 @@ import (
type tblErrorType string
const (
tblErrInvalid tblErrorType = "invalid"
tblErrTypeNotExist tblErrorType = "do not exist"
tblErrTypeInConflict tblErrorType = "are in conflict"
tblErrTypeConstViols tblErrorType = "have constraint violations"
tblErrInvalid tblErrorType = "invalid"
tblErrTypeNotExist tblErrorType = "do not exist"
tblErrTypeInConflict tblErrorType = "are in conflict"
tblErrTypeSchConflict tblErrorType = "have schema conflicts"
tblErrTypeConstViols tblErrorType = "have constraint violations"
)
type TblError struct {
@@ -42,6 +43,10 @@ func NewTblInConflictError(tbls []string) TblError {
return TblError{tbls, tblErrTypeInConflict}
}
func NewTblSchemaConflictError(tbls []string) TblError {
return TblError{tbls, tblErrTypeSchConflict}
}
func NewTblHasConstraintViolations(tbls []string) TblError {
return TblError{tbls, tblErrTypeConstViols}
}
-10
View File
@@ -788,16 +788,6 @@ func (dEnv *DoltEnv) NewWorkingSetMeta(message string) *datas.WorkingSetMeta {
}
}
func (dEnv *DoltEnv) GetTablesWithConflicts(ctx context.Context) ([]string, error) {
root, err := dEnv.WorkingRoot(ctx)
if err != nil {
return nil, err
}
return root.TablesInConflict(ctx)
}
func (dEnv *DoltEnv) CredsDir() (string, error) {
return getCredsDir(dEnv.hdp)
}
+1 -1
View File
@@ -395,7 +395,7 @@ func GetMergeArtifactStatus(ctx context.Context, working *doltdb.WorkingSet) (as
as.SchemaConflictsTables = working.MergeState().TablesWithSchemaConflicts()
}
as.DataConflictTables, err = working.WorkingRoot().TablesInConflict(ctx)
as.DataConflictTables, err = working.WorkingRoot().TablesWithDataConflicts(ctx)
if err != nil {
return as, err
}
@@ -339,7 +339,48 @@ func clearTableAndUpdateRoot(ctx *sql.Context, root *doltdb.RootValue, tbl *dolt
return newRoot, nil
}
func ResolveConflicts(ctx *sql.Context, dSess *dsess.DoltSession, root *doltdb.RootValue, dbName string, ours bool, tblNames []string) error {
func ResolveSchemaConflicts(ctx *sql.Context, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, resolveOurs bool, tables []string) (*doltdb.WorkingSet, error) {
if !ws.MergeActive() {
return ws, nil // no schema conflicts
}
tblSet := set.NewStrSet(tables)
updates := make(map[string]*doltdb.Table)
err := ws.MergeState().IterSchemaConflicts(ctx, ddb, func(table string, conflict doltdb.SchemaConflict) error {
if !tblSet.Contains(table) {
return nil
}
ours, theirs := conflict.GetConflictingTables()
if resolveOurs {
updates[table] = ours
} else {
updates[table] = theirs
}
return nil
})
if err != nil {
return nil, err
}
root := ws.WorkingRoot()
for name, tbl := range updates {
if root, err = root.PutTable(ctx, name, tbl); err != nil {
return nil, err
}
}
// clear resolved schema conflicts
var unmerged []string
for _, tbl := range ws.MergeState().TablesWithSchemaConflicts() {
if tblSet.Contains(tbl) {
continue
}
unmerged = append(unmerged, tbl)
}
return ws.WithWorkingRoot(root).WithUnmergableTables(unmerged), nil
}
func ResolveDataConflicts(ctx *sql.Context, dSess *dsess.DoltSession, root *doltdb.RootValue, dbName string, ours bool, tblNames []string) error {
for _, tblName := range tblNames {
tbl, ok, err := root.GetTable(ctx, tblName)
if err != nil {
@@ -352,7 +393,7 @@ func ResolveConflicts(ctx *sql.Context, dSess *dsess.DoltSession, root *doltdb.R
if has, err := tbl.HasConflicts(ctx); err != nil {
return err
} else if !has {
return nil
continue
}
sch, err := tbl.GetSchema(ctx)
@@ -413,9 +454,14 @@ func DoDoltConflictsResolve(ctx *sql.Context, args []string) (int, error) {
}
dSess := dsess.DSessFromSess(ctx.Session)
roots, ok := dSess.GetRoots(ctx, dbName)
if !ok {
return 1, fmt.Errorf("Could not load database %s", dbName)
ws, err := dSess.WorkingSet(ctx, dbName)
if err != nil {
return 0, err
}
ddb, _ := dSess.GetDoltDB(ctx, dbName)
if err != nil {
return 0, err
}
ours := apr.Contains(cli.OursFlag)
@@ -431,17 +477,21 @@ func DoDoltConflictsResolve(ctx *sql.Context, args []string) (int, error) {
}
// get all tables in conflict
root := roots.Working
tbls := apr.Args
if len(tbls) == 1 && tbls[0] == "." {
if allTables, err := root.TablesInConflict(ctx); err != nil {
return 1, err
} else {
tbls = allTables
all, err := ws.WorkingRoot().GetTableNames(ctx)
if err != nil {
return 1, nil
}
tbls = all
}
err = ResolveConflicts(ctx, dSess, root, dbName, ours, tbls)
ws, err = ResolveSchemaConflicts(ctx, ddb, ws, ours, tbls)
if err != nil {
return 1, err
}
err = ResolveDataConflicts(ctx, dSess, ws.WorkingRoot(), dbName, ours, tbls)
if err != nil {
return 1, err
}
+1 -1
View File
@@ -586,7 +586,7 @@ func (d *DoltSession) NewPendingCommit(ctx *sql.Context, dbName string, roots do
roots.Head = newRoots.Head
}
pendingCommit, err := actions.GetCommitStaged(ctx, roots, sessionState.WorkingSet.MergeActive(), mergeParentCommits, sessionState.dbData.Ddb, props)
pendingCommit, err := actions.GetCommitStaged(ctx, roots, sessionState.WorkingSet, mergeParentCommits, sessionState.dbData.Ddb, props)
if err != nil {
if props.Amend {
_, err = actions.ResetSoftToRef(ctx, sessionState.dbData, headHash.String())
@@ -88,7 +88,7 @@ type MergeStatusIter struct {
func newMergeStatusItr(ctx context.Context, ws *doltdb.WorkingSet) (*MergeStatusIter, error) {
wr := ws.WorkingRoot()
inConflict, err := wr.TablesInConflict(ctx)
inConflict, err := wr.TablesWithDataConflicts(ctx)
if err != nil {
return nil, err
}
@@ -98,8 +98,14 @@ func newMergeStatusItr(ctx context.Context, ws *doltdb.WorkingSet) (*MergeStatus
return nil, err
}
var schConflicts []string
if ws.MergeActive() {
schConflicts = ws.MergeState().TablesWithSchemaConflicts()
}
unmergedTblNames := set.NewStrSet(inConflict)
unmergedTblNames.Add(tblsWithViolations...)
unmergedTblNames.Add(schConflicts...)
var sourceCommitSpecStr *string
var sourceCommitHash *string
@@ -124,7 +124,7 @@ func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) {
}
}
cnfTables, err := roots.Working.TablesInConflict(ctx)
cnfTables, err := roots.Working.TablesWithDataConflicts(ctx)
if err != nil {
return nil, err
}
@@ -122,11 +122,16 @@ func (dt *TableOfTablesInConflict) Partitions(ctx *sql.Context) (sql.PartitionIt
}
root := ws.WorkingRoot()
tblNames, err := root.TablesInConflict(ctx)
tblNames, err := root.TablesWithDataConflicts(ctx)
if err != nil {
return nil, err
}
if ws.MergeActive() {
schConflicts := ws.MergeState().TablesWithSchemaConflicts()
tblNames = append(tblNames, schConflicts...)
}
var partitions []*tableInConflict
for _, tblName := range tblNames {
tbl, ok, err := root.GetTable(ctx, tblName)
-3
View File
@@ -493,9 +493,6 @@ teardown() {
dolt commit -m "added conflicting test row"
dolt checkout main
dolt merge test-branch
run dolt checkout test
[ "$status" -eq 0 ]
[ "$output" = "" ]
run dolt sql -q "select * from test"
[[ "$output" =~ \|[[:space:]]+5 ]] || false
run dolt conflicts cat test