mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-27 03:09:14 -06:00
Added fast forward handling and fleshed out constraint violation messages
This commit is contained in:
committed by
Daylon Wilkins
parent
22a7dd9f48
commit
8d971dcd35
@@ -110,7 +110,7 @@ func toAddVErr(err error) errhand.VerboseError {
|
||||
|
||||
return bdr.Build()
|
||||
|
||||
case actions.IsTblInConflict(err):
|
||||
case actions.IsTblInConflict(err) || actions.IsTblViolatesConstraints(err):
|
||||
tbls := actions.GetTablesForError(err)
|
||||
bdr := errhand.BuildDError("error: not all tables merged")
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ func handleCommitErr(ctx context.Context, dEnv *env.DoltEnv, err error, usage cl
|
||||
if actions.IsNothingStaged(err) {
|
||||
notStagedTbls := actions.NothingStagedTblDiffs(err)
|
||||
notStagedDocs := actions.NothingStagedDocsDiffs(err)
|
||||
n := printDiffsNotStaged(ctx, dEnv, cli.CliOut, notStagedTbls, notStagedDocs, false, 0, []string{})
|
||||
n := printDiffsNotStaged(ctx, dEnv, cli.CliOut, notStagedTbls, notStagedDocs, false, 0, nil, nil)
|
||||
|
||||
if n == 0 {
|
||||
bdr := errhand.BuildDError(`no changes added to commit (use "dolt add")`)
|
||||
@@ -219,12 +219,16 @@ func buildInitalCommitMsg(ctx context.Context, dEnv *env.DoltEnv) string {
|
||||
if err != nil {
|
||||
workingTblsInConflict = []string{}
|
||||
}
|
||||
workingTblsWithViolations, _, _, err := merge.GetTablesWithConstraintViolations(ctx, roots)
|
||||
if err != nil {
|
||||
workingTblsWithViolations = []string{}
|
||||
}
|
||||
|
||||
stagedDocDiffs, notStagedDocDiffs, _ := diff.GetDocDiffs(ctx, roots, dEnv.DocsReadWriter())
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
n := printStagedDiffs(buf, stagedTblDiffs, stagedDocDiffs, true)
|
||||
n = printDiffsNotStaged(ctx, dEnv, buf, notStagedTblDiffs, notStagedDocDiffs, true, n, workingTblsInConflict)
|
||||
n = printDiffsNotStaged(ctx, dEnv, buf, notStagedTblDiffs, notStagedDocDiffs, true, n, workingTblsInConflict, workingTblsWithViolations)
|
||||
|
||||
currBranch := dEnv.RepoStateReader().CWBHeadRef()
|
||||
initialCommitMessage := "\n" + "# Please enter the commit message for your changes. Lines starting" + "\n" +
|
||||
|
||||
@@ -117,13 +117,25 @@ func (cmd MergeCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
}
|
||||
|
||||
// If there are any conflicts or constraint violations then we disallow the merge
|
||||
if has, err := root.HasConflicts(ctx); err != nil {
|
||||
hasCnf, err := root.HasConflicts(ctx)
|
||||
if err != nil {
|
||||
verr = errhand.BuildDError("error: failed to get conflicts").AddCause(err).Build()
|
||||
} else if has {
|
||||
}
|
||||
hasCV, err := root.HasConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
verr = errhand.BuildDError("error: failed to get constraint violations").AddCause(err).Build()
|
||||
}
|
||||
if hasCnf || hasCV {
|
||||
cli.Println("error: Merging is not possible because you have unmerged tables.")
|
||||
cli.Println("hint: Fix them up in the working tree, and then use 'dolt add <table>'")
|
||||
cli.Println("hint: as appropriate to mark resolution and make a commit.")
|
||||
cli.Println("fatal: Exiting because of an unresolved conflict.")
|
||||
if hasCnf && hasCV {
|
||||
cli.Println("fatal: Exiting because of an unresolved conflict and constraint violation.")
|
||||
} else if hasCnf {
|
||||
cli.Println("fatal: Exiting because of an unresolved conflict.")
|
||||
} else {
|
||||
cli.Println("fatal: Exiting because of an unresolved constraint violation.")
|
||||
}
|
||||
return 1
|
||||
} else if mergeActive {
|
||||
cli.Println("error: Merging is not possible because you have not committed an active merge.")
|
||||
@@ -132,13 +144,6 @@ func (cmd MergeCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
return 1
|
||||
}
|
||||
|
||||
if has, err := root.HasConstraintViolations(ctx); err != nil {
|
||||
verr = errhand.BuildDError("error: failed to get constraint violations").AddCause(err).Build()
|
||||
} else if has {
|
||||
cli.Println("fatal: Exiting because of an unresolved constraint violation.")
|
||||
return 1
|
||||
}
|
||||
|
||||
if verr == nil {
|
||||
verr = mergeCommitSpec(ctx, apr, dEnv, commitSpecStr)
|
||||
}
|
||||
@@ -224,6 +229,19 @@ func mergeCommitSpec(ctx context.Context, apr *argparser.ArgParseResults, dEnv *
|
||||
}
|
||||
|
||||
if ok, err := cm1.CanFastForwardTo(ctx, cm2); ok {
|
||||
ancRoot, err := cm1.GetRootValue()
|
||||
if err != nil {
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
mergedRoot, err := cm2.GetRootValue()
|
||||
if err != nil {
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
if cvPossible, err := merge.MayHaveConstraintViolations(ctx, ancRoot, mergedRoot); err != nil {
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
} else if cvPossible {
|
||||
return executeMerge(ctx, squash, dEnv, cm1, cm2, workingDiffs)
|
||||
}
|
||||
if apr.Contains(cli.NoFFParam) {
|
||||
return execNoFFMerge(ctx, apr, dEnv, roots, cm2, verr, workingDiffs)
|
||||
} else {
|
||||
@@ -421,10 +439,16 @@ func mergedRootToWorking(
|
||||
verr := UpdateWorkingWithVErr(dEnv, workingRoot)
|
||||
|
||||
if verr == nil {
|
||||
hasConflicts := printSuccessStats(tblToStats)
|
||||
hasConflicts, hasConstraintViolations := printSuccessStats(tblToStats)
|
||||
|
||||
if hasConflicts {
|
||||
if hasConflicts && hasConstraintViolations {
|
||||
cli.Println("Automatic merge failed; fix conflicts and constraint violations and then commit the result.")
|
||||
} else if hasConflicts {
|
||||
cli.Println("Automatic merge failed; fix conflicts and then commit the result.")
|
||||
} else if hasConstraintViolations {
|
||||
cli.Println("Automatic merge failed; fix constraint violations and then commit the result.\n" +
|
||||
"Constraint violations for the working set may be viewed using the 'dolt_constraint_violations' system table.\n" +
|
||||
"They may be queried and removed per-table using the 'dolt_constraint_violations_TABLENAME' system table.")
|
||||
} else {
|
||||
err = actions.SaveDocsFromWorkingExcludingFSChanges(ctx, dEnv, unstagedDocs)
|
||||
if err != nil {
|
||||
@@ -441,11 +465,12 @@ func mergedRootToWorking(
|
||||
return verr
|
||||
}
|
||||
|
||||
func printSuccessStats(tblToStats map[string]*merge.MergeStats) bool {
|
||||
// printSuccessStats returns whether there are conflicts or constraint violations.
|
||||
func printSuccessStats(tblToStats map[string]*merge.MergeStats) (conflicts bool, constraintViolations bool) {
|
||||
printModifications(tblToStats)
|
||||
printAdditions(tblToStats)
|
||||
printDeletions(tblToStats)
|
||||
return printConflicts(tblToStats)
|
||||
return printConflictsAndViolations(tblToStats)
|
||||
}
|
||||
|
||||
func printAdditions(tblToStats map[string]*merge.MergeStats) {
|
||||
@@ -464,18 +489,24 @@ func printDeletions(tblToStats map[string]*merge.MergeStats) {
|
||||
}
|
||||
}
|
||||
|
||||
func printConflicts(tblToStats map[string]*merge.MergeStats) bool {
|
||||
func printConflictsAndViolations(tblToStats map[string]*merge.MergeStats) (conflicts bool, constraintViolations bool) {
|
||||
hasConflicts := false
|
||||
hasConstraintViolations := false
|
||||
for tblName, stats := range tblToStats {
|
||||
if stats.Operation == merge.TableModified && stats.Conflicts > 0 {
|
||||
if stats.Operation == merge.TableModified && (stats.Conflicts > 0 || stats.ConstraintViolations > 0) {
|
||||
cli.Println("Auto-merging", tblName)
|
||||
cli.Println("CONFLICT (content): Merge conflict in", tblName)
|
||||
|
||||
hasConflicts = true
|
||||
if stats.Conflicts > 0 {
|
||||
cli.Println("CONFLICT (content): Merge conflict in", tblName)
|
||||
hasConflicts = true
|
||||
}
|
||||
if stats.ConstraintViolations > 0 {
|
||||
cli.Println("CONSTRAINT VIOLATION (content): Merge created constraint violation in", tblName)
|
||||
hasConstraintViolations = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasConflicts
|
||||
return hasConflicts, hasConstraintViolations
|
||||
}
|
||||
|
||||
func printModifications(tblToStats map[string]*merge.MergeStats) {
|
||||
@@ -486,7 +517,7 @@ func printModifications(tblToStats map[string]*merge.MergeStats) {
|
||||
rowsChanged := 0
|
||||
var tbls []string
|
||||
for tblName, stats := range tblToStats {
|
||||
if stats.Operation == merge.TableModified && stats.Conflicts == 0 {
|
||||
if stats.Operation == merge.TableModified && stats.Conflicts == 0 && stats.ConstraintViolations == 0 {
|
||||
tbls = append(tbls, tblName)
|
||||
nameLen := len(tblName)
|
||||
modCount := stats.Adds + stats.Modifications + stats.Deletes + stats.Conflicts
|
||||
|
||||
@@ -66,14 +66,9 @@ func (cmd StatusCmd) createArgParser() *argparser.ArgParser {
|
||||
// Exec executes the command
|
||||
func (cmd StatusCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv) int {
|
||||
ap := cmd.createArgParser()
|
||||
help, usage := cli.HelpAndUsagePrinters(cli.GetCommandDocumentation(commandStr, statusDocs, ap))
|
||||
help, _ := cli.HelpAndUsagePrinters(cli.GetCommandDocumentation(commandStr, statusDocs, ap))
|
||||
cli.ParseArgsOrDie(ap, args, help)
|
||||
|
||||
workingRoot, err := dEnv.WorkingRoot(ctx)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Couldn't get working root").AddCause(err).Build(), usage)
|
||||
}
|
||||
|
||||
roots, err := dEnv.Roots(ctx)
|
||||
if err != nil {
|
||||
cli.PrintErrln(toStatusVErr(err).Verbose())
|
||||
@@ -81,33 +76,30 @@ func (cmd StatusCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
}
|
||||
|
||||
staged, notStaged, err := diff.GetStagedUnstagedTableDeltas(ctx, roots)
|
||||
|
||||
if err != nil {
|
||||
cli.PrintErrln(toStatusVErr(err).Verbose())
|
||||
return 1
|
||||
}
|
||||
workingTblsInConflict, _, _, err := merge.GetTablesInConflict(ctx, roots)
|
||||
|
||||
workingTblsInConflict, _, _, err := merge.GetTablesInConflict(ctx, roots)
|
||||
if err != nil {
|
||||
cli.PrintErrln(toStatusVErr(err).Verbose())
|
||||
return 1
|
||||
}
|
||||
|
||||
workingTblsWithViolations, _, _, err := merge.GetTablesWithConstraintViolations(ctx, roots)
|
||||
if err != nil {
|
||||
cli.PrintErrln(toStatusVErr(err).Verbose())
|
||||
return 1
|
||||
}
|
||||
|
||||
stagedDocDiffs, notStagedDocDiffs, err := diff.GetDocDiffs(ctx, roots, dEnv.DocsReadWriter())
|
||||
|
||||
if err != nil {
|
||||
cli.PrintErrln(toStatusVErr(err).Verbose())
|
||||
return 1
|
||||
}
|
||||
|
||||
workingDocsInConflict, err := merge.GetDocsInConflict(ctx, workingRoot, dEnv.DocsReadWriter())
|
||||
|
||||
if err != nil {
|
||||
cli.PrintErrln(toStatusVErr(err).Verbose())
|
||||
return 1
|
||||
}
|
||||
|
||||
err = printStatus(ctx, dEnv, staged, notStaged, workingTblsInConflict, workingDocsInConflict, stagedDocDiffs, notStagedDocDiffs)
|
||||
err = printStatus(ctx, dEnv, staged, notStaged, workingTblsInConflict, workingTblsWithViolations, stagedDocDiffs, notStagedDocDiffs)
|
||||
if err != nil {
|
||||
cli.PrintErrln(toStatusVErr(err).Verbose())
|
||||
return 1
|
||||
@@ -147,11 +139,11 @@ const (
|
||||
stagedHeaderHelp = ` (use "dolt reset <table>..." to unstage)`
|
||||
|
||||
unmergedTablesHeader = `You have unmerged tables.
|
||||
(fix conflicts and run "dolt commit")
|
||||
(fix %s and run "dolt commit")
|
||||
(use "dolt merge --abort" to abort the merge)
|
||||
`
|
||||
|
||||
allMergedHeader = `All conflicts fixed but you are still merging.
|
||||
allMergedHeader = `All conflicts and constraint violations fixed but you are still merging.
|
||||
(use "dolt commit" to conclude merge)
|
||||
`
|
||||
|
||||
@@ -206,27 +198,46 @@ func printStagedDiffs(wr io.Writer, stagedTbls []diff.TableDelta, stagedDocs *di
|
||||
return 0
|
||||
}
|
||||
|
||||
func printDiffsNotStaged(ctx context.Context, dEnv *env.DoltEnv, wr io.Writer, notStagedTbls []diff.TableDelta, notStagedDocs *diff.DocDiffs, printHelp bool, linesPrinted int, workingTblsInConflict []string) int {
|
||||
func printDiffsNotStaged(
|
||||
ctx context.Context,
|
||||
dEnv *env.DoltEnv,
|
||||
wr io.Writer,
|
||||
notStagedTbls []diff.TableDelta,
|
||||
notStagedDocs *diff.DocDiffs,
|
||||
printHelp bool,
|
||||
linesPrinted int,
|
||||
workingTblsInConflict, workingTblsWithViolations []string,
|
||||
) int {
|
||||
inCnfSet := set.NewStrSet(workingTblsInConflict)
|
||||
violationSet := set.NewStrSet(workingTblsWithViolations)
|
||||
|
||||
if len(workingTblsInConflict) > 0 {
|
||||
if len(workingTblsInConflict) > 0 || len(workingTblsWithViolations) > 0 {
|
||||
if linesPrinted > 0 {
|
||||
cli.Println()
|
||||
}
|
||||
|
||||
iohelp.WriteLine(wr, mergedTableHeader)
|
||||
|
||||
if printHelp {
|
||||
iohelp.WriteLine(wr, mergedTableHelp)
|
||||
}
|
||||
|
||||
lines := make([]string, 0, len(notStagedTbls))
|
||||
for _, tblName := range workingTblsInConflict {
|
||||
lines = append(lines, fmt.Sprintf(statusFmt, bothModifiedLabel, tblName))
|
||||
if len(workingTblsInConflict) > 0 {
|
||||
lines := make([]string, 0, len(notStagedTbls))
|
||||
for _, tblName := range workingTblsInConflict {
|
||||
lines = append(lines, fmt.Sprintf(statusFmt, bothModifiedLabel, tblName))
|
||||
}
|
||||
iohelp.WriteLine(wr, color.RedString(strings.Join(lines, "\n")))
|
||||
linesPrinted += len(lines)
|
||||
}
|
||||
|
||||
iohelp.WriteLine(wr, color.RedString(strings.Join(lines, "\n")))
|
||||
linesPrinted += len(lines)
|
||||
if len(workingTblsWithViolations) > 0 {
|
||||
violationOnly, _, _ := violationSet.LeftIntersectionRight(inCnfSet)
|
||||
lines := make([]string, 0, len(notStagedTbls))
|
||||
for _, tblName := range violationOnly.AsSortedSlice() {
|
||||
lines = append(lines, fmt.Sprintf(statusFmt, "modified", tblName))
|
||||
}
|
||||
iohelp.WriteLine(wr, color.RedString(strings.Join(lines, "\n")))
|
||||
linesPrinted += len(lines)
|
||||
}
|
||||
}
|
||||
|
||||
added := 0
|
||||
@@ -245,7 +256,7 @@ func printDiffsNotStaged(ctx context.Context, dEnv *env.DoltEnv, wr io.Writer, n
|
||||
numRemovedOrModified := removeModified + notStagedDocs.NumRemoved + notStagedDocs.NumModified
|
||||
docsInCnf, _ := docCnfsOnWorkingRoot(ctx, dEnv)
|
||||
|
||||
if numRemovedOrModified-inCnfSet.Size() > 0 {
|
||||
if numRemovedOrModified-inCnfSet.Size()-violationSet.Size() > 0 {
|
||||
if linesPrinted > 0 {
|
||||
cli.Println()
|
||||
}
|
||||
@@ -259,7 +270,7 @@ func printDiffsNotStaged(ctx context.Context, dEnv *env.DoltEnv, wr io.Writer, n
|
||||
iohelp.WriteLine(wr, workingHeaderHelp)
|
||||
}
|
||||
|
||||
lines := getModifiedAndRemovedNotStaged(notStagedTbls, notStagedDocs, inCnfSet)
|
||||
lines := getModifiedAndRemovedNotStaged(notStagedTbls, notStagedDocs, inCnfSet, violationSet)
|
||||
|
||||
iohelp.WriteLine(wr, color.RedString(strings.Join(lines, "\n")))
|
||||
linesPrinted += len(lines)
|
||||
@@ -293,10 +304,10 @@ func printDiffsNotStaged(ctx context.Context, dEnv *env.DoltEnv, wr io.Writer, n
|
||||
return linesPrinted
|
||||
}
|
||||
|
||||
func getModifiedAndRemovedNotStaged(notStagedTbls []diff.TableDelta, notStagedDocs *diff.DocDiffs, inCnfSet *set.StrSet) (lines []string) {
|
||||
func getModifiedAndRemovedNotStaged(notStagedTbls []diff.TableDelta, notStagedDocs *diff.DocDiffs, inCnfSet, violationSet *set.StrSet) (lines []string) {
|
||||
lines = make([]string, 0, len(notStagedTbls)+notStagedDocs.Len())
|
||||
for _, td := range notStagedTbls {
|
||||
if td.IsAdd() || inCnfSet.Contains(td.CurName()) || td.CurName() == doltdb.DocTableName {
|
||||
if td.IsAdd() || inCnfSet.Contains(td.CurName()) || violationSet.Contains(td.CurName()) || td.CurName() == doltdb.DocTableName {
|
||||
continue
|
||||
}
|
||||
if td.IsDrop() {
|
||||
@@ -342,7 +353,7 @@ func getAddedNotStaged(notStagedTbls []diff.TableDelta, notStagedDocs *diff.DocD
|
||||
}
|
||||
|
||||
// TODO: working docs in conflict param not used here
|
||||
func printStatus(ctx context.Context, dEnv *env.DoltEnv, stagedTbls, notStagedTbls []diff.TableDelta, workingTblsInConflict []string, workingDocsInConflict *diff.DocDiffs, stagedDocs, notStagedDocs *diff.DocDiffs) error {
|
||||
func printStatus(ctx context.Context, dEnv *env.DoltEnv, stagedTbls, notStagedTbls []diff.TableDelta, workingTblsInConflict, workingTblsWithViolations []string, stagedDocs, notStagedDocs *diff.DocDiffs) error {
|
||||
cli.Printf(branchHeader, dEnv.RepoStateReader().CWBHeadRef().GetPath())
|
||||
|
||||
mergeActive, err := dEnv.IsMergeActive(ctx)
|
||||
@@ -351,15 +362,19 @@ func printStatus(ctx context.Context, dEnv *env.DoltEnv, stagedTbls, notStagedTb
|
||||
}
|
||||
|
||||
if mergeActive {
|
||||
if len(workingTblsInConflict) > 0 {
|
||||
cli.Println(unmergedTablesHeader)
|
||||
if len(workingTblsInConflict) > 0 && len(workingTblsWithViolations) > 0 {
|
||||
cli.Println(fmt.Sprintf(unmergedTablesHeader, "conflicts and constraint violations"))
|
||||
} else if len(workingTblsInConflict) > 0 {
|
||||
cli.Println(fmt.Sprintf(unmergedTablesHeader, "conflicts"))
|
||||
} else if len(workingTblsWithViolations) > 0 {
|
||||
cli.Println(fmt.Sprintf(unmergedTablesHeader, "constraint violations"))
|
||||
} else {
|
||||
cli.Println(allMergedHeader)
|
||||
}
|
||||
}
|
||||
|
||||
n := printStagedDiffs(cli.CliOut, stagedTbls, stagedDocs, true)
|
||||
n = printDiffsNotStaged(ctx, dEnv, cli.CliOut, notStagedTbls, notStagedDocs, true, n, workingTblsInConflict)
|
||||
n = printDiffsNotStaged(ctx, dEnv, cli.CliOut, notStagedTbls, notStagedDocs, true, n, workingTblsInConflict, workingTblsWithViolations)
|
||||
|
||||
if !mergeActive && n == 0 {
|
||||
cli.Println("nothing to commit, working tree clean")
|
||||
|
||||
@@ -406,6 +406,16 @@ func (fkc *ForeignKeyCollection) Stage(ctx context.Context, fksToAdd []ForeignKe
|
||||
}
|
||||
}
|
||||
|
||||
// Tables returns the set of all tables that either declare a foreign key or are referenced by a foreign key.
|
||||
func (fkc *ForeignKeyCollection) Tables() map[string]struct{} {
|
||||
tables := make(map[string]struct{})
|
||||
for _, fk := range fkc.foreignKeys {
|
||||
tables[fk.TableName] = struct{}{}
|
||||
tables[fk.ReferencedTableName] = struct{}{}
|
||||
}
|
||||
return tables
|
||||
}
|
||||
|
||||
// String returns the SQL reference option in uppercase.
|
||||
func (refOp ForeignKeyReferenceOption) String() string {
|
||||
switch refOp {
|
||||
|
||||
@@ -1261,3 +1261,23 @@ func (root *RootValue) DebugString(ctx context.Context, transitive bool) string
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// MapTableHashes returns a map of each table name and hash.
|
||||
func (root *RootValue) MapTableHashes(ctx context.Context) (map[string]hash.Hash, error) {
|
||||
names, err := root.GetTableNames(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameToHash := make(map[string]hash.Hash)
|
||||
for _, name := range names {
|
||||
h, ok, err := root.GetTableHash(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !ok {
|
||||
return nil, fmt.Errorf("root found a table with name '%s' but no hash", name)
|
||||
} else {
|
||||
nameToHash[name] = h
|
||||
}
|
||||
}
|
||||
return nameToHash, nil
|
||||
}
|
||||
|
||||
3
go/libraries/doltcore/env/actions/staged.go
vendored
3
go/libraries/doltcore/env/actions/staged.go
vendored
@@ -111,6 +111,9 @@ func stageTablesNoEnvUpdate(
|
||||
return doltdb.Roots{}, err
|
||||
}
|
||||
roots.Staged, err = MoveTablesBetweenRoots(ctx, tbls, roots.Working, roots.Staged)
|
||||
if err != nil {
|
||||
return doltdb.Roots{}, err
|
||||
}
|
||||
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
29
go/libraries/doltcore/env/repo_state.go
vendored
29
go/libraries/doltcore/env/repo_state.go
vendored
@@ -257,17 +257,17 @@ func MergeWouldStompChanges(ctx context.Context, workingRoot *doltdb.RootValue,
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
headTableHashes, err := mapTableHashes(ctx, headRoot)
|
||||
headTableHashes, err := headRoot.MapTableHashes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
workingTableHashes, err := mapTableHashes(ctx, workingRoot)
|
||||
workingTableHashes, err := workingRoot.MapTableHashes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
mergeTableHashes, err := mapTableHashes(ctx, mergeRoot)
|
||||
mergeTableHashes, err := mergeRoot.MapTableHashes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -343,29 +343,6 @@ func GetGCKeepers(ctx context.Context, env *DoltEnv) ([]hash.Hash, error) {
|
||||
return keepers, nil
|
||||
}
|
||||
|
||||
func mapTableHashes(ctx context.Context, root *doltdb.RootValue) (map[string]hash.Hash, error) {
|
||||
names, err := root.GetTableNames(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nameToHash := make(map[string]hash.Hash)
|
||||
for _, name := range names {
|
||||
h, ok, err := root.GetTableHash(ctx, name)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !ok {
|
||||
panic("GetTableNames returned a table that GetTableHash says isn't there.")
|
||||
} else {
|
||||
nameToHash[name] = h
|
||||
}
|
||||
}
|
||||
|
||||
return nameToHash, nil
|
||||
}
|
||||
|
||||
func diffTableHashes(headTableHashes, otherTableHashes map[string]hash.Hash) map[string]hash.Hash {
|
||||
diffs := make(map[string]hash.Hash)
|
||||
for tName, hh := range headTableHashes {
|
||||
|
||||
@@ -56,7 +56,7 @@ const (
|
||||
)
|
||||
|
||||
// AddConstraintViolations adds all constraint violations to each table.
|
||||
func AddConstraintViolations(ctx context.Context, newRoot, ourRoot, baseRoot *doltdb.RootValue) (*doltdb.RootValue, error) {
|
||||
func AddConstraintViolations(ctx context.Context, newRoot, baseRoot *doltdb.RootValue) (*doltdb.RootValue, error) {
|
||||
fkColl, err := newRoot.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -913,14 +913,58 @@ func MergeRoots(ctx context.Context, ourRoot, theirRoot, ancRoot *doltdb.RootVal
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
newRoot, err = AddConstraintViolations(ctx, newRoot, ourRoot, ancRoot)
|
||||
newRoot, err = AddConstraintViolations(ctx, newRoot, ancRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for tblName, stats := range tblToStats {
|
||||
tbl, ok, err := newRoot.GetTable(ctx, tblName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ok {
|
||||
cvMap, err := tbl.GetConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
stats.ConstraintViolations = int(cvMap.Len())
|
||||
}
|
||||
}
|
||||
|
||||
return newRoot, tblToStats, nil
|
||||
}
|
||||
|
||||
// MayHaveConstraintViolations returns whether the given roots may have constraint violations. For example, a fast
|
||||
// forward merge that does not involve any tables with foreign key constraints or check constraints will not be able
|
||||
// to generate constraint violations. Unique key constraint violations would be caught during the generation of the
|
||||
// merged root, therefore it is not a factor for this function.
|
||||
func MayHaveConstraintViolations(ctx context.Context, ancestor, merged *doltdb.RootValue) (bool, error) {
|
||||
ancTables, err := ancestor.MapTableHashes(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
mergedTables, err := merged.MapTableHashes(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
fkColl, err := merged.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
tablesInFks := fkColl.Tables()
|
||||
for tblName := range tablesInFks {
|
||||
if ancHash, ok := ancTables[tblName]; !ok {
|
||||
// If a table used in a foreign key is new then it's treated as a change
|
||||
return true, nil
|
||||
} else if mergedHash, ok := mergedTables[tblName]; !ok {
|
||||
return false, fmt.Errorf("foreign key uses table '%s' but no hash can be found for this table", tblName)
|
||||
} else if !ancHash.Equal(mergedHash) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func GetTablesInConflict(ctx context.Context, roots doltdb.Roots) (
|
||||
workingInConflict, stagedInConflict, headInConflict []string,
|
||||
err error,
|
||||
@@ -943,6 +987,28 @@ func GetTablesInConflict(ctx context.Context, roots doltdb.Roots) (
|
||||
return workingInConflict, stagedInConflict, headInConflict, err
|
||||
}
|
||||
|
||||
func GetTablesWithConstraintViolations(ctx context.Context, roots doltdb.Roots) (
|
||||
workingViolations, stagedViolations, headViolations []string,
|
||||
err error,
|
||||
) {
|
||||
headViolations, err = roots.Head.TablesWithConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
stagedViolations, err = roots.Staged.TablesWithConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
workingViolations, err = roots.Working.TablesWithConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return workingViolations, stagedViolations, headViolations, err
|
||||
}
|
||||
|
||||
func GetDocsInConflict(ctx context.Context, workingRoot *doltdb.RootValue, drw env.DocsReadWriter) (*diff.DocDiffs, error) {
|
||||
docs, err := drw.GetDocsOnDisk()
|
||||
if err != nil {
|
||||
|
||||
@@ -24,9 +24,10 @@ const (
|
||||
)
|
||||
|
||||
type MergeStats struct {
|
||||
Operation TableMergeOp
|
||||
Adds int
|
||||
Deletes int
|
||||
Modifications int
|
||||
Conflicts int
|
||||
Operation TableMergeOp
|
||||
Adds int
|
||||
Deletes int
|
||||
Modifications int
|
||||
Conflicts int
|
||||
ConstraintViolations int
|
||||
}
|
||||
|
||||
@@ -137,26 +137,38 @@ func (d DoltMergeFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error)
|
||||
}
|
||||
|
||||
if canFF {
|
||||
if apr.Contains(cli.NoFFParam) {
|
||||
ws, err = executeNoFFMerge(ctx, sess, apr, dbName, ws, dbData, headCommit, mergeCommit)
|
||||
if err == doltdb.ErrUnresolvedConflicts {
|
||||
// if there are unresolved conflicts, write the resulting working set back to the session and return an
|
||||
// error message
|
||||
wsErr := sess.SetWorkingSet(ctx, dbName, ws, nil)
|
||||
if wsErr != nil {
|
||||
return nil, wsErr
|
||||
}
|
||||
|
||||
return err.Error(), nil
|
||||
}
|
||||
} else {
|
||||
err = executeFFMerge(ctx, sess, apr.Contains(cli.SquashParam), dbName, ws, dbData, mergeCommit)
|
||||
}
|
||||
|
||||
headRoot, err := headCommit.GetRootValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmh.String(), err
|
||||
mergeRoot, err := mergeCommit.GetRootValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cvPossible, err := merge.MayHaveConstraintViolations(ctx, headRoot, mergeRoot); err != nil {
|
||||
return nil, err
|
||||
} else if !cvPossible {
|
||||
if apr.Contains(cli.NoFFParam) {
|
||||
ws, err = executeNoFFMerge(ctx, sess, apr, dbName, ws, dbData, headCommit, mergeCommit)
|
||||
if err == doltdb.ErrUnresolvedConflicts {
|
||||
// if there are unresolved conflicts, write the resulting working set back to the session and return an
|
||||
// error message
|
||||
wsErr := sess.SetWorkingSet(ctx, dbName, ws, nil)
|
||||
if wsErr != nil {
|
||||
return nil, wsErr
|
||||
}
|
||||
|
||||
return err.Error(), nil
|
||||
}
|
||||
} else {
|
||||
err = executeFFMerge(ctx, sess, apr.Contains(cli.SquashParam), dbName, ws, dbData, mergeCommit)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmh.String(), err
|
||||
}
|
||||
}
|
||||
|
||||
ws, err = executeMerge(ctx, apr.Contains(cli.SquashParam), headCommit, mergeCommit, ws)
|
||||
|
||||
@@ -103,7 +103,19 @@ func (cf *MergeFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
|
||||
}
|
||||
|
||||
if canFF {
|
||||
return cmh.String(), nil
|
||||
ancRoot, err := head.GetRootValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergedRoot, err := cm.GetRootValue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cvPossible, err := merge.MayHaveConstraintViolations(ctx, ancRoot, mergedRoot); err != nil {
|
||||
return nil, err
|
||||
} else if !cvPossible {
|
||||
return cmh.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
mergeRoot, _, err := merge.MergeCommits(ctx, head, cm)
|
||||
|
||||
@@ -79,7 +79,7 @@ func NewConstraintViolationsTable(ctx *sql.Context, tblName string, root *doltdb
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sqlSch, err := sqlutil.FromDoltSchema(doltdb.DoltConfTablePrefix+tblName, dSch)
|
||||
sqlSch, err := sqlutil.FromDoltSchema(doltdb.DoltConstViolTablePrefix+tblName, dSch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -120,8 +120,8 @@ func (s *StrSet) Size() int {
|
||||
return len(s.items)
|
||||
}
|
||||
|
||||
// AsSlice converts the set to a slice of strings. If this is an insensitive set the resulting slice will be lowercase
|
||||
// regardless of the case that was used when adding the string to the set
|
||||
// AsSlice converts the set to a slice of strings. If this is an insensitive set the resulting slice will be lowercase
|
||||
// regardless of the case that was used when adding the string to the set.
|
||||
func (s *StrSet) AsSlice() []string {
|
||||
size := len(s.items)
|
||||
sl := make([]string, size)
|
||||
@@ -135,6 +135,16 @@ func (s *StrSet) AsSlice() []string {
|
||||
return sl
|
||||
}
|
||||
|
||||
// AsSortedSlice converts the set to a slice of strings. If this is an insensitive set the resulting slice will be lowercase
|
||||
// regardless of the case that was used when adding the string to the set. The slice is sorted in ascending order.
|
||||
func (s *StrSet) AsSortedSlice() []string {
|
||||
slice := s.AsSlice()
|
||||
sort.Slice(slice, func(i, j int) bool {
|
||||
return slice[i] < slice[j]
|
||||
})
|
||||
return slice
|
||||
}
|
||||
|
||||
// Iterate accepts a callback which will be called once for each element in the set until all items have been
|
||||
// exhausted or callback returns false.
|
||||
func (s *StrSet) Iterate(callBack func(string) (cont bool)) {
|
||||
@@ -145,7 +155,7 @@ func (s *StrSet) Iterate(callBack func(string) (cont bool)) {
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectionAndMissing takes a slice of strings and returns a slice of strings containing the intersection with the
|
||||
// LeftIntersectionRight takes a slice of strings and returns a slice of strings containing the intersection with the
|
||||
// set, and a slice of strings for the ones missing from the set.
|
||||
func (s *StrSet) LeftIntersectionRight(other *StrSet) (left *StrSet, intersection *StrSet, right *StrSet) {
|
||||
left = NewStrSet(nil)
|
||||
|
||||
@@ -453,7 +453,7 @@ teardown() {
|
||||
[[ ! "$output" =~ "ours" ]] || false
|
||||
[[ ! "$output" =~ "theirs" ]] || false
|
||||
run dolt status
|
||||
[[ "$output" =~ "All conflicts fixed but you are still merging." ]] || false
|
||||
[[ "$output" =~ "All conflicts and constraint violations fixed but you are still merging." ]] || false
|
||||
run dolt merge --abort
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
|
||||
@@ -89,7 +89,7 @@ SQL
|
||||
[[ "$output" =~ "1 rows modified" ]] || false
|
||||
[[ ! "$output" =~ "CONFLICT" ]] || false
|
||||
run dolt status
|
||||
[[ "$output" =~ "All conflicts fixed" ]] || false
|
||||
[[ "$output" =~ "All conflicts and constraint violations fixed" ]] || false
|
||||
[[ "$output" =~ "Changes to be committed:" ]] || false
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ SQL
|
||||
[[ "$output" =~ "1 rows modified" ]] || false
|
||||
[[ ! "$output" =~ "CONFLICT" ]] || false
|
||||
run dolt status
|
||||
[[ "$output" =~ "All conflicts fixed" ]] || false
|
||||
[[ "$output" =~ "All conflicts and constraint violations fixed" ]] || false
|
||||
[[ "$output" =~ "Changes to be committed:" ]] || false
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,56 @@ teardown() {
|
||||
teardown_common
|
||||
}
|
||||
|
||||
@test "constraint-violations: functions blocked with violations" {
|
||||
dolt sql <<"SQL"
|
||||
CREATE TABLE test (pk BIGINT PRIMARY KEY, v1 BIGINT, UNIQUE INDEX(v1));
|
||||
INSERT INTO test VALUES (1, 1), (2, 2);
|
||||
SQL
|
||||
dolt add -A
|
||||
dolt commit -m "MC1"
|
||||
dolt branch other
|
||||
dolt sql -q "INSERT INTO test VALUES (3, 3)"
|
||||
dolt add -A
|
||||
dolt commit -m "MC2"
|
||||
dolt checkout other
|
||||
dolt sql -q "INSERT INTO test VALUES (4, 3), (9, 9)"
|
||||
dolt add -A
|
||||
dolt commit -m "OC1"
|
||||
dolt checkout master
|
||||
|
||||
run dolt merge other
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "fix constraint violations" ]] || false
|
||||
run dolt sql -q "SELECT * FROM dolt_constraint_violations" -r=csv
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "table,num_violations" ]] || false
|
||||
[[ "$output" =~ "test,1" ]] || false
|
||||
[[ "${#lines[@]}" = "2" ]] || false
|
||||
run dolt status
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "fix constraint violations" ]] || false
|
||||
[[ "$output" =~ "test" ]] || false
|
||||
run dolt merge other
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "constraint violation" ]] || false
|
||||
run dolt add test
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "test" ]] || false
|
||||
run dolt commit -m "this should fail"
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "constraint violation" ]] || false
|
||||
|
||||
dolt sql -q "DELETE FROM dolt_constraint_violations_test"
|
||||
run dolt status
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "constraint violations fixed" ]] || false
|
||||
dolt add test
|
||||
dolt commit -m "this works"
|
||||
run dolt merge other
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "up to date" ]] || false
|
||||
}
|
||||
|
||||
@test "constraint-violations: ancestor contains fk, main parent remove, other child add, restrict" {
|
||||
dolt sql <<"SQL"
|
||||
CREATE TABLE parent (pk BIGINT PRIMARY KEY, v1 BIGINT, INDEX(v1));
|
||||
@@ -2579,7 +2629,6 @@ SQL
|
||||
}
|
||||
|
||||
@test "constraint-violations: cyclic foreign keys, illegal deletion" {
|
||||
skip "Figure out how we should handle fast forward with foreign key merging"
|
||||
# We're deleting a reference in a cycle from each table to make sure if properly applies a violation in both instances
|
||||
dolt sql <<"SQL"
|
||||
CREATE TABLE t1 (pk BIGINT PRIMARY KEY, v1 BIGINT, INDEX(v1));
|
||||
|
||||
@@ -833,7 +833,7 @@ SQL
|
||||
[[ "$output" =~ "one-more-time" ]] || false
|
||||
run dolt status
|
||||
echo "output = $output"
|
||||
[[ "$output" =~ "All conflicts fixed" ]] || false
|
||||
[[ "$output" =~ "All conflicts and constraint violations fixed" ]] || false
|
||||
[[ "$output" =~ "Changes to be committed:" ]] || false
|
||||
[[ "$output" =~ "README.md" ]] || false
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ SQL
|
||||
[[ "$output" =~ "3" ]] || false
|
||||
|
||||
run dolt status
|
||||
[[ "$output" =~ "All conflicts fixed but you are still merging" ]] || false
|
||||
[[ "$output" =~ "All conflicts and constraint violations fixed but you are still merging" ]] || false
|
||||
[[ "$output" =~ "Changes to be committed:" ]] || false
|
||||
[[ "$output" =~ ([[:space:]]*modified:[[:space:]]*test) ]] || false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user