Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go/libraries/doltcore/doltdb/root_val.go
Original file line number Diff line number Diff line change
Expand Up @@ -1369,7 +1369,7 @@ func ValidateTagUniqueness(ctx context.Context, root RootValue, tableName string
err = sch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
oldTableName, ok := existing.Get(tag)
if ok && oldTableName != tableName {
return true, schema.ErrTagPrevUsed(tag, col.Name, tableName, oldTableName)
return true, schema.NewErrTagPrevUsed(tag, col.Name, tableName, oldTableName)
}
return false, nil
})
Expand Down
7 changes: 6 additions & 1 deletion go/libraries/doltcore/merge/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func MergeRoots(
// If a Full-Text table was both modified and deleted, then we want to ignore the deletion.
// If there's a true conflict, then the parent table will catch the conflict.
stats = &MergeStats{Operation: TableModified}
} else if errors.Is(ErrTableDeletedAndSchemaModified, err) {
} else if errors.Is(ErrTableDeletedAndSchemaModified, err) || errors.Is(ErrTableDeletedAndModified, err) {
tblToStats[tblName] = &MergeStats{
Operation: TableModified,
SchemaConflicts: 1,
Expand Down Expand Up @@ -307,6 +307,11 @@ func MergeRoots(
}

mergedRoot, err = mergedRoot.PutTable(ctx, tblName, mergedTable.table)
var errTagPreviouslyUsed schema.ErrTagPrevUsed
if errors.As(err, &errTagPreviouslyUsed) {
return nil, fmt.Errorf("cannot merge, column %s on table %s has duplicate tag as table %s. This was likely because one of the tables is a rename of the other",
errTagPreviouslyUsed.NewColName, errTagPreviouslyUsed.NewTableName, errTagPreviouslyUsed.OldTableName)
}
if err != nil {
return nil, err
}
Expand Down
22 changes: 20 additions & 2 deletions go/libraries/doltcore/schema/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,26 @@ const (
ReservedTagMin uint64 = 1 << 50
)

func ErrTagPrevUsed(tag uint64, newColName, newTableName, oldTableName string) error {
return fmt.Errorf("cannot create column %s on table %s, the tag %d was already used in table %s", newColName, newTableName, tag, oldTableName)
type ErrTagPrevUsed struct {
Tag uint64
NewColName string
NewTableName string
OldTableName string
}

var _ error = ErrTagPrevUsed{}

func (e ErrTagPrevUsed) Error() string {
return fmt.Sprintf("cannot create column %s on table %s, the tag %d was already used in table %s", e.NewColName, e.NewTableName, e.Tag, e.OldTableName)
}

func NewErrTagPrevUsed(tag uint64, newColName, newTableName, oldTableName string) ErrTagPrevUsed {
return ErrTagPrevUsed{
Tag: tag,
NewColName: newColName,
NewTableName: newTableName,
OldTableName: oldTableName,
}
}

type TagMapping map[uint64]string
Expand Down
4 changes: 2 additions & 2 deletions go/libraries/doltcore/sqle/alterschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func validateNewColumn(
cols := sch.GetAllCols()
err = cols.Iter(func(currColTag uint64, currCol schema.Column) (stop bool, err error) {
if currColTag == tag {
return false, schema.ErrTagPrevUsed(tag, newColName, tblName, tblName)
return false, schema.NewErrTagPrevUsed(tag, newColName, tblName, tblName)
} else if strings.EqualFold(currCol.Name, newColName) {
return true, fmt.Errorf("A column with the name %s already exists in table %s.", newColName, tblName)
}
Expand All @@ -150,7 +150,7 @@ func validateNewColumn(
return err
}
if found {
return schema.ErrTagPrevUsed(tag, newColName, tblName, oldTblName.Name)
return schema.NewErrTagPrevUsed(tag, newColName, tblName, oldTblName.Name)
}

return nil
Expand Down
2 changes: 1 addition & 1 deletion go/libraries/doltcore/sqle/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -1569,7 +1569,7 @@ func (db Database) createDoltTable(ctx *sql.Context, tableName string, schemaNam
return true, err
}
if exists && oldTableName.Name != tableName {
errStr := schema.ErrTagPrevUsed(tag, col.Name, tableName, oldTableName.Name).Error()
errStr := schema.NewErrTagPrevUsed(tag, col.Name, tableName, oldTableName.Name).Error()
conflictingTbls = append(conflictingTbls, errStr)
}
return false, nil
Expand Down
36 changes: 19 additions & 17 deletions go/libraries/doltcore/sqle/dprocedures/dolt_conflicts_resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,26 +352,28 @@ func ResolveSchemaConflicts(ctx *sql.Context, ddb *doltdb.DoltDB, ws *doltdb.Wor
return ws, nil // no schema conflicts
}

// TODO: There's an issue with using `dolt conflicts resolve` for schema conflicts, since having
//
// schema conflicts reported means that we haven't yet merged the table data. In some case,
// such as when there have ONLY been schema changes and no data changes that need to be
// merged, it is safe to use `dolt conflicts resolve`, but there are many other cases where the
// data changes would not be merged and could surprise customers. So, we are being cautious to
// prevent auto-resolution of schema changes with `dolt conflicts resolve` until we have a fix
// for resolving schema changes AND merging data (including dealing with any data conflicts).
// For more details, see: https://github.com/dolthub/dolt/issues/6616
if ws.MergeState().HasSchemaConflicts() {
return nil, fmt.Errorf("Unable to automatically resolve schema conflicts since data changes may " +
"not have been fully merged yet. " +
"To continue, abort this merge (dolt merge --abort) then apply ALTER TABLE statements to one " +
"side of this merge to get the two schemas in sync with the desired schema, then rerun the merge. " +
"To track resolution of this limitation, follow https://github.com/dolthub/dolt/issues/6616")
}

tblSet := doltdb.NewTableNameSet(tblNames)
updates := make(map[doltdb.TableName]*doltdb.Table)
err := ws.MergeState().IterSchemaConflicts(ctx, ddb, func(table doltdb.TableName, conflict doltdb.SchemaConflict) error {
// If one side dropped the table, we don't need to worry about correctly merging data. Otherwise, be cautious
// and prevent the merge.
if conflict.ToSch != nil && conflict.FromSch != nil {
// TODO: There's an issue with using `dolt conflicts resolve` for schema conflicts, since having
// schema conflicts reported means that we haven't yet merged the table data. In some cases,
// such as when one side of the branch drops the table, or when there have ONLY been schema changes
// and no data changes that need to be merged, it is safe to use `dolt conflicts resolve`, but there are
// many other cases where the data changes would not be merged and could surprise customers. So, we are
// being cautious to prevent auto-resolution of schema changes with `dolt conflicts resolve` until we have
// a fix for resolving schema changes AND merging data (including dealing with any data conflicts).
// For more details, see: https://github.com/dolthub/dolt/issues/6616
if ws.MergeState().HasSchemaConflicts() {
return fmt.Errorf("Unable to automatically resolve schema conflicts since data changes may " +
"not have been fully merged yet. " +
"To continue, abort this merge (dolt merge --abort) then apply ALTER TABLE statements to one " +
"side of this merge to get the two schemas in sync with the desired schema, then rerun the merge. " +
"To track resolution of this limitation, follow https://github.com/dolthub/dolt/issues/6616")
}
}
if !tblSet.Contains(table) {
return nil
}
Expand Down
15 changes: 14 additions & 1 deletion go/libraries/doltcore/sqle/dtables/schema_conflicts_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func newSchemaConflict(ctx *sql.Context, table doltdb.TableName, baseRoot doltdb

baseFKs, _ := fkc.KeysForTable(table)

var description string
var base string
if baseSch != nil {
var err error
Expand All @@ -187,6 +188,12 @@ func newSchemaConflict(ctx *sql.Context, table doltdb.TableName, baseRoot doltdb
}
} else {
ours = "<deleted>"
if schema.SchemasAreEqual(baseSch, c.FromSch) {
// If the schemas are equal, then this conflict represents a dropped table conflicting with a data conflict.
description = "cannot merge a table deletion with data modification"
} else {
description = "cannot merge a table deletion with schema modification"
}
}

var theirs string
Expand All @@ -198,6 +205,12 @@ func newSchemaConflict(ctx *sql.Context, table doltdb.TableName, baseRoot doltdb
}
} else {
theirs = "<deleted>"
if schema.SchemasAreEqual(baseSch, c.ToSch) {
// If the schemas are equal, then this conflict represents a dropped table conflicting with a data conflict.
description = "cannot merge a table deletion with data modification"
} else {
description = "cannot merge a table deletion with schema modification"
}
}

if c.ToSch == nil || c.FromSch == nil {
Expand All @@ -206,7 +219,7 @@ func newSchemaConflict(ctx *sql.Context, table doltdb.TableName, baseRoot doltdb
baseSch: base,
ourSch: ours,
theirSch: theirs,
description: "cannot merge a table deletion with schema modification",
description: description,
}, nil
}

Expand Down
60 changes: 56 additions & 4 deletions integration-tests/bats/merge.bats
Original file line number Diff line number Diff line change
Expand Up @@ -748,10 +748,38 @@ SQL
dolt commit -am "add data to test2"

dolt checkout main
dolt branch main-backup
run dolt merge feature-branch

log_status_eq 1
[[ "$output" =~ "conflict: table with same name deleted and modified" ]] || false
[[ "$output" =~ "CONFLICT (schema): Merge conflict in test" ]] || false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matching all the lines like this seems pretty frail. Would it make more sense to build these into the go engine tests?


run dolt conflicts cat .
[[ "$output" =~ "+------------+-------------------------------------------------------------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false
[[ "$output" =~ "| our_schema | their_schema | base_schema | description |" ]] || false
[[ "$output" =~ "+------------+-------------------------------------------------------------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false
[[ "$output" =~ '| <deleted> | CREATE TABLE `test2` ( | CREATE TABLE `test2` ( | cannot merge a table deletion with data modification |' ]] || false
[[ "$output" =~ '| | `pk` int NOT NULL, | `pk` int NOT NULL, | |' ]] || false
[[ "$output" =~ '| | `c1` int, | `c1` int, | |' ]] || false
[[ "$output" =~ '| | PRIMARY KEY (`pk`) | PRIMARY KEY (`pk`) | |' ]] || false
[[ "$output" =~ '| | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin; | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin; | |' ]] || false
[[ "$output" =~ "+------------+-------------------------------------------------------------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false

dolt conflicts resolve --ours test2
# resolving in favor of "our" branch means that the table is deleted.
run dolt ls
log_status_eq 0
[[ ! "$output" =~ "test2" ]] || false

# also test resolving in favor of "their" branch
dolt reset --hard main-backup
run dolt merge feature-branch
dolt conflicts resolve --theirs test2

run dolt sql -q "select COUNT(*) from test2"
log_status_eq 0
[[ ! "$output" =~ "3" ]] || false

}

@test "merge: merge a branch that edits the schema of a deleted table" {
Expand Down Expand Up @@ -812,7 +840,20 @@ SQL
run dolt merge feature-branch

log_status_eq 1
[[ "$output" =~ "conflict: table with same name deleted and modified" ]] || false
[[ "$output" =~ "CONFLICT (schema): Merge conflict in test" ]] || false

run dolt conflicts cat .
echo "$output"
[[ "$output" =~ "+-------------------------------------------------------------------+--------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false
[[ "$output" =~ "| our_schema | their_schema | base_schema | description |" ]] || false
[[ "$output" =~ "+-------------------------------------------------------------------+--------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false
[[ "$output" =~ '| CREATE TABLE `test2` ( | <deleted> | CREATE TABLE `test2` ( | cannot merge a table deletion with data modification |' ]] || false
[[ "$output" =~ '| `pk` int NOT NULL, | | `pk` int NOT NULL, | |' ]] || false
[[ "$output" =~ '| `c1` int, | | `c1` int, | |' ]] || false
[[ "$output" =~ '| PRIMARY KEY (`pk`) | | PRIMARY KEY (`pk`) | |' ]] || false
[[ "$output" =~ '| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin; | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin; | |' ]] || false
[[ "$output" =~ "+-------------------------------------------------------------------+--------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false

}

@test "merge: merge a branch that deletes a schema-edited table" {
Expand Down Expand Up @@ -1076,7 +1117,17 @@ SQL

run dolt merge merge_branch
log_status_eq 1
[[ "$output" =~ "conflict: table with same name deleted and modified" ]] || false
run dolt conflicts cat .
[[ "$output" =~ "+------------+-------------------------------------------------------------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false
[[ "$output" =~ "| our_schema | their_schema | base_schema | description |" ]] || false
[[ "$output" =~ "+------------+-------------------------------------------------------------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false
[[ "$output" =~ '| <deleted> | CREATE TABLE `test1` ( | CREATE TABLE `test1` ( | cannot merge a table deletion with data modification |' ]] || false
[[ "$output" =~ '| | `pk` int NOT NULL, | `pk` int NOT NULL, | |' ]] || false
[[ "$output" =~ '| | `c1` int, | `c1` int, | |' ]] || false
[[ "$output" =~ '| | PRIMARY KEY (`pk`) | PRIMARY KEY (`pk`) | |' ]] || false
[[ "$output" =~ '| | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin; | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin; | |' ]] || false
[[ "$output" =~ "+------------+-------------------------------------------------------------------+-------------------------------------------------------------------+------------------------------------------------------+" ]] || false

}

@test "merge: ourRoot renames, theirRoot modifies the schema" {
Expand Down Expand Up @@ -1119,7 +1170,7 @@ SQL

run dolt merge merge_branch
log_status_eq 1
[[ "$output" =~ "conflict: table with same name deleted and modified" ]] || false
[[ "$output" =~ "cannot merge, column pk on table new_name has duplicate tag as table test1. This was likely because one of the tables is a rename of the other" ]] || false
}

@test "merge: ourRoot modifies the schema, theirRoot renames" {
Expand All @@ -1134,6 +1185,7 @@ SQL

run dolt merge merge_branch
log_status_eq 1
echo "$output"
[[ "$output" =~ "cannot create column pk on table new_name" ]] || false
[[ "$output" =~ "already used in table test1" ]] || false
}
Expand Down
Loading