Merge branch 'main' into james/alter

This commit is contained in:
James Cor
2023-04-27 16:46:10 -07:00
59 changed files with 1184 additions and 541 deletions
+16 -15
View File
@@ -50,21 +50,6 @@ jobs:
env:
BRANCH_NAME: ${{ github.head_ref }}
CHANGE_TARGET: ${{ github.base_ref }}
- name: Check generated protobufs
working-directory: ./proto
run: |
(cd third_party/protobuf && bazel build //:protoc)
(cd third_party/protobuf-go && go build -o ._protoc-gen-go ./cmd/protoc-gen-go)
(cd third_party/grpc-go/cmd/protoc-gen-go-grpc && go build -o ._protoc-gen-go-grpc .)
make clean all
changes=$(git status --porcelain)
diff=$(git diff)
if [ ! -z "$changes" ]; then
echo "ERROR: Generated protobuf structs are different from the checked in version."
echo "$changes"
echo "$diff"
exit 1
fi
- name: Check generated flatbuffers
working-directory: ./go/serial
run: |
@@ -121,6 +106,7 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.ref || github.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
submodules: true
token: ${{ secrets.GITHUB_TOKEN }}
- name: Run go mod tidy
run: go mod tidy
@@ -168,3 +154,18 @@ jobs:
add: "."
cwd: "."
pull: "--ff"
- name: Check generated protobufs
working-directory: ./proto
run: |
(cd third_party/protobuf && bazel build //:protoc)
(cd third_party/protobuf-go && go build -o ._protoc-gen-go ./cmd/protoc-gen-go)
(cd third_party/grpc-go/cmd/protoc-gen-go-grpc && go build -o ._protoc-gen-go-grpc .)
make clean all
changes=$(git status --porcelain)
diff=$(git diff)
if [ ! -z "$changes" ]; then
echo "ERROR: Generated protobuf structs are different from the checked in version."
echo "$changes"
echo "$diff"
exit 1
fi
+1
View File
@@ -116,6 +116,7 @@ const (
CachedFlag = "cached"
ListFlag = "list"
UserParam = "user"
NoPrettyFlag = "no-pretty"
)
const (
+4
View File
@@ -14,6 +14,10 @@
package cli
import "github.com/dolthub/dolt/go/libraries/utils/argparser"
// CliContexct is used to pass top level command information down to subcommands.
type CliContext interface {
// GlobalArgs returns the arguments passed before the subcommand.
GlobalArgs() *argparser.ArgParseResults
}
+6 -6
View File
@@ -32,7 +32,7 @@ import (
"github.com/dolthub/dolt/go/store/types"
)
func isHelp(str string) bool {
func IsHelp(str string) bool {
str = strings.TrimSpace(str)
if len(str) == 0 {
@@ -50,7 +50,7 @@ func isHelp(str string) bool {
func hasHelpFlag(args []string) bool {
for _, arg := range args {
if isHelp(arg) {
if IsHelp(arg) {
return true
}
}
@@ -171,7 +171,7 @@ func (hc SubCommandHandler) Hidden() bool {
func (hc SubCommandHandler) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx CliContext) int {
if len(args) < 1 && hc.Unspecified == nil {
hc.printUsage(commandStr)
hc.PrintUsage(commandStr)
return 1
}
@@ -190,12 +190,12 @@ func (hc SubCommandHandler) Exec(ctx context.Context, commandStr string, args []
return hc.handleCommand(ctx, commandStr, hc.Unspecified, args, dEnv, cliCtx)
}
if !isHelp(subCommandStr) {
if !IsHelp(subCommandStr) {
PrintErrln(color.RedString("Unknown Command " + subCommandStr))
return 1
}
hc.printUsage(commandStr)
hc.PrintUsage(commandStr)
return 0
}
@@ -302,7 +302,7 @@ func CheckUserNameAndEmail(dEnv *env.DoltEnv) bool {
return true
}
func (hc SubCommandHandler) printUsage(commandStr string) {
func (hc SubCommandHandler) PrintUsage(commandStr string) {
Println("Valid commands for", commandStr, "are")
for _, cmd := range hc.Subcommands {
+1 -1
View File
@@ -90,5 +90,5 @@ func (cmd BlameCmd) Exec(ctx context.Context, commandStr string, args []string,
}
args = []string{"--" + QueryFlag, fmt.Sprintf(blameQueryTemplate, apr.Arg(0))}
return SqlCmd{}.Exec(ctx, "sql", args, dEnv, nil)
return SqlCmd{}.Exec(ctx, "sql", args, dEnv, cliCtx)
}
+121 -94
View File
@@ -16,6 +16,7 @@ package cnfcmds
import (
"context"
"errors"
"fmt"
"io"
"strings"
@@ -23,7 +24,6 @@ import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/commands"
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1"
@@ -39,9 +39,9 @@ import (
var catDocs = cli.CommandDocumentationContent{
ShortDesc: "print conflicts",
LongDesc: `The dolt conflicts cat command reads table conflicts and writes them to the standard output.`,
LongDesc: `The dolt conflicts cat command reads table conflicts from the working set and writes them to the standard output.`,
Synopsis: []string{
"[{{.LessThan}}commit{{.GreaterThan}}] {{.LessThan}}table{{.GreaterThan}}...",
"{{.LessThan}}table{{.GreaterThan}}...",
},
}
@@ -88,41 +88,26 @@ func (cmd CatCmd) Exec(ctx context.Context, commandStr string, args []string, dE
return 1
}
root, verr := commands.GetWorkingWithVErr(dEnv)
if verr != nil {
return exitWithVerr(verr)
ws, err := dEnv.WorkingSet(ctx)
if err != nil {
return exitWithVerr(errhand.VerboseErrorFromError(err))
}
cm, verr := commands.MaybeGetCommitWithVErr(dEnv, args[0])
if verr != nil {
return exitWithVerr(verr)
}
// If no commit was resolved from the first argument, assume the args are all table names and print the conflicts
if cm == nil {
if verr := printConflicts(ctx, dEnv, root, args); verr != nil {
return exitWithVerr(verr)
}
return 0
}
tblNames := args[1:]
tblNames := args
if len(tblNames) == 0 {
cli.Println("No tables specified")
usage()
return 1
} else if len(tblNames) == 1 && tblNames[0] == "." {
tblNames, err = ws.WorkingRoot().GetTableNames(ctx)
if err != nil {
return exitWithVerr(errhand.VerboseErrorFromError(err))
}
}
root, err := cm.GetRootValue(ctx)
if err != nil {
return exitWithVerr(errhand.BuildDError("unable to get the root value").AddCause(err).Build())
if verr := printConflicts(ctx, dEnv, ws, tblNames); verr != nil {
return exitWithVerr(errhand.VerboseErrorFromError(err))
}
if verr = printConflicts(ctx, dEnv, root, tblNames); verr != nil {
return exitWithVerr(verr)
}
return 0
}
@@ -131,79 +116,116 @@ func exitWithVerr(verr errhand.VerboseError) int {
return 1
}
func printConflicts(ctx context.Context, dEnv *env.DoltEnv, root *doltdb.RootValue, tblNames []string) errhand.VerboseError {
if len(tblNames) == 1 && tblNames[0] == "." {
var err error
tblNames, err = root.GetTableNames(ctx)
if err != nil {
return errhand.BuildDError("unable to read tables").AddCause(err).Build()
}
}
func printConflicts(ctx context.Context, dEnv *env.DoltEnv, ws *doltdb.WorkingSet, tblNames []string) error {
eng, dbName, err := engine.NewSqlEngineForEnv(ctx, dEnv)
if err != nil {
return errhand.VerboseErrorFromError(err)
return err
}
stdOut := iohelp.NopWrCloser(cli.CliOut)
for _, tblName := range tblNames {
verr := func() errhand.VerboseError {
if has, err := root.HasTable(ctx, tblName); err != nil {
return errhand.BuildDError("error: unable to read database").AddCause(err).Build()
} else if !has {
return errhand.BuildDError("error: unknown table '%s'", tblName).Build()
}
// first print schema conflicts
if ws.MergeActive() && ws.MergeState().HasSchemaConflicts() {
sqlCtx, err := eng.NewLocalContext(ctx)
if err != nil {
return err
}
sqlCtx.SetCurrentDatabase(dbName)
tbl, _, err := root.GetTable(ctx, tblName)
if err != nil {
return errhand.BuildDError("error: unable to read database").AddCause(err).Build()
for _, table := range tblNames {
if err = printSchemaConflicts(sqlCtx, stdOut, eng, table); err != nil {
return err
}
has, err := tbl.HasConflicts(ctx)
if err != nil {
return errhand.BuildDError("error: unable to read database").AddCause(err).Build()
}
if !has {
return nil
}
baseSch, sch, mergeSch, err := tbl.GetConflictSchemas(ctx, tblName)
if err != nil {
return errhand.BuildDError("failed to fetch conflicts").AddCause(err).Build()
}
unionSch, err := untyped.UntypedSchemaUnion(baseSch, sch, mergeSch)
if err != nil {
return errhand.BuildDError("failed to fetch conflicts").AddCause(err).Build()
}
sqlUnionSch, err := sqlutil.FromDoltSchema(tblName, unionSch)
if err != nil {
return errhand.BuildDError("failed to fetch conflicts").AddCause(err).Build()
}
sqlCtx, err := eng.NewLocalContext(ctx)
if err != nil {
return errhand.BuildDError("failed to fetch conflicts").AddCause(err).Build()
}
sqlCtx.SetCurrentDatabase(dbName)
confSqlSch, rowItr, err := eng.Query(sqlCtx, buildConflictQuery(baseSch, sch, mergeSch, tblName))
if err != nil {
return errhand.BuildDError("failed to fetch conflicts").AddCause(err).Build()
}
tw := tabular.NewFixedWidthConflictTableWriter(sqlUnionSch.Schema, iohelp.NopWrCloser(cli.CliOut), 100)
err = writeConflictResults(sqlCtx, confSqlSch, sqlUnionSch.Schema, rowItr, tw)
if err != nil {
return errhand.BuildDError("failed to print conflicts").AddCause(err).Build()
}
return nil
}()
if verr != nil {
return verr
}
}
// next print data conflicts
root := ws.WorkingRoot()
for _, tblName := range tblNames {
if has, err := root.HasTable(ctx, tblName); err != nil {
return err
} else if !has {
return fmt.Errorf("error: unknown table '%s'", tblName)
}
tbl, _, err := root.GetTable(ctx, tblName)
if err != nil {
return err
}
has, err := tbl.HasConflicts(ctx)
if err != nil {
return err
} else if !has {
continue
}
base, sch, mergeSch, err := tbl.GetConflictSchemas(ctx, tblName)
if err != nil {
return err
}
sqlCtx, err := eng.NewLocalContext(ctx)
if err != nil {
return errhand.BuildDError("failed to fetch conflicts").AddCause(err).Build()
}
sqlCtx.SetCurrentDatabase(dbName)
confSqlSch, rowItr, err := eng.Query(sqlCtx, buildDataConflictQuery(base, sch, mergeSch, tblName))
if err != nil {
return err
}
unionSch, err := untyped.UntypedSchemaUnion(base, sch, mergeSch)
if err != nil {
return err
}
sqlUnionSch, err := sqlutil.FromDoltSchema(tblName, unionSch)
if err != nil {
return err
}
tw := tabular.NewFixedWidthConflictTableWriter(sqlUnionSch.Schema, stdOut, 100)
err = writeConflictResults(sqlCtx, confSqlSch, sqlUnionSch.Schema, rowItr, tw)
if err != nil {
return err
}
}
return nil
}
func printSchemaConflicts(sqlCtx *sql.Context, wrCloser io.WriteCloser, eng *engine.SqlEngine, table string) error {
sqlSch, rowItr, err := eng.Query(sqlCtx, buildSchemaConflictQuery(table))
if err != nil {
return err
}
defer func() {
if cerr := rowItr.Close(sqlCtx); err == nil {
err = cerr
}
}()
tw := tabular.NewFixedWidthTableWriter(sqlSch, wrCloser, 100)
defer func() {
if cerr := tw.Close(sqlCtx); err == nil {
err = cerr
}
}()
for {
r, err := rowItr.Next(sqlCtx)
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return err
}
if err = tw.WriteSqlRow(sqlCtx, r); err != nil {
return err
}
}
return nil
}
@@ -241,7 +263,12 @@ func writeConflictResults(
}
}
func buildConflictQuery(base, sch, mergeSch schema.Schema, tblName string) string {
func buildSchemaConflictQuery(table string) string {
return fmt.Sprintf("select our_schema, their_schema, base_schema, description "+
"from dolt_schema_conflicts where table_name = '%s'", table)
}
func buildDataConflictQuery(base, sch, mergeSch schema.Schema, tblName string) string {
cols := quoteWithPrefix(base.GetAllCols().GetColumnNames(), "base_")
cols = append(cols, quoteWithPrefix(sch.GetAllCols().GetColumnNames(), "our_")...)
cols = append(cols, quoteWithPrefix(mergeSch.GetAllCols().GetColumnNames(), "their_")...)
+108 -16
View File
@@ -17,9 +17,10 @@ package commands
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
@@ -27,11 +28,16 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/util/outputpager"
)
var hashRegex = regexp.MustCompile(`^#?[0-9a-v]{32}$`)
type showOpts struct {
showParents bool
pretty bool
decoration string
specRefs []string
@@ -73,6 +79,7 @@ func (cmd ShowCmd) ArgParser() *argparser.ArgParser {
// Flags inherited from Log
ap.SupportsFlag(cli.ParentsFlag, "", "Shows all parents of each commit in the log.")
ap.SupportsString(cli.DecorateFlag, "", "decorate_fmt", "Shows refs next to commits. Valid options are short, full, no, and auto")
ap.SupportsFlag(cli.NoPrettyFlag, "", "Show the object without making it pretty.")
// Flags inherited from Diff
ap.SupportsFlag(DataFlag, "d", "Show only the data changes, do not show the schema changes (Both shown by default).")
@@ -100,9 +107,18 @@ func (cmd ShowCmd) Exec(ctx context.Context, commandStr string, args []string, d
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
if err := cmd.validateArgs(apr); err != nil {
return handleErrAndExit(err)
}
if !opts.pretty && !dEnv.DoltDB.Format().UsesFlatbuffers() {
cli.PrintErrln("dolt show --no-pretty is not supported when using old LD_1 storage format.")
return 1
}
opts.diffDisplaySettings = parseDiffDisplaySettings(ctx, dEnv, apr)
err = showCommits(ctx, dEnv, opts)
err = showObjects(ctx, dEnv, opts)
return handleErrAndExit(err)
}
@@ -135,25 +151,19 @@ func parseShowArgs(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPar
return &showOpts{
showParents: apr.Contains(cli.ParentsFlag),
pretty: !apr.Contains(cli.NoPrettyFlag),
decoration: decorateOption,
specRefs: apr.Args,
}, nil
}
func showCommits(ctx context.Context, dEnv *env.DoltEnv, opts *showOpts) error {
func showObjects(ctx context.Context, dEnv *env.DoltEnv, opts *showOpts) error {
if len(opts.specRefs) == 0 {
return showCommit(ctx, dEnv, opts, dEnv.RepoStateReader().CWBHeadSpec())
return showCommitSpec(ctx, dEnv, opts, dEnv.RepoStateReader().CWBHeadSpec())
}
for _, specRef := range opts.specRefs {
commitSpec, err := getCommitSpec(specRef)
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: invalid commit spec %s", specRef))
return err
}
err = showCommit(ctx, dEnv, opts, commitSpec)
err := showSpecRef(ctx, dEnv, opts, specRef)
if err != nil {
return err
}
@@ -162,14 +172,96 @@ func showCommits(ctx context.Context, dEnv *env.DoltEnv, opts *showOpts) error {
return nil
}
func showCommit(ctx context.Context, dEnv *env.DoltEnv, opts *showOpts, commitSpec *doltdb.CommitSpec) error {
// parseHashString converts a string representing a hash into a hash.Hash.
func parseHashString(hashStr string) (hash.Hash, error) {
unprefixed := strings.TrimPrefix(hashStr, "#")
parsedHash, ok := hash.MaybeParse(unprefixed)
if !ok {
return hash.Hash{}, errors.New("invalid hash: " + hashStr)
}
return parsedHash, nil
}
comm, err := dEnv.DoltDB.Resolve(ctx, commitSpec, dEnv.RepoStateReader().CWBHeadRef())
func showSpecRef(ctx context.Context, dEnv *env.DoltEnv, opts *showOpts, specRef string) error {
roots, err := dEnv.Roots(ctx)
if err != nil {
cli.PrintErrln(color.HiRedString("Fatal error: cannot resolve commit spec."))
return err
}
upperCaseSpecRef := strings.ToUpper(specRef)
if upperCaseSpecRef == doltdb.Working || upperCaseSpecRef == doltdb.Staged || hashRegex.MatchString(specRef) {
var refHash hash.Hash
var err error
if upperCaseSpecRef == doltdb.Working {
refHash, err = roots.Working.HashOf()
} else if upperCaseSpecRef == doltdb.Staged {
refHash, err = roots.Staged.HashOf()
} else {
refHash, err = parseHashString(specRef)
}
if err != nil {
return err
}
value, err := dEnv.DoltDB.ValueReadWriter().ReadValue(ctx, refHash)
if err != nil {
return err
}
if value == nil {
return fmt.Errorf("Unable to resolve object ref %s", specRef)
}
if !opts.pretty {
cli.Println(value.Kind(), value.HumanReadableString())
}
// If this is a commit, use the pretty printer. To determine whether it's a commit, try calling NewCommitFromValue.
commit, err := doltdb.NewCommitFromValue(ctx, dEnv.DoltDB.ValueReadWriter(), dEnv.DoltDB.NodeStore(), value)
if err == datas.ErrNotACommit {
if !dEnv.DoltDB.Format().UsesFlatbuffers() {
return fmt.Errorf("dolt show cannot show non-commit objects when using the old LD_1 storage format: %s is not a commit", specRef)
}
cli.Println(value.Kind(), value.HumanReadableString())
} else if err == nil {
showCommit(ctx, dEnv, opts, commit)
} else {
return err
}
} else { // specRef is a CommitSpec, which must resolve to a Commit.
commitSpec, err := getCommitSpec(specRef)
if err != nil {
return err
}
err = showCommitSpec(ctx, dEnv, opts, commitSpec)
if err != nil {
return err
}
}
return nil
}
func showCommitSpec(ctx context.Context, dEnv *env.DoltEnv, opts *showOpts, commitSpec *doltdb.CommitSpec) error {
commit, err := dEnv.DoltDB.Resolve(ctx, commitSpec, dEnv.RepoStateReader().CWBHeadRef())
if err != nil {
return err
}
if opts.pretty {
err = showCommit(ctx, dEnv, opts, commit)
if err != nil {
return err
}
} else {
value := commit.Value()
cli.Println(value.Kind(), value.HumanReadableString())
}
return nil
}
func showCommit(ctx context.Context, dEnv *env.DoltEnv, opts *showOpts, comm *doltdb.Commit) error {
cHashToRefs, err := getHashToRefs(ctx, dEnv, opts.decoration)
if err != nil {
return err
@@ -214,7 +306,7 @@ func showCommit(ctx context.Context, dEnv *env.DoltEnv, opts *showOpts, commitSp
}
if comm.NumParents() > 1 {
return fmt.Errorf("Requested commit is a merge commit. 'dolt show' currently only supports viewing non-merge commits.")
return fmt.Errorf("requested commit is a merge commit. 'dolt show' currently only supports viewing non-merge commits")
}
commitRoot, err := comm.GetRootValue(ctx)
+48 -32
View File
@@ -189,6 +189,12 @@ func (cmd SqlCmd) Exec(ctx context.Context, commandStr string, args []string, dE
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
globalArgs := cliCtx.GlobalArgs()
err = validateSqlArgs(globalArgs)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
// We need a username and password for many SQL commands, so set defaults if they don't exist
dEnv.Config.SetFailsafes(env.DefaultFailsafeConfig)
@@ -198,24 +204,33 @@ func (cmd SqlCmd) Exec(ctx context.Context, commandStr string, args []string, dE
username = user
}
mrEnv, verr := getMultiRepoEnv(ctx, apr, dEnv)
// data-dir args come either from the global args or the subcommand args. We need to check both.
var dataDir string
dataDirGiven := false
if multiDbDir, ok := apr.GetValue(MultiDBDirFlag); ok {
// When GlobalArgs migration is complete, drop this flag.
dataDir = multiDbDir
dataDirGiven = true
} else if dataDirPath, ok := apr.GetValue(DataDirFlag); ok {
// TODO: remove this once we remove the deprecated passing of data dir directly to subcommand.
dataDir = dataDirPath
dataDirGiven = true
} else if dataDirPath, ok := globalArgs.GetValue(DataDirFlag); ok {
dataDir = dataDirPath
dataDirGiven = true
}
mrEnv, dataDir, verr := getMultiRepoEnv(ctx, dataDir, dEnv)
if verr != nil {
return HandleVErrAndExitCode(verr, usage)
}
// need to return cfgdirpath and error
var cfgDirPath string
var dataDir string
if multiDbDir, ok := apr.GetValue(MultiDBDirFlag); ok {
dataDir = multiDbDir
} else if dataDirPath, ok := apr.GetValue(DataDirFlag); ok {
dataDir = dataDirPath
}
cfgDir, cfgDirSpecified := apr.GetValue(CfgDirFlag)
if cfgDirSpecified {
cfgDirPath = cfgDir
} else if len(dataDir) != 0 {
} else if dataDirGiven {
cfgDirPath = filepath.Join(dataDir, DefaultCfgDirName)
} else {
// Look in parent directory for doltcfg
@@ -267,10 +282,20 @@ func (cmd SqlCmd) Exec(ctx context.Context, commandStr string, args []string, dE
}
}
se, sqlCtx, err := newEngine(ctx, apr, cfgDirPath, privsFp, branchControlFilePath, username, mrEnv)
format := engine.FormatTabular
if formatSr, ok := apr.GetValue(FormatFlag); ok {
var verr errhand.VerboseError
format, verr = GetResultFormat(formatSr)
if verr != nil {
return HandleVErrAndExitCode(verr, usage)
}
}
se, sqlCtx, err := newEngine(ctx, format, cfgDirPath, privsFp, branchControlFilePath, username, mrEnv)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
defer se.Close()
if query, queryOK := apr.GetValue(QueryFlag); queryOK {
@@ -338,7 +363,7 @@ func (cmd SqlCmd) Exec(ctx context.Context, commandStr string, args []string, dE
func newEngine(
ctx context.Context,
apr *argparser.ArgParseResults,
format engine.PrintResultFormat,
cfgDirPath string,
privsFp string,
branchControlFilePath string,
@@ -346,15 +371,6 @@ func newEngine(
mrEnv *env.MultiRepoEnv,
) (*engine.SqlEngine, *sql.Context, error) {
format := engine.FormatTabular
if formatSr, ok := apr.GetValue(FormatFlag); ok {
var verr errhand.VerboseError
format, verr = GetResultFormat(formatSr)
if verr != nil {
return nil, nil, verr
}
}
config := &engine.SqlEngineConfig{
DoltCfgDirPath: cfgDirPath,
PrivFilePath: privsFp,
@@ -501,26 +517,26 @@ func execSaveQuery(ctx *sql.Context, dEnv *env.DoltEnv, se *engine.SqlEngine, ap
}
// getMultiRepoEnv returns an appropriate MultiRepoEnv for this invocation of the command
func getMultiRepoEnv(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEnv) (*env.MultiRepoEnv, errhand.VerboseError) {
func getMultiRepoEnv(ctx context.Context, workingDir string, dEnv *env.DoltEnv) (mrEnv *env.MultiRepoEnv, resolvedDir string, verr errhand.VerboseError) {
var err error
fs := dEnv.FS
if dataDir, ok := apr.GetValue(MultiDBDirFlag); ok {
fs, err = fs.WithWorkingDir(dataDir)
} else if dataDir, ok := apr.GetValue(DataDirFlag); ok {
fs, err = fs.WithWorkingDir(dataDir)
if len(workingDir) > 0 {
fs, err = fs.WithWorkingDir(workingDir)
}
if err != nil {
return nil, errhand.VerboseErrorFromError(err)
return nil, "", errhand.VerboseErrorFromError(err)
}
mrEnv, err := env.MultiEnvForDirectory(ctx, dEnv.Config.WriteableConfig(), fs, dEnv.Version, dEnv.IgnoreLockFile, dEnv)
resolvedDir, err = fs.Abs("")
if err != nil {
return nil, errhand.VerboseErrorFromError(err)
return nil, "", errhand.VerboseErrorFromError(err)
}
return mrEnv, nil
mrEnv, err = env.MultiEnvForDirectory(ctx, dEnv.Config.WriteableConfig(), fs, dEnv.Version, dEnv.IgnoreLockFile, dEnv)
if err != nil {
return nil, "", errhand.VerboseErrorFromError(err)
}
return mrEnv, resolvedDir, nil
}
func execBatch(
+13 -11
View File
@@ -43,6 +43,8 @@ import (
var tableName = "people"
var stubCliCtx = BuildEmptyCliContext()
// Smoke test: Console opens and exits
func TestSqlConsole(t *testing.T) {
t.Run("SQL console opens and exits", func(t *testing.T) {
@@ -53,7 +55,7 @@ func TestSqlConsole(t *testing.T) {
args := []string{}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, 0, result)
})
@@ -81,7 +83,7 @@ func TestSqlBatchMode(t *testing.T) {
args := []string{"-b", "-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
})
}
@@ -120,7 +122,7 @@ func TestSqlSelect(t *testing.T) {
args := []string{"-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
})
}
@@ -146,7 +148,7 @@ func TestSqlShow(t *testing.T) {
args := []string{"-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
})
}
@@ -179,7 +181,7 @@ func TestCreateTable(t *testing.T) {
args := []string{"-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
working, err = dEnv.WorkingRoot(context.Background())
@@ -219,7 +221,7 @@ func TestShowTables(t *testing.T) {
args := []string{"-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
})
}
@@ -250,7 +252,7 @@ func TestAlterTable(t *testing.T) {
args := []string{"-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
})
}
@@ -277,7 +279,7 @@ func TestDropTable(t *testing.T) {
args := []string{"-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(context.TODO(), commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
})
}
@@ -396,7 +398,7 @@ func TestInsert(t *testing.T) {
args := []string{"-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(ctx, commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(ctx, commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
if result == 0 {
@@ -477,7 +479,7 @@ func TestUpdate(t *testing.T) {
args := []string{"-q", test.query}
commandStr := "dolt sql"
result := SqlCmd{}.Exec(ctx, commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(ctx, commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
if result == 0 {
@@ -552,7 +554,7 @@ func TestDelete(t *testing.T) {
ctx := context.Background()
commandStr := "dolt sql"
result := SqlCmd{}.Exec(ctx, commandStr, args, dEnv, nil)
result := SqlCmd{}.Exec(ctx, commandStr, args, dEnv, stubCliCtx)
assert.Equal(t, test.expectedRes, result)
if result == 0 {
+14 -14
View File
@@ -43,8 +43,8 @@ import (
const (
sqlClientDualFlag = "dual"
sqlClientQueryFlag = "query"
sqlClientUseDbFlag = "use-db"
SqlClientQueryFlag = "query"
SqlClientUseDbFlag = "use-db"
sqlClientResultFormat = "result-format"
)
@@ -84,10 +84,10 @@ func (cmd SqlClientCmd) Docs() *cli.CommandDocumentation {
func (cmd SqlClientCmd) ArgParser() *argparser.ArgParser {
ap := SqlServerCmd{}.ArgParserWithName(cmd.Name())
ap.SupportsFlag(sqlClientDualFlag, "d", "Causes this command to spawn a dolt server that is automatically connected to.")
ap.SupportsString(sqlClientQueryFlag, "q", "string", "Sends the given query to the server and immediately exits.")
ap.SupportsString(sqlClientUseDbFlag, "", "db_name", fmt.Sprintf("Selects the given database before executing a query. "+
"By default, uses the current folder's name. Must be used with the --%s flag.", sqlClientQueryFlag))
ap.SupportsString(sqlClientResultFormat, "", "format", fmt.Sprintf("Returns the results in the given format. Must be used with the --%s flag.", sqlClientQueryFlag))
ap.SupportsString(SqlClientQueryFlag, "q", "string", "Sends the given query to the server and immediately exits.")
ap.SupportsString(SqlClientUseDbFlag, "", "db_name", fmt.Sprintf("Selects the given database before executing a query. "+
"By default, uses the current folder's name. Must be used with the --%s flag.", SqlClientQueryFlag))
ap.SupportsString(sqlClientResultFormat, "", "format", fmt.Sprintf("Returns the results in the given format. Must be used with the --%s flag.", SqlClientQueryFlag))
return ap
}
@@ -123,12 +123,12 @@ func (cmd SqlClientCmd) Exec(ctx context.Context, commandStr string, args []stri
cli.PrintErrln(err.Error())
return 1
}
if apr.Contains(sqlClientQueryFlag) {
cli.PrintErrln(color.RedString(fmt.Sprintf("--%s flag may not be used with --%s", sqlClientDualFlag, sqlClientQueryFlag)))
if apr.Contains(SqlClientQueryFlag) {
cli.PrintErrln(color.RedString(fmt.Sprintf("--%s flag may not be used with --%s", sqlClientDualFlag, SqlClientQueryFlag)))
return 1
}
if apr.Contains(sqlClientUseDbFlag) {
cli.PrintErrln(color.RedString(fmt.Sprintf("--%s flag may not be used with --%s", sqlClientDualFlag, sqlClientUseDbFlag)))
if apr.Contains(SqlClientUseDbFlag) {
cli.PrintErrln(color.RedString(fmt.Sprintf("--%s flag may not be used with --%s", sqlClientDualFlag, SqlClientUseDbFlag)))
return 1
}
if apr.Contains(sqlClientResultFormat) {
@@ -167,14 +167,14 @@ func (cmd SqlClientCmd) Exec(ctx context.Context, commandStr string, args []stri
}
}
query, hasQuery := apr.GetValue(sqlClientQueryFlag)
dbToUse, hasUseDb := apr.GetValue(sqlClientUseDbFlag)
query, hasQuery := apr.GetValue(SqlClientQueryFlag)
dbToUse, hasUseDb := apr.GetValue(SqlClientUseDbFlag)
resultFormat, hasResultFormat := apr.GetValue(sqlClientResultFormat)
if !hasQuery && hasUseDb {
cli.PrintErrln(color.RedString(fmt.Sprintf("--%s may only be used with --%s", sqlClientUseDbFlag, sqlClientQueryFlag)))
cli.PrintErrln(color.RedString(fmt.Sprintf("--%s may only be used with --%s", SqlClientUseDbFlag, SqlClientQueryFlag)))
return 1
} else if !hasQuery && hasResultFormat {
cli.PrintErrln(color.RedString(fmt.Sprintf("--%s may only be used with --%s", sqlClientUseDbFlag, sqlClientResultFormat)))
cli.PrintErrln(color.RedString(fmt.Sprintf("--%s may only be used with --%s", SqlClientUseDbFlag, sqlClientResultFormat)))
return 1
}
if !hasUseDb && hasQuery {
+1 -1
View File
@@ -94,5 +94,5 @@ func (cmd CpCmd) Exec(ctx context.Context, commandStr string, args []string, dEn
fmt.Sprintf("--%s", commands.BatchFlag),
fmt.Sprintf(`--%s`, commands.QueryFlag),
queryStr,
}, dEnv, nil)
}, dEnv, cliCtx)
}
+1 -1
View File
@@ -95,5 +95,5 @@ func (cmd MvCmd) Exec(ctx context.Context, commandStr string, args []string, dEn
fmt.Sprintf("--%s", commands.BatchFlag),
fmt.Sprintf(`--%s`, commands.QueryFlag),
queryStr,
}, dEnv, nil)
}, dEnv, cliCtx)
}
+1 -1
View File
@@ -93,5 +93,5 @@ func (cmd RmCmd) Exec(ctx context.Context, commandStr string, args []string, dEn
fmt.Sprintf("--%s", commands.BatchFlag),
fmt.Sprintf(`--%s`, commands.QueryFlag),
queryStr,
}, dEnv, nil)
}, dEnv, cliCtx)
}
+17
View File
@@ -17,6 +17,9 @@ package commands
import (
"context"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
@@ -68,3 +71,17 @@ func MaybeGetCommitWithVErr(dEnv *env.DoltEnv, maybeCommit string) (*doltdb.Comm
return cm, nil
}
type cliCtx struct{}
func (c cliCtx) GlobalArgs() *argparser.ArgParseResults {
ap := argparser.NewArgParserWithMaxArgs("empty", 0)
apr, _ := ap.Parse(make([]string, 0))
return apr
}
var _ cli.CliContext = cliCtx{}
func BuildEmptyCliContext() cli.CliContext {
return cliCtx{}
}
+93 -3
View File
@@ -18,12 +18,14 @@ import (
"context"
crand "crypto/rand"
"encoding/binary"
"errors"
"math/rand"
"net/http"
_ "net/http/pprof"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/fatih/color"
@@ -52,6 +54,7 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dfunctions"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/libraries/events"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/libraries/utils/filesys"
"github.com/dolthub/dolt/go/store/nbs"
"github.com/dolthub/dolt/go/store/util/tempfiles"
@@ -63,7 +66,8 @@ const (
var dumpDocsCommand = &commands.DumpDocsCmd{}
var dumpZshCommand = &commands.GenZshCompCmd{}
var doltCommand = cli.NewSubCommandHandler("dolt", "it's git for data", []cli.Command{
var doltSubCommands = []cli.Command{
commands.InitCmd{},
commands.StatusCmd{},
commands.AddCmd{},
@@ -114,7 +118,10 @@ var doltCommand = cli.NewSubCommandHandler("dolt", "it's git for data", []cli.Co
docscmds.Commands,
stashcmds.StashCommands,
&commands.Assist{},
})
}
var doltCommand = cli.NewSubCommandHandler("dolt", "it's git for data", doltSubCommands)
var globalArgParser = buildGlobalArgs()
func init() {
dumpDocsCommand.DoltCommand = doltCommand
@@ -147,6 +154,11 @@ func main() {
func runMain() int {
args := os.Args[1:]
if len(args) == 0 {
doltCommand.PrintUsage("dolt")
return 1
}
if os.Getenv("DOLT_VERBOSE_ASSERT_TABLE_FILES_CLOSED") == "" {
nbs.TableIndexGCFinalizerWithStackTrace = false
}
@@ -401,9 +413,28 @@ func runMain() int {
cli.Printf("error: failed to load persisted global variables: %s\n", err.Error())
}
globalArgs, args, initCliContext, printUsage, err := splitArgsOnSubCommand(args)
if printUsage {
doltCommand.PrintUsage("dolt")
return 0
}
if err != nil {
cli.PrintErrln(color.RedString("Failure to parse arguments: %v", err))
return 1
}
start := time.Now()
ctx, stop := context.WithCancel(ctx)
res := doltCommand.Exec(ctx, "dolt", args, dEnv, nil)
var cliCtx cli.CliContext = nil
if initCliContext {
_, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString("dolt", doc, globalArgParser))
apr := cli.ParseArgsOrDie(globalArgParser, globalArgs, usage)
cliCtx = tmpCliContext{globalArgs: apr}
}
res := doltCommand.Exec(ctx, "dolt", args, dEnv, cliCtx)
stop()
if err = dbfactory.CloseAllLocalDatabases(); err != nil {
@@ -422,6 +453,56 @@ func runMain() int {
return res
}
// tmpCliContext is a temporary implementation of the CliContext interface. It is used to pass the global args to the
// subcommands, will be replaced with implementations aware of query contexts shortly.
type tmpCliContext struct {
globalArgs *argparser.ArgParseResults
}
func (t tmpCliContext) GlobalArgs() *argparser.ArgParseResults {
return t.globalArgs
}
var _ cli.CliContext = (*tmpCliContext)(nil)
// splitArgsOnSubCommand splits the args into two slices, the first containing all args before the first subcommand,
// and the second containing all args after the first subcommand. The second slice will start with the subcommand name.
func splitArgsOnSubCommand(args []string) (globalArgs, subArgs []string, initCliContext, printUsages bool, err error) {
commandSet := make(map[string]bool)
for _, cmd := range doltSubCommands {
commandSet[cmd.Name()] = true
}
for i, arg := range args {
arg = strings.ToLower(arg)
if cli.IsHelp(arg) {
// Found --help before any subcommand, so print dolt help.
return nil, nil, false, true, nil
}
if _, ok := commandSet[arg]; ok {
// SQL is the first subcommand to support the CLIContext. We'll need a more general solution when we add more.
// blame, table rm, and table mv commands also depend on the sql command, so they are also included here.
initCliContext := "sql" == arg || "blame" == arg || "table" == arg
return args[:i], args[i:], initCliContext, false, nil
}
}
return nil, nil, false, false, errors.New("No valid dolt subcommand found. See 'dolt --help' for usage.")
}
// doc is currently used only when a `initCliContext` command is specified. This will include all commands in time,
// otherwise you only see these docs if you specify a nonsense argument before the `sql` subcommand.
var doc = cli.CommandDocumentationContent{
ShortDesc: "Dolt is git for data",
LongDesc: `Dolt comprises of multiple subcommands that allow users to import, export, update, and manipulate data with SQL.`,
Synopsis: []string{
"<--data-dir=<path>> subcommand <subcommand arguments>",
},
}
func seedGlobalRand() {
bs := make([]byte, 8)
_, err := crand.Read(bs)
@@ -466,3 +547,12 @@ func interceptSendMetrics(ctx context.Context, args []string) (bool, int) {
dEnv := env.LoadWithoutDB(ctx, env.GetCurrentUserHomeDir, filesys.LocalFS, Version)
return true, doltCommand.Exec(ctx, "dolt", args, dEnv, nil)
}
func buildGlobalArgs() *argparser.ArgParser {
ap := argparser.NewArgParserWithVariableArgs("dolt")
// Pulling this argument forward first to pave the way. Others will follow.
ap.SupportsString(commands.DataDirFlag, "", "directory", "Defines a directory whose subdirectories should all be dolt data repositories accessible as independent databases within. Defaults to the current directory.")
return ap
}
@@ -23,12 +23,13 @@
package eventsapi
import (
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
durationpb "google.golang.org/protobuf/types/known/durationpb"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
@@ -24,6 +24,7 @@ package eventsapi
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
@@ -23,10 +23,11 @@
package eventsapi
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
@@ -21,11 +21,12 @@
package remotesapi
import (
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
@@ -22,6 +22,7 @@ package remotesapi
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
@@ -21,10 +21,11 @@
package remotesapi
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
@@ -22,6 +22,7 @@ package remotesapi
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
-4
View File
@@ -166,10 +166,6 @@ github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1G
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY=
github.com/dolthub/fslock v0.0.3 h1:iLMpUIvJKMKm92+N1fmHVdxJP5NdyDK5bK7z7Ba2s2U=
github.com/dolthub/fslock v0.0.3/go.mod h1:QWql+P17oAAMLnL4HGB5tiovtDuAjdDTPbuqx7bYfa0=
github.com/dolthub/go-mysql-server v0.15.1-0.20230419220841-24dded8c61ef h1:V0FbcJR0nYgeDG1iYhrfy7SxkZsaYqlV8BRfzSkp/2A=
github.com/dolthub/go-mysql-server v0.15.1-0.20230419220841-24dded8c61ef/go.mod h1:1/+9LW/gnZSVmXjc9DpEI+3thxjO/rSwasNw+9h3VvM=
github.com/dolthub/go-mysql-server v0.15.1-0.20230424210837-b0555760f17e h1:677Wo/DrEn72agIX5nDSXqZ+isyF+GpFrlCJ4DOWfj8=
github.com/dolthub/go-mysql-server v0.15.1-0.20230424210837-b0555760f17e/go.mod h1:B+vF4ambU4nWlkqEivILt0KJyvnIV22xFNWB9Xje948=
github.com/dolthub/go-mysql-server v0.15.1-0.20230424215657-62cb35ac61a2 h1:jKtEfBQlcYknZSRpjFfo0V0sqybrWoCQTdmoVkAVdGY=
github.com/dolthub/go-mysql-server v0.15.1-0.20230424215657-62cb35ac61a2/go.mod h1:B+vF4ambU4nWlkqEivILt0KJyvnIV22xFNWB9Xje948=
github.com/dolthub/go-mysql-server v0.15.1-0.20230427234254-064b85c642f0 h1:xqy9NK/hqWvL4Hg6lMhAkSSEpykQWbev3uTEbDO1m6M=
+16 -2
View File
@@ -19,10 +19,9 @@ import (
"errors"
"fmt"
"github.com/dolthub/dolt/go/store/prolly"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/prolly"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/types"
)
@@ -49,6 +48,7 @@ type Commit struct {
var _ Rootish = &Commit{}
// NewCommit generates a new Commit object that wraps a supplies datas.Commit.
func NewCommit(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, commit *datas.Commit) (*Commit, error) {
parents, err := datas.GetCommitParents(ctx, vrw, commit.NomsValue())
if err != nil {
@@ -57,11 +57,25 @@ func NewCommit(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore
return &Commit{vrw, ns, parents, commit}, nil
}
// NewCommitFromValue generates a new Commit object that wraps a supplied types.Value.
func NewCommitFromValue(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, value types.Value) (*Commit, error) {
commit, err := datas.CommitFromValue(vrw.Format(), value)
if err != nil {
return nil, err
}
return NewCommit(ctx, vrw, ns, commit)
}
// HashOf returns the hash of the commit
func (c *Commit) HashOf() (hash.Hash, error) {
return c.dCommit.Addr(), nil
}
// Value returns the types.Value that backs the commit.
func (c *Commit) Value() types.Value {
return c.dCommit.NomsValue()
}
// GetCommitMeta gets the metadata associated with the commit
func (c *Commit) GetCommitMeta(ctx context.Context) (*datas.CommitMeta, error) {
return datas.GetCommitMeta(ctx, c.dCommit.NomsValue())
+26 -25
View File
@@ -235,6 +235,7 @@ func (ddb *DoltDB) Close() error {
return ddb.db.Close()
}
// GetHashForRefStr resolves a ref string (such as a branch name or tag) and resolves it to a hash.Hash.
func (ddb *DoltDB) GetHashForRefStr(ctx context.Context, ref string) (*hash.Hash, error) {
if err := datas.ValidateDatasetId(ref); err != nil {
return nil, fmt.Errorf("invalid ref format: %s", ref)
@@ -275,15 +276,6 @@ func getCommitValForRefStr(ctx context.Context, ddb *DoltDB, ref string) (*datas
return datas.LoadCommitAddr(ctx, ddb.vrw, *commitHash)
}
func getCommitValForHash(ctx context.Context, vr types.ValueReader, c string) (*datas.Commit, error) {
unprefixed := strings.TrimPrefix(c, "#")
hash, ok := hash.MaybeParse(unprefixed)
if !ok {
return nil, errors.New("invalid hash: " + c)
}
return datas.LoadCommitAddr(ctx, vr, hash)
}
// Roots is a convenience struct to package up the three roots that most library functions will need to inspect and
// modify the working set. This struct is designed to be passed by value always: functions should take a Roots as a
// param and return a modified one.
@@ -300,18 +292,14 @@ type Roots struct {
Staged *RootValue
}
// Resolve takes a CommitSpec and returns a Commit, or an error if the commit cannot be found.
// If the CommitSpec is HEAD, Resolve also needs the DoltRef of the current working branch.
func (ddb *DoltDB) Resolve(ctx context.Context, cs *CommitSpec, cwb ref.DoltRef) (*Commit, error) {
if cs == nil {
panic("nil commit spec")
}
var commitVal *datas.Commit
var err error
func (ddb *DoltDB) getHashFromCommitSpec(ctx context.Context, cs *CommitSpec, cwb ref.DoltRef) (*hash.Hash, error) {
switch cs.csType {
case hashCommitSpec:
commitVal, err = getCommitValForHash(ctx, ddb.vrw, cs.baseSpec)
parsedHash, ok := hash.MaybeParse(cs.baseSpec)
if !ok {
return nil, errors.New("invalid hash: " + cs.baseSpec)
}
return &parsedHash, nil
case refCommitSpec:
// For a ref in a CommitSpec, we have the following behavior.
// If it starts with `refs/`, we look for an exact match before
@@ -334,30 +322,43 @@ func (ddb *DoltDB) Resolve(ctx context.Context, cs *CommitSpec, cwb ref.DoltRef)
}
}
for _, candidate := range candidates {
commitVal, err = getCommitValForRefStr(ctx, ddb, candidate)
valueHash, err := ddb.GetHashForRefStr(ctx, candidate)
if err == nil {
break
return valueHash, nil
}
if err != ErrBranchNotFound {
return nil, err
} else {
err = fmt.Errorf("%w: %s", ErrBranchNotFound, cs.baseSpec)
}
}
return nil, fmt.Errorf("%w: %s", ErrBranchNotFound, cs.baseSpec)
case headCommitSpec:
if cwb == nil {
return nil, fmt.Errorf("cannot use a nil current working branch with a HEAD commit spec")
}
commitVal, err = getCommitValForRefStr(ctx, ddb, cwb.String())
return ddb.GetHashForRefStr(ctx, cwb.String())
default:
panic("unrecognized commit spec csType: " + cs.csType)
}
}
// Resolve takes a CommitSpec and returns a Commit, or an error if the commit cannot be found.
// If the CommitSpec is HEAD, Resolve also needs the DoltRef of the current working branch.
func (ddb *DoltDB) Resolve(ctx context.Context, cs *CommitSpec, cwb ref.DoltRef) (*Commit, error) {
if cs == nil {
panic("nil commit spec")
}
hash, err := ddb.getHashFromCommitSpec(ctx, cs, cwb)
if err != nil {
return nil, err
}
commit, err := NewCommit(ctx, ddb.vrw, ddb.ns, commitVal)
commitValue, err := datas.LoadCommitAddr(ctx, ddb.vrw, *hash)
if err != nil {
return nil, err
}
commit, err := NewCommit(ctx, ddb.vrw, ddb.ns, commitValue)
if err != nil {
return nil, err
}
@@ -58,7 +58,7 @@ func (cmd fvCommand) exec(ctx context.Context, dEnv *env.DoltEnv) int {
// execute the command using |cmd.user|'s Feature Version
doltdb.DoltFeatureVersion = cmd.user.vers
defer func() { doltdb.DoltFeatureVersion = DoltFeatureVersionCopy }()
return cmd.cmd.Exec(ctx, cmd.cmd.Name(), cmd.args, dEnv, nil)
return cmd.cmd.Exec(ctx, cmd.cmd.Name(), cmd.args, dEnv, commands.BuildEmptyCliContext())
}
type fvUser struct {
@@ -37,6 +37,8 @@ func TestForeignKeys(t *testing.T) {
}
}
var fkCliCtx = commands.BuildEmptyCliContext()
func TestForeignKeyErrors(t *testing.T) {
skipNewFormat(t)
cmds := []testCommand{
@@ -49,13 +51,14 @@ func TestForeignKeyErrors(t *testing.T) {
dEnv := dtestutils.CreateTestEnv()
for _, c := range cmds {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, fkCliCtx)
require.Equal(t, 0, exitCode)
}
exitCode := commands.SqlCmd{}.Exec(ctx, commands.SqlCmd{}.Name(), []string{"-q", `ALTER TABLE test MODIFY v1 INT;`}, dEnv, nil)
exitCode := commands.SqlCmd{}.Exec(ctx, commands.SqlCmd{}.Name(), []string{"-q", `ALTER TABLE test MODIFY v1 INT;`}, dEnv, fkCliCtx)
require.Equal(t, 1, exitCode)
exitCode = commands.SqlCmd{}.Exec(ctx, commands.SqlCmd{}.Name(), []string{"-q", `ALTER TABLE test2 MODIFY v1 INT;`}, dEnv, nil)
exitCode = commands.SqlCmd{}.Exec(ctx, commands.SqlCmd{}.Name(), []string{"-q", `ALTER TABLE test2 MODIFY v1 INT;`}, dEnv, fkCliCtx)
require.Equal(t, 1, exitCode)
}
@@ -96,11 +99,11 @@ func testForeignKeys(t *testing.T, test foreignKeyTest) {
dEnv := dtestutils.CreateTestEnv()
for _, c := range fkSetupCommon {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, fkCliCtx)
require.Equal(t, 0, exitCode)
}
for _, c := range test.setup {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, fkCliCtx)
require.Equal(t, 0, exitCode)
}
+4 -2
View File
@@ -55,6 +55,8 @@ type gcTest struct {
postGCFunc func(ctx context.Context, t *testing.T, ddb *doltdb.DoltDB, prevRes interface{})
}
var gcCliCtx = commands.BuildEmptyCliContext()
var gcTests = []gcTest{
{
name: "gc test",
@@ -113,7 +115,7 @@ func testGarbageCollection(t *testing.T, test gcTest) {
defer dEnv.DoltDB.Close()
for _, c := range gcSetupCommon {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, gcCliCtx)
require.Equal(t, 0, exitCode)
}
@@ -121,7 +123,7 @@ func testGarbageCollection(t *testing.T, test gcTest) {
for _, stage := range test.stages {
res = stage.preStageFunc(ctx, t, dEnv.DoltDB, res)
for _, c := range stage.commands {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, gcCliCtx)
require.Equal(t, 0, exitCode)
}
}
+8 -4
View File
@@ -54,6 +54,7 @@ type MultiRepoEnv struct {
envs []NamedEnv
fs filesys.Filesys
cfg config.ReadWriteConfig
dialProvider dbfactory.GRPCDialProvider
ignoreLockFile bool
}
@@ -72,6 +73,7 @@ func MultiEnvForDirectory(
envs: make([]NamedEnv, 0),
fs: fs,
cfg: config,
dialProvider: NewGRPCDialProviderFromDoltEnv(dEnv),
ignoreLockFile: ignoreLockFile,
}
@@ -192,6 +194,11 @@ func MultiEnvForPaths(
} else if dEnv.CfgLoadErr != nil {
return nil, fmt.Errorf("error loading environment '%s' at path '%s': %s", name, absPath, dEnv.CfgLoadErr.Error())
}
if mrEnv.dialProvider == nil {
mrEnv.dialProvider = NewGRPCDialProviderFromDoltEnv(dEnv)
}
envSet[name] = dEnv
}
@@ -208,10 +215,7 @@ func (mrEnv *MultiRepoEnv) FileSystem() filesys.Filesys {
}
func (mrEnv *MultiRepoEnv) RemoteDialProvider() dbfactory.GRPCDialProvider {
for _, env := range mrEnv.envs {
return env.env
}
return NewGRPCDialProvider()
return mrEnv.dialProvider
}
func (mrEnv *MultiRepoEnv) Config() config.ReadWriteConfig {
@@ -116,7 +116,7 @@ func TestKeylessMerge(t *testing.T) {
require.NoError(t, err)
for _, c := range test.setup {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cmd.BuildEmptyCliContext())
require.Equal(t, 0, exitCode)
}
@@ -249,7 +249,7 @@ func TestKeylessMergeConflicts(t *testing.T) {
require.NoError(t, err)
for _, c := range cc {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cmd.BuildEmptyCliContext())
// allow merge to fail with conflicts
if _, ok := c.cmd.(cmd.MergeCmd); !ok {
require.Equal(t, 0, exitCode)
+76 -26
View File
@@ -38,18 +38,6 @@ const (
DeletedCheckCollision
)
// todo: link to docs explaining how to resolve schema conflicts.
func SchemaConflictErr(cc ...SchemaConflict) error {
var sb strings.Builder
sb.WriteString("merge aborted: schema conflict found for tables: \n")
for i := range cc {
sb.WriteRune('\t')
sb.WriteString(cc[i].TableName)
sb.WriteRune('\n')
}
return errors.New(sb.String())
}
type SchemaConflict struct {
TableName string
ColConflicts []ColConflict
@@ -63,21 +51,37 @@ func (sc SchemaConflict) Count() int {
return len(sc.ColConflicts) + len(sc.IdxConflicts) + len(sc.ChkConflicts)
}
// String implements fmt.Stringer. This method is used to
// display schema conflicts on schema conflict read paths.
func (sc SchemaConflict) String() string {
return strings.Join(sc.messages(), "\n")
}
// Error implements error. This error will be returned to the
// user if merge is configured to error upon schema conflicts.
// todo: link to docs explaining how to resolve schema conflicts.
func (sc SchemaConflict) Error() string {
template := "merge aborted: schema conflict found for table %s \n" +
" please resolve schema conflicts before merging: %s"
var b strings.Builder
b.WriteString("merge aborted: schema conflict found for table ")
b.WriteString(sc.TableName)
b.WriteString("\n please resolve schema conflicts before merging")
for _, m := range sc.messages() {
b.WriteString("\n\t")
b.WriteString(m)
}
return fmt.Sprintf(template, sc.TableName, b.String())
}
func (sc SchemaConflict) messages() (mm []string) {
for _, c := range sc.ColConflicts {
b.WriteString(fmt.Sprintf("\t%s\n", c.String()))
mm = append(mm, c.String())
}
for _, c := range sc.IdxConflicts {
b.WriteString(fmt.Sprintf("\t%s\n", c.String()))
mm = append(mm, c.String())
}
for _, c := range sc.ChkConflicts {
b.WriteString(fmt.Sprintf("\t%s\n", c.String()))
mm = append(mm, c.String())
}
return b.String()
return
}
type ColConflict struct {
@@ -88,7 +92,7 @@ type ColConflict struct {
func (c ColConflict) String() string {
switch c.Kind {
case NameCollision:
return fmt.Sprintf("two columns with the same name '%s' have different tags. See https://github.com/dolthub/dolt/issues/3963", c.Ours.Name)
return fmt.Sprintf("incompatible column types for column '%s': %s and %s", c.Ours.Name, c.Ours.TypeInfo, c.Theirs.TypeInfo)
case TagCollision:
return fmt.Sprintf("different column definitions for our column %s and their column %s", c.Ours.Name, c.Theirs.Name)
}
@@ -148,7 +152,7 @@ func SchemaMerge(ctx context.Context, format *types.NomsBinFormat, ourSch, their
}
var mergedCC *schema.ColCollection
mergedCC, sc.ColConflicts, err = mergeColumns(ourSch.GetAllCols(), theirSch.GetAllCols(), ancSch.GetAllCols())
mergedCC, sc.ColConflicts, err = mergeColumns(format, ourSch.GetAllCols(), theirSch.GetAllCols(), ancSch.GetAllCols())
if err != nil {
return nil, SchemaConflict{}, err
}
@@ -299,9 +303,11 @@ func ForeignKeysMerge(ctx context.Context, mergedRoot, ourRoot, theirRoot, ancRo
// mergeColumns merges the columns from |ourCC|, |theirCC| into a single column collection, using the ancestor column
// definitions in |ancCC| to determine on which side a column has changed. If merging is not possible because of
// conflicting changes to the columns in |ourCC| and |theirCC|, then a set of ColConflict instances are returned
// describing the conflicts. If any other, unexpected error occurs, then that error is returned and the other response
// fields should be ignored.
func mergeColumns(ourCC, theirCC, ancCC *schema.ColCollection) (*schema.ColCollection, []ColConflict, error) {
// describing the conflicts. |format| indicates what storage format is in use, and is needed to determine compatibility
// between types, since different storage formats have different restrictions on how much types can change and remain
// compatible with the current stored format. If any unexpected error occurs, then that error is returned and the
// other response fields should be ignored.
func mergeColumns(format *types.NomsBinFormat, ourCC, theirCC, ancCC *schema.ColCollection) (*schema.ColCollection, []ColConflict, error) {
columnMappings, err := mapColumns(ourCC, theirCC, ancCC)
if err != nil {
return nil, nil, err
@@ -340,9 +346,25 @@ func mergeColumns(ourCC, theirCC, ancCC *schema.ColCollection) (*schema.ColColle
if oursChanged && theirsChanged {
// This is a schema change conflict and has already been handled by checkSchemaConflicts
} else if theirsChanged {
mergedColumns = append(mergedColumns, *theirs)
if columnTypesAreCompatible(format, *ours, *theirs) {
mergedColumns = append(mergedColumns, *theirs)
} else {
conflicts = append(conflicts, ColConflict{
Kind: NameCollision,
Ours: *ours,
Theirs: *theirs,
})
}
} else {
mergedColumns = append(mergedColumns, *ours)
if columnTypesAreCompatible(format, *theirs, *ours) {
mergedColumns = append(mergedColumns, *ours)
} else {
conflicts = append(conflicts, ColConflict{
Kind: NameCollision,
Ours: *ours,
Theirs: *theirs,
})
}
}
} else if ours.Equals(*theirs) {
// if the columns are identical, just use ours
@@ -473,6 +495,34 @@ func checkSchemaConflicts(columnMappings columnMappings) ([]ColConflict, error)
return conflicts, nil
}
// columnTypesAreCompatible returns true if the change from |from| to |to| is a compatible type change.
// Currently, no type change for the DOLT storage format is considered compatible, but over time we will
// widen this to include safe type migrations (e.g. smallint to bigint, varchar(100) to varchar(200)), which
// can require rewriting existing stored data to be compatible with the new type. For the older LD_1 storage
// format, we are looser with type equality and consider them compatible as long as the types are in the
// same type family/kind.
func columnTypesAreCompatible(format *types.NomsBinFormat, from, to schema.Column) bool {
if !from.TypeInfo.Equals(to.TypeInfo) {
if types.IsFormat_DOLT(format) {
// All type changes are incompatible, for the DOLT storage format.
// TODO: this is overly broad, and should be narrowed down
return false
}
if from.Kind != to.Kind {
return false
}
if schema.IsColSpatialType(to) {
// We need to do this because some spatial type changes require a full table check, but not all.
// TODO: This could be narrowed down to a smaller set of spatial type changes
return false
}
}
return true
}
// columnMapping describes the mapping for a column being merged between the two sides of the merge as well as the ancestor.
type columnMapping struct {
anc *schema.Column
@@ -40,7 +40,7 @@ type testCommand struct {
}
func (tc testCommand) exec(t *testing.T, ctx context.Context, dEnv *env.DoltEnv) int {
return tc.cmd.Exec(ctx, tc.cmd.Name(), tc.args, dEnv, nil)
return tc.cmd.Exec(ctx, tc.cmd.Name(), tc.args, dEnv, commands.BuildEmptyCliContext())
}
type args []string
@@ -173,7 +173,7 @@ func setupMigrationTest(t *testing.T, ctx context.Context, test migrationTest) *
cmd := commands.SqlCmd{}
for _, query := range test.setup {
code := cmd.Exec(ctx, cmd.Name(), []string{"-q", query}, dEnv, nil)
code := cmd.Exec(ctx, cmd.Name(), []string{"-q", query}, dEnv, commands.BuildEmptyCliContext())
require.Equal(t, 0, code)
}
return dEnv
@@ -55,6 +55,8 @@ type testAssertion struct {
rows []sql.Row
}
var cliCtx = cmd.BuildEmptyCliContext()
var setupCommon = []testCommand{
{cmd.SqlCmd{}, args{"-q",
`create table test (
@@ -203,7 +205,7 @@ func setupFilterBranchTests(t *testing.T) *env.DoltEnv {
ctx := context.Background()
dEnv := dtestutils.CreateTestEnv()
for _, c := range setupCommon {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cliCtx)
require.Equal(t, 0, exitCode)
}
@@ -215,13 +217,13 @@ func testFilterBranch(t *testing.T, test filterBranchTest) {
dEnv := setupFilterBranchTests(t)
defer dEnv.DoltDB.Close()
for _, c := range test.setup {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cliCtx)
require.Equal(t, 0, exitCode)
}
for _, a := range test.asserts {
for _, c := range a.setup {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cliCtx)
require.Equal(t, 0, exitCode)
}
@@ -177,7 +177,7 @@ func runTestSql(t *testing.T, ctx context.Context, setup []string) (*doltdb.Dolt
dEnv := dtestutils.CreateTestEnv()
cmd := commands.SqlCmd{}
for _, query := range setup {
code := cmd.Exec(ctx, cmd.Name(), []string{"-q", query}, dEnv, nil)
code := cmd.Exec(ctx, cmd.Name(), []string{"-q", query}, dEnv, commands.BuildEmptyCliContext())
require.Equal(t, 0, code)
}
root, err := dEnv.WorkingRoot(ctx)
@@ -24,9 +24,11 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/diff"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
noms "github.com/dolthub/dolt/go/store/types"
)
var _ sql.Table = (*SchemaConflictsTable)(nil)
@@ -148,8 +150,20 @@ type schemaConflict struct {
description string
}
func newSchemaConflict(ctx context.Context, table string, br *doltdb.RootValue, c doltdb.SchemaConflict) (schemaConflict, error) {
base, err := getCreateTableStatementFromRoot(ctx, table, br)
func newSchemaConflict(ctx context.Context, table string, baseRoot *doltdb.RootValue, c doltdb.SchemaConflict) (schemaConflict, error) {
bs, err := baseRoot.GetAllSchemas(ctx)
if err != nil {
return schemaConflict{}, err
}
baseSch := bs[table]
fkc, err := baseRoot.GetForeignKeyCollection(ctx)
if err != nil {
return schemaConflict{}, err
}
baseFKs, _ := fkc.KeysForTable(table)
base, err := getCreateTableStatement(table, baseSch, baseFKs, bs)
if err != nil {
return schemaConflict{}, err
}
@@ -164,30 +178,20 @@ func newSchemaConflict(ctx context.Context, table string, br *doltdb.RootValue,
return schemaConflict{}, err
}
desc, err := getSchemaConflictDescription(ctx, table, baseSch, c.ToSch, c.FromSch)
if err != nil {
return schemaConflict{}, err
}
return schemaConflict{
table: table,
baseSch: base,
ourSch: ours,
theirSch: theirs,
table: table,
baseSch: base,
ourSch: ours,
theirSch: theirs,
description: desc,
}, nil
}
func getCreateTableStatementFromRoot(ctx context.Context, table string, root *doltdb.RootValue) (string, error) {
schemas, err := root.GetAllSchemas(ctx)
if err != nil {
return "", err
}
sch := schemas[table]
fkc, err := root.GetForeignKeyCollection(ctx)
if err != nil {
return "", err
}
fks, _ := fkc.KeysForTable(table)
return getCreateTableStatement(table, sch, fks, schemas)
}
func getCreateTableStatement(table string, sch schema.Schema, fks []doltdb.ForeignKey, parents map[string]schema.Schema) (string, error) {
pkSch, err := sqlutil.FromDoltSchema(table, sch)
if err != nil {
@@ -196,6 +200,15 @@ func getCreateTableStatement(table string, sch schema.Schema, fks []doltdb.Forei
return diff.GenerateCreateTableStatement(table, sch, pkSch, fks, parents)
}
func getSchemaConflictDescription(ctx context.Context, table string, base, ours, theirs schema.Schema) (string, error) {
nbf := noms.Format_Default
_, conflict, err := merge.SchemaMerge(ctx, nbf, ours, theirs, base, table)
if err != nil {
return "", err
}
return conflict.String(), nil
}
type schemaConflictsIter struct {
conflicts []schemaConflict
baseSchemas map[string]schema.Schema
@@ -2906,7 +2906,7 @@ var SchemaConflictScripts = []queries.ScriptTest{
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `c0` varchar(20),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `c0` datetime(6),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `c0` int,\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"",
"different column definitions for our column c0 and their column c0",
}},
},
{
@@ -3721,6 +3721,8 @@ var DoltVerifyConstraintsTestScripts = []queries.ScriptTest{
var errTmplNoAutomaticMerge = "table %s can't be automatically merged.\nTo merge this table, make the schema on the source and target branch equal."
var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
// Data conflicts during a merge with schema changes
{
Name: "data conflict",
AncSetUpScript: []string{
@@ -3760,51 +3762,8 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
},
{
Name: "unique constraint violation",
AncSetUpScript: []string{
"set autocommit = 0;",
"CREATE table t (pk varchar(100) primary key, col1 int, col2 varchar(100), UNIQUE KEY unique1 (col2));",
"INSERT into t values ('0', 0, '');",
"alter table t add index idx1 (pk, col2);",
},
RightSetUpScript: []string{
"alter table t drop column col1;",
"INSERT into t (pk, col2) values ('10', 'same');",
},
LeftSetUpScript: []string{
"INSERT into t values ('1', 10, 'same');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{0, 0x1}},
},
{
Query: "select * from dolt_conflicts;",
Expected: []sql.Row{},
},
{
Query: "select * from dolt_constraint_violations;",
Expected: []sql.Row{{"t", uint(2)}},
},
{
Query: "select violation_type, pk, col2, violation_info from dolt_constraint_violations_t;",
Expected: []sql.Row{
{uint(2), "1", "same", types.JSONDocument{Val: merge.UniqCVMeta{Columns: []string{"col2"}, Name: "unique1"}}},
{uint(2), "10", "same", types.JSONDocument{Val: merge.UniqCVMeta{Columns: []string{"col2"}, Name: "unique1"}}},
},
},
{
Query: "select pk, col2 from t;",
Expected: []sql.Row{
{"0", ""},
{"1", "same"},
{"10", "same"},
},
},
},
},
// Basic column changes adds/drops/renames/reorders
{
Name: "dropping columns",
AncSetUpScript: []string{
@@ -3877,8 +3836,6 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
"alter table t add index idx4 (col1, col2);",
"alter table t add index idx5 (col2, col1);",
"alter table t add index idx6 (col2, pk, col1);",
// TODO: This duplicate index causes a race condition in the merge code
//"alter table t add index idx7 (col2);",
},
RightSetUpScript: []string{
"alter table t rename column col1 to col11;",
@@ -4034,6 +3991,8 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
},
// Constraint changes
{
Name: "removing a not-null constraint",
AncSetUpScript: []string{
@@ -4160,6 +4119,51 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
},
{
Name: "unique constraint violation",
AncSetUpScript: []string{
"set autocommit = 0;",
"CREATE table t (pk varchar(100) primary key, col1 int, col2 varchar(100), UNIQUE KEY unique1 (col2));",
"INSERT into t values ('0', 0, '');",
"alter table t add index idx1 (pk, col2);",
},
RightSetUpScript: []string{
"alter table t drop column col1;",
"INSERT into t (pk, col2) values ('10', 'same');",
},
LeftSetUpScript: []string{
"INSERT into t values ('1', 10, 'same');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{0, 0x1}},
},
{
Query: "select * from dolt_conflicts;",
Expected: []sql.Row{},
},
{
Query: "select * from dolt_constraint_violations;",
Expected: []sql.Row{{"t", uint(2)}},
},
{
Query: "select violation_type, pk, col2, violation_info from dolt_constraint_violations_t;",
Expected: []sql.Row{
{uint(2), "1", "same", types.JSONDocument{Val: merge.UniqCVMeta{Columns: []string{"col2"}, Name: "unique1"}}},
{uint(2), "10", "same", types.JSONDocument{Val: merge.UniqCVMeta{Columns: []string{"col2"}, Name: "unique1"}}},
},
},
{
Query: "select pk, col2 from t;",
Expected: []sql.Row{
{"0", ""},
{"1", "same"},
{"10", "same"},
},
},
},
},
{
Name: "dropping a unique key",
AncSetUpScript: []string{
@@ -4187,7 +4191,85 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
// Schema conflict test cases
// Schema conflicts
{
// Type widening - these changes move from smaller types to bigger types, so they are guaranteed to be safe.
// TODO: We don't support automatically converting column types in merges yet, so currently these won't
// automatically merge and instead return schema conflicts.
Name: "type widening",
AncSetUpScript: []string{
"CREATE table t (pk int primary key, col1 enum('blue', 'green'), col2 float, col3 smallint, " +
"col4 decimal(4,2), col5 varchar(10), col6 set('a', 'b'), col7 bit(1));",
"INSERT into t values (1, 'blue', 1.0, 1, 0.1, 'one', 'a,b', 1);",
"alter table t add index idx1 (col1);",
},
RightSetUpScript: []string{
"alter table t modify column col1 enum('blue', 'green', 'red');",
"alter table t modify column col2 double;",
"alter table t modify column col3 bigint;",
"alter table t modify column col4 decimal(8,4);",
"alter table t modify column col5 varchar(20);",
"alter table t modify column col6 set('a', 'b', 'c');",
"alter table t modify column col7 bit(2);",
"INSERT into t values (3, 'red', 3.0, 420, 0.001, 'three', 'a,b,c', 3);",
},
LeftSetUpScript: []string{
"INSERT into t values (2, 'green', 2.0, 2, 0.2, 'two', 'a,b', 1);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{0, 0x1}},
},
{
Query: "select table_name, our_schema, their_schema, base_schema from dolt_schema_conflicts;",
Expected: []sql.Row{{"t",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` enum('blue','green'),\n `col2` float,\n `col3` smallint,\n `col4` decimal(4,2),\n `col5` varchar(10),\n `col6` set('a','b'),\n `col7` bit(1),\n PRIMARY KEY (`pk`),\n KEY `idx1` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` enum('blue','green','red'),\n `col2` double,\n `col3` bigint,\n `col4` decimal(8,4),\n `col5` varchar(20),\n `col6` set('a','b','c'),\n `col7` bit(2),\n PRIMARY KEY (`pk`),\n KEY `idx1` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` enum('blue','green'),\n `col2` float,\n `col3` smallint,\n `col4` decimal(4,2),\n `col5` varchar(10),\n `col6` set('a','b'),\n `col7` bit(1),\n PRIMARY KEY (`pk`),\n KEY `idx1` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;"}},
},
},
},
{
// Type shortening these changes move from a larger type to a smaller type and are not always safe.
// For now, we automatically fail all of these with a schema conflict that the user must resolve, but in
// theory, we could try to apply these changes and see if the data in the tables is compatible or not, but
// that's an optimization left for the future. Until then, customers can manually alter their schema to
// get merges to work, based on the schema conflict information.
Name: "type shortening",
AncSetUpScript: []string{
"CREATE TABLE t (pk int primary key, col1 enum('blue','green','red'), col2 double, col3 bigint, col4 decimal(8,4), " +
"col5 varchar(20), col6 set('a','b','c'), col7 bit(2));",
"INSERT into t values (3, 'green', 3.0, 420, 0.001, 'three', 'a,b', 1);",
"alter table t add index idx1 (col1);",
},
RightSetUpScript: []string{
"alter table t modify column col1 enum('blue', 'green');",
"alter table t modify column col2 float;",
"alter table t modify column col3 smallint;",
"alter table t modify column col4 decimal(4,2);",
"alter table t modify column col5 varchar(10);",
"alter table t modify column col6 set('a', 'b');",
"alter table t modify column col7 bit(1);",
"INSERT into t values (1, 'blue', 1.0, 1, 0.1, 'one', 'a,b', 1);",
},
LeftSetUpScript: []string{
"INSERT into t values (2, 'green', 2.0, 2, 0.2, 'two', 'a,b', 1);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{0, 0x1}},
},
{
Query: "select table_name, our_schema, their_schema, base_schema from dolt_schema_conflicts;",
Expected: []sql.Row{{"t",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` enum('blue','green','red'),\n `col2` double,\n `col3` bigint,\n `col4` decimal(8,4),\n `col5` varchar(20),\n `col6` set('a','b','c'),\n `col7` bit(2),\n PRIMARY KEY (`pk`),\n KEY `idx1` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` enum('blue','green'),\n `col2` float,\n `col3` smallint,\n `col4` decimal(4,2),\n `col5` varchar(10),\n `col6` set('a','b'),\n `col7` bit(1),\n PRIMARY KEY (`pk`),\n KEY `idx1` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` enum('blue','green','red'),\n `col2` double,\n `col3` bigint,\n `col4` decimal(8,4),\n `col5` varchar(20),\n `col6` set('a','b','c'),\n `col7` bit(2),\n PRIMARY KEY (`pk`),\n KEY `idx1` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;"}},
},
},
},
{
Name: "index conflicts: both sides add an index with the same name, same columns, but different type",
AncSetUpScript: []string{
@@ -4216,7 +4298,6 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
},
{
// https://github.com/dolthub/dolt/issues/2973
Name: "modifying a column on one side of a merge, and deleting it on the other",
@@ -4262,15 +4343,69 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
},
// Smarter merge conflict detection
{
// This merge tests reports a conflict on pk=1, because the tuple value is different on the left side, right
Name: "changing the type of a column",
AncSetUpScript: []string{
"create table t (pk int primary key, col1 int);",
"insert into t values (1, 10), (2, 20);",
},
RightSetUpScript: []string{
"alter table t modify column col1 varchar(100)",
"insert into t values (3, 'thirty'), (4, 'forty')",
},
LeftSetUpScript: []string{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{0, 1}},
},
{
Query: "select table_name, our_schema, their_schema, base_schema from dolt_schema_conflicts;",
Expected: []sql.Row{{"t",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` int,\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` varchar(100),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` int,\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;"}},
},
},
},
{
Name: "changing the type of a column with an index",
AncSetUpScript: []string{
"create table t (pk int primary key, col1 int, INDEX col1_idx (col1));",
"insert into t values (1, 100), (2, 20);",
},
RightSetUpScript: []string{
"alter table t modify column col1 varchar(100);",
"insert into t values (3, 'thirty'), (4, 'forty')",
},
LeftSetUpScript: []string{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('right');",
Expected: []sql.Row{{0, 1}},
},
{
Query: "select table_name, our_schema, their_schema, base_schema from dolt_schema_conflicts;",
Expected: []sql.Row{{"t",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` int,\n PRIMARY KEY (`pk`),\n KEY `col1_idx` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` varchar(100),\n PRIMARY KEY (`pk`),\n KEY `col1_idx` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` int,\n PRIMARY KEY (`pk`),\n KEY `col1_idx` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;"}},
},
},
},
// Unsupported automatic merge cases
{
// This merge test reports a conflict on pk=1, because the tuple value is different on the left side, right
// side, and base. The value is the base is (10, '100'), on the right is nil, and on the left is ('100'),
// because the data migration for the schema change happens before the diff iterator is invoked.
// This should NOT be a conflict for a user Dolt should not conflate the schema merge data migration with
// a real data conflict created by a user. Allowing this is still better than completely blocking all schema
// merges though, so we can live with this while we continue iterating and fine tuning schema merge logic.
// merges though, so we can live with this while we continue iterating and fine-tuning schema merge logic.
Name: "schema change combined with drop row",
AncSetUpScript: []string{
"SET autocommit = 0",
@@ -4304,10 +4439,8 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
},
// Unsupported automatic merge cases
{
Name: "adding a non-null column to one side",
Name: "adding a non-null column with a default value to one side",
AncSetUpScript: []string{
"create table t (pk int primary key, col1 int);",
"insert into t values (1, 1);",
@@ -4334,71 +4467,7 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
{
Name: "changing the type of a column",
AncSetUpScript: []string{
"create table t (pk int primary key, col1 int);",
"insert into t values (1, 10), (2, 20);",
},
RightSetUpScript: []string{
"alter table t modify column col1 varchar(100)",
"insert into t values (3, 'thirty'), (4, 'forty')",
},
LeftSetUpScript: []string{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('right');",
ExpectedErrStr: fmt.Sprintf(errTmplNoAutomaticMerge, "t"),
},
{
Query: "select pk, col1 from t;",
Expected: []sql.Row{
{1, "10"},
{2, "20"},
{3, "thirty"},
{4, "forty"},
{5, "50"},
{6, "60"},
},
Skip: true,
},
},
},
{
Name: "changing the type of a column with an index",
AncSetUpScript: []string{
"create table t (pk int primary key, col1 int, INDEX col1_idx (col1));",
"insert into t values (1, 100), (2, 20);",
},
RightSetUpScript: []string{
"alter table t modify column col1 varchar(100);",
"insert into t values (3, 'thirty'), (4, 'forty')",
},
LeftSetUpScript: []string{
"insert into t values (5, 50), (6, 60);",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "call dolt_merge('right');",
ExpectedErrStr: fmt.Sprintf(errTmplNoAutomaticMerge, "t"),
},
{
Skip: true,
Query: "select pk, col1 from t order by col1;",
Expected: []sql.Row{
{1, "100"},
{2, "20"},
{3, "thirty"},
{4, "forty"},
{5, "50"},
{6, "60"},
},
},
},
},
{
Name: "adding a not-null constraint with default to a column",
Name: "adding a not-null constraint and default value to a column",
AncSetUpScript: []string{
"create table t (pk int primary key, col1 int);",
"insert into t values (1, null), (2, null);",
@@ -4451,9 +4520,9 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
},
{
// It would make sense if we table-scanned for check constraints during merge
// and flagged failing constraints as violations in `dolt_constraint_violations`.
Name: "adding a check-constraint should abort the merge.",
// TODO: We should scan for check constraints during merge and flag failing
// constraints as violations in `dolt_constraint_violations`.
Name: "adding a check-constraint",
AncSetUpScript: []string{
"create table t (pk int primary key, col1 int);",
"insert into t values (1, 1);",
@@ -4467,14 +4536,18 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
},
Assertions: []queries.ScriptTestAssertion{
{
Skip: true,
Query: "call dolt_merge('right');",
ExpectedErrStr: "some schema error",
// TODO: Dolt currently merges this without an error, but it shouldn't;
// There is a constraint violation that should be reported.
Skip: true,
Query: "call dolt_merge('right');",
Expected: []sql.Row{{0, 0x1}},
},
},
},
{
Name: "changing the collation of an indexed column is broken",
// TODO: Changing a column's collation requires rewriting the table and any indexes containing that column.
// For now, we just detect the schema incompatibility and return schema conflict metadata.
Name: "changing the collation of an indexed column",
AncSetUpScript: []string{
"create table t (pk int primary key, col1 varchar(32) character set utf8mb4 collate utf8mb4_bin, index col1_idx (col1));",
"insert into t values (1, 'ab'), (2, 'Ab');",
@@ -4486,10 +4559,16 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
"insert into t values (3, 'c');",
},
Assertions: []queries.ScriptTestAssertion{
// TODO: Fails secondary index validation. Changing the ordinal ordering of secondary indexes definitely breaks merge
{
Query: "call dolt_merge('right');",
ExpectedErrStr: fmt.Sprintf(errTmplNoAutomaticMerge, "t"),
Query: "call dolt_merge('right');",
Expected: []sql.Row{{0, 1}},
},
{
Query: "select table_name, our_schema, their_schema, base_schema from dolt_schema_conflicts;",
Expected: []sql.Row{{"t",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` varchar(32) COLLATE utf8mb4_bin,\n PRIMARY KEY (`pk`),\n KEY `col1_idx` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` varchar(32) COLLATE utf8mb4_general_ci,\n PRIMARY KEY (`pk`),\n KEY `col1_idx` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `col1` varchar(32) COLLATE utf8mb4_bin,\n PRIMARY KEY (`pk`),\n KEY `col1_idx` (`col1`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;"}},
},
},
},
@@ -154,7 +154,7 @@ func TestDbRevision(t *testing.T) {
setup := append(setupCommon, test.setup...)
for _, c := range setup {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cmd.BuildEmptyCliContext())
require.Equal(t, 0, exitCode)
}
@@ -213,7 +213,7 @@ func setupHistoryTests(t *testing.T) *env.DoltEnv {
ctx := context.Background()
dEnv := dtestutils.CreateTestEnv()
for _, c := range setupCommon {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cmd.BuildEmptyCliContext())
require.Equal(t, 0, exitCode)
}
@@ -237,7 +237,7 @@ func setupHistoryTests(t *testing.T) *env.DoltEnv {
func testHistoryTable(t *testing.T, test historyTableTest, dEnv *env.DoltEnv) {
ctx := context.Background()
for _, c := range test.setup {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cmd.BuildEmptyCliContext())
require.Equal(t, 0, exitCode)
}
@@ -129,7 +129,7 @@ func testJsonValue(t *testing.T, test jsonValueTest, setupCommon []testCommand)
setup := append(setupCommon, test.setup...)
for _, c := range setup {
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cmd.BuildEmptyCliContext())
require.Equal(t, 0, exitCode)
}
@@ -140,7 +140,7 @@ func populateRepo(dEnv *env.DoltEnv, insertData string) {
execSql := func(dEnv *env.DoltEnv, q string) int {
ctx := context.Background()
args := []string{"-r", "null", "-q", q}
return commands.SqlCmd{}.Exec(ctx, "sql", args, dEnv, nil)
return commands.SqlCmd{}.Exec(ctx, "sql", args, dEnv, commands.BuildEmptyCliContext())
}
execSql(dEnv, createTable)
execSql(dEnv, insertData)
+1 -1
View File
@@ -160,7 +160,7 @@ func mustList(l types.List, err error) types.List {
func validate(ctx context.Context, nbf *types.NomsBinFormat, r types.Value) bool {
rootType := mustType(types.MakeMapType(types.PrimitiveTypeMap[types.StringKind], mustType(types.MakeRefType(types.PrimitiveTypeMap[types.ValueKind]))))
if isSub, err := types.IsValueSubtypeOf(ctx, nbf, r, rootType); err != nil {
if isSub, err := types.IsValueSubtypeOf(nbf, r, rootType); err != nil {
panic(err)
} else if !isSub {
fmt.Fprintf(os.Stderr, "Root of database must be %s, but you specified: %s\n", mustString(rootType.Describe(ctx)), mustString(mustType(types.TypeOf(r)).Describe(ctx)))
+2 -2
View File
@@ -196,8 +196,8 @@ func outputEncodedValue(ctx context.Context, w io.Writer, value types.Value) err
if err != nil {
return err
}
fmt.Fprintf(w, "\tPrimary Index (rows %d, depth %d) %s {",
c, node.Level()+1, node.HashOf().String()[:8])
fmt.Fprintf(w, "\tPrimary Index (rows %d, depth %d) #%s {",
c, node.Level()+1, node.HashOf().String())
tree.OutputProllyNode(w, node)
fmt.Fprintf(w, "\t}\n")
+13 -4
View File
@@ -62,6 +62,7 @@ const (
)
var ErrCommitNotFound = errors.New("target commit not found")
var ErrNotACommit = errors.New("value is not a commit")
type Commit struct {
val types.Value
@@ -303,7 +304,15 @@ func commitPtr(nbf *types.NomsBinFormat, v types.Value, r *types.Ref) (*Commit,
}, nil
}
func commitFromValue(nbf *types.NomsBinFormat, v types.Value) (*Commit, error) {
// CommitFromValue deserializes a types.Value into a Commit.
func CommitFromValue(nbf *types.NomsBinFormat, v types.Value) (*Commit, error) {
isCommit, err := IsCommit(v)
if err != nil {
return nil, err
}
if !isCommit {
return nil, ErrNotACommit
}
return commitPtr(nbf, v, nil)
}
@@ -326,7 +335,7 @@ func LoadCommitAddr(ctx context.Context, vr types.ValueReader, addr hash.Hash) (
if v == nil {
return nil, errors.New("target commit not found")
}
return commitFromValue(vr.Format(), v)
return CommitFromValue(vr.Format(), v)
}
func findCommonAncestorUsingParentsList(ctx context.Context, c1, c2 *Commit, vr1, vr2 types.ValueReader, ns1, ns2 tree.NodeStore) (hash.Hash, bool, error) {
@@ -695,9 +704,9 @@ func firstError(l, r error) error {
return r
}
func IsCommit(ctx context.Context, v types.Value) (bool, error) {
func IsCommit(v types.Value) (bool, error) {
if s, ok := v.(types.Struct); ok {
return types.IsValueSubtypeOf(ctx, s.Format(), v, valueCommitType)
return types.IsValueSubtypeOf(s.Format(), v, valueCommitType)
} else if sm, ok := v.(types.SerialMessage); ok {
data := []byte(sm)
return serial.GetFileID(data) == serial.CommitFileID, nil
+4 -4
View File
@@ -257,14 +257,14 @@ func TestCommitWithoutMetaField(t *testing.T) {
"meta": types.EmptyStruct(db.Format()),
})
assert.NoError(err)
assert.True(IsCommit(ctx, metaCommit))
assert.True(IsCommit(metaCommit))
noMetaCommit, err := types.NewStruct(db.Format(), "Commit", types.StructData{
"value": types.Float(9),
"parents": mustSet(types.NewSet(ctx, db)),
})
assert.NoError(err)
assert.False(IsCommit(ctx, noMetaCommit))
assert.False(IsCommit(noMetaCommit))
}
func mustCommitToTargetHashes(vrw types.ValueReadWriter, commits ...types.Value) []hash.Hash {
@@ -638,9 +638,9 @@ func TestFindCommonAncestor(t *testing.T) {
assertCommonAncestor(t, a6, a9, ra9, db, rdb, "common third parent")
a9c, err := commitFromValue(db.Format(), a9)
a9c, err := CommitFromValue(db.Format(), a9)
require.NoError(t, err)
ra9c, err := commitFromValue(rdb.Format(), ra9)
ra9c, err := CommitFromValue(rdb.Format(), ra9)
require.NoError(t, err)
_, _, err = FindCommonAncestor(context.Background(), ra9c, a9c, db, rdb, db.ns, rdb.ns)
assert.Error(err)
+6 -6
View File
@@ -268,7 +268,7 @@ func (db *database) doSetHead(ctx context.Context, ds Dataset, addr hash.Hash) e
headType := newHead.TypeName()
switch headType {
case commitName:
iscommit, err := IsCommit(ctx, newVal)
iscommit, err := IsCommit(newVal)
if err != nil {
return err
}
@@ -291,7 +291,7 @@ func (db *database) doSetHead(ctx context.Context, ds Dataset, addr hash.Hash) e
if err != nil {
return err
}
iscommit, err := IsCommit(ctx, commitval)
iscommit, err := IsCommit(commitval)
if err != nil {
return err
}
@@ -376,7 +376,7 @@ func (db *database) doFastForward(ctx context.Context, ds Dataset, newHeadAddr h
}
v := newHead.value()
iscommit, err := IsCommit(ctx, v)
iscommit, err := IsCommit(v)
if err != nil {
return err
}
@@ -384,7 +384,7 @@ func (db *database) doFastForward(ctx context.Context, ds Dataset, newHeadAddr h
return fmt.Errorf("FastForward: target value of new head address %v is not a commit.", newHeadAddr)
}
newCommit, err := commitFromValue(db.Format(), v)
newCommit, err := CommitFromValue(db.Format(), v)
if err != nil {
return err
}
@@ -392,7 +392,7 @@ func (db *database) doFastForward(ctx context.Context, ds Dataset, newHeadAddr h
currentHeadAddr, ok := ds.MaybeHeadAddr()
if ok {
currentHeadValue, _ := ds.MaybeHead()
currCommit, err := commitFromValue(db.Format(), currentHeadValue)
currCommit, err := CommitFromValue(db.Format(), currentHeadValue)
if err != nil {
return err
}
@@ -905,7 +905,7 @@ func (db *database) validateRefAsCommit(ctx context.Context, r types.Ref) (types
var v types.Value
v = rHead.(nomsHead).st
is, err := IsCommit(ctx, v)
is, err := IsCommit(v)
if err != nil {
return types.Struct{}, err
+4 -4
View File
@@ -220,7 +220,7 @@ func (suite *DatabaseSuite) TestDatabaseCommit() {
suite.NoError(err)
suite.True(ok)
suite.True(h.Equals(a))
comm, err := commitFromValue(suite.db.Format(), mustHead(ds2))
comm, err := CommitFromValue(suite.db.Format(), mustHead(ds2))
suite.NoError(err)
suite.Equal(uint64(1), comm.Height())
@@ -232,7 +232,7 @@ func (suite *DatabaseSuite) TestDatabaseCommit() {
ds, err = CommitValue(context.Background(), suite.db, ds, b)
suite.NoError(err)
suite.True(mustHeadValue(ds).Equals(b))
comm, err = commitFromValue(suite.db.Format(), mustHead(ds))
comm, err = CommitFromValue(suite.db.Format(), mustHead(ds))
suite.NoError(err)
suite.Equal(uint64(2), comm.Height())
@@ -249,7 +249,7 @@ func (suite *DatabaseSuite) TestDatabaseCommit() {
ds, err = CommitValue(context.Background(), suite.db, ds, d)
suite.NoError(err)
suite.True(mustHeadValue(ds).Equals(d))
comm, err = commitFromValue(suite.db.Format(), mustHead(ds))
comm, err = CommitFromValue(suite.db.Format(), mustHead(ds))
suite.NoError(err)
suite.Equal(uint64(3), comm.Height())
@@ -336,7 +336,7 @@ func assertMapOfStringToRefOfCommit(ctx context.Context, proposed, datasets type
}
if targetValue, err := ref.TargetValue(ctx, vr); err != nil {
d.PanicIfError(err)
} else if is, err := IsCommit(ctx, targetValue); err != nil {
} else if is, err := IsCommit(targetValue); err != nil {
d.PanicIfError(err)
} else if !is {
d.Panic("Root of a Database must be a Map<String, Ref<Commit>>, but the ref at key %s points to a %s", change.Key.(types.String), mustString(mustType(types.TypeOf(targetValue)).Describe(ctx)))
+2 -2
View File
@@ -228,7 +228,7 @@ func (ms *MergeState) FromCommit(ctx context.Context, vr types.ValueReader) (*Co
return nil, fmt.Errorf("corrupted MergeState struct")
}
return commitFromValue(vr.Format(), commitV)
return CommitFromValue(vr.Format(), commitV)
}
func (ms *MergeState) FromCommitSpec(ctx context.Context, vr types.ValueReader) (string, error) {
@@ -504,7 +504,7 @@ func newHead(ctx context.Context, head types.Value, addr hash.Hash) (dsHead, err
}
}
matched, err := IsCommit(ctx, head)
matched, err := IsCommit(head)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -38,7 +38,7 @@ func NewStash(ctx context.Context, nbf *types.NomsBinFormat, vrw types.ValueRead
return hash.Hash{}, types.Ref{}, err
}
isCommit, err := IsCommit(ctx, headCommit)
isCommit, err := IsCommit(headCommit)
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
+2 -2
View File
@@ -57,7 +57,7 @@ func newTag(ctx context.Context, db *database, commitAddr hash.Hash, meta *TagMe
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
iscommit, err := IsCommit(ctx, commitSt)
iscommit, err := IsCommit(commitSt)
if err != nil {
return hash.Hash{}, types.Ref{}, err
}
@@ -134,7 +134,7 @@ func tag_flatbuffer(commitAddr hash.Hash, meta *TagMeta) serial.Message {
func IsTag(ctx context.Context, v types.Value) (bool, error) {
if s, ok := v.(types.Struct); ok {
return types.IsValueSubtypeOf(ctx, s.Format(), v, valueTagType)
return types.IsValueSubtypeOf(s.Format(), v, valueTagType)
} else if sm, ok := v.(types.SerialMessage); ok {
data := []byte(sm)
return serial.GetFileID(data) == serial.TagFileID, nil
+1 -1
View File
@@ -174,7 +174,7 @@ func (ml mapLeafSequence) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
return walkRefs(w.buff[:w.offset], ml.format(), cb)
}
func (ml mapLeafSequence) entries(ctx context.Context) (mapEntrySlice, error) {
func (ml mapLeafSequence) entries() (mapEntrySlice, error) {
dec, count := ml.decoderSkipToValues()
entries := mapEntrySlice{
make([]mapEntry, count),
+1 -1
View File
@@ -491,7 +491,7 @@ func (ms metaSequence) getCompositeChildSequence(ctx context.Context, start uint
var valueItems []mapEntry
for _, seq := range output {
entries, err := seq.(mapLeafSequence).entries(ctx)
entries, err := seq.(mapLeafSequence).entries()
if err != nil {
return nil, err
+60 -50
View File
@@ -53,6 +53,15 @@ func (sm SerialMessage) Hash(nbf *NomsBinFormat) (hash.Hash, error) {
}
func (sm SerialMessage) HumanReadableString() string {
return sm.humanReadableStringAtIndentationLevel(0)
}
func printWithIndendationLevel(level int, builder *strings.Builder, format string, a ...any) {
fmt.Fprintf(builder, strings.Repeat("\t", level))
fmt.Fprintf(builder, format, a...)
}
func (sm SerialMessage) humanReadableStringAtIndentationLevel(level int) string {
id := serial.GetFileID(sm)
switch id {
// NOTE: splunk uses a separate path for some printing
@@ -60,101 +69,101 @@ func (sm SerialMessage) HumanReadableString() string {
msg := serial.GetRootAsStoreRoot([]byte(sm), serial.MessagePrefixSz)
ret := &strings.Builder{}
mapbytes := msg.AddressMapBytes()
fmt.Fprintf(ret, "StoreRoot{%s}", SerialMessage(mapbytes).HumanReadableString())
printWithIndendationLevel(level, ret, "StoreRoot{%s}",
SerialMessage(mapbytes).humanReadableStringAtIndentationLevel(level+1))
return ret.String()
case serial.StashListFileID:
msg := serial.GetRootAsStashList([]byte(sm), serial.MessagePrefixSz)
ret := &strings.Builder{}
mapbytes := msg.AddressMapBytes()
fmt.Fprintf(ret, "StashList{%s}", SerialMessage(mapbytes).HumanReadableString())
printWithIndendationLevel(level, ret, "StashList{%s}",
SerialMessage(mapbytes).humanReadableStringAtIndentationLevel(level+1))
return ret.String()
case serial.StashFileID:
msg := serial.GetRootAsStash(sm, serial.MessagePrefixSz)
ret := &strings.Builder{}
fmt.Fprintf(ret, "{\n")
fmt.Fprintf(ret, "\tBranchName: %s\n", msg.BranchName())
fmt.Fprintf(ret, "\tDesc: %s\n", msg.Desc())
fmt.Fprintf(ret, "\tStashRootAddr: #%s\n", hash.New(msg.StashRootAddrBytes()).String())
fmt.Fprintf(ret, "\tHeadCommitAddr: #%s\n", hash.New(msg.HeadCommitAddrBytes()).String())
fmt.Fprintf(ret, "}")
printWithIndendationLevel(level, ret, "{\n")
printWithIndendationLevel(level, ret, "\tBranchName: %s\n", msg.BranchName())
printWithIndendationLevel(level, ret, "\tDesc: %s\n", msg.Desc())
printWithIndendationLevel(level, ret, "\tStashRootAddr: #%s\n", hash.New(msg.StashRootAddrBytes()).String())
printWithIndendationLevel(level, ret, "\tHeadCommitAddr: #%s\n", hash.New(msg.HeadCommitAddrBytes()).String())
printWithIndendationLevel(level, ret, "}")
return ret.String()
case serial.TagFileID:
return "Tag"
case serial.WorkingSetFileID:
msg := serial.GetRootAsWorkingSet(sm, serial.MessagePrefixSz)
ret := &strings.Builder{}
fmt.Fprintf(ret, "{\n")
fmt.Fprintf(ret, "\tName: %s\n", msg.Name())
fmt.Fprintf(ret, "\tDesc: %s\n", msg.Desc())
fmt.Fprintf(ret, "\tEmail: %s\n", msg.Email())
fmt.Fprintf(ret, "\tTime: %s\n", time.UnixMilli((int64)(msg.TimestampMillis())).String())
fmt.Fprintf(ret, "\tWorkingRootAddr: #%s\n", hash.New(msg.WorkingRootAddrBytes()).String())
fmt.Fprintf(ret, "\tStagedRootAddr: #%s\n", hash.New(msg.StagedRootAddrBytes()).String())
fmt.Fprintf(ret, "}")
printWithIndendationLevel(level, ret, "{\n")
printWithIndendationLevel(level, ret, "\tName: %s\n", msg.Name())
printWithIndendationLevel(level, ret, "\tDesc: %s\n", msg.Desc())
printWithIndendationLevel(level, ret, "\tEmail: %s\n", msg.Email())
printWithIndendationLevel(level, ret, "\tTime: %s\n", time.UnixMilli((int64)(msg.TimestampMillis())).String())
printWithIndendationLevel(level, ret, "\tWorkingRootAddr: #%s\n", hash.New(msg.WorkingRootAddrBytes()).String())
printWithIndendationLevel(level, ret, "\tStagedRootAddr: #%s\n", hash.New(msg.StagedRootAddrBytes()).String())
printWithIndendationLevel(level, ret, "}")
return ret.String()
case serial.CommitFileID:
msg := serial.GetRootAsCommit(sm, serial.MessagePrefixSz)
ret := &strings.Builder{}
fmt.Fprintf(ret, "{\n")
fmt.Fprintf(ret, "\tName: %s\n", msg.Name())
fmt.Fprintf(ret, "\tDesc: %s\n", msg.Description())
fmt.Fprintf(ret, "\tEmail: %s\n", msg.Email())
fmt.Fprintf(ret, "\tTime: %s\n", time.UnixMilli((int64)(msg.TimestampMillis())).String())
fmt.Fprintf(ret, "\tHeight: %d\n", msg.Height())
printWithIndendationLevel(level, ret, "{\n")
printWithIndendationLevel(level, ret, "\tName: %s\n", msg.Name())
printWithIndendationLevel(level, ret, "\tDesc: %s\n", msg.Description())
printWithIndendationLevel(level, ret, "\tEmail: %s\n", msg.Email())
printWithIndendationLevel(level, ret, "\tTime: %s\n", time.UnixMilli((int64)(msg.TimestampMillis())).String())
printWithIndendationLevel(level, ret, "\tHeight: %d\n", msg.Height())
fmt.Fprintf(ret, "\tRootValue: {\n")
printWithIndendationLevel(level, ret, "\tRootValue: {\n")
hashes := msg.RootBytes()
for i := 0; i < len(hashes)/hash.ByteLen; i++ {
addr := hash.New(hashes[i*20 : (i+1)*20])
fmt.Fprintf(ret, "\t\t#%s\n", addr.String())
printWithIndendationLevel(level, ret, "\t\t#%s\n", addr.String())
}
fmt.Fprintf(ret, "\t}\n")
printWithIndendationLevel(level, ret, "\t}\n")
fmt.Fprintf(ret, "\tParents: {\n")
printWithIndendationLevel(level, ret, "\tParents: {\n")
hashes = msg.ParentAddrsBytes()
for i := 0; i < msg.ParentAddrsLength()/hash.ByteLen; i++ {
addr := hash.New(hashes[i*20 : (i+1)*20])
fmt.Fprintf(ret, "\t\t#%s\n", addr.String())
printWithIndendationLevel(level, ret, "\t\t#%s\n", addr.String())
}
fmt.Fprintf(ret, "\t}\n")
printWithIndendationLevel(level, ret, "\t}\n")
fmt.Fprintf(ret, "\tParentClosure: {\n")
printWithIndendationLevel(level, ret, "\tParentClosure: {\n")
hashes = msg.ParentClosureBytes()
for i := 0; i < msg.ParentClosureLength()/hash.ByteLen; i++ {
addr := hash.New(hashes[i*20 : (i+1)*20])
fmt.Fprintf(ret, "\t\t#%s\n", addr.String())
printWithIndendationLevel(level, ret, "\t\t#%s\n", addr.String())
}
fmt.Fprintf(ret, "\t}\n")
printWithIndendationLevel(level, ret, "\t}\n")
fmt.Fprintf(ret, "}")
printWithIndendationLevel(level, ret, "}")
return ret.String()
case serial.RootValueFileID:
msg := serial.GetRootAsRootValue(sm, serial.MessagePrefixSz)
ret := &strings.Builder{}
fmt.Fprintf(ret, "{\n")
fmt.Fprintf(ret, "\tFeatureVersion: %d\n", msg.FeatureVersion())
fmt.Fprintf(ret, "\tForeignKeys: #%s\n", hash.New(msg.ForeignKeyAddrBytes()).String())
fmt.Fprintf(ret, "\tTables: {\n\t%s", SerialMessage(msg.TablesBytes()).HumanReadableString())
fmt.Fprintf(ret, "\t}\n")
fmt.Fprintf(ret, "}")
printWithIndendationLevel(level, ret, "{\n")
printWithIndendationLevel(level, ret, "\tFeatureVersion: %d\n", msg.FeatureVersion())
printWithIndendationLevel(level, ret, "\tForeignKeys: #%s\n", hash.New(msg.ForeignKeyAddrBytes()).String())
printWithIndendationLevel(level, ret, "\tTables: %s\n",
SerialMessage(msg.TablesBytes()).humanReadableStringAtIndentationLevel(level+1))
printWithIndendationLevel(level, ret, "}")
return ret.String()
case serial.TableFileID:
msg := serial.GetRootAsTable(sm, serial.MessagePrefixSz)
ret := &strings.Builder{}
fmt.Fprintf(ret, "asdasdf {\n")
fmt.Fprintf(ret, "\tSchema: #%s\n", hash.New(msg.SchemaBytes()).String())
fmt.Fprintf(ret, "\tViolations: #%s\n", hash.New(msg.ViolationsBytes()).String())
printWithIndendationLevel(level, ret, "{\n")
printWithIndendationLevel(level, ret, "\tSchema: #%s\n", hash.New(msg.SchemaBytes()).String())
printWithIndendationLevel(level, ret, "\tViolations: #%s\n", hash.New(msg.ViolationsBytes()).String())
// TODO: merge conflicts, not stable yet
fmt.Fprintf(ret, "\tAutoinc: %d\n", msg.AutoIncrementValue())
printWithIndendationLevel(level, ret, "\tAutoinc: %d\n", msg.AutoIncrementValue())
// TODO: can't use tree package to print here, creates a cycle
fmt.Fprintf(ret, "\tPrimary index: prolly tree\n")
fmt.Fprintf(ret, "\tSecondary indexes: {\n\t%s\n", SerialMessage(msg.SecondaryIndexesBytes()).HumanReadableString())
fmt.Fprintf(ret, "\t}\n")
fmt.Fprintf(ret, "}")
printWithIndendationLevel(level, ret, "\tPrimary index: #%s\n", hash.Of(msg.PrimaryIndexBytes()))
printWithIndendationLevel(level, ret, "\tSecondary indexes: %s\n",
SerialMessage(msg.SecondaryIndexesBytes()).humanReadableStringAtIndentationLevel(level+1))
printWithIndendationLevel(level, ret, "}")
return ret.String()
case serial.AddressMapFileID:
keys, values, _, cnt, err := message.UnpackFields(serial.Message(sm))
@@ -162,16 +171,17 @@ func (sm SerialMessage) HumanReadableString() string {
return fmt.Sprintf("error in HumanReadString(): %s", err)
}
var b strings.Builder
b.Write([]byte("AddressMap{\n"))
b.Write([]byte("AddressMap {\n"))
for i := uint16(0); i < cnt; i++ {
name := keys.GetItem(int(i), serial.Message(sm))
addr := values.GetItem(int(i), serial.Message(sm))
b.Write([]byte("\t"))
b.Write([]byte(strings.Repeat("\t", level+1)))
b.Write(name)
b.Write([]byte(": #"))
b.Write([]byte(hash.New(addr).String()))
b.Write([]byte("\n"))
}
b.Write([]byte(strings.Repeat("\t", level)))
b.Write([]byte("}"))
return b.String()
default:
+13 -15
View File
@@ -21,8 +21,6 @@
package types
import "context"
// IsSubtype determines whether concreteType is a subtype of requiredType. For example, `Float` is a subtype of `Float | String`.
func IsSubtype(nbf *NomsBinFormat, requiredType, concreteType *Type) bool {
isSub, _ := isSubtypeTopLevel(nbf, requiredType, concreteType)
@@ -177,8 +175,8 @@ func compoundSubtype(nbf *NomsBinFormat, requiredType, concreteType *Type, hasEx
return isSubtypeDetails(nbf, requiredType, concreteType, hasExtra, parentStructTypes)
}
func IsValueSubtypeOf(ctx context.Context, nbf *NomsBinFormat, v Value, t *Type) (bool, error) {
isSub, _, err := isValueSubtypeOfDetails(ctx, nbf, v, t, false)
func IsValueSubtypeOf(nbf *NomsBinFormat, v Value, t *Type) (bool, error) {
isSub, _, err := isValueSubtypeOfDetails(nbf, v, t, false)
if err != nil {
return false, err
@@ -211,11 +209,11 @@ func IsValueSubtypeOf(ctx context.Context, nbf *NomsBinFormat, v Value, t *Type)
// }
//
// IsValueSubtypeOfDetails(v, type1) would return isSub == true, and hasExtra == true
func IsValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t *Type) (bool, bool, error) {
return isValueSubtypeOfDetails(ctx, nbf, v, t, false)
func IsValueSubtypeOfDetails(nbf *NomsBinFormat, v Value, t *Type) (bool, bool, error) {
return isValueSubtypeOfDetails(nbf, v, t, false)
}
func isValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t *Type, hasExtra bool) (bool, bool, error) {
func isValueSubtypeOfDetails(nbf *NomsBinFormat, v Value, t *Type, hasExtra bool) (bool, bool, error) {
switch t.TargetKind() {
case ValueKind:
return true, hasExtra, nil
@@ -241,7 +239,7 @@ func isValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t
anonStruct = et
continue
}
isSub, hasMore, err := isValueSubtypeOfDetails(ctx, nbf, v, et, hasExtra)
isSub, hasMore, err := isValueSubtypeOfDetails(nbf, v, et, hasExtra)
if err != nil {
return false, false, err
@@ -254,7 +252,7 @@ func isValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t
}
if anonStruct != nil {
isSub, hasMore, err := isValueSubtypeOfDetails(ctx, nbf, v, anonStruct, hasExtra)
isSub, hasMore, err := isValueSubtypeOfDetails(nbf, v, anonStruct, hasExtra)
if err != nil {
return false, false, err
@@ -300,7 +298,7 @@ func isValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t
return false, hasExtra, nil
}
} else {
isSub, hasMore, err := isValueSubtypeOfDetails(ctx, nbf, fv, f.Type, hasExtra)
isSub, hasMore, err := isValueSubtypeOfDetails(nbf, fv, f.Type, hasExtra)
if err != nil {
return false, false, err
@@ -332,14 +330,14 @@ func isValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t
kt := desc.ElemTypes[0]
vt := desc.ElemTypes[1]
if seq, ok := v.orderedSequence.(mapLeafSequence); ok {
meSl, err := seq.entries(ctx)
meSl, err := seq.entries()
if err != nil {
return false, false, err
}
for _, entry := range meSl.entries {
isSub, hasMore, err := isValueSubtypeOfDetails(ctx, nbf, entry.key, kt, hasExtra)
isSub, hasMore, err := isValueSubtypeOfDetails(nbf, entry.key, kt, hasExtra)
if err != nil {
return false, false, err
@@ -350,7 +348,7 @@ func isValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t
}
hasExtra = hasExtra || hasMore
isSub, hasExtra, err = isValueSubtypeOfDetails(ctx, nbf, entry.value, vt, hasExtra)
isSub, hasExtra, err = isValueSubtypeOfDetails(nbf, entry.value, vt, hasExtra)
if err != nil {
return false, false, err
@@ -373,7 +371,7 @@ func isValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t
return false, false, err
}
for _, v := range vals {
isSub, hasMore, err := isValueSubtypeOfDetails(ctx, nbf, v, et, hasExtra)
isSub, hasMore, err := isValueSubtypeOfDetails(nbf, v, et, hasExtra)
if err != nil {
return false, false, err
@@ -397,7 +395,7 @@ func isValueSubtypeOfDetails(ctx context.Context, nbf *NomsBinFormat, v Value, t
}
for _, v := range vals {
isSub, hasMore, err := isValueSubtypeOfDetails(ctx, nbf, v, et, hasExtra)
isSub, hasMore, err := isValueSubtypeOfDetails(nbf, v, et, hasExtra)
if err != nil {
return false, false, err
+4 -4
View File
@@ -35,7 +35,7 @@ import (
)
func assertSubtype(ctx context.Context, nbf *NomsBinFormat, t *Type, v Value) {
is, err := IsValueSubtypeOf(ctx, nbf, v, t)
is, err := IsValueSubtypeOf(nbf, v, t)
d.PanicIfError(err)
if !is {
@@ -652,11 +652,11 @@ func TestIsValueSubtypeOf(tt *testing.T) {
vs := newTestValueStore()
assertTrue := func(v Value, t *Type) {
assert.True(IsValueSubtypeOf(context.Background(), vs.Format(), v, t))
assert.True(IsValueSubtypeOf(vs.Format(), v, t))
}
assertFalse := func(v Value, t *Type) {
assert.False(IsValueSubtypeOf(context.Background(), vs.Format(), v, t))
assert.False(IsValueSubtypeOf(vs.Format(), v, t))
}
allTypes := []struct {
@@ -971,7 +971,7 @@ func TestIsValueSubtypeOfDetails(tt *testing.T) {
require.NoError(tt, err)
t, err := makeTestStructTypeFromFieldNames(tString)
require.NoError(tt, err)
isSub, hasExtra, err := IsValueSubtypeOfDetails(context.Background(), vs.Format(), v, t)
isSub, hasExtra, err := IsValueSubtypeOfDetails(vs.Format(), v, t)
require.NoError(tt, err)
a.Equal(exp1, isSub, "expected %t for IsSub, received: %t", exp1, isSub)
if isSub {
+1 -1
View File
@@ -5,7 +5,7 @@ set -eo pipefail
script_dir=$(dirname "$0")
cd $script_dir/../..
paths=`find . -maxdepth 1 -mindepth 1 \( -name gen -prune -o -type d -print -o -type f -name '*.go' -print \)`
paths=`find . -maxdepth 1 -mindepth 1 \( -type d -print -o -type f -name '*.go' -print \)`
goimports -w -local github.com/dolthub/dolt $paths
+1 -1
View File
@@ -320,7 +320,7 @@ NOT_VALID_REPO_ERROR="The current directory is not a valid dolt repository."
@test "no-repo: don't panic if invalid HOME" {
DOLT_ROOT_PATH=
HOME=/this/is/garbage
run dolt
run dolt status
[ "$status" -eq 1 ]
[[ ! "$output" =~ "panic" ]]
[[ "$output" =~ "Failed to load the HOME directory" ]]
+176 -3
View File
@@ -10,6 +10,43 @@ teardown() {
teardown_common
}
extract_value() {
key="$1"
input="$2"
echo "$input" | awk "
BEGIN { in_value = 0 }
/$key: {/ { in_value = 1; next }
match("'$0'", /$key: /) { print substr("'$0'", RSTART+RLENGTH) }
/}/ { if (in_value) { in_value = 0 } }
in_value { gsub(/^[ \t]+/, \"\"); print }
"
}
assert_has_key() {
key="$1"
input="$2"
extracted=$(extract_value "$key" "$input")
if [[ -z $extracted ]]; then
echo "Expected to find key $key"
return 1
else
return 0
fi
}
assert_has_key_value() {
key="$1"
value="$2"
input="$3"
extracted=$(extract_value "$key" "$input")
if [[ "$extracted" != "$value" ]]; then
echo "Expected key $key to have value $value, instead found $extracted"
return 1
else
return 0
fi
}
@test "show: on initialized repo" {
run dolt show
[ "$status" -eq "0" ]
@@ -64,7 +101,7 @@ teardown() {
dolt sql -q "create table testtable (pk int PRIMARY KEY)"
dolt add .
dolt commit -m "commit: add table"
run dolt show
[ $status -eq 0 ]
[[ "$output" =~ "commit: add table" ]] || false
@@ -73,14 +110,150 @@ teardown() {
[[ "$output" =~ "+CREATE TABLE \`testtable\` (" ]] || false
[[ "$output" =~ "+ \`pk\` int NOT NULL," ]] || false
[[ "$output" =~ "+ PRIMARY KEY (\`pk\`)" ]] || false
dolt sql -q 'insert into testtable values (4)'
dolt add .
dolt commit -m "commit: add values"
run dolt show
[ $status -eq 0 ]
[[ "$output" =~ "commit: add values" ]] || false
[[ "$output" =~ "| | pk |" ]] || false
[[ "$output" =~ "| + | 4 |" ]] || false
}
@test "show: --no-pretty" {
skip_nbf_ld_1
dolt commit --allow-empty -m "commit: initialize table1"
run dolt show --no-pretty
[ $status -eq 0 ]
[[ "$output" =~ "SerialMessage" ]] || false
assert_has_key "Name" "$output"
assert_has_key_value "Name" "Bats Tests" "$output"
assert_has_key_value "Desc" "commit: initialize table1" "$output"
assert_has_key_value "Name" "Bats Tests" "$output"
assert_has_key_value "Email" "bats@email.fake" "$output"
assert_has_key "Time" "$output"
assert_has_key_value "Height" "2" "$output"
assert_has_key "RootValue" "$output"
assert_has_key "Parents" "$output"
assert_has_key "ParentClosure" "$output"
}
@test "show: HEAD root" {
skip_nbf_ld_1
dolt sql -q "create table table1 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (1), (2), (3)"
dolt add .
dolt commit -m "commit: initialize table1"
dolt sql -q "create table table2 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (4), (5), (6)"
dolt add .
dolt sql -q "create table table3 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (7), (8), (9)"
head=$(dolt show --no-pretty)
rootValue=$(extract_value RootValue "$head")
echo rootValue=$rootValue
[[ ! -z $rootValue ]] || false
run dolt show $rootValue
[ $status -eq 0 ]
[[ "$output" =~ "table1" ]] || false
[[ ! "$output" =~ "table2" ]] || false
[[ ! "$output" =~ "table3" ]] || false
}
@test "show: WORKING" {
skip_nbf_ld_1
dolt sql -q "create table table1 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (1), (2), (3)"
dolt add .
dolt commit -m "commit: initialize table1"
dolt sql -q "create table table2 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (4), (5), (6)"
dolt add .
dolt sql -q "create table table3 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (7), (8), (9)"
run dolt show WORKING
[ $status -eq 0 ]
[[ "$output" =~ "table1" ]] || false
[[ "$output" =~ "table2" ]] || false
[[ "$output" =~ "table3" ]] || false
}
@test "show: STAGED" {
skip_nbf_ld_1
dolt sql -q "create table table1 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (1), (2), (3)"
dolt add .
dolt commit -m "commit: initialize table1"
dolt sql -q "create table table2 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (4), (5), (6)"
dolt add .
dolt sql -q "create table table3 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (7), (8), (9)"
run dolt show STAGED
[ $status -eq 0 ]
[[ "$output" =~ "table1" ]] || false
[[ "$output" =~ "table2" ]] || false
[[ ! "$output" =~ "table3" ]] || false
}
@test "show: table" {
skip_nbf_ld_1
dolt sql -q "create table table1 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (1), (2), (3)"
dolt add .
dolt commit -m "commit: initialize table1"
dolt sql -q "create table table2 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (4), (5), (6)"
dolt add .
dolt sql -q "create table table3 (pk int PRIMARY KEY)"
dolt sql -q "insert into table1 values (7), (8), (9)"
workingRoot=$(dolt show WORKING)
tableAddress=$(extract_value table1 "$workingRoot")
run dolt show $tableAddress
assert_has_key Schema "$output"
assert_has_key Violations "$output"
assert_has_key Autoinc "$output"
assert_has_key "Primary index" "$output"
assert_has_key "Secondary indexes" "$output"
}
@test "show: pretty commit from hash" {
skip_nbf_ld_1
dolt tag v0
dolt commit --allow-empty -m "commit1"
head=$(dolt show --no-pretty)
parentHash=$(extract_value Parents "$head")
run dolt show "$parentHash"
[[ "$output" =~ "tag: v0" ]] || false
}
@test "show: --no-pretty with old format" {
skip_nbf_dolt
run dolt show --no-pretty
[ $status -eq 1 ]
[[ "$output" =~ "dolt show --no-pretty is not supported when using old LD_1 storage format." ]] || false
}
@test "show: non-commit object with old format" {
skip_nbf_dolt
run dolt show WORKING
[ $status -eq 1 ]
[[ "$output" =~ "dolt show cannot show non-commit objects when using the old LD_1 storage format: WORKING is not a commit" ]] || false
}
@test "show: non-existent branch" {
skip_nbf_dolt
run dolt show branch1
[ $status -eq 1 ]
[[ "$output" =~ "branch not found: branch1" ]] || false
}
+32
View File
@@ -204,6 +204,38 @@ teardown() {
rm -rf db_dir
}
@test "sql: check --data-dir can be used as argument before subcommand" {
# remove config files
rm -rf .doltcfg
rm -rf db_dir
# create data dir
mkdir db_dir
cd db_dir
DATADIR=$(pwd)
# create databases
mkdir dba
cd dba
dolt init
cd ..
mkdir dbb
cd dbb
dolt init
# Ensure --data-dir flag is really used.
cd /tmp
# show databases, expect all
run dolt --data-dir="$DATADIR" sql -q "show databases;"
[ "$status" -eq 0 ]
[[ "$output" =~ "dba" ]] || false
[[ "$output" =~ "dbb" ]] || false
dolt --data-dir="$DATADIR" sql -q "use dba; create table tablea (id int);"
}
@test "sql: check configurations specify doltcfg directory" {
# remove any previous config directories
rm -rf .doltcfg