diff --git a/go/libraries/doltcore/merge/merge_prolly_rows.go b/go/libraries/doltcore/merge/merge_prolly_rows.go index 39a5aa9fc0a..83cea355de1 100644 --- a/go/libraries/doltcore/merge/merge_prolly_rows.go +++ b/go/libraries/doltcore/merge/merge_prolly_rows.go @@ -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 := tm.GetNewValueMerger(mergedSch, leftRows) if !valueMerger.leftMapping.IsIdentityMapping() { mergeInfo.LeftNeedsRewrite = true diff --git a/go/libraries/doltcore/merge/merge_rows.go b/go/libraries/doltcore/merge/merge_rows.go index b56a85642e6..900c28e1b0a 100644 --- a/go/libraries/doltcore/merge/merge_rows.go +++ b/go/libraries/doltcore/merge/merge_rows.go @@ -112,6 +112,10 @@ func (tm TableMerger) AncRows(ctx context.Context) (prolly.Map, error) { return rowsFromTable(ctx, tm.ancTbl) } +func (tm TableMerger) InvolvesRootObjects() bool { + return tm.leftRootObj != nil || tm.rightRootObj != nil || tm.ancRootObj != nil +} + 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 { @@ -238,8 +242,7 @@ func (rm *RootMerger) MergeTable( var tbl *doltdb.Table var rootObj doltdb.RootObject - involvesRootObjects := tm.leftRootObj != nil || tm.rightRootObj != nil || tm.ancRootObj != nil - if !involvesRootObjects { + if !tm.InvolvesRootObjects() { if types.IsFormat_DOLT(tm.vrw.Format()) { tbl, stats, err = mergeProllyTable(ctx, tm, mergeSch, mergeInfo, diffInfo) } else { diff --git a/go/libraries/doltcore/sqle/dtablefunctions/dolt_diff.go b/go/libraries/doltcore/sqle/dtablefunctions/dolt_diff.go index a3f0e477cf9..adda8d38948 100644 --- a/go/libraries/doltcore/sqle/dtablefunctions/dolt_diff.go +++ b/go/libraries/doltcore/sqle/dtablefunctions/dolt_diff.go @@ -315,7 +315,7 @@ func loadCommitStrings(ctx *sql.Context, fromRef, toRef, dotRef interface{}, db func interfaceToString(r interface{}) (string, error) { str, ok := r.(string) if !ok { - return "", fmt.Errorf("received '%v' when expecting commit hash string", str) + return "", fmt.Errorf("received '%v' when expecting commit hash string", r) } return str, nil } diff --git a/go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts.go b/go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts.go new file mode 100644 index 00000000000..3d850bafe06 --- /dev/null +++ b/go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts.go @@ -0,0 +1,680 @@ +// 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 ( + "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/doltdb/durable" + "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/libraries/doltcore/sqle/dtables" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil" + "github.com/dolthub/dolt/go/store/hash" + "github.com/dolthub/dolt/go/store/prolly" + "github.com/dolthub/dolt/go/store/prolly/tree" + dtypes "github.com/dolthub/dolt/go/store/types" + "github.com/dolthub/dolt/go/store/val" +) + +var _ sql.TableFunction = (*PreviewMergeConflictsTableFunction)(nil) +var _ sql.ExecSourceRel = (*PreviewMergeConflictsTableFunction)(nil) +var _ sql.AuthorizationCheckerNode = (*PreviewMergeConflictsTableFunction)(nil) + +type PreviewMergeConflictsTableFunction struct { + ctx *sql.Context + leftBranchExpr sql.Expression + rightBranchExpr sql.Expression + tableNameExpr sql.Expression + database sql.Database + + rootInfo rootInfo + tblName doltdb.TableName + sqlSch sql.PrimaryKeySchema + baseSch, ourSch, theirSch schema.Schema +} + +// NewInstance creates a new instance of TableFunction interface +func (pm *PreviewMergeConflictsTableFunction) NewInstance(ctx *sql.Context, db sql.Database, expressions []sql.Expression) (sql.Node, error) { + newInstance := &PreviewMergeConflictsTableFunction{ + ctx: ctx, + database: db, + } + + node, err := newInstance.WithExpressions(expressions...) + if err != nil { + return nil, err + } + + return node, nil +} + +func (pm *PreviewMergeConflictsTableFunction) 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 *PreviewMergeConflictsTableFunction) RowCount(_ *sql.Context) (uint64, bool, error) { + return previewMergeConflictsDefaultRowCount, false, nil +} + +// Database implements the sql.Databaser interface +func (pm *PreviewMergeConflictsTableFunction) Database() sql.Database { + return pm.database +} + +// WithDatabase implements the sql.Databaser interface +func (pm *PreviewMergeConflictsTableFunction) WithDatabase(database sql.Database) (sql.Node, error) { + npm := *pm + npm.database = database + return &npm, nil +} + +// Name implements the sql.TableFunction interface +func (pm *PreviewMergeConflictsTableFunction) Name() string { + return "dolt_preview_merge_conflicts" +} + +// Resolved implements the sql.Resolvable interface +func (pm *PreviewMergeConflictsTableFunction) Resolved() bool { + return pm.leftBranchExpr.Resolved() && pm.rightBranchExpr.Resolved() && pm.tableNameExpr.Resolved() +} + +func (pm *PreviewMergeConflictsTableFunction) IsReadOnly() bool { + return true +} + +// String implements the Stringer interface +func (pm *PreviewMergeConflictsTableFunction) String() string { + return fmt.Sprintf("DOLT_PREVIEW_MERGE_CONFLICTS(%s, %s, %s)", pm.leftBranchExpr.String(), pm.rightBranchExpr.String(), pm.tableNameExpr.String()) +} + +// Schema implements the sql.Node interface. +// Returns the schema for the preview merge conflicts table function, which includes: +// - from_root_ish: Hash of the right-side commit/working set during merge. +// - For each column on the named table, base_[col], our_[col], and their_[col] represent the value of that column on the base, our, and their branches respectively. +// All base columns come first, followed by all our columns, and then their columns. +// - our_diff_type, their_diff_type: Indicates the type of change for our and their columns respectively ("added", "modified", "removed"). +// - base_cardinality, our_cardinality, their_cardinality: Additional columns for keyless tables only, indicating the number of occurrences of the conflicting row in the base, our, and their branches. +// - dolt_conflict_id: Unique identifier for the conflict, derived from the key and the right-side commit hash. +func (pm *PreviewMergeConflictsTableFunction) Schema() sql.Schema { + if !pm.Resolved() { + return nil + } + // Lazy schema generation - generate schema on first access + if pm.sqlSch.Schema == nil { + err := pm.generateSchema(pm.ctx) + if err != nil { + // Schema generation failed, but we can't return an error from Schema() + // This will surface the error when RowIter() is called + return nil + } + } + + return pm.sqlSch.Schema +} + +// Children implements the sql.Node interface. +func (pm *PreviewMergeConflictsTableFunction) Children() []sql.Node { + return nil +} + +// WithChildren implements the sql.Node interface. +func (pm *PreviewMergeConflictsTableFunction) 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 *PreviewMergeConflictsTableFunction) CheckAuth(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool { + if !types.IsText(pm.tableNameExpr.Type()) { + return ExpressionIsDeferred(pm.tableNameExpr) + } + + tableNameVal, err := pm.tableNameExpr.Eval(pm.ctx, nil) + if err != nil { + return false + } + tableName, ok, err := sql.Unwrap[string](ctx, tableNameVal) + if err != nil { + return false + } + if !ok { + return false + } + + subject := sql.PrivilegeCheckSubject{Database: pm.database.Name(), Table: tableName} + // TODO: Add tests for privilege checking + return opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Select)) +} + +// Expressions implements the sql.Expressioner interface. +func (pm *PreviewMergeConflictsTableFunction) Expressions() []sql.Expression { + return []sql.Expression{pm.leftBranchExpr, pm.rightBranchExpr, pm.tableNameExpr} +} + +// WithExpressions implements the sql.Expressioner interface. +func (pm *PreviewMergeConflictsTableFunction) WithExpressions(exprs ...sql.Expression) (sql.Node, error) { + if len(exprs) != 3 { + return nil, sql.ErrInvalidArgumentNumber.New(pm.Name(), "3", 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] + newPmcs.tableNameExpr = exprs[2] + + // 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()) + } + if !types.IsText(newPmcs.tableNameExpr.Type()) && !expression.IsBindVar(newPmcs.tableNameExpr) { + return nil, sql.ErrInvalidArgumentDetails.New(newPmcs.Name(), newPmcs.tableNameExpr.String()) + } + + return &newPmcs, nil +} + +// generateSchema generates the schema if it hasn't been generated yet +func (pm *PreviewMergeConflictsTableFunction) generateSchema(ctx *sql.Context) error { + if pm.sqlSch.Schema != nil { + return nil + } + + if !pm.Resolved() { + return fmt.Errorf("table function not resolved") + } + + sqledb, ok := pm.database.(dsess.SqlDatabase) + if !ok { + return fmt.Errorf("unexpected database type: %T", pm.database) + } + + leftBranchVal, rightBranchVal, tableName, err := pm.evaluateArguments() + if err != nil { + return err + } + + leftBranch, err := interfaceToString(leftBranchVal) + if err != nil { + return err + } + rightBranch, err := interfaceToString(rightBranchVal) + if err != nil { + return err + } + + ri, err := resolveBranchesToRoots(ctx, sqledb, leftBranch, rightBranch) + if err != nil { + return err + } + + tblName := doltdb.TableName{Name: tableName, Schema: doltdb.DefaultSchemaName} + baseSch, ourSch, theirSch, err := getConflictSchemasFromRoots(ctx, tblName, ri.leftRoot, ri.rightRoot, ri.baseRoot) + if err != nil { + return err + } + + confSch, _, err := dtables.CalculateConflictSchema(baseSch, ourSch, theirSch) + if err != nil { + return err + } + + sqlSch, err := sqlutil.FromDoltSchema(sqledb.Name(), pm.Name(), confSch) + if err != nil { + return err + } + + pm.sqlSch = sqlSch + pm.rootInfo = ri + pm.tblName = tblName + pm.baseSch = baseSch + pm.ourSch = ourSch + pm.theirSch = theirSch + + return nil +} + +func getConflictSchemasFromRoots(ctx *sql.Context, tblName doltdb.TableName, leftRoot, rightRoot, baseRoot doltdb.RootValue) (base, sch, mergeSch schema.Schema, err error) { + ourTbl, ourOk, err := leftRoot.GetTable(ctx, tblName) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get table from left root: %w", err) + } + + baseTbl, baseOk, err := baseRoot.GetTable(ctx, tblName) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get table from base root: %w", err) + } + + theirTbl, theirOk, err := rightRoot.GetTable(ctx, tblName) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get table from right root: %w", err) + } + + if !ourOk && !theirOk && !baseOk { + return nil, nil, nil, sql.ErrTableNotFound.New(tblName.String()) + } + + schemas, err := extractSchemas(ctx, ourTbl, ourOk, theirTbl, theirOk, baseTbl, baseOk) + if err != nil { + return nil, nil, nil, err + } + + return applySchemaFallbacks(schemas.our, schemas.their, schemas.base, ourOk, theirOk, baseOk) +} + +type extractedSchemas struct { + our, their, base schema.Schema +} + +func extractSchemas(ctx *sql.Context, ourTbl *doltdb.Table, ourOk bool, theirTbl *doltdb.Table, theirOk bool, baseTbl *doltdb.Table, baseOk bool) (*extractedSchemas, error) { + schemas := &extractedSchemas{} + var err error + + if ourOk { + schemas.our, err = ourTbl.GetSchema(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get schema from our table: %w", err) + } + } + + if theirOk { + schemas.their, err = theirTbl.GetSchema(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get schema from their table: %w", err) + } + } + + if baseOk { + schemas.base, err = baseTbl.GetSchema(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get schema from base table: %w", err) + } + } + + return schemas, nil +} + +func applySchemaFallbacks(ourSch, theirSch, baseSch schema.Schema, ourOk, theirOk, baseOk bool) (schema.Schema, schema.Schema, schema.Schema, error) { + if !theirOk { + if ourOk { + theirSch = ourSch + } else { + theirSch = baseSch + } + } + + if !ourOk { + if theirOk { + ourSch = theirSch + } else { + ourSch = baseSch + } + } + + if !baseOk { + if schema.SchemasAreEqual(ourSch, theirSch) { + return ourSch, ourSch, theirSch, nil + } + return nil, nil, nil, fmt.Errorf("expected our schema to equal their schema since the table did not exist in the ancestor") + } + + return ourSch, theirSch, baseSch, nil +} + +// RowIter implements the sql.Node interface +func (pm *PreviewMergeConflictsTableFunction) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) { + err := pm.generateSchema(ctx) + if err != nil { + return nil, err + } + + merger, err := merge.NewMerger(pm.rootInfo.leftRoot, pm.rootInfo.rightRoot, pm.rootInfo.baseRoot, pm.rootInfo.rightCm, pm.rootInfo.ancCm, pm.rootInfo.leftRoot.VRW(), pm.rootInfo.leftRoot.NodeStore()) + if err != nil { + return nil, err + } + + mergeOpts := merge.MergeOpts{ + IsCherryPick: false, + KeepSchemaConflicts: true, + ReverifyAllConstraints: false, + } + + tm, err := merger.MakeTableMerger(ctx, pm.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 { + return &previewMergeConflictsTableFunctionRowIter{}, nil + } + // Calculate a merge of the schemas, but don't apply it + mergeSch, schConflicts, _, diffInfo, err := tm.SchemaMerge(ctx, pm.tblName) + if err != nil { + return nil, err + } + if schConflicts.Count() > 0 { + // Cannot calculate data conflicts if there are schema conflicts + return nil, fmt.Errorf("schema conflicts found: %d", schConflicts.Count()) + } + + if !tm.InvolvesRootObjects() { + if !dtypes.IsFormat_DOLT(pm.rootInfo.leftRoot.VRW().Format()) { + return nil, fmt.Errorf("preview_merge_conflicts table function only supports dolt format") + } + } else { + return nil, fmt.Errorf("Dolt does not operate on root objects") + } + + 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 + } + + rightHash, err := pm.rootInfo.rightCm.HashOf() + if err != nil { + return nil, err + } + + baseHash, err := pm.rootInfo.ancCm.HashOf() + if err != nil { + return nil, err + } + + cds := dtables.GetConflictDescriptors(pm.baseSch, pm.ourSch, pm.theirSch, pm.rootInfo.baseRoot.NodeStore()) + offsets := dtables.GetConflictOffsets(keyless, cds) + + 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 + } + + return &previewMergeConflictsTableFunctionRowIter{ + itr: differ, + tblName: pm.tblName, + vrw: pm.rootInfo.leftRoot.VRW(), + ns: leftRows.NodeStore(), + ourRows: leftRows, + keyless: keyless, + ourSch: pm.ourSch, + offsets: offsets, + cds: cds, + baseRootish: baseHash, + theirRootish: rightHash, + }, nil +} + +// evaluateArguments returns leftBranchVal amd rightBranchVal. +// It evaluates the argument expressions to turn them into values this PreviewMergeConflictsTableFunction +// can use. Note that this method only evals the expressions, and doesn't validate the values. +func (pm *PreviewMergeConflictsTableFunction) evaluateArguments() (interface{}, interface{}, string, error) { + leftBranchVal, err := pm.leftBranchExpr.Eval(pm.ctx, nil) + if err != nil { + return nil, nil, "", err + } + + rightBranchVal, err := pm.rightBranchExpr.Eval(pm.ctx, nil) + if err != nil { + return nil, nil, "", err + } + + tableNameVal, err := pm.tableNameExpr.Eval(pm.ctx, nil) + if err != nil { + return nil, nil, "", err + } + + tableName, ok := tableNameVal.(string) + if !ok { + return nil, nil, "", ErrInvalidTableName.New(pm.tableNameExpr.String()) + } + + if tableName == "" { + return nil, nil, "", fmt.Errorf("table name cannot be empty") + } + + return leftBranchVal, rightBranchVal, tableName, nil +} + +//-------------------------------------------------- +// previewMergeConflictsTableFunctionRowIter +//-------------------------------------------------- + +var _ sql.RowIter = &previewMergeConflictsTableFunctionRowIter{} + +type previewMergeConflictsTableFunctionRowIter struct { + itr *tree.ThreeWayDiffer[val.Tuple, val.TupleDesc] + tblName doltdb.TableName + vrw dtypes.ValueReadWriter + ns tree.NodeStore + ourRows prolly.Map + keyless bool + ourSch schema.Schema + + cds dtables.ConflictDescriptors + offsets dtables.ConflictOffsets + + baseHash, theirHash hash.Hash + baseRows, theirRows prolly.Map + baseRootish, theirRootish hash.Hash +} + +func (itr *previewMergeConflictsTableFunctionRowIter) Next(ctx *sql.Context) (sql.Row, error) { + if itr.itr == nil { + return nil, io.EOF + } + + row := make(sql.Row, itr.offsets.ColCount) + confVal, err := itr.nextConflictVals(ctx) + if err != nil { + return nil, err + } + + row[0] = confVal.Hash.String() + + if !itr.keyless { + err = itr.putConflictRowVals(ctx, confVal, row) + if err != nil { + return nil, err + } + } else { + err = itr.putKeylessConflictRowVals(ctx, confVal, row) + if err != nil { + return nil, err + } + } + + return row, nil +} + +func (itr *previewMergeConflictsTableFunctionRowIter) nextConflictVals(ctx *sql.Context) (confVal dtables.ConflictVal, err error) { + for { + ca, err := itr.itr.Next(ctx) + if err != nil { + return dtables.ConflictVal{}, err + } + isConflict := ca.Op == tree.DiffOpDivergentModifyConflict || ca.Op == tree.DiffOpDivergentDeleteConflict + isKeylessConflict := itr.keyless && (ca.Op == tree.DiffOpConvergentAdd || ca.Op == tree.DiffOpConvergentModify || ca.Op == tree.DiffOpConvergentDelete) + if !isConflict && !isKeylessConflict { + // If this is not a conflict, continue to next iteration + continue + } + + confVal.Key = ca.Key + confVal.Hash = itr.theirRootish + + buf := make([]byte, 0, len(ca.Key)+len(confVal.Hash)) + buf = append(buf, ca.Key...) + confVal.Id = dtables.GetConflictId(buf, confVal.Hash) + + err = itr.loadTableMaps(ctx, itr.baseRootish, itr.theirRootish) + if err != nil { + return dtables.ConflictVal{}, err + } + + err = itr.baseRows.Get(ctx, ca.Key, func(_, v val.Tuple) error { + confVal.Base = v + return nil + }) + if err != nil { + return dtables.ConflictVal{}, err + } + err = itr.ourRows.Get(ctx, ca.Key, func(_, v val.Tuple) error { + confVal.Ours = v + return nil + }) + if err != nil { + return dtables.ConflictVal{}, err + } + err = itr.theirRows.Get(ctx, ca.Key, func(_, v val.Tuple) error { + confVal.Theirs = v + return nil + }) + if err != nil { + return dtables.ConflictVal{}, err + } + + return confVal, nil + } +} + +// loadTableMaps loads the maps specified in the metadata if they are different from +// the currently loaded maps. |baseHash| and |theirHash| are table hashes. +func (itr *previewMergeConflictsTableFunctionRowIter) loadTableMaps(ctx *sql.Context, baseHash, theirHash hash.Hash) error { + if itr.baseHash.Compare(baseHash) != 0 { + rv, err := doltdb.LoadRootValueFromRootIshAddr(ctx, itr.vrw, itr.ns, baseHash) + if err != nil { + return err + } + baseTbl, ok, err := rv.GetTable(ctx, itr.tblName) + if err != nil { + return err + } + + var idx durable.Index + if !ok { + idx, err = durable.NewEmptyPrimaryIndex(ctx, itr.vrw, itr.ns, itr.ourSch) + } else { + idx, err = baseTbl.GetRowData(ctx) + } + + if err != nil { + return err + } + + itr.baseRows, err = durable.ProllyMapFromIndex(idx) + if err != nil { + return err + } + + itr.baseHash = baseHash + } + + if itr.theirHash.Compare(theirHash) != 0 { + rv, err := doltdb.LoadRootValueFromRootIshAddr(ctx, itr.vrw, itr.ns, theirHash) + if err != nil { + return err + } + + theirTbl, ok, err := rv.GetTable(ctx, itr.tblName) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("failed to find table %s in right root value", itr.tblName) + } + + idx, err := theirTbl.GetRowData(ctx) + if err != nil { + return err + } + itr.theirRows, err = durable.ProllyMapFromIndex(idx) + if err != nil { + return err + } + itr.theirHash = theirHash + } + + return nil +} + +func (itr *previewMergeConflictsTableFunctionRowIter) putConflictRowVals(ctx *sql.Context, confVal dtables.ConflictVal, row sql.Row) error { + ns := itr.baseRows.NodeStore() + return dtables.PutConflictRowVals(ctx, confVal, row, itr.offsets, itr.cds, ns) +} + +func (itr *previewMergeConflictsTableFunctionRowIter) putKeylessConflictRowVals(ctx *sql.Context, confVal dtables.ConflictVal, row sql.Row) (err error) { + ns := itr.baseRows.NodeStore() + return dtables.PutKeylessConflictRowVals(ctx, confVal, row, itr.offsets, itr.cds, ns) +} + +func (d *previewMergeConflictsTableFunctionRowIter) Close(context *sql.Context) error { + if d.itr == nil { + return nil + } + return d.itr.Close() +} diff --git a/go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts_summary.go b/go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts_summary.go index 4b23c217c4f..e8c5666dea3 100644 --- a/go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts_summary.go +++ b/go/libraries/doltcore/sqle/dtablefunctions/dolt_preview_merge_conflicts_summary.go @@ -278,50 +278,58 @@ func getRowFromConflict(conflict tableConflict) sql.Row { return row } +type rootInfo struct { + leftRoot doltdb.RootValue + rightRoot doltdb.RootValue + baseRoot doltdb.RootValue + rightCm doltdb.Rootish + ancCm doltdb.Rootish +} + // 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) { +// and finds the common merge base. +func resolveBranchesToRoots(ctx *sql.Context, db dsess.SqlDatabase, leftBranch, rightBranch string) (rootInfo, error) { sess := dsess.DSessFromSess(ctx.Session) headRef, err := sess.CWBHeadRef(ctx, db.Name()) if err != nil { - return nil, nil, nil, err + return rootInfo{}, err } leftCm, err := resolveCommit(ctx, db.DbData().Ddb, headRef, leftBranch) if err != nil { - return nil, nil, nil, err + return rootInfo{}, err } rightCm, err := resolveCommit(ctx, db.DbData().Ddb, headRef, rightBranch) if err != nil { - return nil, nil, nil, err + return rootInfo{}, err } - optCmt, err := doltdb.GetCommitAncestor(ctx, leftCm, rightCm) + optCm, err := doltdb.GetCommitAncestor(ctx, leftCm, rightCm) if err != nil { - return nil, nil, nil, err + return rootInfo{}, err } - mergeBase, ok := optCmt.ToCommit() + ancCm, ok := optCm.ToCommit() if !ok { - return nil, nil, nil, doltdb.ErrGhostCommitEncountered + return rootInfo{}, doltdb.ErrGhostCommitEncountered } rightRoot, err := rightCm.GetRootValue(ctx) if err != nil { - return nil, nil, nil, err + return rootInfo{}, err } leftRoot, err := leftCm.GetRootValue(ctx) if err != nil { - return nil, nil, nil, err + return rootInfo{}, err } - baseRoot, err := mergeBase.GetRootValue(ctx) + baseRoot, err := ancCm.GetRootValue(ctx) if err != nil { - return nil, nil, nil, err + return rootInfo{}, err } - return leftRoot, rightRoot, baseRoot, nil + return rootInfo{leftRoot, rightRoot, baseRoot, rightCm, ancCm}, nil } type tableConflict struct { @@ -334,17 +342,17 @@ type tableConflict struct { // 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) + ri, 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()) + merger, err := merge.NewMerger(ri.leftRoot, ri.rightRoot, ri.baseRoot, ri.rightRoot, ri.baseRoot, ri.leftRoot.VRW(), ri.leftRoot.NodeStore()) if err != nil { return nil, err } - tblNames, err := doltdb.UnionTableNames(ctx, leftRoot, rightRoot) + tblNames, err := doltdb.UnionTableNames(ctx, ri.leftRoot, ri.rightRoot) if err != nil { return nil, err } diff --git a/go/libraries/doltcore/sqle/dtablefunctions/init.go b/go/libraries/doltcore/sqle/dtablefunctions/init.go index 218d6af1b7b..4c06fa126f9 100644 --- a/go/libraries/doltcore/sqle/dtablefunctions/init.go +++ b/go/libraries/doltcore/sqle/dtablefunctions/init.go @@ -24,6 +24,7 @@ var DoltTableFunctions = []sql.TableFunction{ &LogTableFunction{}, &PatchTableFunction{}, &PreviewMergeConflictsSummaryTableFunction{}, + &PreviewMergeConflictsTableFunction{}, &SchemaDiffTableFunction{}, &ReflogTableFunction{}, &QueryDiffTableFunction{}, diff --git a/go/libraries/doltcore/sqle/dtables/conflicts_tables_prolly.go b/go/libraries/doltcore/sqle/dtables/conflicts_tables_prolly.go index e22b6d97365..29406b5291d 100644 --- a/go/libraries/doltcore/sqle/dtables/conflicts_tables_prolly.go +++ b/go/libraries/doltcore/sqle/dtables/conflicts_tables_prolly.go @@ -53,7 +53,7 @@ func newProllyConflictsTable( if err != nil { return nil, err } - confSch, versionMappings, err := calculateConflictSchema(baseSch, ourSch, theirSch) + confSch, versionMappings, err := CalculateConflictSchema(baseSch, ourSch, theirSch) if err != nil { return nil, err } @@ -135,20 +135,81 @@ type prollyConflictRowIter struct { keyless bool ourSch schema.Schema - kd val.TupleDesc - baseVD, oursVD, theirsVD val.TupleDesc - // offsets for each version - b, o, t int - n int + cds ConflictDescriptors + offsets ConflictOffsets baseHash, theirHash hash.Hash baseRows prolly.Map theirRows prolly.Map } +// ConflictOffsets holds the offsets of the columns in a conflict row. The +// offsets are used to put the values in the correct place in the row. +// Base is the offset of the first base column, Ours is the offset of the first +// ours column, and Theirs is the offset of the first theirs column. +type ConflictOffsets struct { + Base, Ours, Theirs int + ColCount int +} + +// ConflictDescriptors holds the descriptors for the key and base, ours, and theirs values for a row. +type ConflictDescriptors struct { + BaseVal, OurVal, TheirVal val.TupleDesc + Key val.TupleDesc +} + +// GetConflictOffsets returns the offsets of the columns in a conflict row. +// +// For keyed tables, the conflict row structure is: +// [from_root_ish] [base_key...] [base_vals...] [our_key...] [our_vals...] [our_diff_type] [their_key...] [their_vals...] [their_diff_type] [dolt_conflict_id] +// +// For keyless tables, the conflict row structure is: +// [from_root_ish] [base_vals...] [our_vals...] [our_diff_type] [their_vals...] [their_diff_type] [dolt_conflict_id] [base_cardinality] [our_cardinality] [their_cardinality] +func GetConflictOffsets(keyless bool, cds ConflictDescriptors) ConflictOffsets { + // Skip index 0 which is always from_root_ish + baseOffset := 1 + var ourOffset, theirOffset, colCount int + if !keyless { + // Base section: base key columns + base value columns + ourOffset = baseOffset + cds.Key.Count() + cds.BaseVal.Count() + // +1 for our_diff_type column that follows the ours section + theirOffset = ourOffset + cds.Key.Count() + cds.OurVal.Count() + 1 + // +2 for their_diff_type and dolt_conflict_id columns at the end + colCount = theirOffset + cds.Key.Count() + cds.TheirVal.Count() + 2 + } else { + // For keyless: base values (excluding cardinality which comes at the end) + ourOffset = baseOffset + cds.BaseVal.Count() - 1 + // Ours section: our value columns (excluding cardinality) + theirOffset = ourOffset + cds.OurVal.Count() + // +4 for our_diff_type, their_diff_type, dolt_conflict_id, and 3 cardinality columns + colCount = theirOffset + cds.TheirVal.Count() + 4 + } + + return ConflictOffsets{ + Base: baseOffset, + Ours: ourOffset, + Theirs: theirOffset, + ColCount: colCount, + } +} + +// GetConflictDescriptors returns the descriptors for the key and base, ours, and theirs +// values. +func GetConflictDescriptors(baseSch, ourSch, theirSch schema.Schema, ns tree.NodeStore) ConflictDescriptors { + key := baseSch.GetKeyDescriptor(ns) + baseVD := baseSch.GetValueDescriptor(ns) + oursVD := ourSch.GetValueDescriptor(ns) + theirsVD := theirSch.GetValueDescriptor(ns) + return ConflictDescriptors{ + BaseVal: baseVD, + OurVal: oursVD, + TheirVal: theirsVD, + Key: key, + } +} + var _ sql.RowIter = (*prollyConflictRowIter)(nil) -// base_cols, our_cols, our_diff_type, their_cols, their_diff_type func newProllyConflictRowIter(ctx *sql.Context, ct ProllyConflictsTable) (*prollyConflictRowIter, error) { idx, err := ct.tbl.GetRowData(ctx) if err != nil { @@ -165,122 +226,116 @@ func newProllyConflictRowIter(ctx *sql.Context, ct ProllyConflictsTable) (*proll } keyless := schema.IsKeyless(ct.ourSch) - - kd := ct.baseSch.GetKeyDescriptor(ct.root.NodeStore()) - baseVD := ct.baseSch.GetValueDescriptor(ct.root.NodeStore()) - oursVD := ct.ourSch.GetValueDescriptor(ct.root.NodeStore()) - theirsVD := ct.theirSch.GetValueDescriptor(ct.root.NodeStore()) - - b := 1 - var o, t, n int - if !keyless { - o = b + kd.Count() + baseVD.Count() - t = o + kd.Count() + oursVD.Count() + 1 - n = t + kd.Count() + theirsVD.Count() + 2 - } else { - o = b + baseVD.Count() - 1 - t = o + oursVD.Count() - n = t + theirsVD.Count() + 4 - } + cds := GetConflictDescriptors(ct.baseSch, ct.ourSch, ct.theirSch, ct.root.NodeStore()) + offsets := GetConflictOffsets(keyless, cds) return &prollyConflictRowIter{ - itr: itr, - tblName: ct.tblName, - vrw: ct.tbl.ValueReadWriter(), - ns: ct.tbl.NodeStore(), - ourRows: ourRows, - keyless: keyless, - ourSch: ct.ourSch, - kd: kd, - baseVD: baseVD, - oursVD: oursVD, - theirsVD: theirsVD, - b: b, - o: o, - t: t, - n: n, + itr: itr, + tblName: ct.tblName, + vrw: ct.tbl.ValueReadWriter(), + ns: ct.tbl.NodeStore(), + ourRows: ourRows, + keyless: keyless, + ourSch: ct.ourSch, + cds: cds, + offsets: offsets, }, nil } func (itr *prollyConflictRowIter) Next(ctx *sql.Context) (sql.Row, error) { - c, err := itr.nextConflictVals(ctx) + confVal, err := itr.nextConflictVals(ctx) if err != nil { return nil, err } - r := make(sql.Row, itr.n) - r[0] = c.h.String() + row := make(sql.Row, itr.offsets.ColCount) + row[0] = confVal.Hash.String() // from_root_ish if !itr.keyless { - for i := 0; i < itr.kd.Count(); i++ { - f, err := tree.GetField(ctx, itr.kd, i, c.k, itr.baseRows.NodeStore()) - if err != nil { - return nil, err - } - if c.bV != nil { - r[itr.b+i] = f - } - if c.oV != nil { - r[itr.o+i] = f - } - if c.tV != nil { - r[itr.t+i] = f - } - } - - err = itr.putConflictRowVals(ctx, c, r) + err = itr.putConflictRowVals(ctx, confVal, row) if err != nil { return nil, err } } else { - - err = itr.putKeylessConflictRowVals(ctx, c, r) + err = itr.putKeylessConflictRowVals(ctx, confVal, row) if err != nil { return nil, err } } - return r, nil + return row, nil } -func (itr *prollyConflictRowIter) putConflictRowVals(ctx *sql.Context, c conf, r sql.Row) error { - if c.bV != nil { - for i := 0; i < itr.baseVD.Count(); i++ { - f, err := tree.GetField(ctx, itr.baseVD, i, c.bV, itr.baseRows.NodeStore()) +// PutConflictRowVals puts the values of the conflict row into the given row. +func PutConflictRowVals(ctx *sql.Context, confVal ConflictVal, row sql.Row, offsets ConflictOffsets, cds ConflictDescriptors, ns tree.NodeStore) error { + // Sets key columns for the conflict row. + for i := 0; i < cds.Key.Count(); i++ { + f, err := tree.GetField(ctx, cds.Key, i, confVal.Key, ns) + if err != nil { + return err + } + if confVal.Base != nil { + row[offsets.Base+i] = f + } + if confVal.Ours != nil { + row[offsets.Ours+i] = f + } + if confVal.Theirs != nil { + row[offsets.Theirs+i] = f + } + } + + if confVal.Base != nil { + for i := 0; i < cds.BaseVal.Count(); i++ { + f, err := tree.GetField(ctx, cds.BaseVal, i, confVal.Base, ns) if err != nil { return err } - r[itr.b+itr.kd.Count()+i] = f + baseColOffset := offsets.Base + cds.Key.Count() + i + row[baseColOffset] = f // base_[col] } } - if c.oV != nil { - for i := 0; i < itr.oursVD.Count(); i++ { - f, err := tree.GetField(ctx, itr.oursVD, i, c.oV, itr.baseRows.NodeStore()) + if confVal.Ours != nil { + for i := 0; i < cds.OurVal.Count(); i++ { + f, err := tree.GetField(ctx, cds.OurVal, i, confVal.Ours, ns) if err != nil { return err } - r[itr.o+itr.kd.Count()+i] = f + ourColOffset := offsets.Ours + cds.Key.Count() + i + row[ourColOffset] = f // our_[col] } } - r[itr.o+itr.kd.Count()+itr.oursVD.Count()] = getDiffType(c.bV, c.oV) - if c.tV != nil { - for i := 0; i < itr.theirsVD.Count(); i++ { - f, err := tree.GetField(ctx, itr.theirsVD, i, c.tV, itr.baseRows.NodeStore()) + ourDiffTypeOffset := offsets.Ours + cds.Key.Count() + cds.OurVal.Count() + row[ourDiffTypeOffset] = getConflictDiffType(confVal.Base, confVal.Ours) // our_diff_type + + if confVal.Theirs != nil { + for i := 0; i < cds.TheirVal.Count(); i++ { + f, err := tree.GetField(ctx, cds.TheirVal, i, confVal.Theirs, ns) if err != nil { return err } - r[itr.t+itr.kd.Count()+i] = f + theirColOffset := offsets.Theirs + cds.Key.Count() + i + row[theirColOffset] = f // their_[col] } } - r[itr.t+itr.kd.Count()+itr.theirsVD.Count()] = getDiffType(c.bV, c.tV) - r[itr.t+itr.kd.Count()+itr.theirsVD.Count()+1] = c.id + + theirDiffTypeOffset := offsets.Theirs + cds.Key.Count() + cds.TheirVal.Count() + row[theirDiffTypeOffset] = getConflictDiffType(confVal.Base, confVal.Theirs) // their_diff_type + + conflictIdOffset := theirDiffTypeOffset + 1 + row[conflictIdOffset] = confVal.Id // dolt_conflict_id return nil } -func getDiffType(base val.Tuple, other val.Tuple) string { +func (itr *prollyConflictRowIter) putConflictRowVals(ctx *sql.Context, confVal ConflictVal, row sql.Row) error { + ns := itr.baseRows.NodeStore() + return PutConflictRowVals(ctx, confVal, row, itr.offsets, itr.cds, ns) +} + +func getConflictDiffType(base val.Tuple, other val.Tuple) string { if base == nil { return merge.ConflictDiffTypeAdded } else if other == nil { @@ -291,116 +346,129 @@ func getDiffType(base val.Tuple, other val.Tuple) string { return merge.ConflictDiffTypeModified } -func (itr *prollyConflictRowIter) putKeylessConflictRowVals(ctx *sql.Context, c conf, r sql.Row) (err error) { - ns := itr.baseRows.NodeStore() - - if c.bV != nil { - // Cardinality - r[itr.n-3], err = tree.GetField(ctx, itr.baseVD, 0, c.bV, ns) +// PutKeylessConflictRowVals puts the values of the keyless conflict row into the given row. +func PutKeylessConflictRowVals(ctx *sql.Context, confVal ConflictVal, row sql.Row, offsets ConflictOffsets, cds ConflictDescriptors, ns tree.NodeStore) (err error) { + if confVal.Base != nil { + f, err := tree.GetField(ctx, cds.BaseVal, 0, confVal.Base, ns) if err != nil { return err } + row[offsets.ColCount-3] = f // base_cardinality - for i := 0; i < itr.baseVD.Count()-1; i++ { - f, err := tree.GetField(ctx, itr.baseVD, i+1, c.bV, ns) + for i := 0; i < cds.BaseVal.Count()-1; i++ { + f, err := tree.GetField(ctx, cds.BaseVal, i+1, confVal.Base, ns) if err != nil { return err } - r[itr.b+i] = f + baseColOffset := offsets.Base + i + row[baseColOffset] = f // base_[col] } } else { - r[itr.n-3] = uint64(0) + row[offsets.ColCount-3] = uint64(0) // base_cardinality } - if c.oV != nil { - r[itr.n-2], err = tree.GetField(ctx, itr.oursVD, 0, c.oV, ns) + if confVal.Ours != nil { + f, err := tree.GetField(ctx, cds.OurVal, 0, confVal.Ours, ns) if err != nil { return err } + row[offsets.ColCount-2] = f // our_cardinality - for i := 0; i < itr.oursVD.Count()-1; i++ { - f, err := tree.GetField(ctx, itr.oursVD, i+1, c.oV, ns) + for i := 0; i < cds.OurVal.Count()-1; i++ { + f, err := tree.GetField(ctx, cds.OurVal, i+1, confVal.Ours, ns) if err != nil { return err } - r[itr.o+i] = f + row[offsets.Ours+i] = f // our_[col] } } else { - r[itr.n-2] = uint64(0) + row[offsets.ColCount-2] = uint64(0) // our_cardinality } - r[itr.o+itr.oursVD.Count()-1] = getDiffType(c.bV, c.oV) + ourDiffTypeOffset := offsets.Ours + cds.OurVal.Count() - 1 + row[ourDiffTypeOffset] = getConflictDiffType(confVal.Base, confVal.Ours) // our_diff_type - if c.tV != nil { - r[itr.n-1], err = tree.GetField(ctx, itr.theirsVD, 0, c.tV, ns) + if confVal.Theirs != nil { + f, err := tree.GetField(ctx, cds.TheirVal, 0, confVal.Theirs, ns) if err != nil { return err } + row[offsets.ColCount-1] = f // their_cardinality - for i := 0; i < itr.theirsVD.Count()-1; i++ { - f, err := tree.GetField(ctx, itr.theirsVD, i+1, c.tV, ns) + for i := 0; i < cds.TheirVal.Count()-1; i++ { + f, err := tree.GetField(ctx, cds.TheirVal, i+1, confVal.Theirs, ns) if err != nil { return err } - r[itr.t+i] = f + row[offsets.Theirs+i] = f // their_[col] } } else { - r[itr.n-1] = uint64(0) + row[offsets.ColCount-1] = uint64(0) // their_cardinality } - o := itr.t + itr.theirsVD.Count() - 1 - r[o] = getDiffType(c.bV, c.tV) - r[itr.n-4] = c.id + theirDiffTypeOffset := offsets.Theirs + cds.TheirVal.Count() - 1 + row[theirDiffTypeOffset] = getConflictDiffType(confVal.Base, confVal.Theirs) // their_diff_type + + row[offsets.ColCount-4] = confVal.Id // dolt_conflict_id return nil } -type conf struct { - k, bV, oV, tV val.Tuple - h hash.Hash - id string +func (itr *prollyConflictRowIter) putKeylessConflictRowVals(ctx *sql.Context, confVal ConflictVal, row sql.Row) (err error) { + ns := itr.baseRows.NodeStore() + return PutKeylessConflictRowVals(ctx, confVal, row, itr.offsets, itr.cds, ns) +} + +type ConflictVal struct { + Key, Base, Ours, Theirs val.Tuple + Hash hash.Hash + Id string +} + +// GetConflictId gets the conflict ID, ensuring that it is unique by hashing both theirRootish and the key of the table. +func GetConflictId(key val.Tuple, confHash hash.Hash) string { + b := xxh3.Hash128(append(key, confHash[:]...)).Bytes() + return base64.RawStdEncoding.EncodeToString(b[:]) } -func (itr *prollyConflictRowIter) nextConflictVals(ctx *sql.Context) (c conf, err error) { +func (itr *prollyConflictRowIter) nextConflictVals(ctx *sql.Context) (confVal ConflictVal, err error) { ca, err := itr.itr.Next(ctx) if err != nil { - return conf{}, err + return ConflictVal{}, err } - c.k = ca.Key - c.h = ca.TheirRootIsh - // To ensure that the conflict id is unique, we hash both TheirRootIsh and the key of the table. - b := xxh3.Hash128(append(ca.Key, c.h[:]...)).Bytes() - c.id = base64.RawStdEncoding.EncodeToString(b[:]) + confVal.Key = ca.Key + confVal.Hash = ca.TheirRootIsh + confVal.Id = GetConflictId(ca.Key, confVal.Hash) err = itr.loadTableMaps(ctx, ca.Metadata.BaseRootIsh, ca.TheirRootIsh) if err != nil { - return conf{}, err + return ConflictVal{}, err } err = itr.baseRows.Get(ctx, ca.Key, func(_, v val.Tuple) error { - c.bV = v + confVal.Base = v return nil }) if err != nil { - return conf{}, err + return ConflictVal{}, err } err = itr.ourRows.Get(ctx, ca.Key, func(_, v val.Tuple) error { - c.oV = v + confVal.Ours = v return nil }) if err != nil { - return conf{}, err + return ConflictVal{}, err } err = itr.theirRows.Get(ctx, ca.Key, func(_, v val.Tuple) error { - c.tV = v + confVal.Theirs = v return nil }) if err != nil { - return conf{}, err + return ConflictVal{}, err } - return c, nil + return confVal, nil } // loadTableMaps loads the maps specified in the metadata if they are different from @@ -689,7 +757,7 @@ type versionMappings struct { } // returns the schema of the rows returned by the conflicts table and a mappings between each version and the source table. -func calculateConflictSchema(base, ours, theirs schema.Schema) (schema.Schema, *versionMappings, error) { +func CalculateConflictSchema(base, ours, theirs schema.Schema) (schema.Schema, *versionMappings, error) { keyless := schema.IsKeyless(ours) n := 4 + ours.GetAllCols().Size() + theirs.GetAllCols().Size() + base.GetAllCols().Size() if keyless { diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_merge.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_merge.go index a168837ab20..74e2cbd0278 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_merge.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_merge.go @@ -265,6 +265,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')", Expected: []sql.Row{}, }, + { + Query: "SELECT * FROM dolt_preview_merge_conflicts('main', 'feature-branch', 'test')", + Expected: []sql.Row{}, + }, { // FF-Merge Query: "CALL DOLT_MERGE('feature-branch')", @@ -347,6 +351,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')", ExpectedErrStr: "this operation is not supported while in a detached head state", }, + { + Query: "SELECT * FROM dolt_preview_merge_conflicts('main', 'feature-branch', 'test')", + 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", @@ -548,6 +556,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')", Expected: []sql.Row{{"test", uint64(1), uint64(0)}}, }, + { + Query: "SELECT base_pk, base_val, our_pk, our_val, our_diff_type, their_pk, their_val, their_diff_type FROM dolt_preview_merge_conflicts('main', 'feature-branch', 'test')", + Expected: []sql.Row{{0, 0, 0, 1001, "modified", 0, 1000, "modified"}}, + }, { Query: "SELECT is_merging, source, target, unmerged_tables FROM DOLT_MERGE_STATUS;", Expected: []sql.Row{{false, nil, nil, nil}}, @@ -580,6 +592,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_conflicts", Expected: []sql.Row{{"test", uint64(1)}}, }, + { + Query: "SELECT base_pk, base_val, our_pk, our_val, our_diff_type, their_pk, their_val, their_diff_type FROM dolt_conflicts_test", + Expected: []sql.Row{{0, 0, 0, 1001, "modified", 0, 1000, "modified"}}, + }, { Query: "DELETE FROM dolt_conflicts_test", Expected: []sql.Row{{types.NewOkResult(1)}}, @@ -1087,6 +1103,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('HEAD', 'HEAD~1')", Expected: []sql.Row{}, }, + { + Query: "SELECT * FROM dolt_preview_merge_conflicts('HEAD', 'HEAD~1', 'test')", + 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"}}, @@ -1095,6 +1115,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('HEAD', 'HEAD')", Expected: []sql.Row{}, }, + { + Query: "SELECT * FROM dolt_preview_merge_conflicts('HEAD', 'HEAD', 'test')", + Expected: []sql.Row{}, + }, { Query: "CALL DOLT_MERGE('HEAD')", Expected: []sql.Row{{"", 0, 0, "Everything up-to-date"}}, @@ -1122,6 +1146,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')", Expected: []sql.Row{{"test", uint64(1), uint64(0)}}, }, + { + Query: "SELECT COUNT(*) FROM dolt_preview_merge_conflicts('main', 'feature-branch', 'test')", + Expected: []sql.Row{{1}}, + }, { Query: "CALL DOLT_MERGE('feature-branch')", Expected: []sql.Row{{"", 0, 1, "conflicts found"}}, @@ -1275,6 +1303,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'feature-branch')", Expected: []sql.Row{}, }, + { + Query: "SELECT * FROM dolt_preview_merge_conflicts('main', 'feature-branch', 'test')", + 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.", @@ -1307,6 +1339,10 @@ var MergeScripts = []queries.ScriptTest{ Query: "SELECT * FROM dolt_preview_merge_conflicts_summary('main', 'b1')", Expected: []sql.Row{}, }, + { + Query: "SELECT * FROM dolt_preview_merge_conflicts('main', 'b1', 't1')", + Expected: []sql.Row{}, + }, { Query: "call dolt_merge('b1')", Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}}, @@ -4659,6 +4695,7 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ "call dolt_branch('branch2')", }, Assertions: []queries.ScriptTestAssertion{ + // dolt_preview_merge_conflicts_summary { Query: "SELECT * from dolt_preview_merge_conflicts_summary();", ExpectedErr: sql.ErrInvalidArgumentNumber, @@ -4703,6 +4740,63 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ Query: "SELECT * from dolt_preview_merge_conflicts_summary(hashof('main'), 'branch1');", ExpectedErr: dtablefunctions.ErrInvalidNonLiteralArgument, }, + // dolt_preview_merge_conflicts + { + Query: "SELECT * from dolt_preview_merge_conflicts();", + ExpectedErr: sql.ErrInvalidArgumentNumber, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('t');", + ExpectedErr: sql.ErrInvalidArgumentNumber, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1');", + ExpectedErr: sql.ErrInvalidArgumentNumber, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't', 'extra');", + ExpectedErr: sql.ErrInvalidArgumentNumber, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts(null, null, null);", + ExpectedErr: sql.ErrInvalidArgumentDetails, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 123, 't');", + ExpectedErr: sql.ErrInvalidArgumentDetails, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts(123, 'branch1', 't');", + ExpectedErr: sql.ErrInvalidArgumentDetails, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 123);", + ExpectedErr: sql.ErrInvalidArgumentDetails, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('fake-branch', 'main', 't');", + ExpectedErrStr: "branch not found: fake-branch", + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'fake-branch', 't');", + ExpectedErrStr: "branch not found: fake-branch", + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main...branch1', 'branch2', 't');", + ExpectedErrStr: "string is not a valid branch or hash", + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', concat('branch', '1'), 't');", + ExpectedErr: dtablefunctions.ErrInvalidNonLiteralArgument, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts(hashof('main'), 'branch1', 't');", + ExpectedErr: dtablefunctions.ErrInvalidNonLiteralArgument, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 'nope');", + ExpectedErr: sql.ErrTableNotFound, + }, }, }, { @@ -4733,22 +4827,42 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')", Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't')", + Expected: []sql.Row{}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')", Expected: []sql.Row{{"t", uint64(1), uint64(0)}}, }, + { + Query: "SELECT base_pk, base_c1, base_c2, our_pk, our_c1, our_c2, our_diff_type, their_pk, their_c1, their_c2, their_diff_type from dolt_preview_merge_conflicts('main', 'branch2', 't')", + Expected: []sql.Row{{1, "one", "two", 1, "one?", "two", "modified", 1, "one!", "two", "modified"}}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')", Expected: []sql.Row{{"t", uint64(1), uint64(0)}}, }, + { + Query: "SELECT base_pk, base_c1, base_c2, our_pk, our_c1, our_c2, our_diff_type, their_pk, their_c1, their_c2, their_diff_type from dolt_preview_merge_conflicts('branch1', 'branch2', 't')", + Expected: []sql.Row{{1, "one", "two", 1, "one?", "two", "modified", 1, "one!", "two", "modified"}}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary(@Commit1, @Commit2)", // not branches Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts(@Commit1, @Commit2, 't')", // not branches + Expected: []sql.Row{}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch2', 'main')", Expected: []sql.Row{{"t", uint64(1), uint64(0)}}, }, + { + Query: "SELECT base_pk, base_c1, base_c2, our_pk, our_c1, our_c2, our_diff_type, their_pk, their_c1, their_c2, their_diff_type from dolt_preview_merge_conflicts('branch2', 'main', 't')", + Expected: []sql.Row{{1, "one", "two", 1, "one!", "two", "modified", 1, "one?", "two", "modified"}}, + }, }, }, { @@ -4779,26 +4893,134 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')", Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't')", + Expected: []sql.Row{}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')", Expected: []sql.Row{{"t", uint64(1), uint64(0)}}, }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('main', 'branch2', 't')", + Expected: []sql.Row{{1}}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')", Expected: []sql.Row{{"t", uint64(1), uint64(0)}}, }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch1', 'branch2', 't')", + Expected: []sql.Row{{1}}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary(@Commit1, @Commit2)", // not branches Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts(@Commit1, @Commit2, 't')", // not branches + Expected: []sql.Row{}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch2', 'main')", Expected: []sql.Row{{"t", uint64(1), uint64(0)}}, }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch2', 'main', 't')", + Expected: []sql.Row{{1}}, + }, }, }, { - Name: "basic case with multiple tables", + Name: "basic case with multiple tables, data conflicts", + 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", + "update t2 set c1='hundred!' where pk=100", + "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", + "update t2 set c1='hundred?' where pk=100", + "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('main', 'branch1', 't')", + Expected: []sql.Row{}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't2')", + Expected: []sql.Row{}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch2')", + Expected: []sql.Row{{"t", uint64(1), uint64(0)}, {"t2", uint64(1), uint64(0)}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('main', 'branch2', 't')", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('main', 'branch2', 't2')", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')", + Expected: []sql.Row{{"t", uint64(1), uint64(0)}, {"t2", uint64(1), uint64(0)}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch1', 'branch2', 't')", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch1', 'branch2', 't2')", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts_summary(@Commit1, @Commit2)", // not branches + Expected: []sql.Row{}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts(@Commit1, @Commit2, 't')", // not branches + Expected: []sql.Row{}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch2', 'main')", + Expected: []sql.Row{{"t", uint64(1), uint64(0)}, {"t2", uint64(1), uint64(0)}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch2', 'main', 't')", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch2', 'main', 't2')", + Expected: []sql.Row{{1}}, + }, + }, + }, + { + Name: "basic case with multiple tables, schema conflict", 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));", @@ -4831,22 +5053,58 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')", Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't')", + Expected: []sql.Row{}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't2')", + 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 count(*) from dolt_preview_merge_conflicts('main', 'branch2', 't')", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('main', 'branch2', 't2')", + ExpectedErrStr: "schema conflicts found: 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 count(*) from dolt_preview_merge_conflicts('branch1', 'branch2', 't')", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch1', 'branch2', 't2')", + ExpectedErrStr: "schema conflicts found: 1", + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary(@Commit1, @Commit2)", // not branches Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts(@Commit1, @Commit2, 't')", // 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)}}, }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch2', 'main', 't')", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT count(*) from dolt_preview_merge_conflicts('branch2', 'main', 't2')", + ExpectedErrStr: "schema conflicts found: 1", + }, }, }, { @@ -4874,14 +5132,26 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')", Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't')", + 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('main', 'branch2', 't')", + ExpectedErrStr: "schema conflicts found: 1", + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')", Expected: []sql.Row{{"t", nil, uint64(1)}}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('branch1', 'branch2', 't')", + ExpectedErrStr: "schema conflicts found: 1", + }, }, }, { @@ -4911,14 +5181,26 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')", Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't')", + 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('main', 'branch2', 't')", + ExpectedErrStr: "schema conflicts found: 1", + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')", Expected: []sql.Row{{"t", nil, uint64(1)}}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('branch1', 'branch2', 't')", + ExpectedErrStr: "schema conflicts found: 1", + }, }, }, { @@ -5091,54 +5373,58 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ Query: "SELECT * from dolt_preview_merge_conflicts_summary('main', 'branch1')", Expected: []sql.Row{}, }, + { + Query: "SELECT COUNT(*) from dolt_preview_merge_conflicts('main', 'branch1', 't')", + Expected: []sql.Row{{0}}, + }, { 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 COUNT(*) from dolt_preview_merge_conflicts('main', 'branch2', 't')", + Expected: []sql.Row{{4}}, + }, { Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')", Expected: []sql.Row{{"t", uint64(4), uint64(0)}}, }, + { + Query: "SELECT COUNT(*) from dolt_preview_merge_conflicts('branch1', 'branch2', 't')", + Expected: []sql.Row{{4}}, + }, }, }, { - Name: "multiple tables with different conflict types", + Name: "additional conflicts testing with multiple columns", 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');", + "create table test_table (pk int primary key, col1 varchar(20), col2 int);", + "insert into test_table values (1, 'original', 100), (2, 'second', 200);", "call dolt_add('.')", - "call dolt_commit('-am', 'initial setup');", + "call dolt_commit('-am', 'initial commit');", "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');", + + "update test_table set col1 = 'branch2_val', col2 = 300 where pk = 1;", + "call dolt_add('.')", + "call dolt_commit('-am', 'modify 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');", + "update test_table set col1 = 'branch1_val', col2 = 400 where pk = 1;", + "call dolt_add('.')", + "call dolt_commit('-am', 'modify 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 count(*) from dolt_preview_merge_conflicts('branch1', 'branch2', 'test_table')", + Expected: []sql.Row{{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)}}, + Query: "SELECT base_pk, our_col1, their_col1 from dolt_preview_merge_conflicts('branch1', 'branch2', 'test_table')", + Expected: []sql.Row{{1, "branch1_val", "branch2_val"}}, }, }, }, @@ -5175,6 +5461,18 @@ var PreviewMergeConflictsFunctionScripts = []queries.ScriptTest{ Query: "SELECT * from dolt_preview_merge_conflicts_summary('branch1', 'branch2')", Expected: []sql.Row{}, }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch1', 't')", + Expected: []sql.Row{}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('main', 'branch2', 't')", + Expected: []sql.Row{}, + }, + { + Query: "SELECT * from dolt_preview_merge_conflicts('branch1', 'branch2', 't')", + Expected: []sql.Row{}, + }, }, }, } diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_schema_merge.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_schema_merge.go index 0bff7e870bf..4628a552a42 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_schema_merge.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_schema_merge.go @@ -1595,6 +1595,10 @@ var SchemaChangeTestsTypeChanges = []MergeScriptTest{ Query: "select * from dolt_preview_merge_conflicts_summary('main', 'right');", Expected: []sql.Row{{"t", nil, uint64(1)}}, }, + { + Query: "select * from dolt_preview_merge_conflicts('main', 'right', 't');", + ExpectedErrStr: "schema conflicts found: 1", + }, { Query: "call dolt_merge('right');", Expected: []sql.Row{{"", 0, 1, "conflicts found"}},