mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-09 16:16:08 -06:00
Migrate dolt conflicts resolve to use only SQL.
This commit is contained in:
@@ -15,22 +15,13 @@
|
||||
package cnfcmds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/cli"
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/commands"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/set"
|
||||
"github.com/gocraft/dbr/v2"
|
||||
"github.com/gocraft/dbr/v2/dialect"
|
||||
)
|
||||
|
||||
type AutoResolveStrategy int
|
||||
@@ -40,418 +31,35 @@ const (
|
||||
AutoResolveStrategyTheirs
|
||||
)
|
||||
|
||||
var ErrConfSchIncompatible = errors.New("the conflict schema's columns are not equal to the current schema's columns, please resolve manually")
|
||||
|
||||
// AutoResolveAll resolves all conflicts in all tables according to the given
|
||||
// |strategy|.
|
||||
func AutoResolveAll(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolveStrategy) error {
|
||||
root, err := dEnv.WorkingRoot(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tbls, err := root.GetTableNames(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return AutoResolveTables(ctx, dEnv, strategy, tbls)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func AutoResolveTables(queryist cli.Queryist, sqlCtx *sql.Context, strategy AutoResolveStrategy, tbls []string) error {
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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()
|
||||
for _, tableName := range tbls {
|
||||
resolveQuery := "CALL dolt_conflicts_resolve(?, ?)"
|
||||
var resolveParams []interface{}
|
||||
switch strategy {
|
||||
case AutoResolveStrategyOurs:
|
||||
updates[table] = ours
|
||||
resolveParams = []interface{}{"--ours", tableName}
|
||||
case AutoResolveStrategyTheirs:
|
||||
updates[table] = theirs
|
||||
resolveParams = []interface{}{"--theirs", tableName}
|
||||
default:
|
||||
panic("unhandled auto resolve strategy")
|
||||
return errors.New("invalid 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 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
|
||||
}
|
||||
if !ok {
|
||||
return doltdb.ErrTableNotFound
|
||||
}
|
||||
has, err := tbl.HasConflicts(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return nil
|
||||
}
|
||||
|
||||
sch, err := tbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, ourSch, theirSch, err := tbl.GetConflictSchemas(ctx, tblName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch strategy {
|
||||
case AutoResolveStrategyOurs:
|
||||
if !schema.ColCollsAreEqual(sch.GetAllCols(), ourSch.GetAllCols()) {
|
||||
return ErrConfSchIncompatible
|
||||
}
|
||||
case AutoResolveStrategyTheirs:
|
||||
if !schema.ColCollsAreEqual(sch.GetAllCols(), theirSch.GetAllCols()) {
|
||||
return ErrConfSchIncompatible
|
||||
}
|
||||
default:
|
||||
panic("unhandled auto resolve strategy")
|
||||
}
|
||||
|
||||
before, err := dEnv.WorkingRoot(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eng, dbName, err := engine.NewSqlEngineForEnv(ctx, dEnv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sqlCtx, err := eng.NewLocalContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sqlCtx.SetCurrentDatabase(dbName)
|
||||
|
||||
v, err := getFirstColumn(sqlCtx, eng, "SELECT @@DOLT_ALLOW_COMMIT_CONFLICTS;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldAllowCommitConflicts, ok := v.(int8)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of @DOLT_ALLOW_COMMIT_CONFLICTS: %T", v)
|
||||
}
|
||||
|
||||
// Resolving conflicts for one table, will not resolve conflicts on another.
|
||||
err = execute(sqlCtx, eng, "SET DOLT_ALLOW_COMMIT_CONFLICTS = 1;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err2 := execute(sqlCtx, eng, fmt.Sprintf("SET DOLT_ALLOW_COMMIT_CONFLICTS = %d", oldAllowCommitConflicts))
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
}()
|
||||
|
||||
if !schema.IsKeyless(sch) {
|
||||
err = resolvePkTable(sqlCtx, tblName, sch, strategy, eng)
|
||||
} else {
|
||||
err = resolveKeylessTable(sqlCtx, tblName, sch, strategy, eng)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = execute(sqlCtx, eng, "COMMIT;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
after, err := dEnv.WorkingRoot(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = validateConstraintViolations(ctx, before, after, tblName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolvePkTable(ctx *sql.Context, tblName string, sch schema.Schema, strategy AutoResolveStrategy, eng *engine.SqlEngine) error {
|
||||
identCols := getIdentifyingColumnNames(sch)
|
||||
allCols := sch.GetAllCols().GetColumnNames()
|
||||
|
||||
switch strategy {
|
||||
case AutoResolveStrategyOurs:
|
||||
err := oursPKResolver(ctx, eng, tblName)
|
||||
q, err := dbr.InterpolateForDialect(resolveQuery, resolveParams, dialect.MySQL)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error interpolating resolve conflicts query for table %s: %w", tableName, err)
|
||||
}
|
||||
case AutoResolveStrategyTheirs:
|
||||
err := theirsPKResolver(ctx, eng, tblName, allCols, identCols)
|
||||
_, err = commands.GetRowsForSql(queryist, sqlCtx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error resolving conflicts for table %s: %w", tableName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveKeylessTable(sqlCtx *sql.Context, tblName string, sch schema.Schema, strategy AutoResolveStrategy, eng *engine.SqlEngine) error {
|
||||
allCols := sch.GetAllCols().GetColumnNames()
|
||||
baseCols := strings.Join(quoteWithPrefix(allCols, "base_"), ", ")
|
||||
ourCols := strings.Join(quoteWithPrefix(allCols, "our_"), ", ")
|
||||
theirCols := strings.Join(quoteWithPrefix(allCols, "their_"), ", ")
|
||||
confTblName := fmt.Sprintf("`dolt_conflicts_%s`", tblName)
|
||||
|
||||
selectConfsQ := fmt.Sprintf(
|
||||
`SELECT
|
||||
%s,
|
||||
%s,
|
||||
%s,
|
||||
our_diff_type,
|
||||
their_diff_type,
|
||||
base_cardinality,
|
||||
our_cardinality,
|
||||
their_cardinality
|
||||
FROM %s;`, baseCols, ourCols, theirCols, confTblName)
|
||||
|
||||
sqlSch, itr, err := eng.Query(sqlCtx, selectConfsQ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := sqlutil.FromDoltSchema(tblName, sch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
confSplitter, err := newConflictSplitter(sqlSch[:len(sqlSch)-3], s.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
r, err := itr.Next(sqlCtx)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ourCardinality := r[len(r)-2].(uint64)
|
||||
theirCardinality := r[len(r)-1].(uint64)
|
||||
split, err := confSplitter.splitConflictRow(r[:len(r)-3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// In a keyless conflict, the non-null versions have equivalent rows.
|
||||
// The first version in the split is always non-null.
|
||||
rowVals := split[0].row
|
||||
|
||||
var rowDelta int64
|
||||
switch strategy {
|
||||
case AutoResolveStrategyOurs:
|
||||
rowDelta = 0
|
||||
case AutoResolveStrategyTheirs:
|
||||
rowDelta = int64(theirCardinality) - int64(ourCardinality)
|
||||
}
|
||||
|
||||
var stmt string
|
||||
var n int64
|
||||
if rowDelta > 0 {
|
||||
stmt, err = sqlfmt.SqlRowAsInsertStmt(rowVals, tblName, sch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n = rowDelta
|
||||
} else if rowDelta < 0 {
|
||||
stmt, err = sqlfmt.SqlRowAsDeleteStmt(rowVals, tblName, sch, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n = rowDelta * -1
|
||||
}
|
||||
|
||||
for i := int64(0); i < n; i++ {
|
||||
err = execute(sqlCtx, eng, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = execute(sqlCtx, eng, fmt.Sprintf("DELETE FROM %s", confTblName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func oursPKResolver(sqlCtx *sql.Context, eng *engine.SqlEngine, tblName string) error {
|
||||
del := fmt.Sprintf("DELETE FROM `dolt_conflicts_%s`;", tblName)
|
||||
err := execute(sqlCtx, eng, del)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIdentifyingColumnNames(sch schema.Schema) []string {
|
||||
if schema.IsKeyless(sch) {
|
||||
return sch.GetAllCols().GetColumnNames()
|
||||
} else {
|
||||
return sch.GetPKCols().GetColumnNames()
|
||||
}
|
||||
}
|
||||
|
||||
func theirsPKResolver(sqlCtx *sql.Context, eng *engine.SqlEngine, tblName string, allCols []string, identCols []string) error {
|
||||
dstCols := strings.Join(quoted(allCols), ", ")
|
||||
srcCols := strings.Join(quoteWithPrefix(allCols, "their_"), ", ")
|
||||
|
||||
cnfTbl := fmt.Sprintf("`dolt_conflicts_%s`", tblName)
|
||||
qName := fmt.Sprintf("`%s`", tblName)
|
||||
|
||||
q1 := fmt.Sprintf(
|
||||
`
|
||||
REPLACE INTO %s (%s) (
|
||||
SELECT %s
|
||||
FROM %s
|
||||
WHERE their_diff_type = 'modified' OR their_diff_type = 'added'
|
||||
);
|
||||
`, qName, dstCols, srcCols, cnfTbl)
|
||||
err := execute(sqlCtx, eng, q1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selCols := strings.Join(coalesced(identCols), ", ")
|
||||
q2 := fmt.Sprintf("SELECT %s from `dolt_conflicts_%s` WHERE their_diff_type = 'removed';", selCols, tblName)
|
||||
sch, itr, err := eng.Query(sqlCtx, q2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
row, err := itr.Next(sqlCtx)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
deleteFilter, err := buildFilter(identCols, row, sch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
del := fmt.Sprintf("DELETE from `%s` WHERE %s;", tblName, deleteFilter)
|
||||
err = execute(sqlCtx, eng, del)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
q3 := fmt.Sprintf("DELETE FROM `dolt_conflicts_%s`;", tblName)
|
||||
err = execute(sqlCtx, eng, q3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildFilter(columns []string, row sql.Row, rowSch sql.Schema) (string, error) {
|
||||
if len(columns) != len(row) {
|
||||
return "", errors.New("cannot build filter since number of columns does not match number of values")
|
||||
}
|
||||
vals, err := sqlfmt.SqlRowAsStrings(row, rowSch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var b strings.Builder
|
||||
var seen bool
|
||||
for i, v := range vals {
|
||||
_, _ = fmt.Fprintf(&b, "`%s` = %s", columns[i], v)
|
||||
if seen {
|
||||
_, _ = fmt.Fprintf(&b, "AND `%s` = %s", columns[i], v)
|
||||
}
|
||||
seen = true
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func coalesced(cols []string) []string {
|
||||
out := make([]string, len(cols))
|
||||
for i := range out {
|
||||
out[i] = fmt.Sprintf("coalesce(`base_%s`, `our_%s`, `their_%s`)", cols[i], cols[i], cols[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func quoted(cols []string) []string {
|
||||
out := make([]string, len(cols))
|
||||
for i := range out {
|
||||
out[i] = fmt.Sprintf("`%s`", cols[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func quoteWithPrefix(arr []string, prefix string) []string {
|
||||
out := make([]string, len(arr))
|
||||
for i := range arr {
|
||||
@@ -459,47 +67,3 @@ func quoteWithPrefix(arr []string, prefix string) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func execute(ctx *sql.Context, eng *engine.SqlEngine, query string) error {
|
||||
_, itr, err := eng.Query(ctx, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = itr.Next(ctx)
|
||||
for err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFirstColumn(ctx *sql.Context, eng *engine.SqlEngine, query string) (interface{}, error) {
|
||||
_, itr, err := eng.Query(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := itr.Next(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(r) == 0 {
|
||||
return nil, fmt.Errorf("no columns returned")
|
||||
}
|
||||
return r[0], nil
|
||||
}
|
||||
|
||||
func validateConstraintViolations(ctx context.Context, before, after *doltdb.RootValue, table string) error {
|
||||
tables, err := after.GetTableNames(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
violators, err := merge.GetForeignKeyViolatedTables(ctx, after, before, set.NewStrSet(tables))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if violators.Size() > 0 {
|
||||
return fmt.Errorf("resolving conflicts for table %s created foreign key violations", table)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package cnfcmds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/cli"
|
||||
@@ -93,13 +94,17 @@ func (cmd ResolveCmd) Exec(ctx context.Context, commandStr string, args []string
|
||||
help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, resDocumentation, ap))
|
||||
apr := cli.ParseArgsOrDie(ap, args, help)
|
||||
|
||||
if dEnv.IsLocked() {
|
||||
return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(env.ErrActiveServerLock.New(dEnv.LockFile())), usage)
|
||||
queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx)
|
||||
if err != nil {
|
||||
return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
if closeFunc != nil {
|
||||
defer closeFunc()
|
||||
}
|
||||
|
||||
var verr errhand.VerboseError
|
||||
if apr.ContainsAny(autoResolverParams...) {
|
||||
verr = autoResolve(ctx, apr, dEnv)
|
||||
verr = autoResolve(queryist, sqlCtx, apr)
|
||||
} else {
|
||||
verr = errhand.BuildDError("--ours or --theirs must be supplied").SetPrintUsage().Build()
|
||||
}
|
||||
@@ -107,7 +112,7 @@ func (cmd ResolveCmd) Exec(ctx context.Context, commandStr string, args []string
|
||||
return commands.HandleVErrAndExitCode(verr, usage)
|
||||
}
|
||||
|
||||
func autoResolve(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEnv) errhand.VerboseError {
|
||||
func autoResolve(queryist cli.Queryist, sqlCtx *sql.Context, apr *argparser.ArgParseResults) errhand.VerboseError {
|
||||
funcFlags := apr.FlagsEqualTo(autoResolverParams, true)
|
||||
|
||||
if funcFlags.Size() > 1 {
|
||||
@@ -121,12 +126,9 @@ func autoResolve(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.
|
||||
autoResolveStrategy := autoResolveStrategies[autoResolveFlag]
|
||||
|
||||
var err error
|
||||
|
||||
tbls := apr.Args
|
||||
if len(tbls) == 1 && tbls[0] == "." {
|
||||
err = AutoResolveAll(ctx, dEnv, autoResolveStrategy)
|
||||
} else {
|
||||
err = AutoResolveTables(ctx, dEnv, autoResolveStrategy, tbls)
|
||||
}
|
||||
err = AutoResolveTables(queryist, sqlCtx, autoResolveStrategy, tbls)
|
||||
if err != nil {
|
||||
return errhand.BuildDError("error: failed to resolve").AddCause(err).Build()
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ func createPrintData(err error, queryist cli.Queryist, sqlCtx *sql.Context, show
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statusRows, err := getRowsForSql(queryist, sqlCtx, "select * from dolt_status;")
|
||||
statusRows, err := GetRowsForSql(queryist, sqlCtx, "select * from dolt_status;")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -320,7 +320,7 @@ func getRemoteInfo(queryist cli.Queryist, sqlCtx *sql.Context, branchName string
|
||||
// get remote branch
|
||||
remoteBranchRef := fmt.Sprintf("remotes/%s/%s", remoteName, remoteBranchName)
|
||||
q := fmt.Sprintf("select name, hash from dolt_remote_branches where name = '%s';", remoteBranchRef)
|
||||
remoteBranches, err := getRowsForSql(queryist, sqlCtx, q)
|
||||
remoteBranches, err := GetRowsForSql(queryist, sqlCtx, q)
|
||||
if err != nil {
|
||||
return ahead, behind, err
|
||||
}
|
||||
@@ -330,7 +330,7 @@ func getRemoteInfo(queryist cli.Queryist, sqlCtx *sql.Context, branchName string
|
||||
remoteBranchCommit := remoteBranches[0][1].(string)
|
||||
|
||||
q = fmt.Sprintf("call dolt_count_commits('--from', '%s', '--to', '%s')", currentBranchCommit, remoteBranchCommit)
|
||||
rows, err := getRowsForSql(queryist, sqlCtx, q)
|
||||
rows, err := GetRowsForSql(queryist, sqlCtx, q)
|
||||
if err != nil {
|
||||
return ahead, behind, err
|
||||
}
|
||||
@@ -357,7 +357,7 @@ func getLocalBranchInfo(queryist cli.Queryist, sqlCtx *sql.Context, branchName s
|
||||
currentBranchCommit = ""
|
||||
remoteBranchName = ""
|
||||
|
||||
localBranches, err := getRowsForSql(queryist, sqlCtx, "select name, hash, remote, branch from dolt_branches;")
|
||||
localBranches, err := GetRowsForSql(queryist, sqlCtx, "select name, hash, remote, branch from dolt_branches;")
|
||||
if err != nil {
|
||||
return remoteName, remoteBranchName, currentBranchCommit, err
|
||||
}
|
||||
@@ -376,7 +376,7 @@ func getLocalBranchInfo(queryist cli.Queryist, sqlCtx *sql.Context, branchName s
|
||||
}
|
||||
|
||||
func getMergeStatus(queryist cli.Queryist, sqlCtx *sql.Context) (bool, error) {
|
||||
mergeRows, err := getRowsForSql(queryist, sqlCtx, "select is_merging from dolt_merge_status;")
|
||||
mergeRows, err := GetRowsForSql(queryist, sqlCtx, "select is_merging from dolt_merge_status;")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -396,7 +396,7 @@ func getMergeStatus(queryist cli.Queryist, sqlCtx *sql.Context) (bool, error) {
|
||||
|
||||
func getDataConflictsTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[string]bool, error) {
|
||||
dataConflictTables := make(map[string]bool)
|
||||
dataConflicts, err := getRowsForSql(queryist, sqlCtx, "select * from dolt_conflicts;")
|
||||
dataConflicts, err := GetRowsForSql(queryist, sqlCtx, "select * from dolt_conflicts;")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -409,7 +409,7 @@ func getDataConflictsTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[str
|
||||
|
||||
func getConstraintViolationTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[string]bool, error) {
|
||||
constraintViolationTables := make(map[string]bool)
|
||||
constraintViolations, err := getRowsForSql(queryist, sqlCtx, "select * from dolt_constraint_violations;")
|
||||
constraintViolations, err := GetRowsForSql(queryist, sqlCtx, "select * from dolt_constraint_violations;")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -423,7 +423,7 @@ func getConstraintViolationTables(queryist cli.Queryist, sqlCtx *sql.Context) (m
|
||||
func getWorkingStagedTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[string]bool, map[string]bool, error) {
|
||||
stagedTableNames := make(map[string]bool)
|
||||
workingTableNames := make(map[string]bool)
|
||||
diffs, err := getRowsForSql(queryist, sqlCtx, "select * from dolt_diff where commit_hash='WORKING' OR commit_hash='STAGED';")
|
||||
diffs, err := GetRowsForSql(queryist, sqlCtx, "select * from dolt_diff where commit_hash='WORKING' OR commit_hash='STAGED';")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -441,7 +441,7 @@ func getWorkingStagedTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[str
|
||||
|
||||
func getIgnoredTablePatternsFromSql(queryist cli.Queryist, sqlCtx *sql.Context) (doltdb.IgnorePatterns, error) {
|
||||
var ignorePatterns []doltdb.IgnorePattern
|
||||
ignoreRows, err := getRowsForSql(queryist, sqlCtx, fmt.Sprintf("select * from %s", doltdb.IgnoreTableName))
|
||||
ignoreRows, err := GetRowsForSql(queryist, sqlCtx, fmt.Sprintf("select * from %s", doltdb.IgnoreTableName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ func newLateBindingEngine(
|
||||
return lateBinder, nil
|
||||
}
|
||||
|
||||
func getRowsForSql(queryist cli.Queryist, sqlCtx *sql.Context, query string) ([]sql.Row, error) {
|
||||
func GetRowsForSql(queryist cli.Queryist, sqlCtx *sql.Context, query string) ([]sql.Row, error) {
|
||||
schema, rowIter, err := queryist.Query(sqlCtx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -294,7 +294,7 @@ func getRowsForSql(queryist cli.Queryist, sqlCtx *sql.Context, query string) ([]
|
||||
|
||||
func getActiveBranchName(sqlCtx *sql.Context, queryEngine cli.Queryist) (string, error) {
|
||||
query := "SELECT active_branch()"
|
||||
rows, err := getRowsForSql(queryEngine, sqlCtx, query)
|
||||
rows, err := GetRowsForSql(queryEngine, sqlCtx, query)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
233
integration-tests/bats/conflicts-resolve.bats
Normal file
233
integration-tests/bats/conflicts-resolve.bats
Normal file
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env bats
|
||||
load $BATS_TEST_DIRNAME/helper/common.bash
|
||||
|
||||
setup() {
|
||||
setup_common
|
||||
}
|
||||
|
||||
basic_conflict() {
|
||||
dolt sql -q "create table t (i int primary key, t text)"
|
||||
dolt add .
|
||||
dolt commit -am "init commit"
|
||||
dolt checkout -b other
|
||||
dolt sql -q "insert into t values (1,'other')"
|
||||
dolt commit -am "other commit"
|
||||
dolt checkout main
|
||||
dolt sql -q "insert into t values (1,'main')"
|
||||
dolt commit -am "main commit"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
assert_feature_version
|
||||
teardown_common
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: call with no arguments, errors" {
|
||||
run dolt conflicts resolve
|
||||
[ $status -eq 1 ]
|
||||
[[ $output =~ "--ours or --theirs must be supplied" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: call without specifying table, errors" {
|
||||
run dolt conflicts resolve --theirs
|
||||
[ $status -eq 1 ]
|
||||
[[ $output =~ "specify at least one table to resolve conflicts" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: call with non-existent table, errors" {
|
||||
run dolt conflicts resolve --ours notexists
|
||||
[ $status -eq 1 ]
|
||||
[[ $output =~ "table not found" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: no conflicts, no changes" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout main
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
dolt checkout other
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
|
||||
dolt checkout main
|
||||
run dolt conflicts resolve --ours t
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
run dolt conflicts resolve --theirs t
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
dolt checkout other
|
||||
run dolt conflicts resolve --ours t
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
|
||||
run dolt conflicts resolve --theirs t
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: merge other into main, resolve with ours" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout main
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
run dolt merge other
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt conflicts resolve --ours .
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: merge other into main, resolve with ours, specify table" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout main
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
run dolt merge other
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt conflicts resolve --ours t
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: merge other into main, resolve with theirs" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout main
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
run dolt merge other
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt conflicts resolve --theirs .
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: merge other into main, resolve with theirs, specify table" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout main
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
run dolt merge other
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt conflicts resolve --theirs t
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: merge main into other, resolve with ours" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout other
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
|
||||
run dolt merge main
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt conflicts resolve --ours .
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: merge main into other, resolve with ours, specify table" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout other
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
|
||||
run dolt merge main
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt conflicts resolve --ours t
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: merge main into other, resolve with theirs" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout other
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
|
||||
run dolt merge main
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt conflicts resolve --theirs .
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
}
|
||||
|
||||
@test "conflicts-resolve: merge main into other, resolve with theirs, specify table" {
|
||||
basic_conflict
|
||||
|
||||
dolt checkout other
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "other" ]] || false
|
||||
|
||||
run dolt merge main
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt conflicts resolve --theirs t
|
||||
[ $status -eq 0 ]
|
||||
run dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
}
|
||||
@@ -47,6 +47,18 @@ get_staged_tables() {
|
||||
'
|
||||
}
|
||||
|
||||
basic_conflict() {
|
||||
dolt --user dolt sql -q "create table t (i int primary key, t text)"
|
||||
dolt --user dolt add .
|
||||
dolt --user dolt commit -am "init commit"
|
||||
dolt --user dolt checkout -b other
|
||||
dolt --user dolt sql -q "insert into t values (1,'other')"
|
||||
dolt --user dolt commit -am "other commit"
|
||||
dolt --user dolt checkout main
|
||||
dolt --user dolt sql -q "insert into t values (1,'main')"
|
||||
dolt --user dolt commit -am "main commit"
|
||||
}
|
||||
|
||||
@test "sql-local-remote: test switch between server/no server" {
|
||||
start_sql_server defaultDB
|
||||
|
||||
@@ -439,3 +451,46 @@ get_staged_tables() {
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "When a password is provided, a user must also be provided" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-local-remote: verify dolt conflicts resolve behavior" {
|
||||
start_sql_server defaultDB
|
||||
cd defaultDB
|
||||
|
||||
basic_conflict
|
||||
dolt --user dolt checkout main
|
||||
run dolt --user dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
run dolt --user dolt merge other
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt --user dolt conflicts resolve --ours .
|
||||
[ $status -eq 0 ]
|
||||
remoteOutput=$output
|
||||
run dolt --user dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
stop_sql_server 1
|
||||
|
||||
basic_conflict
|
||||
dolt --user dolt checkout main
|
||||
run dolt --user dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
run dolt --user dolt merge other
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt --user dolt conflicts resolve --ours .
|
||||
[ $status -eq 0 ]
|
||||
localOutput=$output
|
||||
run dolt --user dolt sql -q "select * from t"
|
||||
[ $status -eq 0 ]
|
||||
[[ $output =~ "main" ]] || false
|
||||
|
||||
[[ "$remoteOutput" == "$localOutput" ]] || false
|
||||
}
|
||||
Reference in New Issue
Block a user