Merge pull request #9233 from dolthub/taylor/conflicts-preview

Add a `dolt_preview_merge_conflicts_summary` table function
This commit is contained in:
Taylor Bantle
2025-06-09 14:53:24 -07:00
committed by GitHub
23 changed files with 1513 additions and 49 deletions
+3 -3
View File
@@ -85,9 +85,9 @@ jobs:
- name: Install Maven
working-directory: ./.ci_bin
run: |
curl -LO https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz
tar -xf apache-maven-3.9.9-bin.tar.gz
echo "$(pwd)/apache-maven-3.9.9/bin" >> $GITHUB_PATH
curl -LO https://dlcdn.apache.org/maven/maven-3/3.9.10/binaries/apache-maven-3.9.10-bin.tar.gz
tar -xf apache-maven-3.9.10-bin.tar.gz
echo "$(pwd)/apache-maven-3.9.10/bin" >> $GITHUB_PATH
- name: Install Hadoop
working-directory: ./.ci_bin
run: |
+3 -3
View File
@@ -78,9 +78,9 @@ jobs:
if: ${{ env.use_credentials != 'true' }}
working-directory: ./.ci_bin
run: |
curl -LO https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz
tar -xf apache-maven-3.9.9-bin.tar.gz
echo "$(pwd)/apache-maven-3.9.9/bin" >> $GITHUB_PATH
curl -LO https://dlcdn.apache.org/maven/maven-3/3.9.10/binaries/apache-maven-3.9.10-bin.tar.gz
tar -xf apache-maven-3.9.10-bin.tar.gz
echo "$(pwd)/apache-maven-3.9.10/bin" >> $GITHUB_PATH
- name: Install Hadoop
if: ${{ env.use_credentials != 'true' }}
working-directory: ./.ci_bin
+3 -3
View File
@@ -91,9 +91,9 @@ jobs:
- name: Install Maven
working-directory: ./.ci_bin
run: |
curl -LO https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz
tar -xf apache-maven-3.9.9-bin.tar.gz
echo "$(pwd)/apache-maven-3.9.9/bin" >> $GITHUB_PATH
curl -LO https://dlcdn.apache.org/maven/maven-3/3.9.10/binaries/apache-maven-3.9.10-bin.tar.gz
tar -xf apache-maven-3.9.10-bin.tar.gz
echo "$(pwd)/apache-maven-3.9.10/bin" >> $GITHUB_PATH
- name: Install Hadoop
working-directory: ./.ci_bin
run: |
+1 -1
View File
@@ -77,7 +77,7 @@ jobs:
TMPDIR=$gw/tmp
./${{ env.SCRIPT_DIR}}/setup.sh $TMPDIR $DATADIR
# small python script times merge, we suppres errcodes but print error messages
# small python script times merge, we suppress errcodes but print error messages
cd $TMPDIR
python3 -c "import time, subprocess, sys; start = time.time(); res=subprocess.run(['dolt', 'merge', '--squash', 'main'], capture_output=True); err = res.stdout + res.stderr if res.returncode != 0 else ''; latency = time.time() -start; print(latency); sys.stderr.write(str(err))" 1> lat.log 2>err.log
latency=$(cat lat.log)
@@ -78,7 +78,7 @@ func mergeProllyTable(
if err != nil {
return nil, nil, err
}
valueMerger := newValueMerger(mergedSch, tm.leftSch, tm.rightSch, tm.ancSch, leftRows.Pool(), tm.ns)
valueMerger := NewValueMerger(mergedSch, tm.leftSch, tm.rightSch, tm.ancSch, leftRows.Pool(), tm.ns)
if !valueMerger.leftMapping.IsIdentityMapping() {
mergeInfo.LeftNeedsRewrite = true
@@ -397,7 +397,7 @@ func threeWayDiffer(ctx context.Context, tm *TableMerger, valueMerger *valueMerg
leftRows.Tuples(),
rightRows.Tuples(),
ancRows.Tuples(),
valueMerger.tryMerge,
valueMerger.TryMerge,
valueMerger.keyless,
diffInfo,
leftRows.Tuples().Order,
@@ -1681,7 +1681,7 @@ type valueMerger struct {
ns tree.NodeStore
}
func newValueMerger(merged, leftSch, rightSch, baseSch schema.Schema, syncPool pool.BuffPool, ns tree.NodeStore) *valueMerger {
func NewValueMerger(merged, leftSch, rightSch, baseSch schema.Schema, syncPool pool.BuffPool, ns tree.NodeStore) *valueMerger {
leftMapping, rightMapping, baseMapping := generateSchemaMappings(merged, leftSch, rightSch, baseSch)
baseToLeftMapping, baseToRightMapping, baseToResultMapping := generateSchemaMappings(baseSch, leftSch, rightSch, merged)
@@ -1753,11 +1753,11 @@ func findNonPKColumnMappingByTagOrName(sch schema.Schema, col schema.Column) int
}
}
// tryMerge performs a cell-wise merge given left, right, and base cell value
// TryMerge performs a cell-wise merge given left, right, and base cell value
// tuples. It returns the merged cell value tuple and a bool indicating if a
// conflict occurred. tryMerge should only be called if left and right produce
// conflict occurred. TryMerge should only be called if left and right produce
// non-identical diffs against base.
func (m *valueMerger) tryMerge(ctx *sql.Context, left, right, base val.Tuple) (val.Tuple, bool, error) {
func (m *valueMerger) TryMerge(ctx *sql.Context, left, right, base val.Tuple) (val.Tuple, bool, error) {
// If we're merging a keyless table and the keys match, but the values are different,
// that means that the row data is the same, but the cardinality has changed, and if the
// cardinality has changed in different ways on each merge side, we can't auto resolve.
+38 -5
View File
@@ -29,6 +29,7 @@ import (
"github.com/dolthub/dolt/go/libraries/utils/set"
"github.com/dolthub/dolt/go/store/atomicerr"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/prolly"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/types"
)
@@ -83,6 +84,34 @@ type TableMerger struct {
recordViolations bool
}
func (tm TableMerger) GetNewValueMerger(mergeSch schema.Schema, leftRows prolly.Map) *valueMerger {
return NewValueMerger(mergeSch, tm.leftSch, tm.rightSch, tm.ancSch, leftRows.Pool(), leftRows.NodeStore())
}
func rowsFromTable(ctx context.Context, tbl *doltdb.Table) (prolly.Map, error) {
rd, err := tbl.GetRowData(ctx)
if err != nil {
return prolly.Map{}, err
}
rows, err := durable.ProllyMapFromIndex(rd)
if err != nil {
return prolly.Map{}, err
}
return rows, nil
}
func (tm TableMerger) LeftRows(ctx context.Context) (prolly.Map, error) {
return rowsFromTable(ctx, tm.leftTbl)
}
func (tm TableMerger) RightRows(ctx context.Context) (prolly.Map, error) {
return rowsFromTable(ctx, tm.rightTbl)
}
func (tm TableMerger) AncRows(ctx context.Context) (prolly.Map, error) {
return rowsFromTable(ctx, tm.ancTbl)
}
func (tm TableMerger) tableHashes(ctx context.Context) (left, right, anc hash.Hash, err error) {
if tm.leftTbl != nil {
if left, err = tm.leftTbl.HashOf(); err != nil {
@@ -114,6 +143,10 @@ func (tm TableMerger) tableHashes(ctx context.Context) (left, right, anc hash.Ha
return
}
func (tm TableMerger) SchemaMerge(ctx *sql.Context, tblName doltdb.TableName) (schema.Schema, SchemaConflict, MergeInfo, tree.ThreeWayDiffInfo, error) {
return SchemaMerge(ctx, tm.vrw.Format(), tm.leftSch, tm.rightSch, tm.ancSch, tblName)
}
type RootMerger struct {
left doltdb.RootValue
right doltdb.RootValue
@@ -171,19 +204,19 @@ func (rm *RootMerger) MergeTable(
opts editor.Options,
mergeOpts MergeOpts,
) (*MergedResult, *MergeStats, error) {
tm, err := rm.makeTableMerger(ctx, tblName, mergeOpts)
tm, err := rm.MakeTableMerger(ctx, tblName, mergeOpts)
if err != nil {
return nil, nil, err
}
// short-circuit here if we can
finished, finishedRootObj, stats, err := rm.maybeShortCircuit(ctx, tm, mergeOpts)
finished, finishedRootObj, stats, err := rm.MaybeShortCircuit(ctx, tm, mergeOpts)
if finished != nil || stats != nil || err != nil {
return &MergedResult{table: finished, rootObj: finishedRootObj}, stats, err
}
// Calculate a merge of the schemas, but don't apply it yet
mergeSch, schConflicts, mergeInfo, diffInfo, err := SchemaMerge(ctx, tm.vrw.Format(), tm.leftSch, tm.rightSch, tm.ancSch, tblName)
mergeSch, schConflicts, mergeInfo, diffInfo, err := tm.SchemaMerge(ctx, tblName)
if err != nil {
return nil, nil, err
}
@@ -233,7 +266,7 @@ func (rm *RootMerger) MergeTable(
return &MergedResult{table: tbl, rootObj: rootObj}, stats, nil
}
func (rm *RootMerger) makeTableMerger(ctx context.Context, tblName doltdb.TableName, mergeOpts MergeOpts) (*TableMerger, error) {
func (rm *RootMerger) MakeTableMerger(ctx context.Context, tblName doltdb.TableName, mergeOpts MergeOpts) (*TableMerger, error) {
recordViolations := true
if mergeOpts.RecordViolationsForTables != nil {
if _, ok := mergeOpts.RecordViolationsForTables[tblName.ToLower()]; !ok {
@@ -335,7 +368,7 @@ func (rm *RootMerger) makeTableMerger(ctx context.Context, tblName doltdb.TableN
return &tm, nil
}
func (rm *RootMerger) maybeShortCircuit(ctx context.Context, tm *TableMerger, opts MergeOpts) (*doltdb.Table, doltdb.RootObject, *MergeStats, error) {
func (rm *RootMerger) MaybeShortCircuit(ctx context.Context, tm *TableMerger, opts MergeOpts) (*doltdb.Table, doltdb.RootObject, *MergeStats, error) {
// If we need to re-verify all constraints as part of this merge, then we can't short
// circuit considering any tables, so return immediately
if opts.ReverifyAllConstraints {
@@ -211,9 +211,9 @@ func TestRowMerge(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
v := newValueMerger(test.mergedSch, test.leftSch, test.rightSch, test.baseSch, syncPool, nil)
v := NewValueMerger(test.mergedSch, test.leftSch, test.rightSch, test.baseSch, syncPool, nil)
merged, ok, err := v.tryMerge(ctx, test.row, test.mergeRow, test.ancRow)
merged, ok, err := v.TryMerge(ctx, test.row, test.mergeRow, test.ancRow)
assert.NoError(t, err)
assert.Equal(t, test.expectConflict, !ok)
vD := test.mergedSch.GetValueDescriptor(v.ns)
@@ -0,0 +1,460 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dtablefunctions
import (
"errors"
"fmt"
"io"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/store/prolly/tree"
)
const previewMergeConflictsDefaultRowCount = 100
var _ sql.TableFunction = (*PreviewMergeConflictsSummaryTableFunction)(nil)
var _ sql.ExecSourceRel = (*PreviewMergeConflictsSummaryTableFunction)(nil)
var _ sql.AuthorizationCheckerNode = (*PreviewMergeConflictsSummaryTableFunction)(nil)
type PreviewMergeConflictsSummaryTableFunction struct {
ctx *sql.Context
leftBranchExpr sql.Expression
rightBranchExpr sql.Expression
database sql.Database
}
var previewMergeConflictsSummarySchema = sql.Schema{
&sql.Column{Name: "table", Type: types.Text, Nullable: false},
&sql.Column{Name: "num_data_conflicts", Type: types.Uint64, Nullable: true},
&sql.Column{Name: "num_schema_conflicts", Type: types.Uint64, Nullable: true},
}
// NewInstance creates a new instance of TableFunction interface
func (pm *PreviewMergeConflictsSummaryTableFunction) NewInstance(ctx *sql.Context, db sql.Database, expressions []sql.Expression) (sql.Node, error) {
newInstance := &PreviewMergeConflictsSummaryTableFunction{
ctx: ctx,
database: db,
}
node, err := newInstance.WithExpressions(expressions...)
if err != nil {
return nil, err
}
return node, nil
}
func (pm *PreviewMergeConflictsSummaryTableFunction) DataLength(ctx *sql.Context) (uint64, error) {
numBytesPerRow := schema.SchemaAvgLength(pm.Schema())
numRows, _, err := pm.RowCount(ctx)
if err != nil {
return 0, err
}
return numBytesPerRow * numRows, nil
}
func (pm *PreviewMergeConflictsSummaryTableFunction) RowCount(_ *sql.Context) (uint64, bool, error) {
return previewMergeConflictsDefaultRowCount, false, nil
}
// Database implements the sql.Databaser interface
func (pm *PreviewMergeConflictsSummaryTableFunction) Database() sql.Database {
return pm.database
}
// WithDatabase implements the sql.Databaser interface
func (pm *PreviewMergeConflictsSummaryTableFunction) WithDatabase(database sql.Database) (sql.Node, error) {
npm := *pm
npm.database = database
return &npm, nil
}
// Name implements the sql.TableFunction interface
func (pm *PreviewMergeConflictsSummaryTableFunction) Name() string {
return "dolt_preview_merge_conflicts_summary"
}
// Resolved implements the sql.Resolvable interface
func (pm *PreviewMergeConflictsSummaryTableFunction) Resolved() bool {
return pm.leftBranchExpr.Resolved() && pm.rightBranchExpr.Resolved()
}
func (pm *PreviewMergeConflictsSummaryTableFunction) IsReadOnly() bool {
return true
}
// String implements the Stringer interface
func (pm *PreviewMergeConflictsSummaryTableFunction) String() string {
return fmt.Sprintf("DOLT_PREVIEW_MERGE_CONFLICTS_SUMMARY(%s, %s)", pm.leftBranchExpr.String(), pm.rightBranchExpr.String())
}
// Schema implements the sql.Node interface.
func (pm *PreviewMergeConflictsSummaryTableFunction) Schema() sql.Schema {
return previewMergeConflictsSummarySchema
}
// Children implements the sql.Node interface.
func (pm *PreviewMergeConflictsSummaryTableFunction) Children() []sql.Node {
return nil
}
// WithChildren implements the sql.Node interface.
func (pm *PreviewMergeConflictsSummaryTableFunction) WithChildren(children ...sql.Node) (sql.Node, error) {
if len(children) != 0 {
return nil, fmt.Errorf("unexpected children")
}
return pm, nil
}
// CheckAuth implements the interface sql.AuthorizationCheckerNode.
func (pm *PreviewMergeConflictsSummaryTableFunction) CheckAuth(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool {
tblNames, err := pm.database.GetTableNames(ctx)
if err != nil {
return false
}
var operations []sql.PrivilegedOperation
for _, tblName := range tblNames {
subject := sql.PrivilegeCheckSubject{Database: pm.database.Name(), Table: tblName}
operations = append(operations, sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Select))
}
return opChecker.UserHasPrivileges(ctx, operations...)
}
// Expressions implements the sql.Expressioner interface.
func (pm *PreviewMergeConflictsSummaryTableFunction) Expressions() []sql.Expression {
return []sql.Expression{pm.leftBranchExpr, pm.rightBranchExpr}
}
// WithExpressions implements the sql.Expressioner interface.
func (pm *PreviewMergeConflictsSummaryTableFunction) WithExpressions(exprs ...sql.Expression) (sql.Node, error) {
if len(exprs) != 2 {
return nil, sql.ErrInvalidArgumentNumber.New(pm.Name(), "2", len(exprs))
}
for _, expr := range exprs {
if !expr.Resolved() {
return nil, ErrInvalidNonLiteralArgument.New(pm.Name(), expr.String())
}
// prepared statements resolve functions beforehand, so above check fails
if _, ok := expr.(sql.FunctionExpression); ok {
return nil, ErrInvalidNonLiteralArgument.New(pm.Name(), expr.String())
}
}
newPmcs := *pm
newPmcs.leftBranchExpr = exprs[0]
newPmcs.rightBranchExpr = exprs[1]
// validate the expressions
if !types.IsText(newPmcs.leftBranchExpr.Type()) && !expression.IsBindVar(newPmcs.leftBranchExpr) {
return nil, sql.ErrInvalidArgumentDetails.New(newPmcs.Name(), newPmcs.leftBranchExpr.String())
}
if !types.IsText(newPmcs.rightBranchExpr.Type()) && !expression.IsBindVar(newPmcs.rightBranchExpr) {
return nil, sql.ErrInvalidArgumentDetails.New(newPmcs.Name(), newPmcs.rightBranchExpr.String())
}
return &newPmcs, nil
}
// RowIter implements the sql.Node interface
func (pm *PreviewMergeConflictsSummaryTableFunction) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) {
leftBranchVal, rightBranchVal, err := pm.evaluateArguments()
if err != nil {
return nil, err
}
leftBranch, err := interfaceToString(leftBranchVal)
if err != nil {
return nil, fmt.Errorf("invalid left branch parameter: %w", err)
}
rightBranch, err := interfaceToString(rightBranchVal)
if err != nil {
return nil, fmt.Errorf("invalid right branch parameter: %w", err)
}
// Validate branch names are not empty
if leftBranch == "" {
return nil, fmt.Errorf("left branch name cannot be empty")
}
if rightBranch == "" {
return nil, fmt.Errorf("right branch name cannot be empty")
}
sqledb, ok := pm.database.(dsess.SqlDatabase)
if !ok {
return nil, fmt.Errorf("unexpected database type: %T", pm.database)
}
conflicts, err := getTablesWithConflicts(ctx, sqledb, leftBranch, rightBranch)
if err != nil {
return nil, err
}
return NewPreviewMergeConflictsSummaryTableFunctionRowIter(conflicts), nil
}
// evaluateArguments returns leftBranchVal and rightBranchVal.
// It evaluates the argument expressions to turn them into values this PreviewMergeConflictsSummaryTableFunction
// can use. Note that this method only evals the expressions, and doesn't validate the values.
func (pm *PreviewMergeConflictsSummaryTableFunction) evaluateArguments() (interface{}, interface{}, error) {
leftBranchVal, err := pm.leftBranchExpr.Eval(pm.ctx, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to evaluate left branch expression: %w", err)
}
rightBranchVal, err := pm.rightBranchExpr.Eval(pm.ctx, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to evaluate right branch expression: %w", err)
}
return leftBranchVal, rightBranchVal, nil
}
//--------------------------------------------------
// previewMergeConflictsSummaryTableFunctionRowIter
//--------------------------------------------------
var _ sql.RowIter = &previewMergeConflictsSummaryTableFunctionRowIter{}
type previewMergeConflictsSummaryTableFunctionRowIter struct {
conflicts []tableConflict
conIdx int
}
func NewPreviewMergeConflictsSummaryTableFunctionRowIter(pm []tableConflict) sql.RowIter {
return &previewMergeConflictsSummaryTableFunctionRowIter{
conflicts: pm,
}
}
func (iter *previewMergeConflictsSummaryTableFunctionRowIter) Next(ctx *sql.Context) (sql.Row, error) {
if iter.conIdx >= len(iter.conflicts) {
return nil, io.EOF
}
conflict := iter.conflicts[iter.conIdx]
iter.conIdx++
return getRowFromConflict(conflict), nil
}
func (iter *previewMergeConflictsSummaryTableFunctionRowIter) Close(context *sql.Context) error {
return nil
}
func getRowFromConflict(conflict tableConflict) sql.Row {
row := sql.Row{
conflict.tableName.String(), // table
}
// num_data_conflicts
if conflict.numSchemaConflicts > 0 {
row = append(row, nil)
} else {
row = append(row, conflict.numDataConflicts)
}
// num_schema_conflicts
row = append(row, conflict.numSchemaConflicts)
return row
}
// resolveBranchesToRoots resolves branch names to their corresponding root values
// and finds the common merge base. Returns left root, right root, and base root.
func resolveBranchesToRoots(ctx *sql.Context, db dsess.SqlDatabase, leftBranch, rightBranch string) (doltdb.RootValue, doltdb.RootValue, doltdb.RootValue, error) {
sess := dsess.DSessFromSess(ctx.Session)
headRef, err := sess.CWBHeadRef(ctx, db.Name())
if err != nil {
return nil, nil, nil, err
}
leftCm, err := resolveCommit(ctx, db.DbData().Ddb, headRef, leftBranch)
if err != nil {
return nil, nil, nil, err
}
rightCm, err := resolveCommit(ctx, db.DbData().Ddb, headRef, rightBranch)
if err != nil {
return nil, nil, nil, err
}
optCmt, err := doltdb.GetCommitAncestor(ctx, leftCm, rightCm)
if err != nil {
return nil, nil, nil, err
}
mergeBase, ok := optCmt.ToCommit()
if !ok {
return nil, nil, nil, doltdb.ErrGhostCommitEncountered
}
rightRoot, err := rightCm.GetRootValue(ctx)
if err != nil {
return nil, nil, nil, err
}
leftRoot, err := leftCm.GetRootValue(ctx)
if err != nil {
return nil, nil, nil, err
}
baseRoot, err := mergeBase.GetRootValue(ctx)
if err != nil {
return nil, nil, nil, err
}
return leftRoot, rightRoot, baseRoot, nil
}
type tableConflict struct {
tableName doltdb.TableName
numDataConflicts uint64 // ignored if schema conflicts exist
numSchemaConflicts uint64
}
// getTablesWithConflicts analyzes the merge between two branches and returns
// a list of tables that would have conflicts. It performs a dry-run merge
// to identify both schema and data conflicts without modifying the database.
func getTablesWithConflicts(ctx *sql.Context, db dsess.SqlDatabase, baseBranch, mergeBranch string) ([]tableConflict, error) {
leftRoot, rightRoot, baseRoot, err := resolveBranchesToRoots(ctx, db, baseBranch, mergeBranch)
if err != nil {
return nil, err
}
merger, err := merge.NewMerger(leftRoot, rightRoot, baseRoot, rightRoot, baseRoot, leftRoot.VRW(), leftRoot.NodeStore())
if err != nil {
return nil, err
}
tblNames, err := doltdb.UnionTableNames(ctx, leftRoot, rightRoot)
if err != nil {
return nil, err
}
mergeOpts := merge.MergeOpts{
IsCherryPick: false,
KeepSchemaConflicts: true,
ReverifyAllConstraints: false,
}
var conflicted []tableConflict
for _, tblName := range tblNames {
tm, err := merger.MakeTableMerger(ctx, tblName, mergeOpts)
if err != nil {
return nil, err
}
// short-circuit here if we can
finished, _, stats, err := merger.MaybeShortCircuit(ctx, tm, mergeOpts)
if err != nil {
return nil, err
}
if finished != nil || stats != nil {
continue
}
// Calculate a merge of the schemas, but don't apply it
mergeSch, schConflicts, _, diffInfo, err := tm.SchemaMerge(ctx, tblName)
if err != nil {
return nil, err
}
numSchemaConflicts := uint64(schConflicts.Count())
if numSchemaConflicts > 0 {
conflicted = append(conflicted, tableConflict{tableName: tblName, numSchemaConflicts: numSchemaConflicts})
// Cannot calculate data conflicts if there are schema conflicts
continue
}
dataConflicts, err := getDataConflictsForTable(ctx, tm, tblName, mergeSch, diffInfo)
if err != nil {
return nil, err
}
if dataConflicts != nil {
conflicted = append(conflicted, *dataConflicts)
}
}
return conflicted, nil
}
// getDataConflictsForTable calculates the number of data conflicts for a specific table.
// It performs a three-way diff to identify rows that cannot be automatically merged.
// Returns nil if no data conflicts are found.
func getDataConflictsForTable(ctx *sql.Context, tm *merge.TableMerger, tblName doltdb.TableName, mergeSch schema.Schema, diffInfo tree.ThreeWayDiffInfo) (*tableConflict, error) {
keyless := schema.IsKeyless(mergeSch)
leftRows, err := tm.LeftRows(ctx)
if err != nil {
return nil, err
}
rightRows, err := tm.RightRows(ctx)
if err != nil {
return nil, err
}
ancRows, err := tm.AncRows(ctx)
if err != nil {
return nil, err
}
valueMerger := tm.GetNewValueMerger(mergeSch, leftRows)
differ, err := tree.NewThreeWayDiffer(
ctx,
leftRows.NodeStore(),
leftRows.Tuples(),
rightRows.Tuples(),
ancRows.Tuples(),
valueMerger.TryMerge,
keyless,
diffInfo,
leftRows.Tuples().Order,
)
if err != nil {
return nil, err
}
var numDataConflicts uint64 = 0
for {
diff, err := differ.Next(ctx)
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, err
}
switch diff.Op {
case tree.DiffOpDivergentModifyConflict, tree.DiffOpDivergentDeleteConflict:
// In this case, a modification or delete was made to one side, and a conflicting delete or modification
// was made to the other side, so these cannot be automatically resolved.
numDataConflicts++
case tree.DiffOpConvergentAdd, tree.DiffOpConvergentModify, tree.DiffOpConvergentDelete:
if keyless {
numDataConflicts++
}
}
}
if numDataConflicts > 0 {
return &tableConflict{tableName: tblName, numSchemaConflicts: uint64(0), numDataConflicts: numDataConflicts}, nil
}
return nil, nil
}
@@ -23,6 +23,7 @@ var DoltTableFunctions = []sql.TableFunction{
&BranchStatusTableFunction{},
&LogTableFunction{},
&PatchTableFunction{},
&PreviewMergeConflictsSummaryTableFunction{},
&SchemaDiffTableFunction{},
&ReflogTableFunction{},
&QueryDiffTableFunction{},
@@ -1342,6 +1342,16 @@ func TestDoltMergeArtifacts(t *testing.T) {
RunDoltMergeArtifacts(t, h)
}
func TestDoltPreviewMergeConflicts(t *testing.T) {
h := newDoltEnginetestHarness(t)
RunDoltPreviewMergeConflictsTests(t, h)
}
func TestDoltPreviewMergeConflictsPrepared(t *testing.T) {
h := newDoltEnginetestHarness(t)
RunDoltPreviewMergeConflictsPreparedTests(t, h)
}
// these tests are temporary while there is a difference between the old format
// and new format merge behaviors.
func TestOldFormatMergeConflictsAndCVs(t *testing.T) {
@@ -988,6 +988,28 @@ func RunDoltMergeArtifacts(t *testing.T, h DoltEnginetestHarness) {
}
}
func RunDoltPreviewMergeConflictsTests(t *testing.T, h DoltEnginetestHarness) {
for _, script := range PreviewMergeConflictsFunctionScripts {
// harness can't reset effectively. Use a new harness for each script
func() {
h := h.NewHarness(t)
defer h.Close()
enginetest.TestScript(t, h, script)
}()
}
}
func RunDoltPreviewMergeConflictsPreparedTests(t *testing.T, h DoltEnginetestHarness) {
for _, script := range PreviewMergeConflictsFunctionScripts {
// harness can't reset effectively. Use a new harness for each script
func() {
h := h.NewHarness(t)
defer h.Close()
enginetest.TestScript(t, h, script)
}()
}
}
func RunDoltResetTest(t *testing.T, h DoltEnginetestHarness) {
for _, script := range DoltResetTestScripts {
// dolt versioning conflicts with reset harness -- use new harness every time
@@ -27,6 +27,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtablefunctions"
)
type MergeScriptTest struct {
@@ -260,6 +261,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL DOLT_CHECKOUT('main');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{},
},
{
// FF-Merge
Query: "CALL DOLT_MERGE('feature-branch')",
@@ -338,6 +343,10 @@ var MergeScripts = []queries.ScriptTest{
"use `mydb/main~`",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
ExpectedErrStr: "this operation is not supported while in a detached head state",
},
{
Query: "CALL DOLT_MERGE('feature-branch')",
ExpectedErrStr: "this operation is not supported while in a detached head state",
@@ -535,6 +544,18 @@ var MergeScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-a', '-m', 'update a value', '--date', '2022-08-06T12:00:03');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "SELECT is_merging, source, target, unmerged_tables FROM DOLT_MERGE_STATUS;",
Expected: []sql.Row{{false, nil, nil, nil}},
},
{
Query: "SELECT * from dolt_status",
Expected: []sql.Row{},
},
{
Query: "CALL DOLT_MERGE('feature-branch', '-m', 'this is a merge')",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -556,8 +577,8 @@ var MergeScripts = []queries.ScriptTest{
Expected: []sql.Row{{"update a value"}},
},
{
Query: "SELECT COUNT(*) FROM dolt_conflicts",
Expected: []sql.Row{{1}},
Query: "SELECT * FROM dolt_conflicts",
Expected: []sql.Row{{"test", uint64(1)}},
},
{
Query: "DELETE FROM dolt_conflicts_test",
@@ -575,6 +596,26 @@ var MergeScripts = []queries.ScriptTest{
Query: "SELECT * from test ORDER BY pk",
Expected: []sql.Row{{0, 1001}, {1, 1}},
},
{
Query: "SELECT is_merging, source, target, unmerged_tables FROM DOLT_MERGE_STATUS;",
Expected: []sql.Row{{true, "feature-branch", "refs/heads/main", ""}},
},
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}}, // merge wasn't committed yet, so still shows conflict between branches
},
{
Query: "CALL DOLT_COMMIT('-m', 'merged');",
Expected: []sql.Row{{doltCommit}},
},
{
Query: "SELECT is_merging, source, target, unmerged_tables FROM DOLT_MERGE_STATUS;",
Expected: []sql.Row{{false, nil, nil, nil}},
},
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{},
},
},
},
{
@@ -1042,10 +1083,18 @@ var MergeScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-A', '-m', 'commit');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('HEAD', 'HEAD~1')",
Expected: []sql.Row{},
},
{
Query: "CALL DOLT_MERGE('HEAD~1')",
Expected: []sql.Row{{"", 0, 0, "cannot fast forward from a to b. a is ahead of b already"}},
},
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('HEAD', 'HEAD')",
Expected: []sql.Row{},
},
{
Query: "CALL DOLT_MERGE('HEAD')",
Expected: []sql.Row{{"", 0, 0, "Everything up-to-date"}},
@@ -1069,17 +1118,25 @@ var MergeScripts = []queries.ScriptTest{
"set dolt_allow_commit_conflicts = on",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('feature-branch')",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
},
{
Query: "SELECT count(*) from dolt_conflicts_test",
Query: "SELECT * FROM dolt_conflicts",
Expected: []sql.Row{{"test", uint64(1)}},
},
{
Query: "SELECT COUNT(*) FROM dolt_conflicts_test",
Expected: []sql.Row{{1}},
},
{
// Test case-insensitive table name
Query: "SELECT count(*) from dolt_conflicts_TeST",
Query: "SELECT count(*) FROM dolt_conflicts_TeST",
Expected: []sql.Row{{1}},
},
{
@@ -1214,6 +1271,10 @@ var MergeScripts = []queries.ScriptTest{
"UPDATE test SET val=1001 WHERE pk=0;",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{},
},
{
Query: "CALL DOLT_MERGE('feature-branch', '-m', 'this is a merge')",
ExpectedErrStr: "error: local changes would be stomped by merge:\n\ttest\n Please commit your changes before you merge.",
@@ -1242,6 +1303,10 @@ var MergeScripts = []queries.ScriptTest{
"call dolt_commit('-am', 'main primary key change')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'b1')",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('b1')",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1548,6 +1613,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'left');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other')",
ExpectedErrStr: "table with same name 't' added in 2 commits can't be merged",
},
{
Query: "CALL DOLT_MERGE('other');",
ExpectedErrStr: "table with same name 't' added in 2 commits can't be merged",
@@ -1571,6 +1640,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'left');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other')",
Expected: []sql.Row{},
},
{
Query: "CALL DOLT_MERGE('other');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1598,6 +1671,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'left');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('other');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1656,6 +1733,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL dolt_checkout('main');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'test')",
Expected: []sql.Row{},
},
{
Query: "CALL dolt_merge('test');",
Expected: []sql.Row{{doltCommit, 1, 0, "merge successful"}},
@@ -1693,6 +1774,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL dolt_commit('-a', '-m', 'cm3');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'test')",
Expected: []sql.Row{},
},
{
Query: "CALL dolt_merge('test');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1764,6 +1849,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL dolt_commit('-am', 'cm3');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'test')",
Expected: []sql.Row{},
},
{
Query: "CALL dolt_merge('test');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1803,6 +1892,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'left cm');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'right')",
Expected: []sql.Row{},
},
{
Query: "CALL DOLT_MERGE('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1950,6 +2043,11 @@ var MergeScripts = []queries.ScriptTest{
"set dolt_force_transaction_commit = on;",
},
Assertions: []queries.ScriptTestAssertion{
{
Skip: true, // TODO: constraint violations
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('other');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -2099,6 +2197,10 @@ var MergeScripts = []queries.ScriptTest{
"call dolt_commit('-am', 'left update')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature')",
Expected: []sql.Row{{"xyz", uint64(1), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('feature');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -2126,6 +2228,10 @@ var MergeScripts = []queries.ScriptTest{
"call dolt_commit('-am', 'left update')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature')",
Expected: []sql.Row{{"xyz", uint64(1), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('feature');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -2153,6 +2259,10 @@ var MergeScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'left commit');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'right')",
ExpectedErrStr: "error: cannot merge because table t has different primary keys",
},
{
Query: "CALL DOLT_MERGE('right');",
ExpectedErrStr: "error: cannot merge because table t has different primary keys",
@@ -2384,8 +2494,13 @@ var MergeScripts = []queries.ScriptTest{
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('other')",
SkipResultsCheck: true, // contains commit hash, we just need it to not error
Skip: true, // TODO: conflict: table with same name deleted and modified
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other')",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('other')",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
},
{
Query: "SELECT v1 FROM test WHERE MATCH(v1) AGAINST ('abc def ghi');",
@@ -2419,6 +2534,10 @@ var MergeScripts = []queries.ScriptTest{
"SET @PreMergeBranch1Commit = dolt_hashof('HEAD');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('branch1', 'main')",
Expected: []sql.Row{},
},
{
// We can merge from main -> branch1, even though the column tags are not identical
Query: "call dolt_merge('main')",
@@ -2437,6 +2556,10 @@ var MergeScripts = []queries.ScriptTest{
Query: "CALL dolt_checkout('main');",
Expected: []sql.Row{{0, "Switched to branch 'main'"}},
},
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
// We can merge from branch1 -> main, even though the column tags are not identical
Query: "call dolt_merge('branch1')",
@@ -2522,6 +2645,10 @@ var MergeScripts = []queries.ScriptTest{
"call dolt_commit('-Am', 'change default on other');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('other', 'main')",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('main')",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -2686,6 +2813,10 @@ var DoltConflictTableNameTableTests = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'left edit');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other')",
Expected: []sql.Row{{"t", uint64(4), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('other');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -2730,6 +2861,10 @@ var DoltConflictTableNameTableTests = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'left');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'right')",
Expected: []sql.Row{{"t", uint64(6), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -3273,6 +3408,10 @@ var MergeArtifactsScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-afm', 'update pk 1 to 1000');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other2')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('other2');",
ExpectedErrStr: "the existing conflicts are of a different schema than the conflicts generated by this merge. Please resolve them and try again",
@@ -3435,6 +3574,11 @@ var MergeArtifactsScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'left insert');",
},
Assertions: []queries.ScriptTestAssertion{
{
Skip: true, // TODO: constraint violations
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'left2')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "CALL DOLT_MERGE('left2');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -3836,6 +3980,10 @@ var SchemaConflictScripts = []queries.ScriptTest{
"call dolt_commit('-am', 'altered t on branch main')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other')",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('other')",
ExpectedErrStr: dsess.ErrUnresolvedConflictsAutoCommit.Error(),
@@ -3864,6 +4012,10 @@ var SchemaConflictScripts = []queries.ScriptTest{
"call dolt_commit('-am', 'altered t on branch main')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'other')",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('other')",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -4261,6 +4413,10 @@ var GeneratedColumnMergeTestScripts = []queries.ScriptTest{
"call dolt_checkout('main')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('branch1')",
Expected: []sql.Row{{doltCommit, 1, 0, "merge successful"}},
@@ -4313,6 +4469,10 @@ var GeneratedColumnMergeTestScripts = []queries.ScriptTest{
"call dolt_checkout('main')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('branch1')",
Expected: []sql.Row{{doltCommit, 1, 0, "merge successful"}},
@@ -4328,6 +4488,10 @@ var GeneratedColumnMergeTestScripts = []queries.ScriptTest{
Query: "select id from t1 where v3 = 5",
Expected: []sql.Row{{1}},
},
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('branch2')",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -4401,6 +4565,10 @@ var GeneratedColumnMergeTestScripts = []queries.ScriptTest{
"call dolt_checkout('main')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('branch1')",
Expected: []sql.Row{{doltCommit, 1, 0, "merge successful"}},
@@ -4478,6 +4646,539 @@ var GeneratedColumnMergeTestScripts = []queries.ScriptTest{
},
}
var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{
{
Name: "invalid arguments",
SetUpScript: []string{
"create table t (pk int primary key, c1 varchar(20), c2 varchar(20));",
"insert into t values (1, 'one', 'two'), (2, 'two', 'three');",
"call dolt_add('.')",
"call dolt_commit('-am', 'creating table t');",
"call dolt_branch('branch1')",
"call dolt_branch('branch2')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary();",
ExpectedErr: sql.ErrInvalidArgumentNumber,
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('t');",
ExpectedErr: sql.ErrInvalidArgumentNumber,
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1', 't');",
ExpectedErr: sql.ErrInvalidArgumentNumber,
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary(null, null);",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 123);",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary(123, 'branch1');",
ExpectedErr: sql.ErrInvalidArgumentDetails,
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('fake-branch', 'main');",
ExpectedErrStr: "branch not found: fake-branch",
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'fake-branch');",
ExpectedErrStr: "branch not found: fake-branch",
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main...branch1', 'branch2');",
ExpectedErrStr: "string is not a valid branch or hash",
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', concat('branch', '1'));",
ExpectedErr: dtablefunctions.ErrInvalidNonLiteralArgument,
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary(hashof('main'), 'branch1');",
ExpectedErr: dtablefunctions.ErrInvalidNonLiteralArgument,
},
},
},
{
Name: "basic case with single table",
SetUpScript: []string{
"create table t (pk int primary key, c1 varchar(20), c2 varchar(20));",
"insert into t values (1, 'one', 'two'), (2, 'two', 'three');",
"call dolt_add('.')",
"set @Commit1 = '';",
"call dolt_commit_hash_out(@Commit1, '-am', 'creating table t');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"update t set c1='one!' where pk=1",
"set @Commit2 = '';",
"call dolt_commit_hash_out(@Commit2, '-am', 'update row 1 on branch2');",
"call dolt_checkout('branch1')",
"update t set c1='one?' where pk=1",
"set @Commit3 = '';",
"call dolt_commit_hash_out(@Commit3, '-am', 'update row 1 on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary(@Commit1, @Commit2)", // not branches
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch2', 'main')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
},
},
{
Name: "basic case with keyless table",
SetUpScript: []string{
"create table t (pk int, c1 varchar(20), c2 varchar(20));",
"insert into t values (1, 'one', 'two'), (2, 'two', 'three');",
"call dolt_add('.')",
"set @Commit1 = '';",
"call dolt_commit_hash_out(@Commit1, '-am', 'creating table t');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"update t set c1='one!' where pk=1",
"set @Commit2 = '';",
"call dolt_commit_hash_out(@Commit2, '-am', 'update row 1 on branch2');",
"call dolt_checkout('branch1')",
"update t set c1='one?' where pk=1",
"set @Commit3 = '';",
"call dolt_commit_hash_out(@Commit3, '-am', 'update row 1 on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary(@Commit1, @Commit2)", // not branches
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch2', 'main')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
},
},
{
Name: "basic case with multiple tables",
SetUpScript: []string{
"create table t (pk int primary key, c1 varchar(20), c2 varchar(20));",
"create table t2 (pk int primary key, c1 varchar(20));",
"insert into t values (1, 'one', 'two'), (2, 'two', 'three');",
"insert into t2 values(100, 'hundred');",
"call dolt_add('.')",
"set @Commit1 = '';",
"call dolt_commit_hash_out(@Commit1, '-am', 'creating table t');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"update t set c1='one!' where pk=1",
"alter table t2 alter column c1 set default 'default';",
"set @Commit2 = '';",
"call dolt_commit_hash_out(@Commit2, '-am', 'update row 1 on branch2');",
"call dolt_checkout('branch1')",
"update t set c1='one?' where pk=1",
"alter table t2 alter column c1 set default 'default2';",
"set @Commit3 = '';",
"call dolt_commit_hash_out(@Commit3, '-am', 'update row 1 on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
"create table keyless (id int);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}, {"t2", nil, uint64(1)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}, {"t2", nil, uint64(1)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary(@Commit1, @Commit2)", // not branches
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch2', 'main')",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}, {"t2", nil, uint64(1)}},
},
},
},
{
Name: "schema-only conflicts",
SetUpScript: []string{
"create table t (pk int primary key, c1 varchar(20) default 'orig', c2 varchar(20));",
"insert into t values (1, 'one', 'two'), (2, 'two', 'three');",
"call dolt_add('.')",
"call dolt_commit('-am', 'creating table t');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"alter table t alter column c1 set default 'default1';",
"call dolt_commit('-am', 'change default on branch2');",
"call dolt_checkout('branch1')",
"alter table t alter column c1 set default 'default2';",
"call dolt_commit('-am', 'change default on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
},
},
{
Name: "mixed schema and data conflicts in same table",
SetUpScript: []string{
"create table t (pk int primary key, c1 varchar(20) default 'orig', c2 varchar(20));",
"insert into t values (1, 'one', 'two'), (2, 'two', 'three');",
"call dolt_add('.')",
"call dolt_commit('-am', 'creating table t');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"update t set c1='one!' where pk=1",
"alter table t alter column c1 set default 'default1';",
"call dolt_commit('-am', 'data and schema changes on branch2');",
"call dolt_checkout('branch1')",
"update t set c1='one?' where pk=1",
"alter table t alter column c1 set default 'default2';",
"call dolt_commit('-am', 'data and schema changes on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
},
},
{
Name: "column type conflicts",
SetUpScript: []string{
"create table t (pk int primary key, c1 varchar(20));",
"insert into t values (1, 'one');",
"call dolt_add('.')",
"call dolt_commit('-am', 'initial commit');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"alter table t modify column c1 varchar(50);",
"call dolt_commit('-am', 'change column to varchar(50) on branch2');",
"call dolt_checkout('branch1')",
"alter table t modify column c1 text;",
"call dolt_commit('-am', 'change column to text on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
},
},
{
Name: "foreign key constraint conflicts",
SetUpScript: []string{
"create table parent (pk int primary key, name varchar(20));",
"create table child (pk int primary key, parent_pk int, data varchar(20));",
"insert into parent values (1, 'parent1'), (2, 'parent2');",
"insert into child values (1, 1, 'child1'), (2, 2, 'child2');",
"call dolt_add('.')",
"call dolt_commit('-am', 'initial tables');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"alter table child add constraint fk1 foreign key (parent_pk) references parent(pk) on delete cascade;",
"call dolt_commit('-am', 'add fk with cascade on branch2');",
"call dolt_checkout('branch1')",
"alter table child add constraint fk1 foreign key (parent_pk) references parent(pk) on delete restrict;",
"call dolt_commit('-am', 'add fk with restrict on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{},
},
},
},
{
Name: "check constraint conflicts",
SetUpScript: []string{
"create table t (pk int primary key, score int);",
"insert into t values (1, 85), (2, 92);",
"call dolt_add('.')",
"call dolt_commit('-am', 'initial table');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"alter table t add constraint chk1 check (score >= 0);",
"call dolt_commit('-am', 'add check >= 0 on branch2');",
"call dolt_checkout('branch1')",
"alter table t add constraint chk1 check (score > 0);",
"call dolt_commit('-am', 'add check > 0 on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(2)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(2)}},
},
},
},
{
Name: "index conflicts",
SetUpScript: []string{
"create table t (pk int primary key, name varchar(20), email varchar(50));",
"insert into t values (1, 'alice', 'alice@email.com'), (2, 'bob', 'bob@email.com');",
"call dolt_add('.')",
"call dolt_commit('-am', 'initial table');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"create index idx_name on t(name);",
"call dolt_commit('-am', 'add name index on branch2');",
"call dolt_checkout('branch1')",
"create unique index idx_name on t(name);",
"call dolt_commit('-am', 'add unique name index on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(2)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", nil, uint64(2)}},
},
},
},
{
Name: "many data conflicts",
SetUpScript: []string{
"create table t (pk int primary key, c1 varchar(20), c2 varchar(20), c3 varchar(20));",
"insert into t values (1, 'a1', 'b1', 'c1'), (2, 'a2', 'b2', 'c2'), (3, 'a3', 'b3', 'c3'), (4, 'a4', 'b4', 'c4'), (5, 'a5', 'b5', 'c5');",
"call dolt_add('.')",
"call dolt_commit('-am', 'initial data');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"update t set c1=concat(c1, '_branch2') where pk in (1,2,3);",
"update t set c2=concat(c2, '_branch2') where pk in (2,4);",
"call dolt_commit('-am', 'modify multiple rows on branch2');",
"call dolt_checkout('branch1')",
"update t set c1=concat(c1, '_branch1') where pk in (1,2,3);",
"update t set c2=concat(c2, '_branch1') where pk in (2,4);",
"update t set c3=concat(c3, '_branch1') where pk = 5;",
"call dolt_commit('-am', 'modify multiple rows on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{{"t", uint64(4), uint64(0)}}, // 4 rows have conflicts (1,2,3,4)
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{{"t", uint64(4), uint64(0)}},
},
},
},
{
Name: "multiple tables with different conflict types",
SetUpScript: []string{
"create table data_conflicts (pk int primary key, value varchar(20));",
"create table schema_conflicts (pk int primary key, name varchar(20) default 'default');",
"create table no_conflicts (pk int primary key, info varchar(20));",
"insert into data_conflicts values (1, 'original'), (2, 'data');",
"insert into schema_conflicts values (1, 'schema');",
"insert into no_conflicts values (1, 'unchanged');",
"call dolt_add('.')",
"call dolt_commit('-am', 'initial setup');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"update data_conflicts set value='branch2_value' where pk=1;",
"alter table schema_conflicts alter column name set default 'branch2_default';",
"call dolt_commit('-am', 'changes on branch2');",
"call dolt_checkout('branch1')",
"update data_conflicts set value='branch1_value' where pk=1;",
"alter table schema_conflicts alter column name set default 'branch1_default';",
"call dolt_commit('-am', 'changes on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2') order by `table`",
Expected: []sql.Row{{"data_conflicts", uint64(1), uint64(0)}, {"schema_conflicts", nil, uint64(1)}},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2') order by `table`",
Expected: []sql.Row{{"data_conflicts", uint64(1), uint64(0)}, {"schema_conflicts", nil, uint64(1)}},
},
},
},
{
Name: "empty result when no conflicts",
SetUpScript: []string{
"create table t (pk int primary key, value varchar(20));",
"insert into t values (1, 'original');",
"call dolt_add('.')",
"call dolt_commit('-am', 'initial setup');",
"call dolt_branch('branch1')",
"call dolt_checkout('-b', 'branch2')",
"insert into t values (2, 'branch2_new');",
"call dolt_commit('-am', 'add row on branch2');",
"call dolt_checkout('branch1')",
"insert into t values (3, 'branch1_new');",
"call dolt_commit('-am', 'add row on branch1');",
"call dolt_checkout('main')",
"call dolt_merge('branch1')",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')",
Expected: []sql.Row{},
},
{
Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')",
Expected: []sql.Row{},
},
},
},
}
// convertMergeScriptTest converts a MergeScriptTest into a standard ScriptTest. If flipSides is true, then the
// left and right setup is swapped (i.e. left setup is done on right branch and right setup is done on main branch).
// This enables us to test merges in both directions, since the merge code is asymmetric and some code paths currently
@@ -41,6 +41,10 @@ var SchemaChangeTestsForDataConflicts = []MergeScriptTest{
"update t set col1=-1000 where t.pk = 1;",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -96,6 +100,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50, '500'), (6, 60, '600');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -124,6 +132,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50, '500'), (6, 60, '600');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -159,6 +171,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50, '500'), (6, 60, '600');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -193,6 +209,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50, '500'), (6, 60, '600');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -223,6 +243,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (3, 3);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -248,6 +272,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (3);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -273,6 +301,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (3, NULL);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -298,6 +330,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values ('3');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -326,6 +362,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"alter table t drop column c1;",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -350,6 +390,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values ('3', 'BAD', 'hi');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -376,6 +420,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -409,6 +457,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -443,6 +495,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"alter table t add index (col1);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -479,6 +535,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50, '500'), (6, 60, '600');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -513,6 +573,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"alter table t drop column col1;",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -549,6 +613,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"alter table t add constraint fk1 foreign key (col3) references parent(id);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -580,6 +648,11 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Skip: true,
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Skip: true,
Query: "call dolt_merge('right');",
@@ -606,6 +679,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -638,6 +715,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"INSERT INTO t VALUES (UUID(), NOW())",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -662,6 +743,10 @@ var SchemaChangeTestsBasicCases = []MergeScriptTest{
"INSERT INTO t VALUES (5), (6)",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -697,6 +782,10 @@ var SchemaChangeTestsCollations = []MergeScriptTest{
"insert into t values (3, '30');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -727,6 +816,10 @@ var SchemaChangeTestsCollations = []MergeScriptTest{
"insert into t values (3, '30');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
ExpectedErr: merge.ErrDefaultCollationConflict,
},
{
Query: "call dolt_merge('right');",
ExpectedErr: merge.ErrDefaultCollationConflict,
@@ -749,6 +842,10 @@ var SchemaChangeTestsCollations = []MergeScriptTest{
"insert into t values (3, '30');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1426,6 +1523,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1456,6 +1557,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1486,6 +1591,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1516,6 +1625,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1546,6 +1659,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1576,6 +1693,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1602,6 +1723,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1628,6 +1753,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1654,6 +1783,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, '321');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1684,6 +1817,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, 0x010203);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1721,6 +1858,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, 0x010203);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1754,6 +1895,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, 0x010203);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1782,6 +1927,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{
"INSERT into t values (3, 'the teeniest of tiny text');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -1821,6 +1970,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"INSERT into t values (2, 'green', 2.0, 2, 0.2, 'two', 'a,b', 1);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(4)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1862,6 +2015,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"INSERT into t values (2, 'green', 2.0, 2, 0.2, 'two', 'a,b', 1);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(7)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1894,6 +2051,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"INSERT into t values (3, '300');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1923,6 +2084,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"INSERT into t values (2, 20, '200');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(2)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1951,6 +2116,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"alter table t modify column j varchar(24);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1974,6 +2143,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"alter table t modify column j float;",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1999,6 +2172,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -2027,6 +2204,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", nil, uint64(1)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -2055,6 +2236,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"UPDATE t SET a = 2;",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -2082,6 +2267,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"insert into t values (2, 20);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
ExpectedErr: merge.ErrUnmergeableNewColumn,
},
{
Query: "call dolt_merge('right');",
ExpectedErr: merge.ErrUnmergeableNewColumn,
@@ -2115,6 +2304,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"insert into t values (5, 50, '500'), (6, 60, '600');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
// See the comment above about why this should NOT report a conflict and why this is skipped
Skip: true,
@@ -2144,6 +2337,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"insert into t values (3, 3);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -2175,6 +2372,10 @@ var SchemaChangeTestsSchemaConflicts = []MergeScriptTest{
"insert into t values (3, 3);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
SkipResultsCheck: true,
Query: "call dolt_merge('right');",
@@ -2325,6 +2526,10 @@ var SchemaChangeTestsGeneratedColumns = []MergeScriptTest{
"insert into t (pk, col1) values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -2590,6 +2795,10 @@ var SchemaChangeTestsForJsonConflicts = []MergeScriptTest{
`update t set j = '{"b": 2}';`,
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}},
@@ -2619,6 +2828,10 @@ var SchemaChangeTestsForJsonConflicts = []MergeScriptTest{
`update t set j = '{"b": 2}';`,
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');",
Expected: []sql.Row{{"t", uint64(1), uint64(0)}},
},
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1046,6 +1046,10 @@ var DoltConflictHandlingTests = []queries.TransactionTest{
Query: "/* client b */ call dolt_commit('-am', 'commit on new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ select * from dolt_preview_merge_conflicts_summary('new-branch', 'main')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "/* client b */ call dolt_merge('main')",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1137,6 +1141,10 @@ var DoltConflictHandlingTests = []queries.TransactionTest{
Query: "/* client b */ call dolt_commit('-am', 'commit on new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ select * from dolt_preview_merge_conflicts_summary('new-branch', 'main')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "/* client b */ call dolt_merge('main')",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1226,6 +1234,10 @@ var DoltConflictHandlingTests = []queries.TransactionTest{
Query: "/* client b */ call dolt_commit('-am', 'commit on new-branch')",
SkipResultsCheck: true,
},
{
Query: "/* client b */ select * from dolt_preview_merge_conflicts_summary('new-branch', 'main')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "/* client b */ call dolt_merge('main')",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1459,6 +1471,14 @@ var DoltStoredProcedureTransactionTests = []queries.TransactionTest{
Query: "/* client b */ start transaction",
Expected: []sql.Row{},
},
{
Query: "/* client b */ select * from dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "/* client a */ select * from dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "/* client a */ CALL DOLT_MERGE('feature-branch')",
Expected: []sql.Row{{"", 0, 1, "conflicts found"}},
@@ -1507,6 +1527,10 @@ var DoltStoredProcedureTransactionTests = []queries.TransactionTest{
Query: "/* client a */ SET @@dolt_allow_commit_conflicts = 0",
Expected: []sql.Row{{}},
},
{
Query: "/* client a */ select * from dolt_preview_merge_conflicts_summary('main', 'feature-branch')",
Expected: []sql.Row{{"test", uint64(1), uint64(0)}},
},
{
Query: "/* client a */ CALL DOLT_MERGE('feature-branch')",
ExpectedErrStr: dsess.ErrUnresolvedConflictsAutoCommit.Error(),
+20 -20
View File
@@ -4,23 +4,23 @@ FROM --platform=${BUILDPLATFORM} ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update -y && \
apt install -y \
curl \
gnupg \
software-properties-common && \
curl \
gnupg \
software-properties-common && \
curl -sL https://deb.nodesource.com/setup_22.x | bash -
RUN apt update -y && \
apt install -y \
nodejs \
python3.9 \
python3-pip \
git \
mysql-client \
libmysqlclient-dev \
# weird issue: installing openjdk-17-jdk errors if `maven` or possibly any other package is not installed after it
openjdk-17-jdk \
# currently, `apt install maven` installs v3.6.0 which does not work with openjdk-17-jdk
maven \
bats && \
nodejs \
python3.9 \
python3-pip \
git \
mysql-client \
libmysqlclient-dev \
# weird issue: installing openjdk-17-jdk errors if `maven` or possibly any other package is not installed after it
openjdk-17-jdk \
# currently, `apt install maven` installs v3.6.0 which does not work with openjdk-17-jdk
maven \
bats && \
update-ca-certificates -f
# install go
@@ -42,14 +42,14 @@ RUN pip3 install mysql-connector-python PyMySQL sqlalchemy
# Setup JAVA_HOME -- useful for docker commandline
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/
# install the current latest maven version, `v3.9.9`, because apt installed one does not work with jdk 17
ADD https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz apache-maven-3.9.9-bin.tar.gz
RUN tar zxvf apache-maven-3.9.9-bin.tar.gz && \
cp -r apache-maven-3.9.9 /opt && \
rm -rf apache-maven-3.9.9 apache-maven-3.9.9-bin.tar.gz
# install the current latest maven version, `v3.9.10`, because apt installed one does not work with jdk 17
ADD https://dlcdn.apache.org/maven/maven-3/3.9.10/binaries/apache-maven-3.9.10-bin.tar.gz apache-maven-3.9.10-bin.tar.gz
RUN tar zxvf apache-maven-3.9.10-bin.tar.gz && \
cp -r apache-maven-3.9.10 /opt && \
rm -rf apache-maven-3.9.10 apache-maven-3.9.10-bin.tar.gz
# add maven binary
ENV PATH=/opt/apache-maven-3.9.9/bin:$PATH
ENV PATH=/opt/apache-maven-3.9.10/bin:$PATH
# install dolt from source
WORKDIR /root/building