From df95cbc367cd554abef4db7dfc65dcc9da82aa0b Mon Sep 17 00:00:00 2001 From: Pavel Safronov Date: Tue, 27 Jun 2023 14:09:31 -0700 Subject: [PATCH] Migrate `dolt conflicts resolve` to use only SQL. --- go/cmd/dolt/commands/cnfcmds/auto_resolve.go | 466 +----------------- go/cmd/dolt/commands/cnfcmds/resolve.go | 20 +- go/cmd/dolt/commands/status.go | 18 +- go/cmd/dolt/commands/utils.go | 4 +- integration-tests/bats/conflicts-resolve.bats | 233 +++++++++ integration-tests/bats/sql-local-remote.bats | 55 +++ 6 files changed, 325 insertions(+), 471 deletions(-) create mode 100644 integration-tests/bats/conflicts-resolve.bats diff --git a/go/cmd/dolt/commands/cnfcmds/auto_resolve.go b/go/cmd/dolt/commands/cnfcmds/auto_resolve.go index 1b3f10376d..7a1879bcb2 100644 --- a/go/cmd/dolt/commands/cnfcmds/auto_resolve.go +++ b/go/cmd/dolt/commands/cnfcmds/auto_resolve.go @@ -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 -} diff --git a/go/cmd/dolt/commands/cnfcmds/resolve.go b/go/cmd/dolt/commands/cnfcmds/resolve.go index a01d06ff94..77a291de93 100644 --- a/go/cmd/dolt/commands/cnfcmds/resolve.go +++ b/go/cmd/dolt/commands/cnfcmds/resolve.go @@ -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() } diff --git a/go/cmd/dolt/commands/status.go b/go/cmd/dolt/commands/status.go index 82f6eaa4df..59af81031a 100644 --- a/go/cmd/dolt/commands/status.go +++ b/go/cmd/dolt/commands/status.go @@ -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 } diff --git a/go/cmd/dolt/commands/utils.go b/go/cmd/dolt/commands/utils.go index 6be511f476..f3f4f784f6 100644 --- a/go/cmd/dolt/commands/utils.go +++ b/go/cmd/dolt/commands/utils.go @@ -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 } diff --git a/integration-tests/bats/conflicts-resolve.bats b/integration-tests/bats/conflicts-resolve.bats new file mode 100644 index 0000000000..e663f16f2b --- /dev/null +++ b/integration-tests/bats/conflicts-resolve.bats @@ -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 +} diff --git a/integration-tests/bats/sql-local-remote.bats b/integration-tests/bats/sql-local-remote.bats index a8c3a350ca..4674ac2e45 100644 --- a/integration-tests/bats/sql-local-remote.bats +++ b/integration-tests/bats/sql-local-remote.bats @@ -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 +} \ No newline at end of file