Drop support for deleting conflicts through cli and add cardinality column for keyless table conflicts (#3777)

* reimplement dolt_transaction_merge_stomp at merge layer


add idx

* [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh

* fix tests

* remove stomp conflicts

* conflict reader must be reset each time PartitionRows is called

* reimplement dolt conflict resolve --theirs|--ours through sql path

Also drops manual conflict resolving through cli

* add cardinality columns to dolt_conflicts_table_name for keyless tables

* Adds keyless conflict deleting for new format

* dolt conflicts resolve sql impl for keyless tables

* [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh

* delete cli.ParseKeyValues

* fix tests

* temporarily allow dolt_docs to be mutated from SQL path

Because conflicts resolve uses the SQL path, dolt_docs needs to be editable.

* fix untested code, comment out docs tests

* bats

* fix a couple found merge bugs

* [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh

* if statements...

* add comments

Co-authored-by: druvv <druvv@users.noreply.github.com>
This commit is contained in:
Dhruv Sringari
2022-07-12 17:34:20 -07:00
committed by GitHub
parent ce4aa09045
commit 14899d2909
30 changed files with 917 additions and 879 deletions
-131
View File
@@ -15,17 +15,10 @@
package cli
import (
"context"
"errors"
"os"
"strings"
"github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo"
"github.com/dolthub/dolt/go/libraries/doltcore/row"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/store/types"
)
var ErrEmptyDefTuple = errors.New("empty definition tuple")
@@ -84,127 +77,3 @@ func HelpAndUsagePrinters(cmdDoc *CommandDocumentation) (UsagePrinter, UsagePrin
PrintUsage(cmdDoc.CommandStr, synopsis, cmdDoc.ArgParser)
}
}
type ColumnError struct {
name string
msg string
}
func IsColumnError(err error) bool {
_, ok := err.(ColumnError)
return ok
}
func ColumnNameFromColumnError(err error) string {
ce, ok := err.(ColumnError)
if !ok {
panic("Bug. Test IsColumnError before calling")
}
return ce.name
}
func (ce ColumnError) Error() string {
return ce.msg
}
func parseTuples(args []string, pkCols *schema.ColCollection) ([]map[uint64]string, error) {
defTpl := strings.Split(args[0], ",")
if len(defTpl) == 0 {
return nil, ErrEmptyDefTuple
}
defTags := make([]uint64, len(defTpl))
for i, colName := range defTpl {
col, ok := pkCols.GetByName(colName)
if !ok {
return nil, ColumnError{colName, colName + " is not a known primary key column."}
}
defTags[i] = col.Tag
}
var results []map[uint64]string
for _, arg := range args[1:] {
valTpl := strings.Split(arg, ",")
result := make(map[uint64]string)
for i, key := range defTags {
if i < len(valTpl) {
result[key] = valTpl[i]
}
}
results = append(results, result)
}
return results, nil
}
func ParseKeyValues(ctx context.Context, vrw types.ValueReadWriter, sch schema.Schema, args []string) ([]types.Value, error) {
pkCols := sch.GetPKCols()
var pkMaps []map[uint64]string
if sch.GetPKCols().Size() == 1 {
pkCol := pkCols.GetByIndex(0)
start := 0
if args[start] == pkCol.Name {
start = 1
}
for _, pk := range args[start:] {
pkMaps = append(pkMaps, map[uint64]string{pkCol.Tag: pk})
}
} else {
var err error
pkMaps, err = parseTuples(args, pkCols)
if err != nil {
return nil, err
}
}
convFuncs := make(map[uint64]typeinfo.TypeConverter)
err := sch.GetPKCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
tc, _, err := typeinfo.GetTypeConverter(ctx, typeinfo.StringDefaultType, col.TypeInfo)
if err != nil {
return true, err
}
convFuncs[tag] = tc
return false, nil
})
if err != nil {
return nil, err
}
var pkVals []types.Value
for _, pkMap := range pkMaps {
taggedVals := make(row.TaggedValues)
for k, v := range pkMap {
val, err := convFuncs[k](ctx, vrw, types.String(v))
if err != nil {
return nil, err
}
taggedVals[k] = val
}
tpl, err := taggedVals.NomsTupleForPKCols(vrw.Format(), pkCols).Value(ctx)
if err != nil {
return nil, err
}
pkVals = append(pkVals, tpl)
}
return pkVals, nil
}
-175
View File
@@ -1,175 +0,0 @@
// Copyright 2019 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 cli
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/libraries/doltcore/row"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/store/types"
)
func mustValue(v types.Value, err error) types.Value {
if err != nil {
panic(err)
}
return v
}
func mustString(str string, err error) string {
if err != nil {
panic(err)
}
return str
}
func TestParseKeyValues(t *testing.T) {
ctx := context.Background()
const (
lnColName = "last"
fnColName = "first"
mnColName = "middle"
lnColTag = 1
fnColTag = 0
mnColTag = 2
)
testKeyColColl := schema.NewColCollection(
schema.NewColumn(lnColName, lnColTag, types.StringKind, true),
schema.NewColumn(fnColName, fnColTag, types.StringKind, true),
schema.NewColumn(mnColName, mnColTag, types.StringKind, true),
)
sch, err := schema.SchemaFromCols(testKeyColColl)
require.NoError(t, err)
singleKeyColColl := schema.NewColCollection(
schema.NewColumn(lnColName, lnColTag, types.StringKind, true),
)
singleKeySch, err := schema.SchemaFromCols(singleKeyColColl)
require.NoError(t, err)
tests := []struct {
sch schema.Schema
args []string
expectedKeys []types.Value
expectErr bool
}{
{
singleKeySch,
[]string{"robertson"},
[]types.Value{
mustValue(row.TaggedValues{lnColTag: types.String("robertson")}.NomsTupleForPKCols(types.Format_Default, singleKeyColColl).Value(ctx)),
},
false,
},
{
singleKeySch,
[]string{"last", "robertson"},
[]types.Value{
mustValue(row.TaggedValues{lnColTag: types.String("robertson")}.NomsTupleForPKCols(types.Format_Default, singleKeyColColl).Value(ctx)),
},
false,
},
{
singleKeySch,
[]string{"last"},
[]types.Value{},
false,
},
{
singleKeySch,
[]string{"last", "last"},
[]types.Value{
mustValue(row.TaggedValues{lnColTag: types.String("last")}.NomsTupleForPKCols(types.Format_Default, singleKeyColColl).Value(ctx)),
},
false,
},
{
singleKeySch,
[]string{"last", "robertson", "johnson"},
[]types.Value{
mustValue(row.TaggedValues{lnColTag: types.String("robertson")}.NomsTupleForPKCols(types.Format_Default, singleKeyColColl).Value(ctx)),
mustValue(row.TaggedValues{lnColTag: types.String("johnson")}.NomsTupleForPKCols(types.Format_Default, singleKeyColColl).Value(ctx)),
},
false,
},
{
sch,
[]string{"last"},
nil,
false,
},
{
sch,
[]string{"last", "robertson", "johnson"},
[]types.Value{
mustValue(row.TaggedValues{lnColTag: types.String("robertson")}.NomsTupleForPKCols(types.Format_Default, testKeyColColl).Value(ctx)),
mustValue(row.TaggedValues{lnColTag: types.String("johnson")}.NomsTupleForPKCols(types.Format_Default, testKeyColColl).Value(ctx)),
},
false,
},
{
sch,
[]string{"first,last", "robert,robertson", "john,johnson"},
[]types.Value{
mustValue(row.TaggedValues{lnColTag: types.String("robertson"), fnColTag: types.String("robert")}.NomsTupleForPKCols(types.Format_Default, testKeyColColl).Value(ctx)),
mustValue(row.TaggedValues{lnColTag: types.String("johnson"), fnColTag: types.String("john")}.NomsTupleForPKCols(types.Format_Default, testKeyColColl).Value(ctx)),
},
false,
},
}
for _, test := range tests {
vrw := types.NewMemoryValueStore()
actual, err := ParseKeyValues(ctx, vrw, test.sch, test.args)
if test.expectErr != (err != nil) {
t.Error(test.args, "produced an unexpected error")
} else {
longer := len(actual)
if len(test.expectedKeys) > longer {
longer = len(test.expectedKeys)
}
for i := 0; i < longer; i++ {
var currActual types.Value = types.NullValue
var currExpected types.Value = types.NullValue
if i < len(test.expectedKeys) {
currExpected = test.expectedKeys[i]
}
if i < len(actual) {
currActual = actual[i]
}
if !currActual.Equals(currExpected) {
t.Error("actual:", mustString(types.EncodedValue(context.Background(), currActual)), "!= expected:", mustString(types.EncodedValue(context.Background(), currExpected)))
}
}
}
}
}
@@ -0,0 +1,375 @@
// Copyright 2019 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 cnfcmds
import (
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
"github.com/dolthub/dolt/go/libraries/utils/set"
"github.com/dolthub/dolt/go/store/hash"
)
type AutoResolveStrategy int
const (
AutoResolveStrategyOurs AutoResolveStrategy = iota
AutoResolveStrategyTheirs
)
var ErrConfSchIncompatible = errors.New("the conflict schema's columns are not equal to the current schema's columns, please resolve manually")
// AutoResolveAll resolves all conflicts in all tables according to the given
// |strategy|.
func AutoResolveAll(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolveStrategy) error {
root, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
tbls, err := root.TablesInConflict(ctx)
if err != nil {
return err
}
return AutoResolveTables(ctx, dEnv, strategy, tbls)
}
// AutoResolveTables resolves all conflicts in the given tables according to the
// given |strategy|.
func AutoResolveTables(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolveStrategy, tbls []string) error {
root, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
for _, tblName := range tbls {
err = ResolveTable(ctx, dEnv, root, tblName, strategy)
if err != nil {
return err
}
}
return nil
}
// ResolveTable resolves all conflicts in the given table according to the given
// |strategy|. It errors if the schema of the conflict version you are choosing
// differs from the current schema.
func ResolveTable(ctx context.Context, dEnv *env.DoltEnv, root *doltdb.RootValue, tblName string, strategy AutoResolveStrategy) error {
tbl, ok, err := root.GetTable(ctx, tblName)
if err != nil {
return err
}
if !ok {
return doltdb.ErrTableNotFound
}
has, err := tbl.HasConflicts(ctx)
if err != nil {
return err
}
if !has {
return nil
}
sch, err := tbl.GetSchema(ctx)
if err != nil {
return err
}
_, ourSch, theirSch, err := tbl.GetConflictSchemas(ctx, tblName)
if err != nil {
return err
}
switch strategy {
case AutoResolveStrategyOurs:
if !schema.ColCollsAreEqual(sch.GetAllCols(), ourSch.GetAllCols()) {
return ErrConfSchIncompatible
}
case AutoResolveStrategyTheirs:
if !schema.ColCollsAreEqual(sch.GetAllCols(), theirSch.GetAllCols()) {
return ErrConfSchIncompatible
}
default:
panic("unhandled auto resolve strategy")
}
before, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
eng, err := engine.NewSqlEngineForEnv(ctx, dEnv)
if err != nil {
return err
}
sqlCtx, err := engine.NewLocalSqlContext(ctx, eng)
if err != nil {
return err
}
if !schema.IsKeyless(sch) {
err = resolvePkTable(sqlCtx, tblName, sch, strategy, eng)
} else {
err = resolveKeylessTable(sqlCtx, tblName, sch, strategy, eng)
}
if err != nil {
return err
}
after, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
err = validateConstraintViolations(ctx, before, after, tblName)
if err != nil {
return err
}
return nil
}
func resolvePkTable(sqlCtx *sql.Context, tblName string, sch schema.Schema, strategy AutoResolveStrategy, eng *engine.SqlEngine) error {
queries := getResolveQueries(strategy, tblName, sch)
for _, query := range queries {
err := execute(sqlCtx, eng, query)
if err != nil {
return err
}
}
return nil
}
func resolveKeylessTable(sqlCtx *sql.Context, tblName string, sch schema.Schema, strategy AutoResolveStrategy, eng *engine.SqlEngine) error {
allCols := sch.GetAllCols().GetColumnNames()
baseCols := strings.Join(withPrefix(allCols, "base_"), ", ")
ourCols := strings.Join(withPrefix(allCols, "our_"), ", ")
theirCols := strings.Join(withPrefix(allCols, "their_"), ", ")
selectConfsQ := fmt.Sprintf(
`SELECT
%s,
%s,
%s,
our_diff_type,
their_diff_type,
base_cardinality,
our_cardinality,
their_cardinality
FROM dolt_conflicts_%s;`, baseCols, ourCols, theirCols, tblName)
sqlSch, itr, err := eng.Query(sqlCtx, selectConfsQ)
if err != nil {
return err
}
s, err := sqlutil.FromDoltSchema(tblName, sch)
if err != nil {
return err
}
confSplitter, err := newConflictSplitter(sqlSch[:len(sqlSch)-3], s.Schema)
if err != nil {
return err
}
for {
r, err := itr.Next(sqlCtx)
if err == io.EOF {
break
}
if err != nil {
return err
}
ourCardinality := r[len(r)-2].(uint64)
theirCardinality := r[len(r)-1].(uint64)
split, err := confSplitter.splitConflictRow(r[:len(r)-3])
if err != nil {
return err
}
// In a keyless conflict, the non-null versions have equivalent rows.
// The first version in the split is always non-null.
rowVals := split[0].row
var rowDelta int64
switch strategy {
case AutoResolveStrategyOurs:
rowDelta = 0
case AutoResolveStrategyTheirs:
rowDelta = int64(theirCardinality) - int64(ourCardinality)
}
var stmt string
var n int64
if rowDelta > 0 {
stmt, err = sqlfmt.SqlRowAsInsertStmt(rowVals, tblName, sch)
if err != nil {
return err
}
n = rowDelta
} else if rowDelta < 0 {
stmt, err = sqlfmt.SqlRowAsDeleteStmt(rowVals, tblName, sch, 1)
if err != nil {
return err
}
n = rowDelta * -1
}
for i := int64(0); i < n; i++ {
err = execute(sqlCtx, eng, stmt)
if err != nil {
return err
}
}
err = execute(sqlCtx, eng, fmt.Sprintf("DELETE FROM dolt_conflicts_%s", tblName))
if err != nil {
return err
}
err = execute(sqlCtx, eng, "COMMIT;")
if err != nil {
return err
}
}
return nil
}
func execute(ctx *sql.Context, eng *engine.SqlEngine, query string) error {
_, itr, err := eng.Query(ctx, query)
if err != nil {
return err
}
_, err = itr.Next(ctx)
for err != nil && err != io.EOF {
return err
}
return nil
}
func getResolveQueries(strategy AutoResolveStrategy, tblName string, sch schema.Schema) (queries []string) {
identCols := getIdentifyingColumnNames(sch)
allCols := sch.GetAllCols().GetColumnNames()
r := autoResolverMap[strategy]
queries = r(tblName, allCols, identCols)
// auto_commit is off
queries = append(queries, "COMMIT;")
return
}
func getIdentifyingColumnNames(sch schema.Schema) []string {
if schema.IsKeyless(sch) {
return sch.GetAllCols().GetColumnNames()
} else {
return sch.GetPKCols().GetColumnNames()
}
}
var autoResolverMap = map[AutoResolveStrategy]autoResolver{
AutoResolveStrategyOurs: ours,
AutoResolveStrategyTheirs: theirs,
}
type autoResolver func(tblName string, allCols []string, identCols []string) []string
func theirs(tblName string, allCols []string, identCols []string) []string {
dstCols := strings.Join(allCols, ", ")
srcCols := strings.Join(withPrefix(allCols, "their_"), ", ")
q1 := fmt.Sprintf(
`
REPLACE INTO %s (%s) (
SELECT %s
FROM dolt_conflicts_%s
WHERE their_diff_type = 'modified' OR their_diff_type = 'added'
);
`, tblName, dstCols, srcCols, tblName)
q2 := fmt.Sprintf(
`
DELETE t1
FROM %s t1
WHERE (
SELECT count(*) from dolt_conflicts_%s t2
WHERE %s AND t2.their_diff_type = 'removed'
) > 0;
`, tblName, tblName, buildJoinCond(identCols, "base_"))
q3 := fmt.Sprintf("DELETE FROM dolt_conflicts_%s;", tblName)
return []string{q1, q2, q3}
}
func ours(tblName string, allCols []string, identCols []string) []string {
q3 := fmt.Sprintf("DELETE FROM dolt_conflicts_%s;", tblName)
return []string{q3}
}
func buildJoinCond(identCols []string, prefix string) string {
b := &strings.Builder{}
var seenOne bool
for _, col := range identCols {
if seenOne {
_, _ = b.WriteString(" AND ")
}
seenOne = true
_, _ = fmt.Fprintf(b, "t1.%s = t2.%s%s", col, prefix, col)
}
return b.String()
}
func withPrefix(arr []string, prefix string) []string {
out := make([]string, len(arr))
for i := range arr {
out[i] = prefix + arr[i]
}
return out
}
func validateConstraintViolations(ctx context.Context, before, after *doltdb.RootValue, table string) error {
tables, err := after.GetTableNames(ctx)
if err != nil {
return err
}
_, violators, err := merge.AddForeignKeyViolations(ctx, after, before, set.NewStrSet(tables), hash.Of(nil))
if err != nil {
return err
}
if violators.Size() > 0 {
return fmt.Errorf("resolving conflicts for table %s created foreign key violations", table)
}
return nil
}
+15 -108
View File
@@ -18,30 +18,23 @@ import (
"context"
"strings"
eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1"
"github.com/dolthub/dolt/go/libraries/doltcore/row"
"github.com/dolthub/dolt/go/store/types"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/commands"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
)
var resDocumentation = cli.CommandDocumentationContent{
ShortDesc: "Removes rows from list of conflicts",
ShortDesc: "Automatically resolves all conflicts taking either ours or theirs for the given tables",
LongDesc: `
When a merge operation finds conflicting changes, the rows with the conflicts are added to list of conflicts that must be resolved. Once the value for the row is resolved in the working set of tables, then the conflict should be resolved.
In its first form {{.EmphasisLeft}}dolt conflicts resolve <table> <key>...{{.EmphasisRight}}, resolve runs in manual merge mode resolving the conflicts whose keys are provided.
When a merge finds conflicting changes, it documents them in the dolt_conflicts table. A conflict is between two versions: ours (the rows at the destination branch head) and theirs (the rows at the source branch head).
In its second form {{.EmphasisLeft}}dolt conflicts resolve --ours|--theirs <table>...{{.EmphasisRight}}, resolve runs in auto resolve mode. Where conflicts are resolved using a rule to determine which version of a row should be used.
dolt conflicts resolve will automatically resolve the conflicts by taking either the ours or theirs versions for each row.
`,
Synopsis: []string{
`{{.LessThan}}table{{.GreaterThan}} [{{.LessThan}}key_definition{{.GreaterThan}}] {{.LessThan}}key{{.GreaterThan}}...`,
`--ours|--theirs {{.LessThan}}table{{.GreaterThan}}...`,
},
}
@@ -51,16 +44,16 @@ const (
theirsFlag = "theirs"
)
var autoResolvers = map[string]merge.AutoResolver{
oursFlag: merge.Ours,
theirsFlag: merge.Theirs,
var autoResolveStrategies = map[string]AutoResolveStrategy{
oursFlag: AutoResolveStrategyOurs,
theirsFlag: AutoResolveStrategyTheirs,
}
var autoResolverParams []string
func init() {
autoResolverParams = make([]string, 0, len(autoResolvers))
for k := range autoResolvers {
autoResolverParams = make([]string, 0, len(autoResolveStrategies))
for k := range autoResolveStrategies {
autoResolverParams = append(autoResolverParams, k)
}
}
@@ -77,10 +70,6 @@ func (cmd ResolveCmd) Description() string {
return "Removes rows from list of conflicts"
}
func (cmd ResolveCmd) GatedForNBF(nbf *types.NomsBinFormat) bool {
return types.IsFormat_DOLT_1(nbf)
}
func (cmd ResolveCmd) Docs() *cli.CommandDocumentation {
ap := cmd.ArgParser()
return cli.NewCommandDocumentation(resDocumentation, ap)
@@ -93,11 +82,9 @@ func (cmd ResolveCmd) EventType() eventsapi.ClientEventType {
func (cmd ResolveCmd) ArgParser() *argparser.ArgParser {
ap := argparser.NewArgParser()
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"table", "List of tables to be printed. When in auto-resolve mode, '.' can be used to resolve all tables."})
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"key", "key(s) of rows within a table whose conflicts have been resolved"})
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"table", "List of tables to be resolved. '.' can be used to resolve all tables."})
ap.SupportsFlag("ours", "", "For all conflicts, take the version from our branch and resolve the conflict")
ap.SupportsFlag("theirs", "", "For all conflicts, take the version from their branch and resolve the conflict")
return ap
}
@@ -115,7 +102,7 @@ func (cmd ResolveCmd) Exec(ctx context.Context, commandStr string, args []string
if apr.ContainsAny(autoResolverParams...) {
verr = autoResolve(ctx, apr, dEnv)
} else {
verr = manualResolve(ctx, apr, dEnv)
verr = errhand.BuildDError("--ours or --theirs must be supplied").SetPrintUsage().Build()
}
return commands.HandleVErrAndExitCode(verr, usage)
@@ -126,20 +113,20 @@ func autoResolve(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.
if funcFlags.Size() > 1 {
ff := strings.Join(autoResolverParams, ", ")
return errhand.BuildDError("specify a resolver func from [ %s ]", ff).SetPrintUsage().Build()
return errhand.BuildDError("specify only one from [ %s ]", ff).SetPrintUsage().Build()
} else if apr.NArg() == 0 {
return errhand.BuildDError("specify at least one table to resolve conflicts").SetPrintUsage().Build()
}
autoResolveFlag := funcFlags.AsSlice()[0]
autoResolveFunc := autoResolvers[autoResolveFlag]
autoResolveStrategy := autoResolveStrategies[autoResolveFlag]
var err error
tbls := apr.Args
if len(tbls) == 1 && tbls[0] == "." {
err = merge.AutoResolveAll(ctx, dEnv, autoResolveFunc)
err = AutoResolveAll(ctx, dEnv, autoResolveStrategy)
} else {
err = merge.AutoResolveTables(ctx, dEnv, autoResolveFunc, tbls)
err = AutoResolveTables(ctx, dEnv, autoResolveStrategy, tbls)
}
if err != nil {
@@ -149,86 +136,6 @@ func autoResolve(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.
return saveDocsOnResolve(ctx, dEnv)
}
func manualResolve(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEnv) errhand.VerboseError {
args := apr.Args
if len(args) < 2 {
return errhand.BuildDError("at least two args are required").SetPrintUsage().Build()
}
root, verr := commands.GetWorkingWithVErr(dEnv)
if verr != nil {
return verr
}
tblName := args[0]
if has, err := root.HasTable(ctx, tblName); err != nil {
return errhand.BuildDError("error: could not read tables").AddCause(err).Build()
} else if !has {
return errhand.BuildDError("error: table '%s' not found", tblName).Build()
}
tbl, _, err := root.GetTable(ctx, tblName)
if err != nil {
return errhand.BuildDError("error: failed to get table '%s'", tblName).AddCause(err).Build()
}
sch, err := tbl.GetSchema(ctx)
if err != nil {
return errhand.BuildDError("error: failed to get schema").AddCause(err).Build()
}
keysToResolve, err := cli.ParseKeyValues(ctx, root.VRW(), sch, args[1:])
if err != nil {
return errhand.BuildDError("error: parsing command line").AddCause(err).Build()
}
if keysToResolve == nil {
return errhand.BuildDError("no primary keys were given to be resolved").Build()
}
invalid, notFound, updatedTbl, err := tbl.ResolveConflicts(ctx, keysToResolve)
if err != nil {
return errhand.BuildDError("fatal: Failed to resolve conflicts").AddCause(err).Build()
}
for _, key := range invalid {
cli.Printf("(%s) is not a valid key\n", row.TupleFmt(ctx, key.(types.Tuple)))
}
for _, key := range notFound {
cli.Printf("(%s) is not the primary key of a conflicting row\n", row.TupleFmt(ctx, key.(types.Tuple)))
}
if updatedTbl == nil {
return errhand.BuildDError("error: No changes were resolved").Build()
}
updatedHash, err := updatedTbl.HashOf()
if err != nil {
return errhand.BuildDError("error: failed to get table hash").AddCause(err).Build()
}
hash, err := tbl.HashOf()
if err != nil {
return errhand.BuildDError("error: failed to get table hash").AddCause(err).Build()
}
if hash != updatedHash {
root, err := root.PutTable(ctx, tblName, updatedTbl)
if err != nil {
return errhand.BuildDError("").AddCause(err).Build()
}
if verr := commands.UpdateWorkingWithVErr(dEnv, root); verr != nil {
return verr
}
}
valid := len(keysToResolve) - len(invalid) - len(notFound)
cli.Println(valid, "rows resolved successfully")
return saveDocsOnResolve(ctx, dEnv)
}
func saveDocsOnResolve(ctx context.Context, dEnv *env.DoltEnv) errhand.VerboseError {
err := actions.SaveTrackedDocsFromWorking(ctx, dEnv)
if err != nil {
@@ -172,6 +172,9 @@ func TestSystemTableTags(t *testing.T) {
doltConflictsMin := sysTableMin + uint64(7000)
assert.Equal(t, doltConflictsMin+0, schema.DoltConflictsOurDiffTypeTag)
assert.Equal(t, doltConflictsMin+1, schema.DoltConflictsTheirDiffTypeTag)
assert.Equal(t, doltConflictsMin+2, schema.DoltConflictsBaseCardinalityTag)
assert.Equal(t, doltConflictsMin+3, schema.DoltConflictsOurCardinalityTag)
assert.Equal(t, doltConflictsMin+4, schema.DoltConflictsTheirCardinalityTag)
})
}
@@ -121,6 +121,7 @@ var writeableSystemTables = []string{
DoltQueryCatalogTableName,
SchemasTableName,
ProceduresTableName,
DocTableName,
}
var persistedSystemTables = []string{
+24 -13
View File
@@ -274,20 +274,34 @@ func (t *Table) getProllyConflictSchemas(ctx context.Context, tblName string) (b
return nil, nil, nil, err
}
baseTbl, err := tableFromRootIsh(ctx, t.ValueReadWriter(), t.NodeStore(), art.Metadata.BaseRootIsh, tblName)
baseTbl, baseOk, err := tableFromRootIsh(ctx, t.ValueReadWriter(), t.NodeStore(), art.Metadata.BaseRootIsh, tblName)
if err != nil {
return nil, nil, nil, err
}
theirTbl, err := tableFromRootIsh(ctx, t.ValueReadWriter(), t.NodeStore(), art.TheirRootIsh, tblName)
theirTbl, theirOK, err := tableFromRootIsh(ctx, t.ValueReadWriter(), t.NodeStore(), art.TheirRootIsh, tblName)
if err != nil {
return nil, nil, nil, err
}
if !theirOK {
return nil, nil, nil, fmt.Errorf("could not find tbl %s in right root value", tblName)
}
theirSch, err := theirTbl.GetSchema(ctx)
if err != nil {
return nil, nil, nil, err
}
baseSch, err := baseTbl.GetSchema(ctx)
if err != nil {
return nil, nil, nil, err
// If the table does not exist in the ancestor, pretend it existed and that
// it was completely empty.
if !baseOk {
if schema.SchemasAreEqual(ourSch, theirSch) {
return ourSch, ourSch, theirSch, nil
} else {
return nil, nil, nil, fmt.Errorf("expected our schema to equal their schema since the table did not exist in the ancestor")
}
}
theirSch, err := theirTbl.GetSchema(ctx)
baseSch, err := baseTbl.GetSchema(ctx)
if err != nil {
return nil, nil, nil, err
}
@@ -295,19 +309,16 @@ func (t *Table) getProllyConflictSchemas(ctx context.Context, tblName string) (b
return baseSch, ourSch, theirSch, nil
}
func tableFromRootIsh(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, h hash.Hash, tblName string) (*Table, error) {
func tableFromRootIsh(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, h hash.Hash, tblName string) (*Table, bool, error) {
rv, err := LoadRootValueFromRootIshAddr(ctx, vrw, ns, h)
if err != nil {
return nil, err
return nil, false, err
}
tbl, ok, err := rv.GetTable(ctx, tblName)
if err != nil {
return nil, err
return nil, false, err
}
if !ok {
return nil, fmt.Errorf("could not find tbl %s in root value", tblName)
}
return tbl, nil
return tbl, ok, nil
}
func (t *Table) getNomsConflictSchemas(ctx context.Context) (base, sch, mergeSch schema.Schema, err error) {
+136 -28
View File
@@ -49,6 +49,7 @@ type ConflictReader struct {
joiner *rowconv.Joiner
sch schema.Schema
nbf *types.NomsBinFormat
keyless bool
}
// NewConflictReader returns a new conflict reader for a given table
@@ -79,7 +80,6 @@ func NewConflictReader(ctx context.Context, tbl *doltdb.Table) (*ConflictReader,
if err != nil {
return nil, err
}
readerSch := joiner.GetSchema()
readerSch, err = readerSch.AddColumn(schema.NewColumn("our_diff_type", schema.DoltConflictsOurDiffTypeTag, types.StringKind, false), nil)
if err != nil {
@@ -90,6 +90,41 @@ func NewConflictReader(ctx context.Context, tbl *doltdb.Table) (*ConflictReader,
return nil, err
}
var keyless bool
if schema.IsKeyless(sch) {
keyless = true
readerSch, err = readerSch.AddColumn(
schema.NewColumn(
"base_cardinality",
schema.DoltConflictsBaseCardinalityTag,
types.UintKind,
false),
nil)
if err != nil {
return nil, err
}
readerSch, err = readerSch.AddColumn(
schema.NewColumn(
"our_cardinality",
schema.DoltConflictsOurCardinalityTag,
types.UintKind,
false),
nil)
if err != nil {
return nil, err
}
readerSch, err = readerSch.AddColumn(
schema.NewColumn(
"their_cardinality",
schema.DoltConflictsTheirCardinalityTag,
types.UintKind,
false),
nil)
if err != nil {
return nil, err
}
}
_, confIdx, err := tbl.GetConflicts(ctx)
if err != nil {
return nil, err
@@ -105,7 +140,13 @@ func NewConflictReader(ctx context.Context, tbl *doltdb.Table) (*ConflictReader,
return nil, err
}
return &ConflictReader{confItr: confItr, joiner: joiner, sch: readerSch, nbf: tbl.Format()}, nil
return &ConflictReader{
confItr: confItr,
joiner: joiner,
sch: readerSch,
nbf: tbl.Format(),
keyless: keyless,
}, nil
}
// GetSchema gets the schema of the rows that this reader will return
@@ -134,43 +175,27 @@ func (cr *ConflictReader) NextConflict(ctx context.Context) (row.Row, pipeline.I
keyTpl := key.(types.Tuple)
conflict, err := conflict.ConflictFromTuple(value.(types.Tuple))
if err != nil {
return nil, pipeline.NoProps, err
}
namedRows := make(map[string]row.Row)
if !types.IsNull(conflict.Base) {
namedRows[baseStr], err = row.FromNoms(cr.joiner.SchemaForName(baseStr), keyTpl, conflict.Base.(types.Tuple))
if err != nil {
return nil, pipeline.NoProps, err
}
var joinedRow row.Row
if !cr.keyless {
joinedRow, err = cr.pkJoinedRow(keyTpl, conflict)
} else {
joinedRow, err = cr.keylessJoinedRow(keyTpl, conflict)
}
if !types.IsNull(conflict.Value) {
namedRows[oursStr], err = row.FromNoms(cr.joiner.SchemaForName(oursStr), keyTpl, conflict.Value.(types.Tuple))
if err != nil {
return nil, pipeline.NoProps, err
}
if err != nil {
return nil, pipeline.NoProps, err
}
if !types.IsNull(conflict.MergeValue) {
namedRows[theirsStr], err = row.FromNoms(cr.joiner.SchemaForName(theirsStr), keyTpl, conflict.MergeValue.(types.Tuple))
if err != nil {
return nil, pipeline.NoProps, err
}
}
joinedRow, err := cr.joiner.Join(namedRows)
ourDiffType := getDiffType(conflict.Base, conflict.Value)
theirDiffType := getDiffType(conflict.Base, conflict.MergeValue)
joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsOurDiffTypeTag, types.String(ourDiffType), cr.sch)
if err != nil {
return nil, pipeline.NoProps, err
}
joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsTheirDiffTypeTag, types.String(theirDiffType), cr.sch)
if err != nil {
return nil, pipeline.NoProps, err
}
@@ -178,6 +203,89 @@ func (cr *ConflictReader) NextConflict(ctx context.Context) (row.Row, pipeline.I
return joinedRow, pipeline.NoProps, nil
}
func (cr *ConflictReader) pkJoinedRow(key types.Tuple, conflict conflict.Conflict) (row.Row, error) {
var err error
namedRows := make(map[string]row.Row)
if !types.IsNull(conflict.Base) {
namedRows[baseStr], err = row.FromNoms(cr.joiner.SchemaForName(baseStr), key, conflict.Base.(types.Tuple))
if err != nil {
return nil, err
}
}
if !types.IsNull(conflict.Value) {
namedRows[oursStr], err = row.FromNoms(cr.joiner.SchemaForName(oursStr), key, conflict.Value.(types.Tuple))
if err != nil {
return nil, err
}
}
if !types.IsNull(conflict.MergeValue) {
namedRows[theirsStr], err = row.FromNoms(cr.joiner.SchemaForName(theirsStr), key, conflict.MergeValue.(types.Tuple))
if err != nil {
return nil, err
}
}
joinedRow, err := cr.joiner.Join(namedRows)
if err != nil {
return nil, err
}
return joinedRow, nil
}
func (cr *ConflictReader) keylessJoinedRow(key types.Tuple, conflict conflict.Conflict) (row.Row, error) {
var err error
namedRows := make(map[string]row.Row)
var baseCard, ourCard, theirCard uint64
if !types.IsNull(conflict.Base) {
namedRows[baseStr], baseCard, err = row.KeylessRowsFromTuples(key, conflict.Base.(types.Tuple))
if err != nil {
return nil, err
}
}
if !types.IsNull(conflict.Value) {
namedRows[oursStr], ourCard, err = row.KeylessRowsFromTuples(key, conflict.Value.(types.Tuple))
if err != nil {
return nil, err
}
}
if !types.IsNull(conflict.MergeValue) {
namedRows[theirsStr], theirCard, err = row.KeylessRowsFromTuples(key, conflict.MergeValue.(types.Tuple))
if err != nil {
return nil, err
}
}
joinedRow, err := cr.joiner.Join(namedRows)
if err != nil {
return nil, err
}
joinedRow, err = setCardinalities(types.Uint(baseCard), types.Uint(ourCard), types.Uint(theirCard), joinedRow, cr.sch)
if err != nil {
return nil, err
}
return joinedRow, nil
}
func setCardinalities(base, ours, theirs types.Uint, joinedRow row.Row, sch schema.Schema) (row.Row, error) {
joinedRow, err := joinedRow.SetColVal(schema.DoltConflictsBaseCardinalityTag, base, sch)
if err != nil {
return nil, err
}
joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsOurCardinalityTag, ours, sch)
if err != nil {
return nil, err
}
joinedRow, err = joinedRow.SetColVal(schema.DoltConflictsTheirCardinalityTag, theirs, sch)
if err != nil {
return nil, err
}
return joinedRow, nil
}
func getDiffType(base types.Value, other types.Value) string {
if types.IsNull(base) {
return ConflictDiffTypeAdded
@@ -263,10 +263,6 @@ func TestKeylessMergeConflicts(t *testing.T) {
// conflict resolution
t.Run(test.name+"_resolved_ours", func(t *testing.T) {
if types.IsFormat_DOLT_1(types.Format_Default) {
// TODO (dhruv): unskip when resolve command is implemented
t.Skip()
}
dEnv := dtu.CreateTestEnv()
setupTest(t, ctx, dEnv, test.setup)
@@ -284,10 +280,6 @@ func TestKeylessMergeConflicts(t *testing.T) {
assertKeylessRows(t, ctx, tbl, test.oursExpected)
})
t.Run(test.name+"_resolved_theirs", func(t *testing.T) {
if types.IsFormat_DOLT_1(types.Format_Default) {
// TODO (dhruv): unskip when resolve command is implemented
t.Skip()
}
dEnv := dtu.CreateTestEnv()
setupTest(t, ctx, dEnv, test.setup)
-302
View File
@@ -1,302 +0,0 @@
// Copyright 2019 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 merge
import (
"context"
"fmt"
"github.com/dolthub/dolt/go/libraries/doltcore/conflict"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/row"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/table"
"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
"github.com/dolthub/dolt/go/libraries/utils/set"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/types"
)
type AutoResolver func(key types.Value, conflict conflict.Conflict) (types.Value, error)
func Ours(key types.Value, cnf conflict.Conflict) (types.Value, error) {
return cnf.Value, nil
}
func Theirs(key types.Value, cnf conflict.Conflict) (types.Value, error) {
return cnf.MergeValue, nil
}
func ResolveTable(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, tblName string, root *doltdb.RootValue, autoResFunc AutoResolver, opts editor.Options) (*doltdb.RootValue, error) {
tbl, ok, err := root.GetTable(ctx, tblName)
if err != nil {
return nil, err
}
if !ok {
return nil, doltdb.ErrTableNotFound
}
if has, err := tbl.HasConflicts(ctx); err != nil {
return nil, err
} else if !has {
return root, nil
}
tblSch, err := tbl.GetSchema(ctx)
if err != nil {
return nil, err
}
if schema.IsKeyless(tblSch) {
tbl, err = resolveKeylessTable(ctx, tbl, autoResFunc)
} else {
tbl, err = resolvePkTable(ctx, tbl, tblName, opts, autoResFunc)
}
if err != nil {
return nil, err
}
schemas, _, err := tbl.GetConflicts(ctx)
if err != nil {
return nil, err
}
confIdx, err := durable.NewEmptyConflictIndex(ctx, vrw, ns, schemas.Schema, schemas.MergeSchema, schemas.Base)
if err != nil {
return nil, err
}
tbl, err = tbl.SetConflicts(ctx, schemas, confIdx)
if err != nil {
return nil, err
}
numRowsInConflict, err := tbl.NumRowsInConflict(ctx)
if err != nil {
return nil, err
}
if numRowsInConflict == 0 {
tbl, err = tbl.ClearConflicts(ctx)
if err != nil {
return nil, err
}
}
newRoot, err := root.PutTable(ctx, tblName, tbl)
if err != nil {
return nil, err
}
err = validateConstraintViolations(ctx, root, newRoot, tblName)
if err != nil {
return nil, err
}
return newRoot, nil
}
func resolvePkTable(ctx context.Context, tbl *doltdb.Table, tblName string, opts editor.Options, auto AutoResolver) (*doltdb.Table, error) {
tblSch, err := tbl.GetSchema(ctx)
if err != nil {
return nil, err
}
_, confIdx, err := tbl.GetConflicts(ctx)
if err != nil {
return nil, err
}
tableEditor, err := editor.NewTableEditor(ctx, tbl, tblSch, tblName, opts)
if err != nil {
return nil, err
}
if confIdx.Format() == types.Format_DOLT_1 {
panic("resolvePkTable not implemented for new storage format")
}
conflicts := durable.NomsMapFromConflictIndex(confIdx)
err = conflicts.Iter(ctx, func(key, value types.Value) (stop bool, err error) {
cnf, err := conflict.ConflictFromTuple(value.(types.Tuple))
if err != nil {
return false, err
}
updated, err := auto(key, cnf)
if err != nil {
return false, err
}
if types.IsNull(updated) {
originalRow, err := row.FromNoms(tblSch, key.(types.Tuple), cnf.Base.(types.Tuple))
if err != nil {
return false, err
}
err = tableEditor.DeleteRow(ctx, originalRow)
if err != nil {
return false, err
}
} else {
updatedRow, err := row.FromNoms(tblSch, key.(types.Tuple), updated.(types.Tuple))
if err != nil {
return false, err
}
if isValid, err := row.IsValid(updatedRow, tblSch); err != nil {
return false, err
} else if !isValid {
return false, table.NewBadRow(updatedRow, "error resolving conflicts", fmt.Sprintf("row with primary key %v in table %s does not match constraints or types of the table's schema.", key, tblName))
}
if types.IsNull(cnf.Value) {
err = tableEditor.InsertRow(ctx, updatedRow, nil)
if err != nil {
return false, err
}
} else {
originalRow, err := row.FromNoms(tblSch, key.(types.Tuple), cnf.Value.(types.Tuple))
if err != nil {
return false, err
}
err = tableEditor.UpdateRow(ctx, originalRow, updatedRow, nil)
if err != nil {
return false, err
}
}
}
return false, nil
})
if err != nil {
return nil, err
}
return tableEditor.Table(ctx)
}
func resolveKeylessTable(ctx context.Context, tbl *doltdb.Table, auto AutoResolver) (*doltdb.Table, error) {
_, confIdx, err := tbl.GetConflicts(ctx)
if err != nil {
return nil, err
}
if confIdx.Format() == types.Format_DOLT_1 {
panic("resolvePkTable not implemented for new storage format")
}
conflicts := durable.NomsMapFromConflictIndex(confIdx)
rowData, err := tbl.GetNomsRowData(ctx)
if err != nil {
return nil, err
}
edit := rowData.Edit()
err = conflicts.Iter(ctx, func(key, value types.Value) (stop bool, err error) {
cnf, err := conflict.ConflictFromTuple(value.(types.Tuple))
if err != nil {
return false, err
}
resolved, err := auto(key, cnf)
if err != nil {
return false, err
}
if types.IsNull(resolved) {
edit.Remove(key)
} else {
edit.Set(key, resolved)
}
return false, nil
})
if err != nil {
return nil, err
}
rowData, err = edit.Map(ctx)
if err != nil {
return nil, err
}
return tbl.UpdateNomsRows(ctx, rowData)
}
func validateConstraintViolations(ctx context.Context, before, after *doltdb.RootValue, table string) error {
tables, err := after.GetTableNames(ctx)
if err != nil {
return err
}
// TODO: fix resolve in the new storage format
_, violators, err := AddForeignKeyViolations(ctx, after, before, set.NewStrSet(tables), hash.Of(nil))
if err != nil {
return err
}
if violators.Size() > 0 {
return fmt.Errorf("resolving conflicts for table %s created foreign key violations", table)
}
return nil
}
type AutoResolveStats struct {
}
func AutoResolveAll(ctx context.Context, dEnv *env.DoltEnv, autoResolver AutoResolver) error {
root, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
tbls, err := root.TablesInConflict(ctx)
if err != nil {
return err
}
return autoResolve(ctx, dEnv, root, autoResolver, tbls)
}
func AutoResolveTables(ctx context.Context, dEnv *env.DoltEnv, autoResolver AutoResolver, tbls []string) error {
root, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
return autoResolve(ctx, dEnv, root, autoResolver, tbls)
}
func autoResolve(ctx context.Context, dEnv *env.DoltEnv, root *doltdb.RootValue, autoResolver AutoResolver, tbls []string) error {
var err error
opts := editor.Options{Deaf: dEnv.DbEaFactory(), Tempdir: dEnv.TempTableFilesDir()}
for _, tblName := range tbls {
root, err = ResolveTable(ctx, root.VRW(), root.NodeStore(), tblName, root, autoResolver, opts)
if err != nil {
return err
}
}
return dEnv.UpdateWorkingRoot(ctx, root)
}
@@ -104,4 +104,7 @@ const (
const (
DoltConflictsOurDiffTypeTag = iota + SystemTableReservedMin + uint64(7000)
DoltConflictsTheirDiffTypeTag
DoltConflictsBaseCardinalityTag
DoltConflictsOurCardinalityTag
DoltConflictsTheirCardinalityTag
)
@@ -65,6 +65,7 @@ func newNomsConflictsTable(ctx *sql.Context, tbl *doltdb.Table, tblName string,
}
var _ sql.Table = ConflictsTable{}
var _ sql.DeletableTable = ConflictsTable{}
// ConflictsTable is a sql.Table implementation that provides access to the conflicts that exist for a user table
type ConflictsTable struct {
@@ -102,7 +103,12 @@ func (ct ConflictsTable) Partitions(ctx *sql.Context) (sql.PartitionIter, error)
// PartitionRows returns a RowIter for the given partition
func (ct ConflictsTable) PartitionRows(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
return conflictRowIter{ct.rd}, nil
// conflict reader must be reset each time partitionRows is called.
rd, err := merge.NewConflictReader(ctx, ct.tbl)
if err != nil {
return nil, err
}
return conflictRowIter{rd}, nil
}
// Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted,
@@ -111,6 +111,7 @@ type prollyConflictRowIter struct {
ns tree.NodeStore
ourRows prolly.Map
keyless bool
ourSch schema.Schema
kd val.TupleDesc
baseVD, oursVD, theirsVD val.TupleDesc
@@ -154,7 +155,7 @@ func newProllyConflictRowIter(ctx *sql.Context, ct ProllyConflictsTable) (*proll
} else {
o = b + baseVD.Count() - 1
t = o + oursVD.Count()
n = t + theirsVD.Count()
n = t + theirsVD.Count() + 3
}
return &prollyConflictRowIter{
@@ -164,6 +165,7 @@ func newProllyConflictRowIter(ctx *sql.Context, ct ProllyConflictsTable) (*proll
ns: ct.tbl.NodeStore(),
ourRows: ourRows,
keyless: keyless,
ourSch: ct.ourSch,
kd: kd,
baseVD: baseVD,
oursVD: oursVD,
@@ -262,38 +264,65 @@ func getDiffType(base val.Tuple, other val.Tuple) string {
return merge.ConflictDiffTypeModified
}
func (itr *prollyConflictRowIter) putKeylessConflictRowVals(ctx *sql.Context, c conf, r sql.Row) error {
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 = index.GetField(ctx, itr.baseVD, 0, c.bV, ns)
if err != nil {
return err
}
for i := 0; i < itr.baseVD.Count()-1; i++ {
f, err := index.GetField(ctx, itr.baseVD, i+1, c.bV, itr.baseRows.NodeStore())
f, err := index.GetField(ctx, itr.baseVD, i+1, c.bV, ns)
if err != nil {
return err
}
r[itr.b+i] = f
}
} else {
r[itr.n-3] = uint64(0)
}
if c.oV != nil {
r[itr.n-2], err = index.GetField(ctx, itr.oursVD, 0, c.oV, ns)
if err != nil {
return err
}
for i := 0; i < itr.oursVD.Count()-1; i++ {
f, err := index.GetField(ctx, itr.oursVD, i+1, c.oV, itr.baseRows.NodeStore())
f, err := index.GetField(ctx, itr.oursVD, i+1, c.oV, ns)
if err != nil {
return err
}
r[itr.o+i] = f
}
} else {
r[itr.n-2] = uint64(0)
}
r[itr.o+itr.oursVD.Count()-1] = getDiffType(c.bV, c.oV)
if c.tV != nil {
r[itr.n-1], err = index.GetField(ctx, itr.theirsVD, 0, c.tV, ns)
if err != nil {
return err
}
for i := 0; i < itr.theirsVD.Count()-1; i++ {
f, err := index.GetField(ctx, itr.theirsVD, i+1, c.tV, itr.baseRows.NodeStore())
f, err := index.GetField(ctx, itr.theirsVD, i+1, c.tV, ns)
if err != nil {
return err
}
r[itr.t+i] = f
}
} else {
r[itr.n-1] = uint64(0)
}
r[itr.t+itr.theirsVD.Count()-1] = getDiffType(c.bV, c.tV)
o := itr.t + itr.theirsVD.Count() - 1
r[o] = getDiffType(c.bV, c.tV)
return nil
}
@@ -353,14 +382,18 @@ func (itr *prollyConflictRowIter) loadTableMaps(ctx context.Context, baseHash, t
if err != nil {
return err
}
var idx durable.Index
if !ok {
return fmt.Errorf("failed to find table %s in base root value", itr.tblName)
idx, err = durable.NewEmptyIndex(ctx, itr.vrw, itr.ns, itr.ourSch)
} else {
idx, err = baseTbl.GetRowData(ctx)
}
idx, err := baseTbl.GetRowData(ctx)
if err != nil {
return err
}
itr.baseRows = durable.ProllyMapFromIndex(idx)
itr.baseHash = baseHash
}
@@ -394,54 +427,54 @@ func (itr *prollyConflictRowIter) Close(ctx *sql.Context) error {
}
type prollyConflictDeleter struct {
kd val.TupleDesc
kB *val.TupleBuilder
pool pool.BuffPool
ed prolly.ArtifactsEditor
ct ProllyConflictsTable
rs RootSetter
baseSch, ourSch, theirSch schema.Schema
kd, vd val.TupleDesc
kB, vB *val.TupleBuilder
pool pool.BuffPool
ed prolly.ArtifactsEditor
ct ProllyConflictsTable
rs RootSetter
ourDiffTypeIdx int
baseColSize int
ourColSize int
}
func newProllyConflictDeleter(ct ProllyConflictsTable) *prollyConflictDeleter {
kd, _ := ct.artM.Descriptors()
ed := ct.artM.Editor()
kB := val.NewTupleBuilder(kd)
vd := shim.ValueDescriptorFromSchema(ct.ourSch)
vB := val.NewTupleBuilder(vd)
p := ct.artM.Pool()
baseColSize := ct.baseSch.GetAllCols().Size()
ourColSize := ct.ourSch.GetAllCols().Size()
// root_ish, base_cols..., our_cols, our_diff_type
ourDiffTypeIdx := 1 + baseColSize + ourColSize
return &prollyConflictDeleter{
kd: kd,
kB: kB,
pool: p,
ed: ed,
ct: ct,
kd: kd,
vd: vd,
kB: kB,
vB: vB,
pool: p,
ed: ed,
ct: ct,
ourDiffTypeIdx: ourDiffTypeIdx,
baseColSize: baseColSize,
ourColSize: ourColSize,
}
}
func (cd *prollyConflictDeleter) Delete(ctx *sql.Context, r sql.Row) error {
if schema.IsKeyless(cd.ct.ourSch) {
panic("conflict deleter for keyless tables not implemented")
}
// get keys from either base, ours, or theirs
o := func() int {
if o := 1; r[o] != nil {
return o
} else if o = 1 + cd.kd.Count(); r[o] != nil {
return o
} else if o = 1 + cd.kd.Count()*2; r[o] != nil {
return o
} else {
panic("neither base, ours, or theirs had a key")
}
}()
func (cd *prollyConflictDeleter) Delete(ctx *sql.Context, r sql.Row) (err error) {
// first part of the artifact key is the keys of the source table
for i := 0; i < cd.kd.Count()-2; i++ {
err := index.PutField(ctx, cd.ed.Mut.NodeStore(), cd.kB, i, r[o+i])
if err != nil {
return err
}
if !schema.IsKeyless(cd.ct.ourSch) {
err = cd.putPrimaryKeys(ctx, r)
} else {
err = cd.putKeylessHash(ctx, r)
}
if err != nil {
return err
}
// then the hash follows. It is the first column of the row and the second to last in the key
@@ -452,7 +485,7 @@ func (cd *prollyConflictDeleter) Delete(ctx *sql.Context, r sql.Row) error {
cd.kB.PutUint8(cd.kd.Count()-1, uint8(prolly.ArtifactTypeConflict))
key := cd.kB.Build(cd.pool)
err := cd.ed.Delete(ctx, key)
err = cd.ed.Delete(ctx, key)
if err != nil {
return err
}
@@ -460,6 +493,56 @@ func (cd *prollyConflictDeleter) Delete(ctx *sql.Context, r sql.Row) error {
return nil
}
func (cd *prollyConflictDeleter) putPrimaryKeys(ctx *sql.Context, r sql.Row) error {
// get keys from either base, ours, or theirs
o := func() int {
if o := 1; r[o] != nil {
return o
} else if o = 1 + cd.kd.Count() - 2 + cd.vd.Count(); r[o] != nil {
return o
} else if o = 1 + (cd.kd.Count()-2+cd.vd.Count())*2 + 1; r[o] != nil {
return o
} else {
panic("neither base, ours, or theirs had a key")
}
}()
for i := 0; i < cd.kd.Count()-2; i++ {
err := index.PutField(ctx, cd.ed.Mut.NodeStore(), cd.kB, i, r[o+i])
if err != nil {
return err
}
}
return nil
}
func (cd *prollyConflictDeleter) putKeylessHash(ctx *sql.Context, r sql.Row) error {
var rowVals sql.Row
if r[cd.ourDiffTypeIdx] == merge.ConflictDiffTypeAdded {
// use our cols
rowVals = r[1+cd.baseColSize : 1+cd.baseColSize+cd.ourColSize]
} else {
// use base cols
rowVals = r[1 : 1+cd.baseColSize]
}
// init cardinality to 0
cd.vB.PutUint64(0, 0)
for i, v := range rowVals {
err := index.PutField(ctx, cd.ed.Mut.NodeStore(), cd.vB, i+1, v)
if err != nil {
return err
}
}
v := cd.vB.Build(cd.pool)
k := val.HashTupleFromValue(cd.pool, v)
cd.kB.PutHash128(0, k.GetField(0))
return nil
}
// StatementBegin implements the interface sql.TableEditor. Currently a no-op.
func (cd *prollyConflictDeleter) StatementBegin(ctx *sql.Context) {}
@@ -498,7 +581,13 @@ func (cd *prollyConflictDeleter) Close(ctx *sql.Context) error {
}
func CalculateConflictSchema(base, ours, theirs schema.Schema) (schema.Schema, error) {
cols := make([]schema.Column, 3+ours.GetAllCols().Size()+theirs.GetAllCols().Size()+base.GetAllCols().Size())
keyless := schema.IsKeyless(ours)
n := 3 + ours.GetAllCols().Size() + theirs.GetAllCols().Size() + base.GetAllCols().Size()
if keyless {
n += 3
}
cols := make([]schema.Column, n)
// the commit hash or working set hash of the right side during merge
cols[0] = schema.NewColumn("from_root_ish", 0, types.StringKind, false)
@@ -544,6 +633,16 @@ func CalculateConflictSchema(base, ours, theirs schema.Schema) (schema.Schema, e
return nil, err
}
cols[i] = schema.NewColumn("their_diff_type", uint64(i), types.StringKind, false)
i++
if keyless {
cols[i] = schema.NewColumn("base_cardinality", uint64(i), types.UintKind, false)
i++
cols[i] = schema.NewColumn("our_cardinality", uint64(i), types.UintKind, false)
i++
cols[i] = schema.NewColumn("their_cardinality", uint64(i), types.UintKind, false)
i++
}
return schema.UnkeyedSchemaFromCols(schema.NewColCollection(cols...)), nil
}
@@ -741,7 +741,7 @@ func TestDoltMerge(t *testing.T) {
}
func TestDoltConflictsTableNameTable(t *testing.T) {
for _, script := range DoltConflictDiffTypeScripts {
for _, script := range DoltConflictTableNameTableTests {
enginetest.TestScript(t, newDoltHarness(t), script)
}
}
@@ -1549,6 +1549,81 @@ var MergeScripts = []queries.ScriptTest{
},
},
},
{
Name: "insert two tables with the same name and different schema",
SetUpScript: []string{
"SET dolt_allow_commit_conflicts = on;",
"CALL DOLT_CHECKOUT('-b', 'other');",
"CREATE TABLE t (pk int PRIMARY key, col1 int, extracol int);",
"INSERT into t VALUES (1, 1, 1);",
"CALL DOLT_COMMIT('-am', 'right');",
"CALL DOLT_CHECKOUT('main');",
"CREATE TABLE t (pk int PRIMARY key, col1 int);",
"INSERT into t VALUES (2, 2);",
"CALL DOLT_COMMIT('-am', 'left');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL DOLT_MERGE('other');",
ExpectedErrStr: "table with same name added in 2 commits can't be merged",
},
},
},
{
Name: "insert two tables with the same name and schema that don't conflict",
SetUpScript: []string{
"SET dolt_allow_commit_conflicts = on;",
"CALL DOLT_CHECKOUT('-b', 'other');",
"CREATE TABLE t (pk int PRIMARY key, col1 int);",
"INSERT into t VALUES (1, 1);",
"CALL DOLT_COMMIT('-am', 'right');",
"CALL DOLT_CHECKOUT('main');",
"CREATE TABLE t (pk int PRIMARY key, col1 int);",
"INSERT into t VALUES (2, 2);",
"CALL DOLT_COMMIT('-am', 'left');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL DOLT_MERGE('other');",
Expected: []sql.Row{{0, 0}},
},
{
Query: "SELECT * from t;",
Expected: []sql.Row{{1, 1}, {2, 2}},
},
},
},
{
Name: "insert two tables with the same name and schema that conflict",
SetUpScript: []string{
"SET dolt_allow_commit_conflicts = on;",
"CALL DOLT_CHECKOUT('-b', 'other');",
"CREATE TABLE t (pk int PRIMARY key, col1 int);",
"INSERT into t VALUES (1, -1);",
"CALL DOLT_COMMIT('-am', 'right');",
"CALL DOLT_CHECKOUT('main');",
"CREATE TABLE t (pk int PRIMARY key, col1 int);",
"INSERT into t VALUES (1, 1);",
"CALL DOLT_COMMIT('-am', 'left');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL DOLT_MERGE('other');",
Expected: []sql.Row{{0, 1}},
},
{
Query: "SELECT base_pk, base_col1, our_pk, our_col1, their_pk, their_col1 from dolt_conflicts_t;",
Expected: []sql.Row{{nil, nil, 1, 1, 1, -1}},
},
{
Query: "SELECT * from t;",
Expected: []sql.Row{{1, 1}},
},
},
},
}
var KeylessMergeCVsAndConflictsScripts = []queries.ScriptTest{
@@ -1646,7 +1721,7 @@ var KeylessMergeCVsAndConflictsScripts = []queries.ScriptTest{
},
}
var DoltConflictDiffTypeScripts = []queries.ScriptTest{
var DoltConflictTableNameTableTests = []queries.ScriptTest{
{
Name: "conflict diff types",
SetUpScript: []string{
@@ -1688,6 +1763,50 @@ var DoltConflictDiffTypeScripts = []queries.ScriptTest{
},
},
},
{
Name: "keyless cardinality columns",
SetUpScript: []string{
"SET dolt_allow_commit_conflicts = on;",
"CREATE table t (col1 int);",
"INSERT INTO t VALUES (1), (2), (3), (4), (6);",
"CALL DOLT_COMMIT('-am', 'init');",
"CALL DOLT_CHECKOUT('-b', 'right');",
"INSERT INTO t VALUES (1);",
"DELETE FROM t where col1 = 2;",
"INSERT INTO t VALUES (3);",
"INSERT INTO t VALUES (4), (4);",
"INSERT INTO t VALUES (5);",
"DELETE from t where col1 = 6;",
"CALL DOLT_COMMIT('-am', 'right');",
"CALL DOLT_CHECKOUT('main');",
"DELETE FROM t WHERE col1 = 1;",
"INSERT INTO t VALUES (2);",
"INSERT INTO t VALUES (3);",
"INSERT INTO t VALUES (4);",
"INSERT INTO t VALUES (5);",
"DELETE from t where col1 = 6;",
"CALL DOLT_COMMIT('-am', 'left');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL DOLT_MERGE('right');",
Expected: []sql.Row{{0, 1}},
},
{
Query: "SELECT base_col1, our_col1, their_col1, our_diff_type, their_diff_type, base_cardinality, our_cardinality, their_cardinality from dolt_conflicts_t ORDER BY COALESCE(base_col1, our_col1, their_col1) ASC;",
Expected: []sql.Row{
{1, nil, 1, "removed", "modified", uint64(1), uint64(0), uint64(2)},
{2, 2, nil, "modified", "removed", uint64(1), uint64(2), uint64(0)},
{3, 3, 3, "modified", "modified", uint64(1), uint64(2), uint64(2)},
{4, 4, 4, "modified", "modified", uint64(1), uint64(2), uint64(3)},
{nil, 5, 5, "added", "added", uint64(0), uint64(1), uint64(1)},
{6, nil, nil, "removed", "removed", uint64(1), uint64(0), uint64(0)},
},
},
},
},
}
// MergeArtifactsScripts tests new format merge behavior where
@@ -1799,10 +1918,12 @@ var MergeArtifactsScripts = []queries.ScriptTest{
"CALL DOLT_COMMIT('-am', 'create table');",
"INSERT INTO t VALUES (1, 1);",
"CALL DOLT_COMMIT('-am', 'insert pk 1');",
"CALL DOLT_BRANCH('other');",
"UPDATE t set col1 = 100 where pk = 1;",
"CALL DOLT_COMMIT('-am', 'left edit');",
"CALL DOLT_CHECKOUT('other');",
"UPDATE T set col1 = -100 where pk = 1;",
"CALL DOLT_COMMIT('-am', 'right edit');",
"CALL DOLT_CHECKOUT('main');",
+4 -5
View File
@@ -26,7 +26,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdocs"
"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/row"
@@ -774,7 +773,7 @@ func TestRenameTableStatements(t *testing.T) {
}
func TestAlterSystemTables(t *testing.T) {
systemTableNames := []string{"dolt_docs", "dolt_log", "dolt_history_people", "dolt_diff_people", "dolt_commit_diff_people"}
systemTableNames := []string{"dolt_log", "dolt_history_people", "dolt_diff_people", "dolt_commit_diff_people"} // "dolt_docs",
reservedTableNames := []string{"dolt_schemas", "dolt_query_catalog"}
var dEnv *env.DoltEnv
@@ -782,9 +781,9 @@ func TestAlterSystemTables(t *testing.T) {
dEnv = dtestutils.CreateTestEnv()
CreateTestDatabase(dEnv, t)
dtestutils.CreateTestTable(t, dEnv, "dolt_docs",
doltdocs.DocsSchema,
NewRow(types.String("LICENSE.md"), types.String("A license")))
//dtestutils.CreateTestTable(t, dEnv, "dolt_docs",
// doltdocs.DocsSchema,
// NewRow(types.String("LICENSE.md"), types.String("A license")))
dtestutils.CreateTestTable(t, dEnv, doltdb.DoltQueryCatalogTableName,
dtables.DoltQueryCatalogSchema,
NewRow(types.String("abc123"), types.Uint(1), types.String("example"), types.String("select 2+2 from dual"), types.String("description")))
+8 -9
View File
@@ -23,7 +23,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdocs"
"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtables"
@@ -190,14 +189,14 @@ func TestExecuteDeleteSystemTables(t *testing.T) {
}
var systemTableDeleteTests = []DeleteTest{
{
Name: "delete dolt_docs",
AdditionalSetup: CreateTableFn("dolt_docs",
doltdocs.DocsSchema,
NewRow(types.String("LICENSE.md"), types.String("A license"))),
DeleteQuery: "delete from dolt_docs",
ExpectedErr: "cannot delete from table",
},
//{
// Name: "delete dolt_docs",
// AdditionalSetup: CreateTableFn("dolt_docs",
// doltdocs.DocsSchema,
// NewRow(types.String("LICENSE.md"), types.String("A license"))),
// DeleteQuery: "delete from dolt_docs",
// ExpectedErr: "cannot delete from table",
//},
{
Name: "delete dolt_query_catalog",
AdditionalSetup: CreateTableFn(doltdb.DoltQueryCatalogTableName,
+11 -1
View File
@@ -351,7 +351,8 @@ func SqlRowAsTupleString(r sql.Row, tableSch schema.Schema) (string, error) {
return b.String(), nil
}
func SqlRowAsDeleteStmt(r sql.Row, tableName string, tableSch schema.Schema) (string, error) {
// SqlRowAsDeleteStmt generates a sql statement. Non-zero |limit| adds a limit clause.
func SqlRowAsDeleteStmt(r sql.Row, tableName string, tableSch schema.Schema, limit uint64) (string, error) {
var b strings.Builder
b.WriteString("DELETE FROM ")
b.WriteString(QuoteIdentifier(tableName))
@@ -383,6 +384,15 @@ func SqlRowAsDeleteStmt(r sql.Row, tableName string, tableSch schema.Schema) (st
return "", err
}
if limit != 0 {
b.WriteString(" LIMIT ")
s, err := interfaceValueAsSqlString(typeinfo.FromKind(types.UintKind), limit)
if err != nil {
return "", err
}
b.WriteString(s)
}
b.WriteString(";")
return b.String(), nil
}
+8 -9
View File
@@ -24,7 +24,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdocs"
"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtables"
@@ -377,14 +376,14 @@ func TestExecuteInsert(t *testing.T) {
}
var systemTableInsertTests = []InsertTest{
{
Name: "insert into dolt_docs",
AdditionalSetup: CreateTableFn("dolt_docs",
doltdocs.DocsSchema,
NewRow(types.String("LICENSE.md"), types.String("A license"))),
InsertQuery: "insert into dolt_docs (doc_name, doc_text) values ('README.md', 'Some text')",
ExpectedErr: "cannot insert into table",
},
//{
// Name: "insert into dolt_docs",
// AdditionalSetup: CreateTableFn("dolt_docs",
// doltdocs.DocsSchema,
// NewRow(types.String("LICENSE.md"), types.String("A license"))),
// InsertQuery: "insert into dolt_docs (doc_name, doc_text) values ('README.md', 'Some text')",
// ExpectedErr: "cannot insert into table",
//},
{
Name: "insert into dolt_query_catalog",
AdditionalSetup: CreateTableFn(doltdb.DoltQueryCatalogTableName,
+15 -18
View File
@@ -24,7 +24,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdocs"
"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtables"
@@ -252,14 +251,14 @@ func TestExecuteReplace(t *testing.T) {
}
var systemTableReplaceTests = []ReplaceTest{
{
Name: "replace into dolt_docs",
AdditionalSetup: CreateTableFn("dolt_docs",
doltdocs.DocsSchema,
NewRow(types.String("LICENSE.md"), types.String("A license"))),
ReplaceQuery: "replace into dolt_docs (doc_name, doc_text) values ('README.md', 'Some text')",
ExpectedErr: "cannot insert into table",
},
//{
// Name: "replace into dolt_docs",
// AdditionalSetup: CreateTableFn("dolt_docs",
// doltdocs.DocsSchema,
// NewRow(types.String("LICENSE.md"), types.String("A license"))),
// ReplaceQuery: "replace into dolt_docs (doc_name, doc_text) values ('README.md', 'Some text')",
// ExpectedErr: "cannot insert into table",
//},
{
Name: "replace into dolt_query_catalog",
AdditionalSetup: CreateTableFn(doltdb.DoltQueryCatalogTableName,
@@ -267,7 +266,7 @@ var systemTableReplaceTests = []ReplaceTest{
NewRow(types.String("existingEntry"), types.Uint(1), types.String("example"), types.String("select 2+2 from dual"), types.String("description"))),
ReplaceQuery: "replace into dolt_query_catalog (id, display_order, name, query, description) values ('existingEntry', 1, 'example', 'select 1+1 from dual', 'description')",
SelectQuery: "select * from dolt_query_catalog",
ExpectedRows: ToSqlRows(dtables.DoltQueryCatalogSchema,
ExpectedRows: ToSqlRows(CompressSchema(dtables.DoltQueryCatalogSchema),
NewRow(types.String("existingEntry"), types.Uint(1), types.String("example"), types.String("select 1+1 from dual"), types.String("description")),
),
ExpectedSchema: CompressSchema(dtables.DoltQueryCatalogSchema),
@@ -276,20 +275,18 @@ var systemTableReplaceTests = []ReplaceTest{
Name: "replace into dolt_schemas",
AdditionalSetup: CreateTableFn(doltdb.SchemasTableName,
SchemasTableSchema(),
NewRowWithPks([]types.Value{types.String("view"), types.String("name")}, types.String("select 2+2 from dual"))),
ReplaceQuery: "replace into dolt_schemas (type, name, fragment) values ('view', 'name', 'select 1+1 from dual')",
SelectQuery: "select * from dolt_schemas",
ExpectedRows: ToSqlRows(SchemasTableSchema(),
NewRow(types.String("view"), types.String("name"), types.String("select 1+1 from dual")),
),
NewRowWithSchema(SchemasTableSchema(), types.String("view"), types.String("name"), types.String("select 2+2 from dual"), types.Int(1), types.NullValue)),
ReplaceQuery: "replace into dolt_schemas (id, type, name, fragment) values ('1', 'view', 'name', 'select 1+1 from dual')",
SelectQuery: "select type, name, fragment, id, extra from dolt_schemas",
ExpectedRows: []sql.Row{{"view", "name", "select 1+1 from dual", int64(1), nil}},
ExpectedSchema: CompressSchema(SchemasTableSchema()),
},
}
func TestReplaceIntoSystemTables(t *testing.T) {
for _, test := range systemTableInsertTests {
for _, test := range systemTableReplaceTests {
t.Run(test.Name, func(t *testing.T) {
testInsertQuery(t, test)
testReplaceQuery(t, test)
})
}
}
@@ -67,7 +67,7 @@ func (w SqlDiffWriter) WriteRow(
return iohelp.WriteLine(w.writeCloser, stmt)
case diff.Removed:
stmt, err := sqlfmt.SqlRowAsDeleteStmt(row, w.tableName, w.sch)
stmt, err := sqlfmt.SqlRowAsDeleteStmt(row, w.tableName, w.sch, 0)
if err != nil {
return err
}
+30
View File
@@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"io"
"strings"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/pool"
@@ -574,3 +575,32 @@ func calcArtifactsDescriptors(srcKd val.TupleDesc) (kd, vd val.TupleDesc) {
return val.NewTupleDescriptor(keyTypes...), val.NewTupleDescriptor(valTypes...)
}
func ArtifactDebugFormat(ctx context.Context, m ArtifactMap) (string, error) {
kd, vd := m.Descriptors()
iter, err := m.tuples.iterAll(ctx)
if err != nil {
return "", err
}
c := m.Count()
var sb strings.Builder
sb.WriteString(fmt.Sprintf("Artifact Map (count: %d) {\n", c))
for {
k, v, err := iter.Next(ctx)
if err == io.EOF {
break
}
if err != nil {
return "", err
}
sb.WriteString("\t")
sb.WriteString(kd.Format(k))
sb.WriteString(": ")
sb.WriteString(vd.Format(v))
sb.WriteString(",\n")
}
sb.WriteString("}")
return sb.String(), nil
}
-3
View File
@@ -389,7 +389,6 @@ teardown() {
}
@test "1pk5col-ints: generate a merge conflict and resolve with ours" {
skip_nbf_dolt_1
dolt add test
dolt commit -m "added test table"
dolt branch test-branch
@@ -434,7 +433,6 @@ teardown() {
}
@test "1pk5col-ints: generate a merge conflict and try to roll back using dolt merge --abort" {
skip_nbf_dolt_1
dolt add test
dolt commit -m "added test table"
dolt branch test-branch
@@ -466,7 +464,6 @@ teardown() {
}
@test "1pk5col-ints: generate a merge conflict and resolve with theirs" {
skip_nbf_dolt_1
dolt add test
dolt commit -m "added test table"
dolt branch test-branch
@@ -382,7 +382,6 @@ SQL
}
@test "conflict-detection-2: two branches, one deletes rows, one modifies those same rows. merge. conflict" {
skip_nbf_dolt_1
dolt sql <<SQL
CREATE TABLE foo (
pk INT PRIMARY KEY,
@@ -415,11 +414,6 @@ SQL
dolt checkout -b merge-into-modified modifier
dolt merge deleter
# Test resolve nonexistant key
run dolt conflicts resolve foo 999
[ "$status" -eq 1 ]
[[ "$output" =~ "no conflicts resolved" ]] || false
dolt conflicts resolve --theirs foo
run dolt sql -q 'select count(*) from foo'
[ "$status" -eq 0 ]
@@ -501,7 +495,6 @@ SQL
}
@test "conflict-detection-2: conflicts table properly cleared on dolt conflicts resolve" {
skip_nbf_dolt_1
dolt sql -q "create table test(pk int, c1 int, primary key(pk))"
run dolt conflicts cat test
+4
View File
@@ -650,6 +650,7 @@ SQL
}
@test "docs: dolt table commands do not allow write operations on dolt_docs" {
skip "temporarily allow operations on docs"
echo "a readme" > README.md
echo "a license" > LICENSE.md
dolt add .
@@ -670,6 +671,7 @@ SQL
}
@test "docs: dolt schema command does not show dolt_docs" {
skip "temporarily allow operations on docs"
echo "a readme" > README.md
echo "a license" > LICENSE.md
dolt add .
@@ -707,6 +709,8 @@ SQL
}
@test "docs: dolt sql operation on dolt_docs" {
skip "temporarily allow operations on docs"
echo "a readme" > README.md
echo "a license" > LICENSE.md
run dolt sql -q "show tables"
-2
View File
@@ -1259,8 +1259,6 @@ SQL
}
@test "foreign-keys: Resolve catches violations" {
skip_nbf_dolt_1 "resolve not implemented"
dolt sql <<SQL
ALTER TABLE child ADD CONSTRAINT fk_v1 FOREIGN KEY (v1) REFERENCES parent(v1);
INSERT INTO parent VALUES (0,0,0);
+4 -1
View File
@@ -2297,7 +2297,10 @@ SQL
dolt commit -m "other changes"
dolt checkout main
dolt merge other
dolt conflicts resolve onepk 4
run dolt sql <<SQL
SET dolt_allow_commit_conflicts = on;
DELETE from dolt_conflicts_onepk where our_pk1 = 4;
SQL
dolt sql <<SQL
set autocommit = off;
UPDATE onepk SET v1 = -11, v2 = 11 WHERE pk1 = 1;
-7
View File
@@ -404,7 +404,6 @@ SQL
}
@test "keyless: merge duplicate deletes" {
skip_nbf_dolt_1 "conflicts resolve not implemented"
make_dupe_table
@@ -455,7 +454,6 @@ SQL
}
@test "keyless: merge duplicate updates" {
skip_nbf_dolt_1 "conflicts resolve not implemented"
make_dupe_table
@@ -624,7 +622,6 @@ CSV
}
@test "keyless: merge with in-place updates (branches)" {
skip_nbf_dolt_1 "conflicts resolve not implemented"
dolt sql -q "INSERT INTO keyless VALUES (7,7),(8,8),(9,9);"
dolt commit -am "added rows"
@@ -660,7 +657,6 @@ CSV
}
@test "keyless: diff branches with reordered mutation history" {
skip_nbf_dolt_1 "keyless diff not implemented"
dolt branch other
@@ -721,7 +717,6 @@ SQL
}
@test "keyless: merge branches with convergent mutation history" {
skip_nbf_dolt_1 "conflicts resolve not implemented"
dolt branch other
dolt sql -q "INSERT INTO keyless VALUES (7,7),(8,8),(9,9);"
@@ -767,7 +762,6 @@ SQL
}
@test "keyless: merge branches with offset mutation history" {
skip_nbf_dolt_1 "conflicts resolve not implemented"
dolt branch other
dolt sql -q "INSERT INTO keyless VALUES (7,7),(8,8),(9,9);"
@@ -827,7 +821,6 @@ SQL
}
@test "keyless: merge delete+add on two branches" {
skip_nbf_dolt_1 "conflicts resolve not implemented"
dolt branch left
dolt checkout -b right
-1
View File
@@ -347,7 +347,6 @@ SQL
}
@test "merge: Add views on two branches, merge" {
skip_nbf_dolt_1
dolt branch other
dolt sql -q "CREATE VIEW pkpk AS SELECT pk*pk FROM test1;"
dolt add . && dolt commit -m "added view on table test1"
-1
View File
@@ -983,7 +983,6 @@ SQL
}
@test "remotes: generate a merge with a conflict with a remote branch" {
skip_nbf_dolt_1 "uses dolt conflicts resolve"
dolt remote add test-remote http://localhost:50051/test-org/test-repo
dolt sql <<SQL
CREATE TABLE test (