mirror of
https://github.com/dolthub/dolt.git
synced 2026-05-20 19:38:55 -05:00
Merge branch 'main' into zachmu/projected-cols
This commit is contained in:
@@ -129,7 +129,9 @@ var mergeAbortDetails = `Abort the current conflict resolution process, and try
|
||||
If there were uncommitted working set changes present when the merge started, {{.EmphasisLeft}}dolt merge --abort{{.EmphasisRight}} will be unable to reconstruct these changes. It is therefore recommended to always commit or stash your changes before running dolt merge.
|
||||
`
|
||||
|
||||
// Creates the argparser shared dolt commit cli and DOLT_COMMIT.
|
||||
var branchForceFlagDesc = "Reset {{.LessThan}}branchname{{.GreaterThan}} to {{.LessThan}}startpoint{{.GreaterThan}}, even if {{.LessThan}}branchname{{.GreaterThan}} exists already. Without {{.EmphasisLeft}}-f{{.EmphasisRight}}, {{.EmphasisLeft}}dolt branch{{.EmphasisRight}} refuses to change an existing branch. In combination with {{.EmphasisLeft}}-d{{.EmphasisRight}} (or {{.EmphasisLeft}}--delete{{.EmphasisRight}}), allow deleting the branch irrespective of its merged status. In combination with -m (or {{.EmphasisLeft}}--move{{.EmphasisRight}}), allow renaming the branch even if the new branch name already exists, the same applies for {{.EmphasisLeft}}-c{{.EmphasisRight}} (or {{.EmphasisLeft}}--copy{{.EmphasisRight}})."
|
||||
|
||||
// CreateCommitArgParser creates the argparser shared dolt commit cli and DOLT_COMMIT.
|
||||
func CreateCommitArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsString(MessageArg, "m", "msg", "Use the given {{.LessThan}}msg{{.GreaterThan}} as the commit message.")
|
||||
@@ -174,7 +176,7 @@ func CreatePushArgParser() *argparser.ArgParser {
|
||||
func CreateAddArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"table", "Working table(s) to add to the list tables staged to be committed. The abbreviation '.' can be used to add all tables."})
|
||||
ap.SupportsFlag("all", "A", "Stages any and all changes (adds, deletes, and modifications).")
|
||||
ap.SupportsFlag(AllFlag, "A", "Stages any and all changes (adds, deletes, and modifications).")
|
||||
return ap
|
||||
}
|
||||
|
||||
@@ -228,7 +230,6 @@ func CreateCherryPickArgParser() *argparser.ArgParser {
|
||||
|
||||
func CreateFetchArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsFlag(ForceFlag, "f", "Update refs to remote branches with the current state of the remote, overwriting any conflicting history.")
|
||||
return ap
|
||||
}
|
||||
|
||||
@@ -256,11 +257,12 @@ func CreatePullArgParser() *argparser.ArgParser {
|
||||
|
||||
func CreateBranchArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsFlag(ForceFlag, "f", "Ignores any foreign key warnings and proceeds with the commit.")
|
||||
ap.SupportsFlag(ForceFlag, "f", branchForceFlagDesc)
|
||||
ap.SupportsFlag(CopyFlag, "c", "Create a copy of a branch.")
|
||||
ap.SupportsFlag(MoveFlag, "m", "Move/rename a branch")
|
||||
ap.SupportsFlag(DeleteFlag, "d", "Delete a branch. The branch must be fully merged in its upstream branch.")
|
||||
ap.SupportsFlag(DeleteForceFlag, "", "Shortcut for {{.EmphasisLeft}}--delete --force{{.EmphasisRight}}.")
|
||||
ap.SupportsString(TrackFlag, "t", "", "When creating a new branch, set up 'upstream' configuration.")
|
||||
|
||||
return ap
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ func printBackups(dEnv *env.DoltEnv, apr *argparser.ArgParseResults) errhand.Ver
|
||||
}
|
||||
|
||||
for _, r := range backups {
|
||||
if apr.Contains(verboseFlag) {
|
||||
if apr.Contains(cli.VerboseFlag) {
|
||||
paramStr := make([]byte, 0)
|
||||
if len(r.Params) > 0 {
|
||||
paramStr, _ = json.Marshal(r.Params)
|
||||
|
||||
@@ -34,8 +34,6 @@ import (
|
||||
"github.com/dolthub/dolt/go/libraries/utils/set"
|
||||
)
|
||||
|
||||
var branchForceFlagDesc = "Reset {{.LessThan}}branchname{{.GreaterThan}} to {{.LessThan}}startpoint{{.GreaterThan}}, even if {{.LessThan}}branchname{{.GreaterThan}} exists already. Without {{.EmphasisLeft}}-f{{.EmphasisRight}}, {{.EmphasisLeft}}dolt branch{{.EmphasisRight}} refuses to change an existing branch. In combination with {{.EmphasisLeft}}-d{{.EmphasisRight}} (or {{.EmphasisLeft}}--delete{{.EmphasisRight}}), allow deleting the branch irrespective of its merged status. In combination with -m (or {{.EmphasisLeft}}--move{{.EmphasisRight}}), allow renaming the branch even if the new branch name already exists, the same applies for {{.EmphasisLeft}}-c{{.EmphasisRight}} (or {{.EmphasisLeft}}--copy{{.EmphasisRight}})."
|
||||
|
||||
var branchDocs = cli.CommandDocumentationContent{
|
||||
ShortDesc: `List, create, or delete branches`,
|
||||
LongDesc: `If {{.EmphasisLeft}}--list{{.EmphasisRight}} is given, or if there are no non-option arguments, existing branches are listed. The current branch will be highlighted with an asterisk. With no options, only local branches are listed. With {{.EmphasisLeft}}-r{{.EmphasisRight}}, only remote branches are listed. With {{.EmphasisLeft}}-a{{.EmphasisRight}} both local and remote branches are listed. {{.EmphasisLeft}}-v{{.EmphasisRight}} causes the hash of the commit that the branches are at to be printed as well.
|
||||
@@ -60,15 +58,7 @@ With a {{.EmphasisLeft}}-d{{.EmphasisRight}}, {{.LessThan}}branchname{{.GreaterT
|
||||
|
||||
const (
|
||||
listFlag = "list"
|
||||
forceFlag = "force"
|
||||
copyFlag = "copy"
|
||||
moveFlag = "move"
|
||||
deleteFlag = "delete"
|
||||
deleteForceFlag = "D"
|
||||
verboseFlag = cli.VerboseFlag
|
||||
allFlag = "all"
|
||||
datasetsFlag = "datasets"
|
||||
remoteFlag = "remote"
|
||||
showCurrentFlag = "show-current"
|
||||
)
|
||||
|
||||
@@ -92,20 +82,14 @@ func (cmd BranchCmd) Docs() *cli.CommandDocumentation {
|
||||
}
|
||||
|
||||
func (cmd BranchCmd) ArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap := cli.CreateBranchArgParser()
|
||||
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"start-point", "A commit that a new branch should point at."})
|
||||
ap.SupportsFlag(listFlag, "", "List branches")
|
||||
ap.SupportsFlag(forceFlag, "f", branchForceFlagDesc)
|
||||
ap.SupportsFlag(copyFlag, "c", "Create a copy of a branch.")
|
||||
ap.SupportsFlag(moveFlag, "m", "Move/rename a branch")
|
||||
ap.SupportsFlag(deleteFlag, "d", "Delete a branch. The branch must be fully merged in its upstream branch.")
|
||||
ap.SupportsFlag(deleteForceFlag, "", "Shortcut for {{.EmphasisLeft}}--delete --force{{.EmphasisRight}}.")
|
||||
ap.SupportsFlag(verboseFlag, "v", "When in list mode, show the hash and commit subject line for each head")
|
||||
ap.SupportsFlag(allFlag, "a", "When in list mode, shows remote tracked branches")
|
||||
ap.SupportsFlag(cli.VerboseFlag, "v", "When in list mode, show the hash and commit subject line for each head")
|
||||
ap.SupportsFlag(cli.AllFlag, "a", "When in list mode, shows remote tracked branches")
|
||||
ap.SupportsFlag(datasetsFlag, "", "List all datasets in the database")
|
||||
ap.SupportsFlag(remoteFlag, "r", "When in list mode, show only remote tracked branches. When with -d, delete a remote tracking branch.")
|
||||
ap.SupportsFlag(cli.RemoteParam, "r", "When in list mode, show only remote tracked branches. When with -d, delete a remote tracking branch.")
|
||||
ap.SupportsFlag(showCurrentFlag, "", "Print the name of the current branch")
|
||||
ap.SupportsString(cli.TrackFlag, "t", "", "When creating a new branch, set up 'upstream' configuration.")
|
||||
return ap
|
||||
}
|
||||
|
||||
@@ -121,13 +105,13 @@ func (cmd BranchCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
apr := cli.ParseArgsOrDie(ap, args, help)
|
||||
|
||||
switch {
|
||||
case apr.Contains(moveFlag):
|
||||
case apr.Contains(cli.MoveFlag):
|
||||
return moveBranch(ctx, dEnv, apr, usage)
|
||||
case apr.Contains(copyFlag):
|
||||
case apr.Contains(cli.CopyFlag):
|
||||
return copyBranch(ctx, dEnv, apr, usage)
|
||||
case apr.Contains(deleteFlag):
|
||||
return deleteBranches(ctx, dEnv, apr, usage, apr.Contains(forceFlag))
|
||||
case apr.Contains(deleteForceFlag):
|
||||
case apr.Contains(cli.DeleteFlag):
|
||||
return deleteBranches(ctx, dEnv, apr, usage, apr.Contains(cli.ForceFlag))
|
||||
case apr.Contains(cli.DeleteForceFlag):
|
||||
return deleteBranches(ctx, dEnv, apr, usage, true)
|
||||
case apr.Contains(listFlag):
|
||||
return printBranches(ctx, dEnv, apr, usage)
|
||||
@@ -145,9 +129,9 @@ func (cmd BranchCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
func printBranches(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgParseResults, _ cli.UsagePrinter) int {
|
||||
branchSet := set.NewStrSet(apr.Args)
|
||||
|
||||
verbose := apr.Contains(verboseFlag)
|
||||
verbose := apr.Contains(cli.VerboseFlag)
|
||||
printRemote := apr.Contains(cli.RemoteParam)
|
||||
printAll := apr.Contains(allFlag)
|
||||
printAll := apr.Contains(cli.AllFlag)
|
||||
|
||||
branches, err := dEnv.DoltDB.GetHeadRefs(ctx)
|
||||
|
||||
@@ -186,7 +170,6 @@ func printBranches(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPar
|
||||
} else if branch.GetType() == ref.RemoteRefType {
|
||||
branchName = " " + color.RedString("remotes/"+branch.GetPath())
|
||||
branchLen += len("remotes/")
|
||||
|
||||
}
|
||||
|
||||
if verbose {
|
||||
@@ -260,7 +243,7 @@ func moveBranch(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgParseR
|
||||
return 1
|
||||
}
|
||||
|
||||
force := apr.Contains(forceFlag)
|
||||
force := apr.Contains(cli.ForceFlag)
|
||||
src := apr.Arg(0)
|
||||
dest := apr.Arg(1)
|
||||
err := actions.RenameBranch(ctx, dEnv.DbData(), src, apr.Arg(1), dEnv, force)
|
||||
@@ -290,7 +273,7 @@ func copyBranch(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgParseR
|
||||
return 1
|
||||
}
|
||||
|
||||
force := apr.Contains(forceFlag)
|
||||
force := apr.Contains(cli.ForceFlag)
|
||||
src := apr.Arg(0)
|
||||
dest := apr.Arg(1)
|
||||
err := actions.CopyBranch(ctx, dEnv, src, dest, force)
|
||||
@@ -323,7 +306,7 @@ func deleteBranches(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPa
|
||||
|
||||
err := actions.DeleteBranch(ctx, dEnv.DbData(), brName, actions.DeleteOptions{
|
||||
Force: force,
|
||||
Remote: apr.Contains(remoteFlag),
|
||||
Remote: apr.Contains(cli.RemoteParam),
|
||||
}, dEnv)
|
||||
|
||||
if err != nil {
|
||||
@@ -373,9 +356,8 @@ func createBranch(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars
|
||||
}
|
||||
|
||||
if apr.NArg() == 2 {
|
||||
newBranch = apr.Arg(0)
|
||||
startPt = apr.Arg(1)
|
||||
remote, remoteBranch = ParseRemoteBranchName(startPt)
|
||||
// branchName and startPt are already set
|
||||
remote, remoteBranch = actions.ParseRemoteBranchName(startPt)
|
||||
_, remoteOk := remotes[remote]
|
||||
if !remoteOk {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("'%s' is not a valid remote ref and a branch '%s' cannot be created from it", startPt, newBranch).Build(), usage)
|
||||
@@ -384,12 +366,12 @@ func createBranch(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars
|
||||
// if track option is defined with no value,
|
||||
// the track value can either be starting point name OR branch name
|
||||
startPt = trackVal
|
||||
remote, remoteBranch = ParseRemoteBranchName(startPt)
|
||||
remote, remoteBranch = actions.ParseRemoteBranchName(startPt)
|
||||
_, remoteOk := remotes[remote]
|
||||
if !remoteOk {
|
||||
newBranch = trackVal
|
||||
startPt = apr.Arg(0)
|
||||
remote, remoteBranch = ParseRemoteBranchName(startPt)
|
||||
remote, remoteBranch = actions.ParseRemoteBranchName(startPt)
|
||||
_, remoteOk = remotes[remote]
|
||||
if !remoteOk {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("'%s' is not a valid remote ref and a branch '%s' cannot be created from it", startPt, newBranch).Build(), usage)
|
||||
@@ -398,7 +380,7 @@ func createBranch(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars
|
||||
}
|
||||
}
|
||||
|
||||
err := actions.CreateBranchWithStartPt(ctx, dEnv.DbData(), newBranch, startPt, apr.Contains(forceFlag))
|
||||
err := actions.CreateBranchWithStartPt(ctx, dEnv.DbData(), newBranch, startPt, apr.Contains(cli.ForceFlag))
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError(err.Error()).Build(), usage)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package commands
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
|
||||
|
||||
@@ -154,7 +153,7 @@ func checkoutNewBranch(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.Ar
|
||||
} else if trackVal == "inherit" {
|
||||
return errhand.VerboseErrorFromError(fmt.Errorf("--track='inherit' is not supported yet"))
|
||||
}
|
||||
remoteName, remoteBranchName = ParseRemoteBranchName(startPt)
|
||||
remoteName, remoteBranchName = actions.ParseRemoteBranchName(startPt)
|
||||
remotes, err := dEnv.RepoStateReader().GetRemotes()
|
||||
if err != nil {
|
||||
return errhand.BuildDError(err.Error()).Build()
|
||||
@@ -191,7 +190,7 @@ func checkoutNewBranch(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.Ar
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
remoteName, remoteBranchName = ParseRemoteBranchName(startPt)
|
||||
remoteName, remoteBranchName = actions.ParseRemoteBranchName(startPt)
|
||||
_, remoteOk := remotes[remoteName]
|
||||
if !remoteOk {
|
||||
return nil
|
||||
@@ -326,15 +325,7 @@ func SetRemoteUpstreamForBranchRef(dEnv *env.DoltEnv, remote, remoteBranch strin
|
||||
return errhand.BuildDError(fmt.Errorf("%w: '%s'", err, remote).Error()).Build()
|
||||
}
|
||||
|
||||
src := refSpec.SrcRef(branchRef)
|
||||
dest := refSpec.DestRef(src)
|
||||
|
||||
err = dEnv.RepoStateWriter().UpdateBranch(branchRef.GetPath(), env.BranchConfig{
|
||||
Merge: ref.MarshalableRef{
|
||||
Ref: dest,
|
||||
},
|
||||
Remote: remote,
|
||||
})
|
||||
err = env.SetRemoteUpstreamForRefSpec(dEnv.RepoStateWriter(), refSpec, remote, branchRef)
|
||||
if err != nil {
|
||||
return errhand.BuildDError(err.Error()).Build()
|
||||
}
|
||||
@@ -348,12 +339,3 @@ func unreadableRootToVErr(err error) errhand.VerboseError {
|
||||
bdr := errhand.BuildDError("error: unable to read the %s", rt.String())
|
||||
return bdr.AddCause(doltdb.GetUnreachableRootCause(err)).Build()
|
||||
}
|
||||
|
||||
func ParseRemoteBranchName(startPt string) (string, string) {
|
||||
startPt = strings.TrimPrefix(startPt, "remotes/")
|
||||
names := strings.Split(startPt, "/")
|
||||
if len(names) < 2 {
|
||||
return "", ""
|
||||
}
|
||||
return names[0], strings.Join(names[1:], "/")
|
||||
}
|
||||
|
||||
@@ -77,22 +77,14 @@ func (cmd FetchCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
updateMode := ref.UpdateMode{Force: apr.Contains(cli.ForceFlag)}
|
||||
|
||||
srcDB, err := r.GetRemoteDBWithoutCaching(ctx, dEnv.DbData().Ddb.ValueReadWriter().Format(), dEnv)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
|
||||
err = actions.FetchRefSpecs(ctx, dEnv.DbData(), srcDB, refSpecs, r, updateMode, buildProgStarter(downloadLanguage), stopProgFuncs)
|
||||
switch err {
|
||||
case doltdb.ErrUpToDate:
|
||||
return HandleVErrAndExitCode(nil, usage)
|
||||
case actions.ErrCantFF:
|
||||
verr := errhand.BuildDError("error: fetch failed, can't fast forward remote tracking ref").AddCause(err).Build()
|
||||
return HandleVErrAndExitCode(verr, usage)
|
||||
}
|
||||
if err != nil {
|
||||
err = actions.FetchRefSpecs(ctx, dEnv.DbData(), srcDB, refSpecs, r, ref.UpdateMode{Force: true}, buildProgStarter(downloadLanguage), stopProgFuncs)
|
||||
if err != nil && err != doltdb.ErrUpToDate {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
return HandleVErrAndExitCode(nil, usage)
|
||||
|
||||
@@ -84,9 +84,9 @@ func (cmd FilterBranchCmd) Docs() *cli.CommandDocumentation {
|
||||
|
||||
func (cmd FilterBranchCmd) ArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsFlag(verboseFlag, "v", "logs more information")
|
||||
ap.SupportsFlag(cli.VerboseFlag, "v", "logs more information")
|
||||
ap.SupportsFlag(branchesFlag, "b", "filter all branches")
|
||||
ap.SupportsFlag(allFlag, "a", "filter all branches and tags")
|
||||
ap.SupportsFlag(cli.AllFlag, "a", "filter all branches and tags")
|
||||
return ap
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func (cmd FilterBranchCmd) Exec(ctx context.Context, commandStr string, args []s
|
||||
}
|
||||
|
||||
query := apr.Arg(0)
|
||||
verbose := apr.Contains(verboseFlag)
|
||||
verbose := apr.Contains(cli.VerboseFlag)
|
||||
notFound := make(missingTbls)
|
||||
|
||||
replay := func(ctx context.Context, commit, _, _ *doltdb.Commit) (*doltdb.RootValue, error) {
|
||||
@@ -160,7 +160,7 @@ func (cmd FilterBranchCmd) Exec(ctx context.Context, commandStr string, args []s
|
||||
switch {
|
||||
case apr.Contains(branchesFlag):
|
||||
err = rebase.AllBranches(ctx, dEnv, replay, nerf)
|
||||
case apr.Contains(allFlag):
|
||||
case apr.Contains(cli.AllFlag):
|
||||
err = rebase.AllBranchesAndTags(ctx, dEnv, replay, nerf)
|
||||
default:
|
||||
err = rebase.CurrentBranch(ctx, dEnv, replay, nerf)
|
||||
|
||||
@@ -66,9 +66,9 @@ func (cmd LsCmd) Docs() *cli.CommandDocumentation {
|
||||
|
||||
func (cmd LsCmd) ArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsFlag(verboseFlag, "v", "show the hash of the table")
|
||||
ap.SupportsFlag(cli.VerboseFlag, "v", "show the hash of the table")
|
||||
ap.SupportsFlag(systemFlag, "s", "show system tables")
|
||||
ap.SupportsFlag(allFlag, "a", "show system tables")
|
||||
ap.SupportsFlag(cli.AllFlag, "a", "show system tables")
|
||||
return ap
|
||||
}
|
||||
|
||||
@@ -83,8 +83,8 @@ func (cmd LsCmd) Exec(ctx context.Context, commandStr string, args []string, dEn
|
||||
help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, lsDocs, ap))
|
||||
apr := cli.ParseArgsOrDie(ap, args, help)
|
||||
|
||||
if apr.Contains(systemFlag) && apr.Contains(allFlag) {
|
||||
verr := errhand.BuildDError("--%s and --%s are mutually exclusive", systemFlag, allFlag).SetPrintUsage().Build()
|
||||
if apr.Contains(systemFlag) && apr.Contains(cli.AllFlag) {
|
||||
verr := errhand.BuildDError("--%s and --%s are mutually exclusive", systemFlag, cli.AllFlag).SetPrintUsage().Build()
|
||||
HandleVErrAndExitCode(verr, usage)
|
||||
}
|
||||
|
||||
@@ -99,13 +99,13 @@ func (cmd LsCmd) Exec(ctx context.Context, commandStr string, args []string, dEn
|
||||
}
|
||||
|
||||
if verr == nil {
|
||||
if !apr.Contains(systemFlag) || apr.Contains(allFlag) {
|
||||
verr = printUserTables(ctx, root, label, apr.Contains(verboseFlag))
|
||||
if !apr.Contains(systemFlag) || apr.Contains(cli.AllFlag) {
|
||||
verr = printUserTables(ctx, root, label, apr.Contains(cli.VerboseFlag))
|
||||
cli.Println()
|
||||
}
|
||||
|
||||
if verr == nil && (apr.Contains(systemFlag) || apr.Contains(allFlag)) {
|
||||
verr = printSystemTables(ctx, root, dEnv.DoltDB, apr.Contains(verboseFlag))
|
||||
if verr == nil && (apr.Contains(systemFlag) || apr.Contains(cli.AllFlag)) {
|
||||
verr = printSystemTables(ctx, root, dEnv.DoltDB, apr.Contains(cli.VerboseFlag))
|
||||
cli.Println()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,15 +85,11 @@ func (cmd RemoteCmd) Docs() *cli.CommandDocumentation {
|
||||
}
|
||||
|
||||
func (cmd RemoteCmd) ArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap := cli.CreateRemoteArgParser()
|
||||
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"region", "cloud provider region associated with this remote."})
|
||||
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"creds-type", "credential type. Valid options are role, env, and file. See the help section for additional details."})
|
||||
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"profile", "AWS profile to use."})
|
||||
ap.SupportsFlag(verboseFlag, "v", "When printing the list of remotes adds additional details.")
|
||||
ap.SupportsString(dbfactory.AWSRegionParam, "", "region", "")
|
||||
ap.SupportsValidatedString(dbfactory.AWSCredsTypeParam, "", "creds-type", "", argparser.ValidatorFromStrList(dbfactory.AWSCredsTypeParam, dbfactory.AWSCredTypes))
|
||||
ap.SupportsString(dbfactory.AWSCredsFileParam, "", "file", "AWS credentials file")
|
||||
ap.SupportsString(dbfactory.AWSCredsProfile, "", "profile", "AWS profile to use")
|
||||
ap.SupportsFlag(cli.VerboseFlag, "v", "When printing the list of remotes adds additional details.")
|
||||
ap.SupportsString(dbfactory.OSSCredsFileParam, "", "file", "OSS credentials file")
|
||||
ap.SupportsString(dbfactory.OSSCredsProfile, "", "profile", "OSS profile to use")
|
||||
return ap
|
||||
@@ -216,7 +212,7 @@ func printRemotes(dEnv *env.DoltEnv, apr *argparser.ArgParseResults) errhand.Ver
|
||||
}
|
||||
|
||||
for _, r := range remotes {
|
||||
if apr.Contains(verboseFlag) {
|
||||
if apr.Contains(cli.VerboseFlag) {
|
||||
paramStr := make([]byte, 0)
|
||||
if len(r.Params) > 0 {
|
||||
paramStr, _ = json.Marshal(r.Params)
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/abiosoft/readline"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
@@ -819,6 +820,8 @@ func runMultiStatementMode(ctx *sql.Context, se *engine.SqlEngine, input io.Read
|
||||
}
|
||||
}
|
||||
|
||||
// store start time for query
|
||||
ctx.SetQueryTime(time.Now())
|
||||
sqlSch, rowIter, err := processParsedQuery(ctx, query, se, sqlStatement)
|
||||
if err != nil {
|
||||
handleError(scanner.statementStartLine, query, err)
|
||||
|
||||
@@ -68,6 +68,20 @@ const (
|
||||
disableFkChecks = "disable-fk-checks"
|
||||
)
|
||||
|
||||
var jsonInputFileHelp = "The expected JSON input file format is:" + `
|
||||
|
||||
{ "rows":
|
||||
[
|
||||
{
|
||||
"column_name":"value"
|
||||
...
|
||||
}, ...
|
||||
]
|
||||
}
|
||||
|
||||
where column_name is the name of a column of the table being imported and value is the data for that column in the table.
|
||||
`
|
||||
|
||||
var importDocs = cli.CommandDocumentationContent{
|
||||
ShortDesc: `Imports data into a dolt table`,
|
||||
LongDesc: `If {{.EmphasisLeft}}--create-table | -c{{.EmphasisRight}} is given the operation will create {{.LessThan}}table{{.GreaterThan}} and import the contents of file into it. If a table already exists at this location then the operation will fail, unless the {{.EmphasisLeft}}--force | -f{{.EmphasisRight}} flag is provided. The force flag forces the existing table to be overwritten.
|
||||
@@ -86,6 +100,8 @@ A mapping file can be used to map fields between the file being imported and the
|
||||
|
||||
` + schcmds.MappingFileHelp +
|
||||
`
|
||||
` + jsonInputFileHelp +
|
||||
`
|
||||
In create, update, and replace scenarios the file's extension is used to infer the type of the file. If a file does not have the expected extension then the {{.EmphasisLeft}}--file-type{{.EmphasisRight}} parameter should be used to explicitly define the format of the file in one of the supported formats (csv, psv, json, xlsx). For files separated by a delimiter other than a ',' (type csv) or a '|' (type psv), the --delim parameter can be used to specify a delimiter`,
|
||||
|
||||
Synopsis: []string{
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "0.52.19"
|
||||
Version = "0.52.20"
|
||||
)
|
||||
|
||||
var dumpDocsCommand = &commands.DumpDocsCmd{}
|
||||
@@ -186,7 +186,7 @@ func runMain() int {
|
||||
cli.Println(cyanStar, " /trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.")
|
||||
cli.Println()
|
||||
|
||||
err := http.ListenAndServe("localhost:6060", nil)
|
||||
err := http.ListenAndServe("0.0.0.0:6060", nil)
|
||||
|
||||
if err != nil {
|
||||
cli.Println(color.YellowString("pprof server exited with error: %v", err))
|
||||
|
||||
@@ -15,7 +15,7 @@ require (
|
||||
github.com/dolthub/fslock v0.0.3
|
||||
github.com/dolthub/ishell v0.0.0-20221214210346-d7db0b066488
|
||||
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81
|
||||
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8
|
||||
github.com/dolthub/vitess v0.0.0-20230210003150-3065f526d869
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
||||
@@ -58,7 +58,7 @@ require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.3
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/creasty/defaults v1.6.0
|
||||
github.com/dolthub/go-mysql-server v0.14.1-0.20230203182436-2dac5eaba602
|
||||
github.com/dolthub/go-mysql-server v0.14.1-0.20230210003917-ba6b4d6584b0
|
||||
github.com/google/flatbuffers v2.0.6+incompatible
|
||||
github.com/kch42/buzhash v0.0.0-20160816060738-9bdec3dec7c6
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
|
||||
@@ -161,16 +161,16 @@ github.com/dolthub/flatbuffers v1.13.0-dh.1 h1:OWJdaPep22N52O/0xsUevxJ6Qfw1M2txC
|
||||
github.com/dolthub/flatbuffers v1.13.0-dh.1/go.mod h1:CorYGaDmXjHz1Z7i50PYXG1Ricn31GcA2wNOTFIQAKE=
|
||||
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.14.1-0.20230203182436-2dac5eaba602 h1:2tKO9mNuquQNmpcqFm4YhE6vyQR5/2bepkxUZnW1X9w=
|
||||
github.com/dolthub/go-mysql-server v0.14.1-0.20230203182436-2dac5eaba602/go.mod h1:aVtgxAf6Bfs0hCj+KzIH7Y1aAxg7/7FlslouCh94VVQ=
|
||||
github.com/dolthub/go-mysql-server v0.14.1-0.20230210003917-ba6b4d6584b0 h1:Z8QzgtCJfgApWyrXTIyooASjoRrbBdOW24Im2JSE0Ro=
|
||||
github.com/dolthub/go-mysql-server v0.14.1-0.20230210003917-ba6b4d6584b0/go.mod h1:3PGGtLcVPnJumgozqqAKZPae88QmvkOd1KGS+Z2/RXU=
|
||||
github.com/dolthub/ishell v0.0.0-20221214210346-d7db0b066488 h1:0HHu0GWJH0N6a6keStrHhUAK5/o9LVfkh44pvsV4514=
|
||||
github.com/dolthub/ishell v0.0.0-20221214210346-d7db0b066488/go.mod h1:ehexgi1mPxRTk0Mok/pADALuHbvATulTh6gzr7NzZto=
|
||||
github.com/dolthub/jsonpath v0.0.0-20210609232853-d49537a30474 h1:xTrR+l5l+1Lfq0NvhiEsctylXinUMFhhsqaEcl414p8=
|
||||
github.com/dolthub/jsonpath v0.0.0-20210609232853-d49537a30474/go.mod h1:kMz7uXOXq4qRriCEyZ/LUeTqraLJCjf0WVZcUi6TxUY=
|
||||
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9XGFa6q5Ap4Z/OhNkAMBaK5YeuEzwJt+NZdhiE=
|
||||
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY=
|
||||
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8 h1:h1DBe5+9JIArCVsBV14fA+RHDXWY8ynUheDL5ZVPOTg=
|
||||
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
|
||||
github.com/dolthub/vitess v0.0.0-20230210003150-3065f526d869 h1:RiSFAJqwBJmFbISgxWEdpljUak1uFtNCKG0zGT8xzA4=
|
||||
github.com/dolthub/vitess v0.0.0-20230210003150-3065f526d869/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
|
||||
@@ -931,6 +931,7 @@ func (ddb *DoltDB) GetRefsOfType(ctx context.Context, refTypeFilter map[ref.RefT
|
||||
}
|
||||
|
||||
// NewBranchAtCommit creates a new branch with HEAD at the commit given. Branch names must pass IsValidUserBranchName.
|
||||
// Silently overwrites any existing branch with the same name given, if one exists.
|
||||
func (ddb *DoltDB) NewBranchAtCommit(ctx context.Context, branchRef ref.DoltRef, commit *Commit) error {
|
||||
if !IsValidBranchRef(branchRef) {
|
||||
panic(fmt.Sprintf("invalid branch name %s, use IsValidUserBranchName check", branchRef.String()))
|
||||
|
||||
@@ -155,6 +155,7 @@ var persistedSystemTables = []string{
|
||||
|
||||
var generatedSystemTables = []string{
|
||||
BranchesTableName,
|
||||
RemoteBranchesTableName,
|
||||
LogTableName,
|
||||
TableOfTablesInConflictName,
|
||||
TableOfTablesWithViolationsName,
|
||||
@@ -268,6 +269,9 @@ const (
|
||||
// BranchesTableName is the branches system table name
|
||||
BranchesTableName = "dolt_branches"
|
||||
|
||||
// RemoteBranchesTableName is the all-branches system table name
|
||||
RemoteBranchesTableName = "dolt_remote_branches"
|
||||
|
||||
// RemotesTableName is the remotes system table name
|
||||
RemotesTableName = "dolt_remotes"
|
||||
|
||||
|
||||
+30
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/cli"
|
||||
@@ -494,3 +495,32 @@ func HandleInitRemoteStorageClientErr(name, url string, err error) error {
|
||||
var detail = fmt.Sprintf("the remote: %s '%s' could not be accessed", name, url)
|
||||
return fmt.Errorf("%w; %s; %s", ErrFailedToGetRemoteDb, detail, err.Error())
|
||||
}
|
||||
|
||||
// ParseRemoteBranchName takes remote branch ref name, parses it and returns remote branch name.
|
||||
// For example, it parses the input string 'origin/john/mybranch' and returns remote name 'origin' and branch name 'john/mybranch'.
|
||||
func ParseRemoteBranchName(startPt string) (string, string) {
|
||||
startPt = strings.TrimPrefix(startPt, "remotes/")
|
||||
names := strings.SplitN(startPt, "/", 2)
|
||||
if len(names) < 2 {
|
||||
return "", ""
|
||||
}
|
||||
return names[0], names[1]
|
||||
}
|
||||
|
||||
// GetRemoteBranchRef returns a remote ref with matching name for a branch for each remote.
|
||||
func GetRemoteBranchRef(ctx context.Context, ddb *doltdb.DoltDB, name string) ([]ref.RemoteRef, error) {
|
||||
remoteRefFilter := map[ref.RefType]struct{}{ref.RemoteRefType: {}}
|
||||
refs, err := ddb.GetRefsOfType(ctx, remoteRefFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var remoteRef []ref.RemoteRef
|
||||
for _, rf := range refs {
|
||||
if remRef, ok := rf.(ref.RemoteRef); ok && remRef.GetBranch() == name {
|
||||
remoteRef = append(remoteRef, remRef)
|
||||
}
|
||||
}
|
||||
|
||||
return remoteRef, nil
|
||||
}
|
||||
|
||||
-20
@@ -17,8 +17,6 @@ package actions
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/diff"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/set"
|
||||
@@ -120,21 +118,3 @@ func RemoveDocsTable(tbls []string) []string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetRemoteBranchRef returns a remote ref with matching name for a branch for each remotes.
|
||||
func GetRemoteBranchRef(ctx context.Context, ddb *doltdb.DoltDB, name string) ([]ref.RemoteRef, error) {
|
||||
remoteRefFilter := map[ref.RefType]struct{}{ref.RemoteRefType: {}}
|
||||
refs, err := ddb.GetRefsOfType(ctx, remoteRefFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var remoteRef []ref.RemoteRef
|
||||
for _, rf := range refs {
|
||||
if remRef, ok := rf.(ref.RemoteRef); ok && remRef.GetBranch() == name {
|
||||
remoteRef = append(remoteRef, remRef)
|
||||
}
|
||||
}
|
||||
|
||||
return remoteRef, nil
|
||||
}
|
||||
|
||||
Vendored
+14
@@ -554,3 +554,17 @@ func GetDefaultBranch(dEnv *DoltEnv, branches []ref.DoltRef) string {
|
||||
|
||||
return branches[0].GetPath()
|
||||
}
|
||||
|
||||
// SetRemoteUpstreamForRefSpec set upstream for given RefSpec, remote name and branch ref. It uses given RepoStateWriter
|
||||
// to persist upstream tracking branch information.
|
||||
func SetRemoteUpstreamForRefSpec(rsw RepoStateWriter, refSpec ref.RefSpec, remote string, branchRef ref.DoltRef) error {
|
||||
src := refSpec.SrcRef(branchRef)
|
||||
dest := refSpec.DestRef(src)
|
||||
|
||||
return rsw.UpdateBranch(branchRef.GetPath(), BranchConfig{
|
||||
Merge: ref.MarshalableRef{
|
||||
Ref: dest,
|
||||
},
|
||||
Remote: remote,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -146,39 +146,54 @@ func prollyChildSecDiffFkConstraintViolations(
|
||||
ctx context.Context,
|
||||
foreignKey doltdb.ForeignKey,
|
||||
postParent, postChild *constraintViolationsLoadedTable,
|
||||
preChildScndryIdx prolly.Map,
|
||||
preChildSecIdx prolly.Map,
|
||||
receiver FKViolationReceiver) error {
|
||||
postChildRowData := durable.ProllyMapFromIndex(postChild.RowData)
|
||||
postChildScndryIdx := durable.ProllyMapFromIndex(postChild.IndexData)
|
||||
parentScndryIdx := durable.ProllyMapFromIndex(postParent.IndexData)
|
||||
postChildSecIdx := durable.ProllyMapFromIndex(postChild.IndexData)
|
||||
parentSecIdx := durable.ProllyMapFromIndex(postParent.IndexData)
|
||||
|
||||
idxDesc, _ := parentScndryIdx.Descriptors()
|
||||
partialDesc := idxDesc.PrefixDesc(len(foreignKey.TableColumns))
|
||||
partialKB := val.NewTupleBuilder(partialDesc)
|
||||
parentSecIdxDesc, _ := parentSecIdx.Descriptors()
|
||||
partialDesc := parentSecIdxDesc.PrefixDesc(len(foreignKey.TableColumns))
|
||||
childPriKD, _ := postChildRowData.Descriptors()
|
||||
childPriKB := val.NewTupleBuilder(childPriKD)
|
||||
|
||||
primaryKD, _ := postChildRowData.Descriptors()
|
||||
kb := val.NewTupleBuilder(primaryKD)
|
||||
|
||||
err := prolly.DiffMaps(ctx, preChildScndryIdx, postChildScndryIdx, func(ctx context.Context, diff tree.Diff) error {
|
||||
var parentSecIdxCur *tree.Cursor
|
||||
err := prolly.DiffMaps(ctx, preChildSecIdx, postChildSecIdx, func(ctx context.Context, diff tree.Diff) error {
|
||||
switch diff.Type {
|
||||
case tree.AddedDiff, tree.ModifiedDiff:
|
||||
k, v := val.Tuple(diff.Key), val.Tuple(diff.To)
|
||||
partialKey, hasNulls := makePartialKey(
|
||||
partialKB,
|
||||
foreignKey.TableColumns,
|
||||
postChild.Index,
|
||||
postChild.IndexSchema,
|
||||
k,
|
||||
v,
|
||||
preChildScndryIdx.Pool())
|
||||
if hasNulls {
|
||||
return nil
|
||||
k := val.Tuple(diff.Key)
|
||||
// TODO: possible to skip this if there are not null constraints over entire index
|
||||
for i := 0; i < k.Count(); i++ {
|
||||
if k.FieldIsNull(i) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err := createCVIfNoPartialKeyMatchesSec(ctx, k, v, partialKey, partialDesc, primaryKD, kb, parentScndryIdx, postChildRowData, postChildRowData.Pool(), receiver)
|
||||
if parentSecIdxCur == nil {
|
||||
newCur, err := tree.NewCursorAtKey(ctx, parentSecIdx.NodeStore(), parentSecIdx.Node(), k, partialDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !newCur.Valid() {
|
||||
return createCVForSecIdx(ctx, k, childPriKD, childPriKB, postChildRowData, postChildRowData.Pool(), receiver)
|
||||
}
|
||||
parentSecIdxCur = newCur
|
||||
}
|
||||
|
||||
err := tree.Seek(ctx, parentSecIdxCur, k, partialDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !parentSecIdxCur.Valid() {
|
||||
return createCVForSecIdx(ctx, k, childPriKD, childPriKB, postChildRowData, postChildRowData.Pool(), receiver)
|
||||
}
|
||||
|
||||
// possible that k is less than the smallest key in parentSecIdxCur, so still should compare
|
||||
key := val.Tuple(parentSecIdxCur.CurrentKey())
|
||||
if partialDesc.Compare(k, key) != 0 {
|
||||
return createCVForSecIdx(ctx, k, childPriKD, childPriKB, postChildRowData, postChildRowData.Pool(), receiver)
|
||||
}
|
||||
return nil
|
||||
case tree.RemovedDiff:
|
||||
default:
|
||||
panic("unhandled diff type")
|
||||
@@ -213,27 +228,14 @@ func createCVIfNoPartialKeyMatchesPri(
|
||||
return receiver.ProllyFKViolationFound(ctx, k, v)
|
||||
}
|
||||
|
||||
func createCVIfNoPartialKeyMatchesSec(
|
||||
func createCVForSecIdx(
|
||||
ctx context.Context,
|
||||
k, v, partialKey val.Tuple,
|
||||
partialKeyDesc val.TupleDesc,
|
||||
k val.Tuple,
|
||||
primaryKD val.TupleDesc,
|
||||
primaryKb *val.TupleBuilder,
|
||||
idx prolly.Map,
|
||||
pri prolly.Map,
|
||||
pool pool.BuffPool,
|
||||
receiver FKViolationReceiver) error {
|
||||
itr, err := creation.NewPrefixItr(ctx, partialKey, partialKeyDesc, idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err = itr.Next(ctx)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert secondary idx entry to primary row key
|
||||
// the pks of the table are the last keys of the index
|
||||
@@ -245,7 +247,7 @@ func createCVIfNoPartialKeyMatchesSec(
|
||||
primaryIdxKey := primaryKb.Build(pool)
|
||||
|
||||
var value val.Tuple
|
||||
err = pri.Get(ctx, primaryIdxKey, func(k, v val.Tuple) error {
|
||||
err := pri.Get(ctx, primaryIdxKey, func(k, v val.Tuple) error {
|
||||
value = v
|
||||
return nil
|
||||
})
|
||||
@@ -329,7 +331,8 @@ func createCVsForPartialKeyMatches(
|
||||
}
|
||||
|
||||
func makePartialKey(kb *val.TupleBuilder, tags []uint64, idxSch schema.Index, tblSch schema.Schema, k, v val.Tuple, pool pool.BuffPool) (val.Tuple, bool) {
|
||||
if idxSch.Name() != "" {
|
||||
// Possible that the parent index (idxSch) is longer than the partial key (tags).
|
||||
if idxSch.Name() != "" && len(idxSch.IndexedColumnTags()) <= len(tags) {
|
||||
tags = idxSch.IndexedColumnTags()
|
||||
}
|
||||
for i, tag := range tags {
|
||||
|
||||
@@ -57,7 +57,7 @@ func NewRemoteRef(remote, branch string) RemoteRef {
|
||||
return RemoteRef{remote, branch}
|
||||
}
|
||||
|
||||
// NewRemoteRefFromPathString creates a DoltRef from a string in the format origin/main, or remotes/origin/main, or
|
||||
// NewRemoteRefFromPathStr creates a DoltRef from a string in the format origin/main, or remotes/origin/main, or
|
||||
// refs/remotes/origin/main
|
||||
func NewRemoteRefFromPathStr(remoteAndPath string) (DoltRef, error) {
|
||||
if IsRef(remoteAndPath) {
|
||||
|
||||
@@ -115,6 +115,33 @@ func collationCompare(typ val.Type, collation sql.CollationID, left, right []byt
|
||||
}
|
||||
|
||||
func compareCollatedStrings(collation sql.CollationID, left, right []byte) int {
|
||||
i := 0
|
||||
for i < len(left) && i < len(right) {
|
||||
if left[i] != right[i] {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i >= len(left) || i >= len(right) {
|
||||
if len(left) < len(right) {
|
||||
return -1
|
||||
} else if len(left) > len(right) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
li := i
|
||||
for ; li >= 0 && !utf8.RuneStart(left[li]); li-- {
|
||||
}
|
||||
left = left[li:]
|
||||
|
||||
ri := i
|
||||
for ; ri >= 0 && !utf8.RuneStart(right[ri]); ri-- {
|
||||
}
|
||||
right = right[ri:]
|
||||
|
||||
getRuneWeight := collation.Sorter()
|
||||
for len(left) > 0 && len(right) > 0 {
|
||||
// Binary strings aren't handled through this function, so it is safe to use the utf8 functions
|
||||
@@ -130,12 +157,14 @@ func compareCollatedStrings(collation sql.CollationID, left, right []byte) int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
leftWeight := getRuneWeight(leftRune)
|
||||
rightWeight := getRuneWeight(rightRune)
|
||||
if leftWeight < rightWeight {
|
||||
return -1
|
||||
} else if leftWeight > rightWeight {
|
||||
return 1
|
||||
if leftRune != rightRune {
|
||||
leftWeight := getRuneWeight(leftRune)
|
||||
rightWeight := getRuneWeight(rightRune)
|
||||
if leftWeight < rightWeight {
|
||||
return -1
|
||||
} else if leftWeight > rightWeight {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
left = left[leftRead:]
|
||||
right = right[rightRead:]
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCompareCollatedStrings(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
left []byte
|
||||
right []byte
|
||||
exp int
|
||||
}{
|
||||
{
|
||||
left: []byte("Hello, 人"),
|
||||
right: []byte("Hello, 亻"),
|
||||
exp: -1,
|
||||
},
|
||||
{
|
||||
left: []byte("woÒ"),
|
||||
right: []byte("woÓ"),
|
||||
exp: 0,
|
||||
},
|
||||
{
|
||||
left: []byte("\u07FB"),
|
||||
right: []byte("\u07FC"),
|
||||
exp: -1,
|
||||
},
|
||||
{
|
||||
left: []byte("˧"),
|
||||
right: []byte("˦"),
|
||||
exp: 1,
|
||||
},
|
||||
{
|
||||
left: []byte("ƵƶzƸ"),
|
||||
right: []byte("ƵƶzƷ"),
|
||||
exp: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%s vs %s", tt.left, tt.right), func(t *testing.T) {
|
||||
cmp := compareCollatedStrings(sql.Collation_utf8mb4_0900_ai_ci, tt.left, tt.right)
|
||||
require.Equal(t, tt.exp, cmp)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -432,6 +432,8 @@ func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ds
|
||||
dt, found = dtables.NewTableOfTablesConstraintViolations(ctx, root), true
|
||||
case doltdb.BranchesTableName:
|
||||
dt, found = dtables.NewBranchesTable(ctx, db.ddb), true
|
||||
case doltdb.RemoteBranchesTableName:
|
||||
dt, found = dtables.NewRemoteBranchesTable(ctx, db.ddb), true
|
||||
case doltdb.RemotesTableName:
|
||||
dt, found = dtables.NewRemotesTable(ctx, db.ddb), true
|
||||
case doltdb.CommitsTableName:
|
||||
|
||||
@@ -152,9 +152,8 @@ func (p DoltDatabaseProvider) FileSystem() filesys.Filesys {
|
||||
return p.fs
|
||||
}
|
||||
|
||||
// If this DatabaseProvider is set to standby |true|, it returns every dolt
|
||||
// database as a read only database. Set back to |false| to get read-write
|
||||
// behavior from dolt databases again.
|
||||
// SetIsStandby sets whether this provider is set to standby |true|. Standbys return every dolt database as a read only
|
||||
// database. Set back to |false| to get read-write behavior from dolt databases again.
|
||||
func (p DoltDatabaseProvider) SetIsStandby(standby bool) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
@@ -187,13 +186,16 @@ func (p DoltDatabaseProvider) Database(ctx *sql.Context, name string) (db sql.Da
|
||||
return wrapForStandby(db, standby), nil
|
||||
}
|
||||
|
||||
// Revision databases aren't tracked in the map, just instantiated on demand
|
||||
db, _, ok, err = p.databaseForRevision(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// A final check: if the database doesn't exist and this is a read replica, attempt to clone it from the remote
|
||||
if !ok {
|
||||
db, err = p.databaseForClone(ctx, name)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -203,7 +205,6 @@ func (p DoltDatabaseProvider) Database(ctx *sql.Context, name string) (db sql.Da
|
||||
}
|
||||
}
|
||||
|
||||
// Don't track revision databases, just instantiate them on demand
|
||||
return wrapForStandby(db, standby), nil
|
||||
}
|
||||
|
||||
@@ -263,7 +264,7 @@ func (p DoltDatabaseProvider) attemptCloneReplica(ctx *sql.Context, dbName strin
|
||||
func (p DoltDatabaseProvider) HasDatabase(ctx *sql.Context, name string) bool {
|
||||
_, err := p.Database(ctx, name)
|
||||
if err != nil && !sql.ErrDatabaseNotFound.Is(err) {
|
||||
ctx.GetLogger().Errorf(err.Error())
|
||||
ctx.GetLogger().Warnf("Error getting database %s: %s", name, err.Error())
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
@@ -725,7 +726,7 @@ func (p DoltDatabaseProvider) databaseForRevision(ctx *sql.Context, revDB string
|
||||
return nil, dsess.InitialDbState{}, false, err
|
||||
}
|
||||
|
||||
caseSensitiveBranchName, isBranch, err := isBranch(ctx, srcDb, resolvedRevSpec, p.remoteDialer)
|
||||
caseSensitiveBranchName, isBranch, err := isBranch(ctx, srcDb, resolvedRevSpec)
|
||||
if err != nil {
|
||||
return nil, dsess.InitialDbState{}, false, err
|
||||
}
|
||||
@@ -733,8 +734,9 @@ func (p DoltDatabaseProvider) databaseForRevision(ctx *sql.Context, revDB string
|
||||
if isBranch {
|
||||
// fetch the upstream head if this is a replicated db
|
||||
if replicaDb, ok := srcDb.(ReadReplicaDatabase); ok {
|
||||
// TODO move this out of analysis phase, should only happen at read time
|
||||
err := switchAndFetchReplicaHead(ctx, resolvedRevSpec, replicaDb)
|
||||
// TODO move this out of analysis phase, should only happen at read time, when the transaction begins (like is
|
||||
// the case with a branch that already exists locally)
|
||||
err := p.ensureReplicaHeadExists(ctx, resolvedRevSpec, replicaDb)
|
||||
if err != nil {
|
||||
return nil, dsess.InitialDbState{}, false, err
|
||||
}
|
||||
@@ -751,7 +753,7 @@ func (p DoltDatabaseProvider) databaseForRevision(ctx *sql.Context, revDB string
|
||||
return db, init, true, nil
|
||||
}
|
||||
|
||||
isTag, err := isTag(ctx, srcDb, resolvedRevSpec, p.remoteDialer)
|
||||
isTag, err := isTag(ctx, srcDb, resolvedRevSpec)
|
||||
if err != nil {
|
||||
return nil, dsess.InitialDbState{}, false, err
|
||||
}
|
||||
@@ -759,6 +761,7 @@ func (p DoltDatabaseProvider) databaseForRevision(ctx *sql.Context, revDB string
|
||||
if isTag {
|
||||
// TODO: this should be an interface, not a struct
|
||||
replicaDb, ok := srcDb.(ReadReplicaDatabase)
|
||||
|
||||
if ok {
|
||||
srcDb = replicaDb.Database
|
||||
}
|
||||
@@ -981,69 +984,14 @@ func (p DoltDatabaseProvider) IsRevisionDatabase(ctx *sql.Context, dbName string
|
||||
return revision != "", nil
|
||||
}
|
||||
|
||||
// switchAndFetchReplicaHead tries to pull the latest version of a branch. Will fail if the branch
|
||||
// does not exist on the ReadReplicaDatabase's remote. If the target branch is not a replication
|
||||
// head, the new branch will not be continuously fetched.
|
||||
func switchAndFetchReplicaHead(ctx *sql.Context, branch string, db ReadReplicaDatabase) error {
|
||||
branchRef := ref.NewBranchRef(branch)
|
||||
|
||||
var branchExists bool
|
||||
branches, err := db.ddb.GetBranches(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, br := range branches {
|
||||
if br.String() == branch {
|
||||
branchExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// check whether branch is on remote before creating local tracking branch
|
||||
cm, err := actions.FetchRemoteBranch(ctx, db.tmpDir, db.remote, db.srcDB, db.DbData().Ddb, branchRef, actions.NoopRunProgFuncs, actions.NoopStopProgFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmHash, err := cm.HashOf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create refs/heads/branch dataset
|
||||
if !branchExists {
|
||||
err = db.ddb.NewBranchAtCommit(ctx, branchRef, cm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dSess := dsess.DSessFromSess(ctx.Session)
|
||||
currentBranchRef, err := dSess.CWBHeadRef(ctx, db.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create workingSets/heads/branch and update the working set
|
||||
err = db.RebaseSourceDb(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = pullBranches(ctx, db, []doltdb.RefWithHash{{
|
||||
Ref: branchRef,
|
||||
Hash: cmHash,
|
||||
}}, nil, currentBranchRef, pullBehavior_fastForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
// ensureReplicaHeadExists tries to pull the latest version of a remote branch. Will fail if the branch
|
||||
// does not exist on the ReadReplicaDatabase's remote.
|
||||
func (p DoltDatabaseProvider) ensureReplicaHeadExists(ctx *sql.Context, branch string, db ReadReplicaDatabase) error {
|
||||
return db.CreateLocalBranchFromRemote(ctx, ref.NewBranchRef(branch))
|
||||
}
|
||||
|
||||
// isBranch returns whether a branch with the given name is in scope for the database given
|
||||
func isBranch(ctx context.Context, db SqlDatabase, branchName string, dialer dbfactory.GRPCDialProvider) (string, bool, error) {
|
||||
func isBranch(ctx context.Context, db SqlDatabase, branchName string) (string, bool, error) {
|
||||
var ddbs []*doltdb.DoltDB
|
||||
|
||||
if rdb, ok := db.(ReadReplicaDatabase); ok {
|
||||
@@ -1062,7 +1010,7 @@ func isBranch(ctx context.Context, db SqlDatabase, branchName string, dialer dbf
|
||||
return brName, true, nil
|
||||
}
|
||||
|
||||
brName, branchExists, err = isRemoteBranch(ctx, db, ddbs, branchName)
|
||||
brName, branchExists, err = isRemoteBranch(ctx, ddbs, branchName)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
@@ -1088,21 +1036,15 @@ func isLocalBranch(ctx context.Context, ddbs []*doltdb.DoltDB, branchName string
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// isRemoteBranch is called when the branch in connection string is not available as a local branch, so it searches
|
||||
// for a remote tracking branch. If there is only one match, it creates a new local branch from the remote tracking
|
||||
// branch and sets its upstream to it.
|
||||
func isRemoteBranch(ctx context.Context, srcDB SqlDatabase, ddbs []*doltdb.DoltDB, branchName string) (string, bool, error) {
|
||||
// isRemoteBranch returns whether the given branch name is a remote branch on any of the databases provided.
|
||||
func isRemoteBranch(ctx context.Context, ddbs []*doltdb.DoltDB, branchName string) (string, bool, error) {
|
||||
for _, ddb := range ddbs {
|
||||
bn, branchExists, remoteRef, err := ddb.HasRemoteTrackingBranch(ctx, branchName)
|
||||
bn, branchExists, _, err := ddb.HasRemoteTrackingBranch(ctx, branchName)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if branchExists {
|
||||
err = createLocalBranchFromRemoteTrackingBranch(ctx, srcDB.DbData(), ddb, branchName, remoteRef)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return bn, true, nil
|
||||
}
|
||||
}
|
||||
@@ -1110,36 +1052,8 @@ func isRemoteBranch(ctx context.Context, srcDB SqlDatabase, ddbs []*doltdb.DoltD
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// createLocalBranchFromRemoteTrackingBranch creates a new local branch from given remote tracking branch
|
||||
// and sets its upstream to it.
|
||||
func createLocalBranchFromRemoteTrackingBranch(ctx context.Context, dbData env.DbData, ddb *doltdb.DoltDB, branchName string, remoteRef ref.RemoteRef) error {
|
||||
startPt := remoteRef.GetPath()
|
||||
err := actions.CreateBranchOnDB(ctx, ddb, branchName, startPt, false, remoteRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// at this point the branch is created on db
|
||||
branchRef := ref.NewBranchRef(branchName)
|
||||
remote := remoteRef.GetRemote()
|
||||
refSpec, err := ref.ParseRefSpecForRemote(remote, remoteRef.GetBranch())
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: '%s'", err, remote)
|
||||
}
|
||||
|
||||
src := refSpec.SrcRef(branchRef)
|
||||
dest := refSpec.DestRef(src)
|
||||
|
||||
return dbData.Rsw.UpdateBranch(branchRef.GetPath(), env.BranchConfig{
|
||||
Merge: ref.MarshalableRef{
|
||||
Ref: dest,
|
||||
},
|
||||
Remote: remote,
|
||||
})
|
||||
}
|
||||
|
||||
// isTag returns whether a tag with the given name is in scope for the database given
|
||||
func isTag(ctx context.Context, db SqlDatabase, tagName string, dialer dbfactory.GRPCDialProvider) (bool, error) {
|
||||
func isTag(ctx context.Context, db SqlDatabase, tagName string) (bool, error) {
|
||||
var ddbs []*doltdb.DoltDB
|
||||
|
||||
if rdb, ok := db.(ReadReplicaDatabase); ok {
|
||||
|
||||
@@ -257,26 +257,77 @@ func loadConfig(ctx *sql.Context) *env.DoltCliConfig {
|
||||
}
|
||||
|
||||
func createNewBranch(ctx *sql.Context, dbData env.DbData, apr *argparser.ArgParseResults) error {
|
||||
var branchName string
|
||||
if apr.NArg() == 0 || apr.NArg() > 2 {
|
||||
return InvalidArgErr
|
||||
}
|
||||
|
||||
var branchName = apr.Arg(0)
|
||||
var startPt = "HEAD"
|
||||
if apr.NArg() == 1 {
|
||||
branchName = apr.Arg(0)
|
||||
} else if apr.NArg() == 2 {
|
||||
branchName = apr.Arg(0)
|
||||
if len(branchName) == 0 {
|
||||
return EmptyBranchNameErr
|
||||
}
|
||||
if apr.NArg() == 2 {
|
||||
startPt = apr.Arg(1)
|
||||
if len(startPt) == 0 {
|
||||
return InvalidArgErr
|
||||
}
|
||||
}
|
||||
|
||||
if len(branchName) == 0 {
|
||||
return EmptyBranchNameErr
|
||||
var remoteName, remoteBranch string
|
||||
var refSpec ref.RefSpec
|
||||
var err error
|
||||
trackVal, setTrackUpstream := apr.GetValue(cli.TrackFlag)
|
||||
if setTrackUpstream {
|
||||
if trackVal == "inherit" {
|
||||
return fmt.Errorf("--track='inherit' is not supported yet")
|
||||
} else if trackVal == "direct" && apr.NArg() != 2 {
|
||||
return InvalidArgErr
|
||||
}
|
||||
|
||||
if apr.NArg() == 2 {
|
||||
// branchName and startPt are already set
|
||||
remoteName, remoteBranch = actions.ParseRemoteBranchName(startPt)
|
||||
refSpec, err = ref.ParseRefSpecForRemote(remoteName, remoteBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// if track option is defined with no value,
|
||||
// the track value can either be starting point name OR branch name
|
||||
startPt = trackVal
|
||||
remoteName, remoteBranch = actions.ParseRemoteBranchName(startPt)
|
||||
refSpec, err = ref.ParseRefSpecForRemote(remoteName, remoteBranch)
|
||||
if err != nil {
|
||||
branchName = trackVal
|
||||
startPt = apr.Arg(0)
|
||||
remoteName, remoteBranch = actions.ParseRemoteBranchName(startPt)
|
||||
refSpec, err = ref.ParseRefSpecForRemote(remoteName, remoteBranch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := branch_control.CanCreateBranch(ctx, branchName); err != nil {
|
||||
err = branch_control.CanCreateBranch(ctx, branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return actions.CreateBranchWithStartPt(ctx, dbData, branchName, startPt, apr.Contains(cli.ForceFlag))
|
||||
|
||||
err = actions.CreateBranchWithStartPt(ctx, dbData, branchName, startPt, apr.Contains(cli.ForceFlag))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if setTrackUpstream {
|
||||
// at this point new branch is created
|
||||
err = env.SetRemoteUpstreamForRefSpec(dbData.Rsw, refSpec, remoteName, ref.NewBranchRef(branchName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyBranch(ctx *sql.Context, dbData env.DbData, apr *argparser.ArgParseResults) error {
|
||||
|
||||
@@ -208,15 +208,7 @@ func checkoutRemoteBranch(ctx *sql.Context, dbName string, dbData env.DbData, br
|
||||
return errhand.BuildDError(fmt.Errorf("%w: '%s'", err, remoteRef.GetRemote()).Error()).Build()
|
||||
}
|
||||
|
||||
src := refSpec.SrcRef(dbData.Rsr.CWBHeadRef())
|
||||
dest := refSpec.DestRef(src)
|
||||
|
||||
return dbData.Rsw.UpdateBranch(src.GetPath(), env.BranchConfig{
|
||||
Merge: ref.MarshalableRef{
|
||||
Ref: dest,
|
||||
},
|
||||
Remote: remoteRef.GetRemote(),
|
||||
})
|
||||
return env.SetRemoteUpstreamForRefSpec(dbData.Rsw, refSpec, remoteRef.GetRemote(), dbData.Rsr.CWBHeadRef())
|
||||
} else {
|
||||
return fmt.Errorf("'%s' matched multiple (%v) remote tracking branches", branchName, len(remoteRefs))
|
||||
}
|
||||
|
||||
@@ -62,14 +62,12 @@ func doDoltFetch(ctx *sql.Context, args []string) (int, error) {
|
||||
return cmdFailure, err
|
||||
}
|
||||
|
||||
updateMode := ref.UpdateMode{Force: apr.Contains(cli.ForceFlag)}
|
||||
|
||||
srcDB, err := sess.Provider().GetRemoteDB(ctx, dbData.Ddb.ValueReadWriter().Format(), remote, false)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
err = actions.FetchRefSpecs(ctx, dbData, srcDB, refSpecs, remote, updateMode, runProgFuncs, stopProgFuncs)
|
||||
err = actions.FetchRefSpecs(ctx, dbData, srcDB, refSpecs, remote, ref.UpdateMode{Force: true}, runProgFuncs, stopProgFuncs)
|
||||
if err != nil {
|
||||
return cmdFailure, fmt.Errorf("fetch failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/dolthub/go-mysql-server/sql/types"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
|
||||
)
|
||||
|
||||
@@ -31,37 +32,54 @@ var _ sql.DeletableTable = (*BranchesTable)(nil)
|
||||
var _ sql.InsertableTable = (*BranchesTable)(nil)
|
||||
var _ sql.ReplaceableTable = (*BranchesTable)(nil)
|
||||
|
||||
// BranchesTable is a sql.Table implementation that implements a system table which shows the dolt branches
|
||||
// BranchesTable is the system table that accesses branches
|
||||
type BranchesTable struct {
|
||||
ddb *doltdb.DoltDB
|
||||
ddb *doltdb.DoltDB
|
||||
remote bool
|
||||
}
|
||||
|
||||
// NewBranchesTable creates a BranchesTable
|
||||
func NewBranchesTable(_ *sql.Context, ddb *doltdb.DoltDB) sql.Table {
|
||||
return &BranchesTable{ddb}
|
||||
return &BranchesTable{ddb, false}
|
||||
}
|
||||
|
||||
// NewRemoteBranchesTable creates a BranchesTable with only remote refs
|
||||
func NewRemoteBranchesTable(_ *sql.Context, ddb *doltdb.DoltDB) sql.Table {
|
||||
return &BranchesTable{ddb, true}
|
||||
}
|
||||
|
||||
// Name is a sql.Table interface function which returns the name of the table which is defined by the constant
|
||||
// BranchesTableName
|
||||
func (bt *BranchesTable) Name() string {
|
||||
if bt.remote {
|
||||
return doltdb.RemoteBranchesTableName
|
||||
}
|
||||
return doltdb.BranchesTableName
|
||||
}
|
||||
|
||||
// String is a sql.Table interface function which returns the name of the table which is defined by the constant
|
||||
// BranchesTableName
|
||||
func (bt *BranchesTable) String() string {
|
||||
if bt.remote {
|
||||
return doltdb.RemoteBranchesTableName
|
||||
}
|
||||
return doltdb.BranchesTableName
|
||||
}
|
||||
|
||||
// Schema is a sql.Table interface function that gets the sql.Schema of the branches system table
|
||||
func (bt *BranchesTable) Schema() sql.Schema {
|
||||
tableName := doltdb.BranchesTableName
|
||||
if bt.remote {
|
||||
tableName = doltdb.RemoteBranchesTableName
|
||||
}
|
||||
|
||||
return []*sql.Column{
|
||||
{Name: "name", Type: types.Text, Source: doltdb.BranchesTableName, PrimaryKey: true, Nullable: false},
|
||||
{Name: "hash", Type: types.Text, Source: doltdb.BranchesTableName, PrimaryKey: false, Nullable: false},
|
||||
{Name: "latest_committer", Type: types.Text, Source: doltdb.BranchesTableName, PrimaryKey: false, Nullable: true},
|
||||
{Name: "latest_committer_email", Type: types.Text, Source: doltdb.BranchesTableName, PrimaryKey: false, Nullable: true},
|
||||
{Name: "latest_commit_date", Type: types.Datetime, Source: doltdb.BranchesTableName, PrimaryKey: false, Nullable: true},
|
||||
{Name: "latest_commit_message", Type: types.Text, Source: doltdb.BranchesTableName, PrimaryKey: false, Nullable: true},
|
||||
{Name: "name", Type: types.Text, Source: tableName, PrimaryKey: true, Nullable: false},
|
||||
{Name: "hash", Type: types.Text, Source: tableName, PrimaryKey: false, Nullable: false},
|
||||
{Name: "latest_committer", Type: types.Text, Source: tableName, PrimaryKey: false, Nullable: true},
|
||||
{Name: "latest_committer_email", Type: types.Text, Source: tableName, PrimaryKey: false, Nullable: true},
|
||||
{Name: "latest_commit_date", Type: types.Datetime, Source: tableName, PrimaryKey: false, Nullable: true},
|
||||
{Name: "latest_commit_message", Type: types.Text, Source: tableName, PrimaryKey: false, Nullable: true},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +95,7 @@ func (bt *BranchesTable) Partitions(*sql.Context) (sql.PartitionIter, error) {
|
||||
|
||||
// PartitionRows is a sql.Table interface function that gets a row iterator for a partition
|
||||
func (bt *BranchesTable) PartitionRows(sqlCtx *sql.Context, part sql.Partition) (sql.RowIter, error) {
|
||||
return NewBranchItr(sqlCtx, bt.ddb)
|
||||
return NewBranchItr(sqlCtx, bt.ddb, bt.remote)
|
||||
}
|
||||
|
||||
// BranchItr is a sql.RowItr implementation which iterates over each commit as if it's a row in the table.
|
||||
@@ -88,23 +106,37 @@ type BranchItr struct {
|
||||
}
|
||||
|
||||
// NewBranchItr creates a BranchItr from the current environment.
|
||||
func NewBranchItr(sqlCtx *sql.Context, ddb *doltdb.DoltDB) (*BranchItr, error) {
|
||||
branches, err := ddb.GetBranches(sqlCtx)
|
||||
func NewBranchItr(ctx *sql.Context, ddb *doltdb.DoltDB, remote bool) (*BranchItr, error) {
|
||||
var branchRefs []ref.DoltRef
|
||||
var err error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if remote {
|
||||
branchRefs, err = ddb.GetRefsOfType(ctx, map[ref.RefType]struct{}{ref.RemoteRefType: {}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
branchRefs, err = ddb.GetBranches(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
branchNames := make([]string, len(branches))
|
||||
commits := make([]*doltdb.Commit, len(branches))
|
||||
for i, branch := range branches {
|
||||
commit, err := ddb.ResolveCommitRef(sqlCtx, branch)
|
||||
branchNames := make([]string, len(branchRefs))
|
||||
commits := make([]*doltdb.Commit, len(branchRefs))
|
||||
for i, branch := range branchRefs {
|
||||
commit, err := ddb.ResolveCommitRef(ctx, branch)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
branchNames[i] = branch.GetPath()
|
||||
if branch.GetType() == ref.RemoteRefType {
|
||||
branchNames[i] = "remotes/" + branch.GetPath()
|
||||
} else {
|
||||
branchNames[i] = branch.GetPath()
|
||||
}
|
||||
|
||||
commits[i] = commit
|
||||
}
|
||||
|
||||
|
||||
@@ -1307,6 +1307,46 @@ var Dolt1MergeScripts = []queries.ScriptTest{
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "parent index is longer than child index",
|
||||
SetUpScript: []string{
|
||||
"create table parent (i int primary key, x int, y int, z int, index (y, x, z));",
|
||||
"create table child (y int, x int, primary key(y, x), foreign key (y, x) references parent(y, x));",
|
||||
"insert into parent values (100,1,1,1), (200,2,1,2), (300,1,null,1);",
|
||||
"CALL DOLT_ADD('.')",
|
||||
"CALL DOLT_COMMIT('-am', 'setup');",
|
||||
"CALL DOLT_BRANCH('other');",
|
||||
|
||||
"DELETE from parent WHERE x = 2;",
|
||||
"CALL DOLT_COMMIT('-am', 'main');",
|
||||
|
||||
"CALL DOLT_CHECKOUT('other');",
|
||||
"INSERT INTO child VALUES (1, 2);",
|
||||
"CALL DOLT_COMMIT('-am', 'other');",
|
||||
|
||||
"CALL DOLT_CHECKOUT('main');",
|
||||
"set DOLT_FORCE_TRANSACTION_COMMIT = on;",
|
||||
"CALL DOLT_MERGE('other');",
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "SELECT * from dolt_constraint_violations",
|
||||
Expected: []sql.Row{
|
||||
{"child", uint64(1)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Query: "SELECT * from dolt_constraint_violations_parent",
|
||||
Expected: []sql.Row{},
|
||||
},
|
||||
{
|
||||
Query: "SELECT y, x from dolt_constraint_violations_child",
|
||||
Expected: []sql.Row{
|
||||
{1, 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var KeylessMergeCVsAndConflictsScripts = []queries.ScriptTest{
|
||||
@@ -3212,6 +3252,38 @@ var DoltVerifyConstraintsTestScripts = []queries.ScriptTest{
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "verify-constraints: Stored Procedure ignores null",
|
||||
SetUpScript: []string{
|
||||
"create table parent (id bigint primary key, v1 bigint, v2 bigint, index (v1, v2))",
|
||||
"create table child (id bigint primary key, v1 bigint, v2 bigint, foreign key (v1, v2) references parent(v1, v2))",
|
||||
"insert into parent values (1, 1, 1), (2, 2, 2)",
|
||||
"insert into child values (1, 1, 1), (2, 90, NULL)",
|
||||
"set dolt_force_transaction_commit = 1;",
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "CALL DOLT_VERIFY_CONSTRAINTS('child')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "set foreign_key_checks = 0;",
|
||||
SkipResultsCheck: true,
|
||||
},
|
||||
{
|
||||
Query: "insert into child values (3, 30, 30);",
|
||||
Expected: []sql.Row{{types.OkResult{RowsAffected: 1}}},
|
||||
},
|
||||
{
|
||||
Query: "set foreign_key_checks = 1;",
|
||||
SkipResultsCheck: true,
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_VERIFY_CONSTRAINTS('child')",
|
||||
Expected: []sql.Row{{1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var errTmplNoAutomaticMerge = "table %s can't be automatically merged.\nTo merge this table, make the schema on the source and target branch equal."
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/store/datas"
|
||||
@@ -183,7 +184,7 @@ func (rrd ReadReplicaDatabase) PullFromRemote(ctx *sql.Context) error {
|
||||
}
|
||||
|
||||
remoteRefs = prunedRefs
|
||||
err = pullBranches(ctx, rrd, remoteRefs, localRefs, currentBranchRef, behavior)
|
||||
err = pullBranchesAndUpdateWorkingSet(ctx, rrd, remoteRefs, localRefs, currentBranchRef, behavior)
|
||||
|
||||
if err != nil && !dsess.IgnoreReplicationErrors() {
|
||||
return err
|
||||
@@ -193,7 +194,7 @@ func (rrd ReadReplicaDatabase) PullFromRemote(ctx *sql.Context) error {
|
||||
}
|
||||
|
||||
case allHeads == int8(1):
|
||||
err = pullBranches(ctx, rrd, remoteRefs, localRefs, currentBranchRef, behavior)
|
||||
err = pullBranchesAndUpdateWorkingSet(ctx, rrd, remoteRefs, localRefs, currentBranchRef, behavior)
|
||||
if err != nil && !dsess.IgnoreReplicationErrors() {
|
||||
return err
|
||||
} else if err != nil {
|
||||
@@ -215,8 +216,55 @@ func (rrd ReadReplicaDatabase) PullFromRemote(ctx *sql.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rrd ReadReplicaDatabase) RebaseSourceDb(ctx *sql.Context) error {
|
||||
return rrd.srcDB.Rebase(ctx)
|
||||
// CreateLocalBranchFromRemote pulls the given branch from the remote database and creates a local tracking branch for
|
||||
// it. This is only used for initializing a new local branch being pulled from a remote during connection
|
||||
// initialization, and doesn't do the full work of remote synchronization that happens on transaction start.
|
||||
func (rrd ReadReplicaDatabase) CreateLocalBranchFromRemote(ctx *sql.Context, branchRef ref.BranchRef) error {
|
||||
_, err := rrd.limiter.Run(ctx, "pullNewBranch", func() (any, error) {
|
||||
// because several clients can queue up waiting to create the same local branch, double check to see if this
|
||||
// work was already done and bail early if so
|
||||
_, branchExists, err := rrd.ddb.HasBranch(ctx, branchRef.GetPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if branchExists {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cm, err := actions.FetchRemoteBranch(ctx, rrd.tmpDir, rrd.remote, rrd.srcDB, rrd.ddb, branchRef, actions.NoopRunProgFuncs, actions.NoopStopProgFuncs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmHash, err := cm.HashOf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create refs/heads/branch dataset
|
||||
err = rrd.ddb.NewBranchAtCommit(ctx, branchRef, cm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = rrd.srcDB.Rebase(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = pullBranches(ctx, rrd, []doltdb.RefWithHash{{
|
||||
Ref: branchRef,
|
||||
Hash: cmHash,
|
||||
}}, nil, pullBehavior_fastForward)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type pullBehavior bool
|
||||
@@ -224,9 +272,10 @@ type pullBehavior bool
|
||||
const pullBehavior_fastForward pullBehavior = false
|
||||
const pullBehavior_forcePull pullBehavior = true
|
||||
|
||||
// pullBranches pulls the remote branches named. If a corresponding local branch exists, it will be fast-forwarded. If
|
||||
// it doesn't exist, it will be created.
|
||||
func pullBranches(
|
||||
// pullBranchesAndUpdateWorkingSet pulls the remote branches named. If a corresponding local branch exists, it will be
|
||||
// fast-forwarded. If it doesn't exist, it will be created. Afterward, the working set of the current branch is
|
||||
// updated if the current branch ref was updated by the pull.
|
||||
func pullBranchesAndUpdateWorkingSet(
|
||||
ctx *sql.Context,
|
||||
rrd ReadReplicaDatabase,
|
||||
remoteRefs []doltdb.RefWithHash,
|
||||
@@ -234,68 +283,8 @@ func pullBranches(
|
||||
currentBranchRef ref.DoltRef,
|
||||
behavior pullBehavior,
|
||||
) error {
|
||||
localRefsByPath := make(map[string]doltdb.RefWithHash)
|
||||
remoteRefsByPath := make(map[string]doltdb.RefWithHash)
|
||||
remoteHashes := make([]hash.Hash, len(remoteRefs))
|
||||
|
||||
for i, b := range remoteRefs {
|
||||
remoteRefsByPath[b.Ref.GetPath()] = b
|
||||
remoteHashes[i] = b.Hash
|
||||
}
|
||||
|
||||
for _, b := range localRefs {
|
||||
localRefsByPath[b.Ref.GetPath()] = b
|
||||
}
|
||||
|
||||
// XXX: Our view of which remote branches to pull and what to set the
|
||||
// local branches to was computed outside of the limiter, concurrently
|
||||
// with other possible attempts to pull from the remote. Now we are
|
||||
// applying changes based on that view. This seems capable of rolling
|
||||
// back changes which were applied from another thread.
|
||||
|
||||
_, err := rrd.limiter.Run(ctx, "-all", func() (any, error) {
|
||||
err := rrd.ddb.PullChunks(ctx, rrd.tmpDir, rrd.srcDB, remoteHashes, nil)
|
||||
|
||||
for _, remoteRef := range remoteRefs {
|
||||
localRef, localRefExists := localRefsByPath[remoteRef.Ref.GetPath()]
|
||||
switch {
|
||||
case err != nil:
|
||||
case localRefExists:
|
||||
// TODO: this should work for workspaces too but doesn't, only branches
|
||||
if localRef.Ref.GetType() == ref.BranchRefType {
|
||||
if localRef.Hash != remoteRef.Hash {
|
||||
if behavior == pullBehavior_forcePull {
|
||||
err = rrd.ddb.SetHead(ctx, remoteRef.Ref, remoteRef.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err = rrd.ddb.FastForwardToHash(ctx, remoteRef.Ref, remoteRef.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
switch remoteRef.Ref.GetType() {
|
||||
case ref.BranchRefType:
|
||||
err = rrd.ddb.SetHead(ctx, remoteRef.Ref, remoteRef.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case ref.TagRefType:
|
||||
err = rrd.ddb.SetHead(ctx, remoteRef.Ref, remoteRef.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
ctx.GetLogger().Warnf("skipping replication for unhandled remote ref %s", remoteRef.Ref.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
remoteRefsByPath, err := pullBranches(ctx, rrd, remoteRefs, localRefs, behavior)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -361,6 +350,142 @@ func pullBranches(
|
||||
return nil
|
||||
}
|
||||
|
||||
// pullBranches pulls the remote branches named and returns the map of their hashes keyed by branch path.
|
||||
func pullBranches(
|
||||
ctx *sql.Context,
|
||||
rrd ReadReplicaDatabase,
|
||||
remoteRefs []doltdb.RefWithHash,
|
||||
localRefs []doltdb.RefWithHash,
|
||||
behavior pullBehavior,
|
||||
) (map[string]doltdb.RefWithHash, error) {
|
||||
localRefsByPath := make(map[string]doltdb.RefWithHash)
|
||||
remoteRefsByPath := make(map[string]doltdb.RefWithHash)
|
||||
remoteHashes := make([]hash.Hash, len(remoteRefs))
|
||||
|
||||
for i, b := range remoteRefs {
|
||||
remoteRefsByPath[b.Ref.GetPath()] = b
|
||||
remoteHashes[i] = b.Hash
|
||||
}
|
||||
|
||||
for _, b := range localRefs {
|
||||
localRefsByPath[b.Ref.GetPath()] = b
|
||||
}
|
||||
|
||||
// XXX: Our view of which remote branches to pull and what to set the
|
||||
// local branches to was computed outside of the limiter, concurrently
|
||||
// with other possible attempts to pull from the remote. Now we are
|
||||
// applying changes based on that view. This seems capable of rolling
|
||||
// back changes which were applied from another thread.
|
||||
|
||||
_, err := rrd.limiter.Run(ctx, "-all", func() (any, error) {
|
||||
pullErr := rrd.ddb.PullChunks(ctx, rrd.tmpDir, rrd.srcDB, remoteHashes, nil)
|
||||
|
||||
REFS: // every successful pass through the loop below must end with CONTINUE REFS to get out of the retry loop
|
||||
for _, remoteRef := range remoteRefs {
|
||||
trackingRef := ref.NewRemoteRef(rrd.remote.Name, remoteRef.Ref.GetPath())
|
||||
localRef, localRefExists := localRefsByPath[remoteRef.Ref.GetPath()]
|
||||
|
||||
// loop on optimistic lock failures
|
||||
OPTIMISTIC_RETRY:
|
||||
for {
|
||||
if pullErr != nil || localRefExists {
|
||||
pullErr = nil
|
||||
|
||||
// TODO: this should work for workspaces too but doesn't, only branches
|
||||
if localRef.Ref.GetType() == ref.BranchRefType {
|
||||
err := rrd.pullLocalBranch(ctx, localRef, remoteRef, trackingRef, behavior)
|
||||
if errors.Is(err, datas.ErrOptimisticLockFailed) {
|
||||
continue OPTIMISTIC_RETRY
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
continue REFS
|
||||
} else {
|
||||
switch remoteRef.Ref.GetType() {
|
||||
case ref.BranchRefType:
|
||||
err := rrd.createNewBranchFromRemote(ctx, remoteRef, trackingRef)
|
||||
if errors.Is(err, datas.ErrOptimisticLockFailed) {
|
||||
continue OPTIMISTIC_RETRY
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Establish upstream tracking for this new branch
|
||||
continue REFS
|
||||
case ref.TagRefType:
|
||||
err := rrd.ddb.SetHead(ctx, remoteRef.Ref, remoteRef.Hash)
|
||||
if errors.Is(err, datas.ErrOptimisticLockFailed) {
|
||||
continue OPTIMISTIC_RETRY
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
continue REFS
|
||||
default:
|
||||
ctx.GetLogger().Warnf("skipping replication for unhandled remote ref %s", remoteRef.Ref.String())
|
||||
continue REFS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return remoteRefsByPath, nil
|
||||
}
|
||||
|
||||
func (rrd ReadReplicaDatabase) createNewBranchFromRemote(ctx *sql.Context, remoteRef doltdb.RefWithHash, trackingRef ref.RemoteRef) error {
|
||||
ctx.GetLogger().Tracef("creating local branch %s", remoteRef.Ref.GetPath())
|
||||
|
||||
// If a local branch isn't present for the remote branch, create a new branch for it. We need to use
|
||||
// NewBranchAtCommit so that the branch has its associated working set created at the same time. Creating
|
||||
// branch refs without associate working sets causes errors in other places.
|
||||
spec, err := doltdb.NewCommitSpec(remoteRef.Hash.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm, err := rrd.ddb.Resolve(ctx, spec, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rrd.ddb.NewBranchAtCommit(ctx, remoteRef.Ref, cm)
|
||||
err = rrd.ddb.SetHead(ctx, trackingRef, remoteRef.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return rrd.ddb.SetHead(ctx, trackingRef, remoteRef.Hash)
|
||||
}
|
||||
|
||||
func (rrd ReadReplicaDatabase) pullLocalBranch(ctx *sql.Context, localRef doltdb.RefWithHash, remoteRef doltdb.RefWithHash, trackingRef ref.RemoteRef, behavior pullBehavior) error {
|
||||
if localRef.Hash != remoteRef.Hash {
|
||||
if behavior == pullBehavior_forcePull {
|
||||
err := rrd.ddb.SetHead(ctx, remoteRef.Ref, remoteRef.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := rrd.ddb.FastForwardToHash(ctx, remoteRef.Ref, remoteRef.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := rrd.ddb.SetHead(ctx, trackingRef, remoteRef.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReplicationRefs(ctx *sql.Context, rrd ReadReplicaDatabase) (
|
||||
remoteRefs []doltdb.RefWithHash,
|
||||
localRefs []doltdb.RefWithHash,
|
||||
|
||||
@@ -1937,6 +1937,103 @@ func (t *AlterableDoltTable) RenameIndex(ctx *sql.Context, fromIndexName string,
|
||||
return t.updateFromRoot(ctx, newRoot)
|
||||
}
|
||||
|
||||
// createForeignKey creates a doltdb.ForeignKey from a sql.ForeignKeyConstraint
|
||||
func (t *AlterableDoltTable) createForeignKey(
|
||||
ctx *sql.Context,
|
||||
root *doltdb.RootValue,
|
||||
tbl *doltdb.Table,
|
||||
sqlFk sql.ForeignKeyConstraint,
|
||||
onUpdateRefAction, onDeleteRefAction doltdb.ForeignKeyReferentialAction) (doltdb.ForeignKey, error) {
|
||||
if !sqlFk.IsResolved {
|
||||
return doltdb.ForeignKey{
|
||||
Name: sqlFk.Name,
|
||||
TableName: sqlFk.Table,
|
||||
TableIndex: "",
|
||||
TableColumns: nil,
|
||||
ReferencedTableName: sqlFk.ParentTable,
|
||||
ReferencedTableIndex: "",
|
||||
ReferencedTableColumns: nil,
|
||||
OnUpdate: onUpdateRefAction,
|
||||
OnDelete: onDeleteRefAction,
|
||||
UnresolvedFKDetails: doltdb.UnresolvedFKDetails{
|
||||
TableColumns: sqlFk.Columns,
|
||||
ReferencedTableColumns: sqlFk.ParentColumns,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
colTags := make([]uint64, len(sqlFk.Columns))
|
||||
for i, col := range sqlFk.Columns {
|
||||
tableCol, ok := t.sch.GetAllCols().GetByNameCaseInsensitive(col)
|
||||
if !ok {
|
||||
return doltdb.ForeignKey{}, fmt.Errorf("table `%s` does not have column `%s`", sqlFk.Table, col)
|
||||
}
|
||||
colTags[i] = tableCol.Tag
|
||||
}
|
||||
|
||||
var refTbl *doltdb.Table
|
||||
var refSch schema.Schema
|
||||
if sqlFk.IsSelfReferential() {
|
||||
refTbl = tbl
|
||||
refSch = t.sch
|
||||
} else {
|
||||
var ok bool
|
||||
var err error
|
||||
refTbl, _, ok, err = root.GetTableInsensitive(ctx, sqlFk.ParentTable)
|
||||
if err != nil {
|
||||
return doltdb.ForeignKey{}, err
|
||||
}
|
||||
if !ok {
|
||||
return doltdb.ForeignKey{}, fmt.Errorf("referenced table `%s` does not exist", sqlFk.ParentTable)
|
||||
}
|
||||
refSch, err = refTbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return doltdb.ForeignKey{}, err
|
||||
}
|
||||
}
|
||||
|
||||
refColTags := make([]uint64, len(sqlFk.ParentColumns))
|
||||
for i, name := range sqlFk.ParentColumns {
|
||||
refCol, ok := refSch.GetAllCols().GetByNameCaseInsensitive(name)
|
||||
if !ok {
|
||||
return doltdb.ForeignKey{}, fmt.Errorf("table `%s` does not have column `%s`", sqlFk.ParentTable, name)
|
||||
}
|
||||
refColTags[i] = refCol.Tag
|
||||
}
|
||||
|
||||
var tableIndexName, refTableIndexName string
|
||||
tableIndex, ok, err := findIndexWithPrefix(t.sch, sqlFk.Columns)
|
||||
if err != nil {
|
||||
return doltdb.ForeignKey{}, err
|
||||
}
|
||||
// Use secondary index if found; otherwise it will use empty string, indicating primary key
|
||||
if ok {
|
||||
tableIndexName = tableIndex.Name()
|
||||
}
|
||||
refTableIndex, ok, err := findIndexWithPrefix(refSch, sqlFk.ParentColumns)
|
||||
if err != nil {
|
||||
return doltdb.ForeignKey{}, err
|
||||
}
|
||||
// Use secondary index if found; otherwise it will use empty string, indicating primary key
|
||||
if ok {
|
||||
refTableIndexName = refTableIndex.Name()
|
||||
}
|
||||
return doltdb.ForeignKey{
|
||||
Name: sqlFk.Name,
|
||||
TableName: sqlFk.Table,
|
||||
TableIndex: tableIndexName,
|
||||
TableColumns: colTags,
|
||||
ReferencedTableName: sqlFk.ParentTable,
|
||||
ReferencedTableIndex: refTableIndexName,
|
||||
ReferencedTableColumns: refColTags,
|
||||
OnUpdate: onUpdateRefAction,
|
||||
OnDelete: onDeleteRefAction,
|
||||
UnresolvedFKDetails: doltdb.UnresolvedFKDetails{
|
||||
TableColumns: sqlFk.Columns,
|
||||
ReferencedTableColumns: sqlFk.ParentColumns,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddForeignKey implements sql.ForeignKeyTable
|
||||
func (t *AlterableDoltTable) AddForeignKey(ctx *sql.Context, sqlFk sql.ForeignKeyConstraint) error {
|
||||
if err := branch_control.CheckAccess(ctx, branch_control.Permissions_Write); err != nil {
|
||||
@@ -1970,95 +2067,9 @@ func (t *AlterableDoltTable) AddForeignKey(ctx *sql.Context, sqlFk sql.ForeignKe
|
||||
return err
|
||||
}
|
||||
|
||||
var doltFk doltdb.ForeignKey
|
||||
|
||||
if sqlFk.IsResolved {
|
||||
colTags := make([]uint64, len(sqlFk.Columns))
|
||||
for i, col := range sqlFk.Columns {
|
||||
tableCol, ok := t.sch.GetAllCols().GetByNameCaseInsensitive(col)
|
||||
if !ok {
|
||||
return fmt.Errorf("table `%s` does not have column `%s`", sqlFk.Table, col)
|
||||
}
|
||||
colTags[i] = tableCol.Tag
|
||||
}
|
||||
|
||||
var refTbl *doltdb.Table
|
||||
var ok bool
|
||||
var refSch schema.Schema
|
||||
if sqlFk.IsSelfReferential() {
|
||||
refTbl = tbl
|
||||
refSch = t.sch
|
||||
} else {
|
||||
refTbl, _, ok, err = root.GetTableInsensitive(ctx, sqlFk.ParentTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("referenced table `%s` does not exist", sqlFk.ParentTable)
|
||||
}
|
||||
refSch, err = refTbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
refColTags := make([]uint64, len(sqlFk.ParentColumns))
|
||||
for i, name := range sqlFk.ParentColumns {
|
||||
refCol, ok := refSch.GetAllCols().GetByNameCaseInsensitive(name)
|
||||
if !ok {
|
||||
return fmt.Errorf("table `%s` does not have column `%s`", sqlFk.ParentTable, name)
|
||||
}
|
||||
refColTags[i] = refCol.Tag
|
||||
}
|
||||
|
||||
var tableIndexName, refTableIndexName string
|
||||
tableIndex, ok, err := findIndexWithPrefix(t.sch, sqlFk.Columns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Use secondary index if found; otherwise it will use empty string, indicating primary key
|
||||
if ok {
|
||||
tableIndexName = tableIndex.Name()
|
||||
}
|
||||
refTableIndex, ok, err := findIndexWithPrefix(refSch, sqlFk.ParentColumns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Use secondary index if found; otherwise it will use empty string, indicating primary key
|
||||
if ok {
|
||||
refTableIndexName = refTableIndex.Name()
|
||||
}
|
||||
doltFk = doltdb.ForeignKey{
|
||||
Name: sqlFk.Name,
|
||||
TableName: sqlFk.Table,
|
||||
TableIndex: tableIndexName,
|
||||
TableColumns: colTags,
|
||||
ReferencedTableName: sqlFk.ParentTable,
|
||||
ReferencedTableIndex: refTableIndexName,
|
||||
ReferencedTableColumns: refColTags,
|
||||
OnUpdate: onUpdateRefAction,
|
||||
OnDelete: onDeleteRefAction,
|
||||
UnresolvedFKDetails: doltdb.UnresolvedFKDetails{
|
||||
TableColumns: sqlFk.Columns,
|
||||
ReferencedTableColumns: sqlFk.ParentColumns,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
doltFk = doltdb.ForeignKey{
|
||||
Name: sqlFk.Name,
|
||||
TableName: sqlFk.Table,
|
||||
TableIndex: "",
|
||||
TableColumns: nil,
|
||||
ReferencedTableName: sqlFk.ParentTable,
|
||||
ReferencedTableIndex: "",
|
||||
ReferencedTableColumns: nil,
|
||||
OnUpdate: onUpdateRefAction,
|
||||
OnDelete: onDeleteRefAction,
|
||||
UnresolvedFKDetails: doltdb.UnresolvedFKDetails{
|
||||
TableColumns: sqlFk.Columns,
|
||||
ReferencedTableColumns: sqlFk.ParentColumns,
|
||||
},
|
||||
}
|
||||
doltFk, err := t.createForeignKey(ctx, root, tbl, sqlFk, onUpdateRefAction, onDeleteRefAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fkc, err := root.GetForeignKeyCollection(ctx)
|
||||
@@ -2132,12 +2143,7 @@ func (t *AlterableDoltTable) UpdateForeignKey(ctx *sql.Context, fkName string, s
|
||||
doltFk.UnresolvedFKDetails.TableColumns = sqlFk.Columns
|
||||
doltFk.UnresolvedFKDetails.ReferencedTableColumns = sqlFk.ParentColumns
|
||||
|
||||
if doltFk.IsResolved() && !sqlFk.IsResolved { // Need to unresolve the foreign key
|
||||
doltFk.TableIndex = ""
|
||||
doltFk.TableColumns = nil
|
||||
doltFk.ReferencedTableIndex = ""
|
||||
doltFk.ReferencedTableColumns = nil
|
||||
} else if !doltFk.IsResolved() && sqlFk.IsResolved { // Need to assign tags and indexes since it's resolved
|
||||
if !doltFk.IsResolved() || !sqlFk.IsResolved {
|
||||
tbl, _, ok, err := root.GetTableInsensitive(ctx, t.tableName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2145,129 +2151,10 @@ func (t *AlterableDoltTable) UpdateForeignKey(ctx *sql.Context, fkName string, s
|
||||
if !ok {
|
||||
return sql.ErrTableNotFound.New(t.tableName)
|
||||
}
|
||||
|
||||
colTags := make([]uint64, len(sqlFk.Columns))
|
||||
for i, col := range sqlFk.Columns {
|
||||
tableCol, ok := t.sch.GetAllCols().GetByNameCaseInsensitive(col)
|
||||
if !ok {
|
||||
return fmt.Errorf("table `%s` does not have column `%s`", sqlFk.Table, col)
|
||||
}
|
||||
colTags[i] = tableCol.Tag
|
||||
}
|
||||
|
||||
var refTbl *doltdb.Table
|
||||
var refSch schema.Schema
|
||||
if sqlFk.IsSelfReferential() {
|
||||
refTbl = tbl
|
||||
refSch = t.sch
|
||||
} else {
|
||||
refTbl, _, ok, err = root.GetTableInsensitive(ctx, sqlFk.ParentTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("referenced table `%s` does not exist", sqlFk.ParentTable)
|
||||
}
|
||||
refSch, err = refTbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
refColTags := make([]uint64, len(sqlFk.ParentColumns))
|
||||
for i, name := range sqlFk.ParentColumns {
|
||||
refCol, ok := refSch.GetAllCols().GetByNameCaseInsensitive(name)
|
||||
if !ok {
|
||||
return fmt.Errorf("table `%s` does not have column `%s`", sqlFk.ParentTable, name)
|
||||
}
|
||||
refColTags[i] = refCol.Tag
|
||||
}
|
||||
|
||||
tableIndex, ok, err := findIndexWithPrefix(t.sch, sqlFk.Columns)
|
||||
doltFk, err = t.createForeignKey(ctx, root, tbl, sqlFk, doltFk.OnUpdate, doltFk.OnDelete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
// The engine matched on a primary key, and Dolt does not yet support using the primary key within the
|
||||
// schema.Index interface (which is used internally to represent indexes across the codebase). In the
|
||||
// meantime, we must generate a duplicate key over the primary key.
|
||||
//TODO: use the primary key as-is
|
||||
idxReturn, err := creation.CreateIndex(
|
||||
ctx,
|
||||
tbl,
|
||||
"",
|
||||
sqlFk.Columns,
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
editor.Options{
|
||||
ForeignKeyChecksDisabled: true,
|
||||
Deaf: t.opts.Deaf,
|
||||
Tempdir: t.opts.Tempdir,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tableIndex = idxReturn.NewIndex
|
||||
tbl = idxReturn.NewTable
|
||||
root, err = root.PutTable(ctx, t.tableName, idxReturn.NewTable)
|
||||
if sqlFk.IsSelfReferential() {
|
||||
refTbl = idxReturn.NewTable
|
||||
}
|
||||
}
|
||||
|
||||
refTableIndex, ok, err := findIndexWithPrefix(refSch, sqlFk.ParentColumns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
// The engine matched on a primary key, and Dolt does not yet support using the primary key within the
|
||||
// schema.Index interface (which is used internally to represent indexes across the codebase). In the
|
||||
// meantime, we must generate a duplicate key over the primary key.
|
||||
//TODO: use the primary key as-is
|
||||
var refPkTags []uint64
|
||||
for _, i := range refSch.GetPkOrdinals() {
|
||||
refPkTags = append(refPkTags, refSch.GetAllCols().GetByIndex(i).Tag)
|
||||
}
|
||||
|
||||
var colNames []string
|
||||
for _, t := range refColTags {
|
||||
c, _ := refSch.GetAllCols().GetByTag(t)
|
||||
colNames = append(colNames, c.Name)
|
||||
}
|
||||
|
||||
// Our duplicate index is only unique if it's the entire primary key (which is by definition unique)
|
||||
unique := len(refPkTags) == len(refColTags)
|
||||
idxReturn, err := creation.CreateIndex(
|
||||
ctx,
|
||||
refTbl,
|
||||
"",
|
||||
colNames,
|
||||
nil,
|
||||
unique,
|
||||
false,
|
||||
"",
|
||||
editor.Options{
|
||||
ForeignKeyChecksDisabled: true,
|
||||
Deaf: t.opts.Deaf,
|
||||
Tempdir: t.opts.Tempdir,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refTbl = idxReturn.NewTable
|
||||
refTableIndex = idxReturn.NewIndex
|
||||
root, err = root.PutTable(ctx, sqlFk.ParentTable, idxReturn.NewTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
doltFk.TableIndex = tableIndex.Name()
|
||||
doltFk.TableColumns = colTags
|
||||
doltFk.ReferencedTableIndex = refTableIndex.Name()
|
||||
doltFk.ReferencedTableColumns = refColTags
|
||||
}
|
||||
|
||||
err = fkc.AddKeys(doltFk)
|
||||
|
||||
@@ -111,7 +111,12 @@ func (r *JSONReader) ReadSqlRow(ctx context.Context) (sql.Row, error) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
return r.convToSqlRow(metaRow.Value.(map[string]interface{}))
|
||||
mapVal, ok := metaRow.Value.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected JSON format received, expected format: { \"rows\": [ json_row_objects... ] } ")
|
||||
}
|
||||
|
||||
return r.convToSqlRow(mapVal)
|
||||
}
|
||||
|
||||
func (r *JSONReader) convToSqlRow(rowMap map[string]interface{}) (sql.Row, error) {
|
||||
|
||||
@@ -445,6 +445,9 @@ func GetCommitParents(ctx context.Context, vr types.ValueReader, cv types.Value)
|
||||
return nil, errors.New("GetCommitParents: provided value is not a commit.")
|
||||
}
|
||||
addrs, err := types.SerialCommitParentAddrs(vr.Format(), sm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vals, err := vr.ReadManyValues(ctx, addrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -485,6 +488,9 @@ func GetCommitParents(ctx context.Context, vr types.ValueReader, cv types.Value)
|
||||
refs = append(refs, v.(types.Ref))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
ps, ok, err = c.MaybeGet(parentsField)
|
||||
if err != nil {
|
||||
@@ -496,6 +502,9 @@ func GetCommitParents(ctx context.Context, vr types.ValueReader, cv types.Value)
|
||||
refs = append(refs, v.(types.Ref))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
hashes := make([]hash.Hash, len(refs))
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestAWSTablePersisterPersist(t *testing.T) {
|
||||
|
||||
mt := newMemTable(testMemTableSize)
|
||||
for _, c := range testChunks {
|
||||
assert.True(t, mt.addChunk(computeAddr(c), c))
|
||||
assert.Equal(t, mt.addChunk(computeAddr(c), c), chunkAdded)
|
||||
}
|
||||
|
||||
t.Run("PersistToS3", func(t *testing.T) {
|
||||
@@ -89,8 +89,8 @@ func TestAWSTablePersisterPersist(t *testing.T) {
|
||||
existingTable := newMemTable(testMemTableSize)
|
||||
|
||||
for _, c := range testChunks {
|
||||
assert.True(mt.addChunk(computeAddr(c), c))
|
||||
assert.True(existingTable.addChunk(computeAddr(c), c))
|
||||
assert.Equal(mt.addChunk(computeAddr(c), c), chunkAdded)
|
||||
assert.Equal(existingTable.addChunk(computeAddr(c), c), chunkAdded)
|
||||
}
|
||||
|
||||
s3svc, ddb := makeFakeS3(t), makeFakeDTS(makeFakeDDB(t), nil)
|
||||
|
||||
@@ -233,6 +233,11 @@ type bsTableReaderAt struct {
|
||||
bs blobstore.Blobstore
|
||||
}
|
||||
|
||||
func (bsTRA *bsTableReaderAt) Reader(ctx context.Context) (io.ReadCloser, error) {
|
||||
rc, _, err := bsTRA.bs.Get(ctx, bsTRA.key, blobstore.AllRange)
|
||||
return rc, err
|
||||
}
|
||||
|
||||
// ReadAtWithStats is the bsTableReaderAt implementation of the tableReaderAt interface
|
||||
func (bsTRA *bsTableReaderAt) ReadAtWithStats(ctx context.Context, p []byte, off int64, stats *Stats) (int, error) {
|
||||
br := blobstore.NewBlobRange(off, int64(len(p)))
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
package nbs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -53,6 +54,14 @@ func (t tableNotInDynamoErr) Error() string {
|
||||
return fmt.Sprintf("NBS table %s not present in DynamoDB table %s", t.nbs, t.dynamo)
|
||||
}
|
||||
|
||||
func (dtra *dynamoTableReaderAt) Reader(ctx context.Context) (io.ReadCloser, error) {
|
||||
data, err := dtra.ddb.ReadTable(ctx, dtra.h, &Stats{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return io.NopCloser(bytes.NewReader(data)), nil
|
||||
}
|
||||
|
||||
func (dtra *dynamoTableReaderAt) ReadAtWithStats(ctx context.Context, p []byte, off int64, stats *Stats) (n int, err error) {
|
||||
data, err := dtra.ddb.ReadTable(ctx, dtra.h, stats)
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ func (ecs emptyChunkSource) index() (tableIndex, error) {
|
||||
return onHeapTableIndex{}, nil
|
||||
}
|
||||
|
||||
func (ecs emptyChunkSource) reader(context.Context) (io.Reader, uint64, error) {
|
||||
return &bytes.Buffer{}, 0, nil
|
||||
func (ecs emptyChunkSource) reader(context.Context) (io.ReadCloser, uint64, error) {
|
||||
return io.NopCloser(&bytes.Buffer{}), 0, nil
|
||||
}
|
||||
|
||||
func (ecs emptyChunkSource) getRecordRanges(lookups []getRecord) (map[hash.Hash]Range, error) {
|
||||
|
||||
@@ -139,7 +139,7 @@ func TestFSTablePersisterPersist(t *testing.T) {
|
||||
func persistTableData(p tablePersister, chunx ...[]byte) (src chunkSource, err error) {
|
||||
mt := newMemTable(testMemTableSize)
|
||||
for _, c := range chunx {
|
||||
if !mt.addChunk(computeAddr(c), c) {
|
||||
if mt.addChunk(computeAddr(c), c) == chunkNotAdded {
|
||||
return nil, fmt.Errorf("memTable too full to add %s", computeAddr(c))
|
||||
}
|
||||
}
|
||||
@@ -152,8 +152,8 @@ func TestFSTablePersisterPersistNoData(t *testing.T) {
|
||||
existingTable := newMemTable(testMemTableSize)
|
||||
|
||||
for _, c := range testChunks {
|
||||
assert.True(mt.addChunk(computeAddr(c), c))
|
||||
assert.True(existingTable.addChunk(computeAddr(c), c))
|
||||
assert.Equal(mt.addChunk(computeAddr(c), c), chunkAdded)
|
||||
assert.Equal(existingTable.addChunk(computeAddr(c), c), chunkAdded)
|
||||
}
|
||||
|
||||
dir := makeTempDir(t)
|
||||
|
||||
@@ -55,7 +55,7 @@ func tableFileExists(ctx context.Context, dir string, h addr) (bool, error) {
|
||||
func newFileTableReader(ctx context.Context, dir string, h addr, chunkCount uint32, q MemoryQuotaProvider, fc *fdCache) (cs chunkSource, err error) {
|
||||
path := filepath.Join(dir, h.String())
|
||||
|
||||
index, err := func() (ti onHeapTableIndex, err error) {
|
||||
index, sz, err := func() (ti onHeapTableIndex, sz int64, err error) {
|
||||
|
||||
// Be careful with how |f| is used below. |RefFile| returns a cached
|
||||
// os.File pointer so the code needs to use f in a concurrency-safe
|
||||
@@ -82,7 +82,8 @@ func newFileTableReader(ctx context.Context, dir string, h addr, chunkCount uint
|
||||
}
|
||||
|
||||
idxSz := int64(indexSize(chunkCount) + footerSize)
|
||||
indexOffset := fi.Size() - idxSz
|
||||
sz = fi.Size()
|
||||
indexOffset := sz - idxSz
|
||||
r := io.NewSectionReader(f, indexOffset, idxSz)
|
||||
|
||||
var b []byte
|
||||
@@ -122,7 +123,7 @@ func newFileTableReader(ctx context.Context, dir string, h addr, chunkCount uint
|
||||
return nil, errors.New("unexpected chunk count")
|
||||
}
|
||||
|
||||
tr, err := newTableReader(index, &cacheReaderAt{path, fc}, fileBlockSize)
|
||||
tr, err := newTableReader(index, &cacheReaderAt{path, fc, sz}, fileBlockSize)
|
||||
if err != nil {
|
||||
index.Close()
|
||||
return nil, err
|
||||
@@ -153,6 +154,11 @@ func (mmtr *fileTableReader) clone() (chunkSource, error) {
|
||||
type cacheReaderAt struct {
|
||||
path string
|
||||
fc *fdCache
|
||||
sz int64
|
||||
}
|
||||
|
||||
func (cra *cacheReaderAt) Reader(ctx context.Context) (io.ReadCloser, error) {
|
||||
return io.NopCloser(io.LimitReader(&readerAdapter{cra, 0, ctx}, cra.sz)), nil
|
||||
}
|
||||
|
||||
func (cra *cacheReaderAt) ReadAtWithStats(ctx context.Context, p []byte, off int64, stats *Stats) (n int, err error) {
|
||||
|
||||
@@ -193,9 +193,9 @@ func (s journalChunkSource) hash() addr {
|
||||
}
|
||||
|
||||
// reader implements chunkSource.
|
||||
func (s journalChunkSource) reader(context.Context) (io.Reader, uint64, error) {
|
||||
func (s journalChunkSource) reader(context.Context) (io.ReadCloser, uint64, error) {
|
||||
rdr, sz, err := s.journal.Snapshot()
|
||||
return rdr, uint64(sz), err
|
||||
return io.NopCloser(rdr), uint64(sz), err
|
||||
}
|
||||
|
||||
func (s journalChunkSource) getRecordRanges(requests []getRecord) (map[hash.Hash]Range, error) {
|
||||
|
||||
@@ -33,6 +33,14 @@ import (
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
)
|
||||
|
||||
type addChunkResult int
|
||||
|
||||
const (
|
||||
chunkExists addChunkResult = iota
|
||||
chunkAdded
|
||||
chunkNotAdded
|
||||
)
|
||||
|
||||
func WriteChunks(chunks []chunks.Chunk) (string, []byte, error) {
|
||||
var size uint64
|
||||
for _, chunk := range chunks {
|
||||
@@ -46,7 +54,8 @@ func WriteChunks(chunks []chunks.Chunk) (string, []byte, error) {
|
||||
|
||||
func writeChunksToMT(mt *memTable, chunks []chunks.Chunk) (string, []byte, error) {
|
||||
for _, chunk := range chunks {
|
||||
if !mt.addChunk(addr(chunk.Hash()), chunk.Data()) {
|
||||
res := mt.addChunk(addr(chunk.Hash()), chunk.Data())
|
||||
if res == chunkNotAdded {
|
||||
return "", nil, errors.New("didn't create this memory table with enough space to add all the chunks")
|
||||
}
|
||||
}
|
||||
@@ -78,17 +87,19 @@ func newMemTable(memTableSize uint64) *memTable {
|
||||
return &memTable{chunks: map[addr][]byte{}, maxData: memTableSize}
|
||||
}
|
||||
|
||||
func (mt *memTable) addChunk(h addr, data []byte) bool {
|
||||
func (mt *memTable) addChunk(h addr, data []byte) addChunkResult {
|
||||
if len(data) == 0 {
|
||||
panic("NBS blocks cannot be zero length")
|
||||
}
|
||||
if _, ok := mt.chunks[h]; ok {
|
||||
return true
|
||||
return chunkExists
|
||||
}
|
||||
|
||||
dataLen := uint64(len(data))
|
||||
if mt.totalData+dataLen > mt.maxData {
|
||||
return false
|
||||
return chunkNotAdded
|
||||
}
|
||||
|
||||
mt.totalData += dataLen
|
||||
mt.chunks[h] = data
|
||||
mt.order = append(mt.order, hasRecord{
|
||||
@@ -97,7 +108,7 @@ func (mt *memTable) addChunk(h addr, data []byte) bool {
|
||||
len(mt.order),
|
||||
false,
|
||||
})
|
||||
return true
|
||||
return chunkAdded
|
||||
}
|
||||
|
||||
func (mt *memTable) addChildRefs(addrs hash.HashSet) {
|
||||
|
||||
@@ -24,6 +24,7 @@ package nbs
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -89,7 +90,7 @@ func TestMemTableAddHasGetChunk(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, c := range chunks {
|
||||
assert.True(mt.addChunk(computeAddr(c), c))
|
||||
assert.Equal(mt.addChunk(computeAddr(c), c), chunkAdded)
|
||||
}
|
||||
|
||||
assertChunksInReader(chunks, mt, assert)
|
||||
@@ -114,9 +115,9 @@ func TestMemTableAddOverflowChunk(t *testing.T) {
|
||||
{
|
||||
bigAddr := computeAddr(big)
|
||||
mt := newMemTable(memTableSize)
|
||||
assert.True(mt.addChunk(bigAddr, big))
|
||||
assert.Equal(mt.addChunk(bigAddr, big), chunkAdded)
|
||||
assert.True(mt.has(bigAddr))
|
||||
assert.False(mt.addChunk(computeAddr(little), little))
|
||||
assert.Equal(mt.addChunk(computeAddr(little), little), chunkNotAdded)
|
||||
assert.False(mt.has(computeAddr(little)))
|
||||
}
|
||||
|
||||
@@ -124,12 +125,12 @@ func TestMemTableAddOverflowChunk(t *testing.T) {
|
||||
big := big[:memTableSize-1]
|
||||
bigAddr := computeAddr(big)
|
||||
mt := newMemTable(memTableSize)
|
||||
assert.True(mt.addChunk(bigAddr, big))
|
||||
assert.Equal(mt.addChunk(bigAddr, big), chunkAdded)
|
||||
assert.True(mt.has(bigAddr))
|
||||
assert.True(mt.addChunk(computeAddr(little), little))
|
||||
assert.Equal(mt.addChunk(computeAddr(little), little), chunkAdded)
|
||||
assert.True(mt.has(computeAddr(little)))
|
||||
other := []byte("o")
|
||||
assert.False(mt.addChunk(computeAddr(other), other))
|
||||
assert.Equal(mt.addChunk(computeAddr(other), other), chunkNotAdded)
|
||||
assert.False(mt.has(computeAddr(other)))
|
||||
}
|
||||
}
|
||||
@@ -146,7 +147,7 @@ func TestMemTableWrite(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, c := range chunks {
|
||||
assert.True(mt.addChunk(computeAddr(c), c))
|
||||
assert.Equal(mt.addChunk(computeAddr(c), c), chunkAdded)
|
||||
}
|
||||
|
||||
td1, _, err := buildTable(chunks[1:2])
|
||||
@@ -179,15 +180,20 @@ func TestMemTableWrite(t *testing.T) {
|
||||
}
|
||||
|
||||
type tableReaderAtAdapter struct {
|
||||
*bytes.Reader
|
||||
br *bytes.Reader
|
||||
}
|
||||
|
||||
func tableReaderAtFromBytes(b []byte) tableReaderAt {
|
||||
return tableReaderAtAdapter{bytes.NewReader(b)}
|
||||
}
|
||||
|
||||
func (adapter tableReaderAtAdapter) Reader(ctx context.Context) (io.ReadCloser, error) {
|
||||
r := *adapter.br
|
||||
return io.NopCloser(&r), nil
|
||||
}
|
||||
|
||||
func (adapter tableReaderAtAdapter) ReadAtWithStats(ctx context.Context, p []byte, off int64, stats *Stats) (n int, err error) {
|
||||
return adapter.ReadAt(p, off)
|
||||
return adapter.br.ReadAt(p, off)
|
||||
}
|
||||
|
||||
func TestMemTableSnappyWriteOutOfLine(t *testing.T) {
|
||||
@@ -201,7 +207,7 @@ func TestMemTableSnappyWriteOutOfLine(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, c := range chunks {
|
||||
assert.True(mt.addChunk(computeAddr(c), c))
|
||||
assert.Equal(mt.addChunk(computeAddr(c), c), chunkAdded)
|
||||
}
|
||||
mt.snapper = &outOfLineSnappy{[]bool{false, true, false}} // chunks[1] should trigger a panic
|
||||
|
||||
|
||||
@@ -60,6 +60,10 @@ type s3svc interface {
|
||||
PutObjectWithContext(ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error)
|
||||
}
|
||||
|
||||
func (s3tra *s3TableReaderAt) Reader(ctx context.Context) (io.ReadCloser, error) {
|
||||
return s3tra.s3.Reader(ctx, s3tra.h)
|
||||
}
|
||||
|
||||
func (s3tra *s3TableReaderAt) ReadAtWithStats(ctx context.Context, p []byte, off int64, stats *Stats) (n int, err error) {
|
||||
return s3tra.s3.ReadAt(ctx, s3tra.h, p, off, stats)
|
||||
}
|
||||
@@ -79,6 +83,10 @@ func (s3or *s3ObjectReader) key(k string) string {
|
||||
return k
|
||||
}
|
||||
|
||||
func (s3or *s3ObjectReader) Reader(ctx context.Context, name addr) (io.ReadCloser, error) {
|
||||
return s3or.reader(ctx, name)
|
||||
}
|
||||
|
||||
func (s3or *s3ObjectReader) ReadAt(ctx context.Context, name addr, p []byte, off int64, stats *Stats) (n int, err error) {
|
||||
t1 := time.Now()
|
||||
|
||||
@@ -143,6 +151,18 @@ func (s3or *s3ObjectReader) ReadFromEnd(ctx context.Context, name addr, p []byte
|
||||
return s3or.readRange(ctx, name, p, fmt.Sprintf("%s=-%d", s3RangePrefix, len(p)))
|
||||
}
|
||||
|
||||
func (s3or *s3ObjectReader) reader(ctx context.Context, name addr) (io.ReadCloser, error) {
|
||||
input := &s3.GetObjectInput{
|
||||
Bucket: aws.String(s3or.bucket),
|
||||
Key: aws.String(s3or.key(name.String())),
|
||||
}
|
||||
result, err := s3or.s3.GetObjectWithContext(ctx, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.Body, nil
|
||||
}
|
||||
|
||||
func (s3or *s3ObjectReader) readRange(ctx context.Context, name addr, p []byte, rangeHeader string) (n int, sz uint64, err error) {
|
||||
read := func() (int, uint64, error) {
|
||||
if s3or.readRl != nil {
|
||||
|
||||
@@ -623,8 +623,8 @@ func (nbs *NomsBlockStore) addChunk(ctx context.Context, ch chunks.Chunk, addrs
|
||||
}
|
||||
a := addr(ch.Hash())
|
||||
|
||||
ok := nbs.mt.addChunk(a, ch.Data())
|
||||
if !ok {
|
||||
addChunkRes := nbs.mt.addChunk(a, ch.Data())
|
||||
if addChunkRes == chunkNotAdded {
|
||||
ts, err := nbs.tables.append(ctx, nbs.mt, checker, nbs.stats)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrDanglingRef) {
|
||||
@@ -634,12 +634,12 @@ func (nbs *NomsBlockStore) addChunk(ctx context.Context, ch chunks.Chunk, addrs
|
||||
}
|
||||
nbs.tables = ts
|
||||
nbs.mt = newMemTable(nbs.mtSize)
|
||||
ok = nbs.mt.addChunk(a, ch.Data())
|
||||
addChunkRes = nbs.mt.addChunk(a, ch.Data())
|
||||
}
|
||||
if ok {
|
||||
if addChunkRes == chunkAdded {
|
||||
nbs.mt.addChildRefs(addrs)
|
||||
}
|
||||
return ok, nil
|
||||
return addChunkRes == chunkAdded || addChunkRes == chunkExists, nil
|
||||
}
|
||||
|
||||
// refCheck checks that no dangling references are being committed.
|
||||
@@ -1292,7 +1292,7 @@ func newTableFile(cs chunkSource, info tableSpec) tableFile {
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return io.NopCloser(r), s, nil
|
||||
return r, s, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ type chunkSource interface {
|
||||
hash() addr
|
||||
|
||||
// opens a Reader to the first byte of the chunkData segment of this table.
|
||||
reader(context.Context) (io.Reader, uint64, error)
|
||||
reader(context.Context) (io.ReadCloser, uint64, error)
|
||||
|
||||
// getRecordRanges sets getRecord.found to true, and returns a Range for each present getRecord query.
|
||||
getRecordRanges(requests []getRecord) (map[hash.Hash]Range, error)
|
||||
|
||||
@@ -333,8 +333,6 @@ func (ti onHeapTableIndex) lookupOrdinal(h *addr) (uint32, error) {
|
||||
// findPrefix returns the first position in |tr.prefixes| whose value == |prefix|.
|
||||
// Returns |tr.chunkCount| if absent
|
||||
func (ti onHeapTableIndex) findPrefix(prefix uint64) (idx uint32) {
|
||||
query := make([]byte, addrPrefixSize)
|
||||
binary.BigEndian.PutUint64(query, prefix)
|
||||
// NOTE: The golang impl of sort.Search is basically inlined here. This method can be called in
|
||||
// an extremely tight loop and inlining the code was a significant perf improvement.
|
||||
idx, j := 0, ti.count
|
||||
@@ -342,7 +340,8 @@ func (ti onHeapTableIndex) findPrefix(prefix uint64) (idx uint32) {
|
||||
h := idx + (j-idx)/2 // avoid overflow when computing h
|
||||
// i ≤ h < j
|
||||
o := int64(prefixTupleSize * h)
|
||||
if bytes.Compare(ti.prefixTuples[o:o+addrPrefixSize], query) < 0 {
|
||||
tmp := binary.BigEndian.Uint64(ti.prefixTuples[o : o+addrPrefixSize])
|
||||
if tmp < prefix {
|
||||
idx = h + 1 // preserves f(i-1) == false
|
||||
} else {
|
||||
j = h // preserves f(j) == true
|
||||
|
||||
@@ -130,6 +130,7 @@ func (ir indexResult) Length() uint32 {
|
||||
|
||||
type tableReaderAt interface {
|
||||
ReadAtWithStats(ctx context.Context, p []byte, off int64, stats *Stats) (n int, err error)
|
||||
Reader(ctx context.Context) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// tableReader implements get & has queries against a single nbs table. goroutine safe.
|
||||
@@ -631,10 +632,14 @@ func (tr tableReader) extract(ctx context.Context, chunks chan<- extractRecord)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr tableReader) reader(ctx context.Context) (io.Reader, uint64, error) {
|
||||
func (tr tableReader) reader(ctx context.Context) (io.ReadCloser, uint64, error) {
|
||||
i, _ := tr.index()
|
||||
sz := i.tableFileSize()
|
||||
return io.LimitReader(&readerAdapter{tr.r, 0, ctx}, int64(sz)), sz, nil
|
||||
r, err := tr.r.Reader(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return r, sz, nil
|
||||
}
|
||||
|
||||
func (tr tableReader) getRecordRanges(requests []getRecord) (map[hash.Hash]Range, error) {
|
||||
|
||||
@@ -213,16 +213,16 @@ func randomField(tb *val.TupleBuilder, idx int, typ val.Type, ns NodeStore) {
|
||||
v := uint16(testRand.Intn(math.MaxUint16))
|
||||
tb.PutUint16(idx, v)
|
||||
case val.Int32Enc:
|
||||
v := int32(testRand.Intn(math.MaxInt32) * neg)
|
||||
v := testRand.Int31() * int32(neg)
|
||||
tb.PutInt32(idx, v)
|
||||
case val.Uint32Enc:
|
||||
v := uint32(testRand.Intn(math.MaxUint32))
|
||||
v := testRand.Uint32()
|
||||
tb.PutUint32(idx, v)
|
||||
case val.Int64Enc:
|
||||
v := int64(testRand.Intn(math.MaxInt64) * neg)
|
||||
v := testRand.Int63() * int64(neg)
|
||||
tb.PutInt64(idx, v)
|
||||
case val.Uint64Enc:
|
||||
v := uint64(testRand.Uint64())
|
||||
v := testRand.Uint64()
|
||||
tb.PutUint64(idx, v)
|
||||
case val.Float32Enc:
|
||||
tb.PutFloat32(idx, testRand.Float32())
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
const (
|
||||
maxHeight = 9
|
||||
maxCount = math.MaxUint32 - 1
|
||||
maxCount = math.MaxInt32 - 1
|
||||
sentinelId = nodeId(0)
|
||||
initSize = 8
|
||||
)
|
||||
|
||||
@@ -2861,3 +2861,15 @@ SQL
|
||||
[[ $output =~ "Automatic merge failed; 1 table(s) are unmerged." ]]
|
||||
}
|
||||
|
||||
@test "constraint-violations: altering FKs over PKs does not create bad index" {
|
||||
dolt sql <<SQL
|
||||
set foreign_key_checks=0;
|
||||
create table child (j int primary key, foreign key (j) references parent (i));
|
||||
create table parent (i int primary key);
|
||||
set foreign_key_checks=1;
|
||||
delete from parent where i = 0;
|
||||
SQL
|
||||
|
||||
run dolt index ls
|
||||
[[ "$output" =~ "No indexes in the working set" ]] || false
|
||||
}
|
||||
@@ -112,15 +112,13 @@ stop_sql_server() {
|
||||
|
||||
wait=$1
|
||||
if [ ! -z "$SERVER_PID" ]; then
|
||||
serverpidinuse=$(lsof -i -P -n | grep LISTEN | grep $SERVER_PID | wc -l)
|
||||
if [ $serverpidinuse -gt 0 ]; then
|
||||
kill $SERVER_PID
|
||||
# ignore failures of kill command in the case the server is already dead
|
||||
run kill $SERVER_PID
|
||||
if [ $wait ]; then
|
||||
while ps -p $SERVER_PID > /dev/null; do
|
||||
sleep .1;
|
||||
done
|
||||
fi;
|
||||
fi
|
||||
fi
|
||||
SERVER_PID=
|
||||
}
|
||||
|
||||
@@ -196,6 +196,22 @@ SQL
|
||||
[[ "$output" =~ "An error occurred while moving data" ]] || false
|
||||
}
|
||||
|
||||
@test "import-replace-tables: import table with unexpected JSON format" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE employees (
|
||||
id INTEGER,
|
||||
first_name VARCHAR(20) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
SQL
|
||||
|
||||
echo "[{\"id\": 1,\"first_name\":\"Wednesday\"},{\"id\": 2,\"first_name\":\"Monday\"}]" > unexpected.json
|
||||
run dolt table import -r employees unexpected.json
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "An error occurred while moving data" ]] || false
|
||||
[[ "$output" =~ "unexpected JSON format received, expected format: { \"rows\": [ json_row_objects... ] }" ]] || false
|
||||
}
|
||||
|
||||
@test "import-replace-tables: replace table using xlsx file" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE employees (
|
||||
|
||||
@@ -82,6 +82,8 @@ teardown() {
|
||||
cd repo1
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_to_remote remote1
|
||||
dolt config --local --add sqlserver.global.dolt_async_replication 1
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_all_heads 1
|
||||
|
||||
start_sql_server repo1
|
||||
|
||||
dolt sql-client --use-db repo1 -P $PORT -u dolt -q "CALL DOLT_COMMIT('-am', 'Step 1');"
|
||||
@@ -91,6 +93,7 @@ teardown() {
|
||||
|
||||
cd ../repo2
|
||||
dolt pull remote1
|
||||
|
||||
run dolt sql -q "select * from test" -r csv
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "${lines[0]}" =~ "pk" ]]
|
||||
@@ -198,11 +201,13 @@ teardown() {
|
||||
skiponwindows "Missing dependencies"
|
||||
|
||||
cd repo1
|
||||
dolt checkout -b new_feature
|
||||
dolt checkout -b b1
|
||||
dolt commit -am "first commit"
|
||||
dolt branch new_feature2
|
||||
dolt push remote1 new_feature
|
||||
dolt push remote1 new_feature2
|
||||
dolt branch b2
|
||||
dolt branch b3
|
||||
dolt push remote1 b1
|
||||
dolt push remote1 b2
|
||||
dolt push remote1 b3
|
||||
dolt checkout main
|
||||
dolt push remote1 main
|
||||
|
||||
@@ -216,12 +221,18 @@ teardown() {
|
||||
[ $status -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
|
||||
# Can't use dolt sql-client to connect to branches
|
||||
|
||||
# Connecting to heads that exist only on the remote should work fine (they get fetched)
|
||||
dolt sql-client --use-db "repo2/new_feature" -u dolt -P $PORT -q "show tables" "Tables_in_repo2/new_feature\ntest"
|
||||
dolt sql-client --use-db repo2 -P $PORT -u dolt -q 'use `repo2/new_feature2`'
|
||||
run dolt sql-client --use-db repo2 -P $PORT -u dolt -q 'select * from `repo2/new_feature2`.test'
|
||||
dolt sql-client --use-db "repo2/b1" -u dolt -P $PORT -q "show tables" "Tables_in_repo2/b1\ntest"
|
||||
dolt sql-client --use-db repo2 -P $PORT -u dolt -q 'use `repo2/b2`'
|
||||
run dolt sql-client --use-db repo2 -P $PORT -u dolt -q 'select * from `repo2/b2`.test'
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ "pk" ]] || false
|
||||
[[ "$output" =~ " 0 " ]] || false
|
||||
[[ "$output" =~ " 1 " ]] || false
|
||||
[[ "$output" =~ " 2 " ]] || false
|
||||
|
||||
# Remote branch we have never USEd before
|
||||
run dolt sql-client --use-db repo2 -P $PORT -u dolt -q 'select * from `repo2/b3`.test'
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ "pk" ]] || false
|
||||
[[ "$output" =~ " 0 " ]] || false
|
||||
@@ -229,21 +240,13 @@ teardown() {
|
||||
[[ "$output" =~ " 2 " ]] || false
|
||||
|
||||
# Connecting to heads that don't exist should error out
|
||||
run dolt sql-client --use-db "repo2/notexist" -u dolt -P $PORT -q 'use `repo2/new_feature2`'
|
||||
run dolt sql-client --use-db "repo2/notexist" -u dolt -P $PORT -q 'use `repo2/b2`'
|
||||
[ $status -ne 0 ]
|
||||
[[ $output =~ "database not found" ]] || false
|
||||
|
||||
run dolt sql-client --use-db repo2 -P $PORT -u dolt -q 'use `repo2/notexist`'
|
||||
[ $status -ne 0 ]
|
||||
[[ $output =~ "database not found" ]] || false
|
||||
|
||||
# Creating a branch locally that doesn't exist on the remote
|
||||
# works, but connecting to it is an error (nothing to pull)
|
||||
dolt sql-client --use-db "repo2/new_feature" -u dolt -P $PORT -q "call dolt_checkout('-b', 'new_branch')"
|
||||
|
||||
run dolt sql-client --use-db "repo2/new_branch" -u dolt -P $PORT -q "show tables"
|
||||
[ $status -ne 0 ]
|
||||
[[ $output =~ "database not found" ]] || false
|
||||
}
|
||||
|
||||
@test "remotes-sql-server: pull all heads" {
|
||||
@@ -398,6 +401,9 @@ teardown() {
|
||||
run dolt branch
|
||||
[[ ! "$output" =~ "feature" ]] || false
|
||||
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_all_heads 1
|
||||
dolt config --local --add sqlserver.global.dolt_read_replica_remote remote1
|
||||
|
||||
start_sql_server repo2
|
||||
|
||||
# No data on main
|
||||
@@ -410,11 +416,6 @@ teardown() {
|
||||
[[ "$output" =~ "feature" ]] || false
|
||||
[[ ! "$output" =~ "main" ]] || false
|
||||
|
||||
# connecting to remote branch that does not exist creates new local branch and sets upstream
|
||||
run dolt sql-client --use-db repo2/feature -P $PORT -u dolt -q "call dolt_commit('--allow-empty', '-m', 'empty'); call dolt_push()"
|
||||
[ $status -eq 0 ]
|
||||
[[ ! "$output" =~ "the current branch has no upstream branch" ]] || false
|
||||
|
||||
run dolt sql-client --use-db repo2/feature -P $PORT -u dolt -q "show tables"
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ "Tables_in_repo2/feature" ]] || false
|
||||
@@ -422,12 +423,43 @@ teardown() {
|
||||
|
||||
run dolt branch
|
||||
[[ "$output" =~ "feature" ]] || false
|
||||
}
|
||||
|
||||
@test "remotes-sql-server: connect to remote branch pushed after server starts" {
|
||||
skiponwindows "Missing dependencies"
|
||||
|
||||
cd repo1
|
||||
dolt checkout -b feature
|
||||
dolt commit -am "first commit"
|
||||
dolt push remote1 feature
|
||||
dolt checkout main
|
||||
dolt push remote1 main
|
||||
|
||||
cd ../repo2
|
||||
dolt fetch
|
||||
run dolt branch
|
||||
[[ ! "$output" =~ "feature" ]] || false
|
||||
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_all_heads 1
|
||||
dolt config --local --add sqlserver.global.dolt_read_replica_remote remote1
|
||||
|
||||
start_sql_server repo2
|
||||
|
||||
cd ../repo1
|
||||
dolt checkout feature
|
||||
dolt pull remote1 feature
|
||||
run dolt log -n 1 --oneline
|
||||
[[ "$output" =~ "empty" ]] || false
|
||||
dolt branch newbranch
|
||||
dolt push remote1 newbranch
|
||||
|
||||
run dolt sql-client --use-db repo2/feature -P $PORT -u dolt -q "select active_branch()"
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ "feature" ]] || false
|
||||
|
||||
run dolt sql-client --use-db repo2/newbranch -P $PORT -u dolt -q "select active_branch()"
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ "newbranch" ]] || false
|
||||
|
||||
run dolt branch
|
||||
[[ "$output" =~ "feature" ]] || false
|
||||
[[ "$output" =~ "newbranch" ]] || false
|
||||
}
|
||||
|
||||
@test "remotes-sql-server: connect to remote tracking branch fails if there are multiple remotes" {
|
||||
|
||||
@@ -1217,6 +1217,28 @@ SQL
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "remotes: fetch after force push" {
|
||||
mkdir remote clone1
|
||||
cd clone1
|
||||
dolt init
|
||||
dolt sql -q "create table t (pk int primary key);"
|
||||
dolt commit -Am "commit1"
|
||||
|
||||
dolt remote add origin file://../remote
|
||||
dolt push origin main
|
||||
|
||||
cd ..
|
||||
dolt clone file://./remote clone2
|
||||
|
||||
cd clone1
|
||||
dolt commit --amend -m "commit1 edited"
|
||||
dolt push origin main -f
|
||||
|
||||
cd ../clone2
|
||||
run dolt fetch
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "remotes: force fetch from main" {
|
||||
dolt remote add test-remote http://localhost:50051/test-org/test-repo
|
||||
dolt push --set-upstream test-remote main
|
||||
@@ -1256,11 +1278,9 @@ SQL
|
||||
dolt fetch
|
||||
dolt push -f origin main
|
||||
cd ../../
|
||||
run dolt fetch test-remote
|
||||
[ "$status" -ne 0 ]
|
||||
run dolt pull
|
||||
[ "$status" -ne 0 ]
|
||||
run dolt fetch -f test-remote
|
||||
run dolt fetch test-remote
|
||||
[ "$status" -eq 0 ]
|
||||
run dolt pull --no-edit
|
||||
[ "$status" -eq 0 ]
|
||||
@@ -2104,6 +2124,76 @@ SQL
|
||||
[[ "$output" =~ "branch 'feature3' set up to track 'origin/other'" ]] || false
|
||||
}
|
||||
|
||||
@test "remotes: call dolt_branch track flag sets upstream" {
|
||||
mkdir remote
|
||||
mkdir repo1
|
||||
|
||||
cd repo1
|
||||
dolt init
|
||||
dolt remote add origin file://../remote
|
||||
dolt sql -q "CREATE TABLE a (pk int)"
|
||||
dolt commit -Am "add table a"
|
||||
dolt push --set-upstream origin main
|
||||
dolt checkout -b other
|
||||
dolt push --set-upstream origin other
|
||||
|
||||
cd ..
|
||||
dolt clone file://./remote repo2
|
||||
|
||||
cd repo2
|
||||
dolt branch
|
||||
[[ ! "$output" =~ "other" ]] || false
|
||||
|
||||
dolt sql -q "CALL DOLT_BRANCH('--track','other','origin/other');"
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "On branch main" ]] || false
|
||||
|
||||
run dolt checkout other
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run dolt status
|
||||
[[ "$output" =~ "Your branch is up to date with 'origin/other'." ]] || false
|
||||
|
||||
run dolt pull
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Everything up-to-date." ]] || false
|
||||
|
||||
# NOTE: this command fails with git, requiring `--track=direct`, when both branch name and starting point name are defined, but Dolt allows both formats.
|
||||
dolt sql -q "CALL DOLT_BRANCH('feature','--track','direct','origin/other');"
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "On branch other" ]] || false
|
||||
|
||||
dolt commit --allow-empty -m "new commit to other"
|
||||
dolt push
|
||||
|
||||
run dolt checkout feature
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run dolt pull
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Fast-forward" ]] || false
|
||||
|
||||
run dolt branch feature1 --track origin/other
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "branch 'feature1' set up to track 'origin/other'" ]] || false
|
||||
|
||||
dolt sql -q "CALL DOLT_BRANCH('--track','direct','feature2','origin/other');"
|
||||
run dolt checkout feature2
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run dolt status
|
||||
[[ "$output" =~ "Your branch is up to date with 'origin/other'." ]] || false
|
||||
|
||||
dolt sql -q "CALL DOLT_BRANCH('--track=direct','feature3','origin/other');"
|
||||
run dolt checkout feature3
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run dolt status
|
||||
[[ "$output" =~ "Your branch is up to date with 'origin/other'." ]] || false
|
||||
}
|
||||
|
||||
@test "remotes: dolt_clone failure cleanup" {
|
||||
repoDir="$BATS_TMPDIR/dolt-repo-$$"
|
||||
|
||||
|
||||
@@ -493,6 +493,54 @@ SQL
|
||||
[[ "$output" =~ "v1" ]] || false
|
||||
}
|
||||
|
||||
@test "replication: pull creates remote tracking branches" {
|
||||
dolt clone file://./rem1 repo2
|
||||
cd repo2
|
||||
dolt sql -q "create table t1 (a int primary key);"
|
||||
dolt commit -Am "new table"
|
||||
dolt branch b1
|
||||
dolt branch b2
|
||||
dolt push origin b1
|
||||
dolt push origin b2
|
||||
|
||||
cd ../repo1
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_all_heads 1
|
||||
dolt config --local --add sqlserver.global.dolt_read_replica_remote remote1
|
||||
|
||||
run dolt sql -q 'USE `repo1/b2`; show tables;' -r csv
|
||||
[ "$status" -eq 0 ]
|
||||
[ "${#lines[@]}" -eq 3 ]
|
||||
[[ "$output" =~ "t1" ]] || false
|
||||
|
||||
run dolt branch -a
|
||||
[ "$status" -eq 0 ]
|
||||
[ "${#lines[@]}" -eq 6 ]
|
||||
[[ "$output" =~ "remotes/remote1/b1" ]] || false
|
||||
[[ "$output" =~ "remotes/remote1/b2" ]] || false
|
||||
}
|
||||
|
||||
@test "replication: connect to a branch not on the remote" {
|
||||
dolt clone file://./rem1 repo2
|
||||
cd repo2
|
||||
dolt sql -q "create table t1 (a int primary key);"
|
||||
dolt commit -Am "new table"
|
||||
dolt branch b1
|
||||
dolt push origin b1
|
||||
|
||||
cd ../repo1
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_all_heads 1
|
||||
dolt config --local --add sqlserver.global.dolt_read_replica_remote remote1
|
||||
|
||||
run dolt sql -q 'USE `repo1/B1`; show tables;' -r csv
|
||||
[ "$status" -eq 0 ]
|
||||
[ "${#lines[@]}" -eq 3 ]
|
||||
[[ "$output" =~ "t1" ]] || false
|
||||
|
||||
run dolt sql -q 'USE `repo1/notfound`;' -r csv
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" =~ "database not found" ]] || false
|
||||
}
|
||||
|
||||
@test "replication: push feature head" {
|
||||
cd repo1
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_to_remote remote1
|
||||
@@ -675,6 +723,8 @@ SQL
|
||||
cd repo1
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_to_remote remote1
|
||||
dolt config --local --add sqlserver.global.dolt_async_replication 1
|
||||
dolt config --local --add sqlserver.global.dolt_replicate_all_heads 1
|
||||
|
||||
dolt sql -q "create table t1 (a int primary key)"
|
||||
dolt sql -q "call dolt_add('.')"
|
||||
dolt sql -q "call dolt_commit('-am', 'cm')"
|
||||
|
||||
@@ -357,46 +357,17 @@ teardown() {
|
||||
[[ "$output" =~ "t1" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-fetch: dolt_fetch --force" {
|
||||
@test "sql-fetch: dolt_fetch with forced commit" {
|
||||
# reverse information flow for force fetch repo1->rem1->repo2
|
||||
cd repo2
|
||||
dolt sql -q "create table t2 (a int)"
|
||||
dolt add .
|
||||
dolt commit -am "forced commit"
|
||||
dolt push --force origin main
|
||||
|
||||
cd ../repo1
|
||||
|
||||
run dolt sql -q "call dolt_fetch('origin', 'main')"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "fetch failed: can't fast forward merge" ]] || false
|
||||
|
||||
dolt sql -q "call dolt_fetch('--force', 'origin', 'main')"
|
||||
|
||||
dolt diff main origin/main
|
||||
run dolt diff main origin/main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "deleted table" ]] || false
|
||||
|
||||
run dolt sql -q "show tables as of hashof('origin/main')" -r csv
|
||||
[ "${#lines[@]}" -eq 2 ]
|
||||
[[ "$output" =~ "Table" ]] || false
|
||||
[[ "$output" =~ "t2" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-fetch: CALL dolt_fetch --force" {
|
||||
# reverse information flow for force fetch repo1->rem1->repo2
|
||||
cd repo2
|
||||
dolt sql -q "create table t2 (a int)"
|
||||
dolt add .
|
||||
dolt commit -am "forced commit"
|
||||
dolt push --force origin main
|
||||
|
||||
cd ../repo1
|
||||
run dolt sql -q "CALL dolt_fetch('origin', 'main')"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "fetch failed: can't fast forward merge" ]] || false
|
||||
|
||||
dolt sql -q "CALL dolt_fetch('--force', 'origin', 'main')"
|
||||
|
||||
run dolt diff main origin/main
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
@@ -1873,3 +1873,13 @@ s.close()
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ "$EXPECTED" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-server: binary literal is printed as hex string for utf8 charset result set" {
|
||||
cd repo1
|
||||
start_sql_server
|
||||
dolt sql-client -P $PORT -u dolt --use-db repo1 -q "SET character_set_results = utf8; CREATE TABLE mapping(branch_id binary(16) PRIMARY KEY, user_id binary(16) NOT NULL, company_id binary(16) NOT NULL);"
|
||||
|
||||
run dolt sql-client -P $PORT -u dolt --use-db repo1 -q "EXPLAIN SELECT m.* FROM mapping m WHERE user_id = uuid_to_bin('1c4c4e33-8ad7-4421-8450-9d5182816ac3');"
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ "0x1C4C4E338AD7442184509D5182816AC3" ]] || false
|
||||
}
|
||||
|
||||
@@ -2779,3 +2779,17 @@ SQL
|
||||
run dolt sql -q 'INSERT INTO dts (`created_at`) VALUES ("0001-01-01 00:00:00");'
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "sql: multi statement query returns accurate timing" {
|
||||
dolt sql -q "CREATE TABLE t(a int);"
|
||||
dolt sql -q "INSERT INTO t VALUES (1);"
|
||||
dolt sql -q "CREATE TABLE t1(b int);"
|
||||
run dolt sql <<SQL
|
||||
insert into t1 (SELECT * FROM t WHERE EXISTS(SELECT SLEEP(1) UNION SELECT 1));
|
||||
insert into t1 (SELECT * FROM t WHERE EXISTS(SELECT SLEEP(2) UNION SELECT 1));
|
||||
insert into t1 (SELECT * FROM t WHERE EXISTS(SELECT SLEEP(3) UNION SELECT 1));
|
||||
SQL
|
||||
[[ "$output" =~ "Query OK, 1 row affected (1".*" sec)" ]] || false
|
||||
[[ "$output" =~ "Query OK, 1 row affected (2".*" sec)" ]] || false
|
||||
[[ "$output" =~ "Query OK, 1 row affected (3".*" sec)" ]] || false
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ teardown() {
|
||||
[[ "$output" =~ "dolt_log" ]] || false
|
||||
[[ "$output" =~ "dolt_conflicts" ]] || false
|
||||
[[ "$output" =~ "dolt_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remote_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remotes" ]] || false
|
||||
[[ "$output" =~ "dolt_status" ]] || false
|
||||
[[ ! "$output" =~ " test" ]] || false # spaces are impt!
|
||||
@@ -33,6 +34,7 @@ teardown() {
|
||||
[[ "$output" =~ "dolt_commit_ancestors" ]] || false
|
||||
[[ "$output" =~ "dolt_conflicts" ]] || false
|
||||
[[ "$output" =~ "dolt_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remote_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remotes" ]] || false
|
||||
[[ "$output" =~ "dolt_status" ]] || false
|
||||
[[ "$output" =~ "test" ]] || false
|
||||
@@ -65,6 +67,7 @@ teardown() {
|
||||
[[ "$output" =~ "dolt_log" ]] || false
|
||||
[[ "$output" =~ "dolt_conflicts" ]] || false
|
||||
[[ "$output" =~ "dolt_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remote_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remotes" ]] || false
|
||||
[[ ! "$output" =~ "dolt_history_test" ]] || false
|
||||
[[ ! "$output" =~ "dolt_diff_test" ]] || false
|
||||
@@ -74,6 +77,7 @@ teardown() {
|
||||
[[ "$output" =~ "dolt_log" ]] || false
|
||||
[[ "$output" =~ "dolt_conflicts" ]] || false
|
||||
[[ "$output" =~ "dolt_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remote_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remotes" ]] || false
|
||||
[[ "$output" =~ "dolt_history_test" ]] || false
|
||||
[[ "$output" =~ "dolt_commit_diff_test" ]] || false
|
||||
@@ -90,6 +94,7 @@ teardown() {
|
||||
[[ "$output" =~ "dolt_log" ]] || false
|
||||
[[ "$output" =~ "dolt_conflicts" ]] || false
|
||||
[[ "$output" =~ "dolt_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remote_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remotes" ]] || false
|
||||
[[ ! "$output" =~ "dolt_history_test" ]] || false
|
||||
[[ ! "$output" =~ "dolt_diff_test" ]] || false
|
||||
@@ -99,6 +104,7 @@ teardown() {
|
||||
[[ "$output" =~ "dolt_log" ]] || false
|
||||
[[ "$output" =~ "dolt_conflicts" ]] || false
|
||||
[[ "$output" =~ "dolt_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remote_branches" ]] || false
|
||||
[[ "$output" =~ "dolt_remotes" ]] || false
|
||||
[[ "$output" =~ "dolt_history_test" ]] || false
|
||||
[[ "$output" =~ "dolt_diff_test" ]] || false
|
||||
@@ -136,8 +142,61 @@ teardown() {
|
||||
[[ "$output" =~ "create-table-branch" ]] || false
|
||||
}
|
||||
|
||||
@test "system-tables: query dolt_remotes system table" {
|
||||
@test "system-tables: query dolt_remote_branches system table" {
|
||||
dolt checkout -b create-table-branch
|
||||
dolt sql -q "create table test (pk int, c1 int, primary key(pk))"
|
||||
dolt add test
|
||||
dolt commit -m "Added test table"
|
||||
dolt branch "b1"
|
||||
mkdir ./remote1
|
||||
dolt remote add rem1 file://./remote1
|
||||
dolt push rem1 b1
|
||||
dolt branch -d b1
|
||||
|
||||
run dolt sql -q "select * from dolt_branches"
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ main.*Initialize\ data\ repository ]] || false
|
||||
[[ "$output" =~ create-table-branch.*Added\ test\ table ]] || false
|
||||
[[ ! "$output" =~ b1 ]] || false
|
||||
|
||||
run dolt sql -q "select * from dolt_remote_branches"
|
||||
[ $status -eq 0 ]
|
||||
[[ ! "$output" =~ main.*Initialize\ data\ repository ]] || false
|
||||
[[ ! "$output" =~ create-table-branch.*Added\ test\ table ]] || false
|
||||
[[ "$output" =~ "remotes/rem1/b1" ]] || false
|
||||
|
||||
run dolt sql -q "select * from dolt_remote_branches where latest_commit_message ='Initialize data repository'"
|
||||
[ $status -eq 0 ]
|
||||
[[ ! "$output" =~ "main" ]] || false
|
||||
[[ ! "$output" =~ "create-table-branch" ]] || false
|
||||
[[ ! "$output" =~ "remotes/rem1/b1" ]] || false
|
||||
|
||||
run dolt sql -q "select * from dolt_remote_branches where latest_commit_message ='Added test table'"
|
||||
[ $status -eq 0 ]
|
||||
[[ ! "$output" =~ "main" ]] || false
|
||||
[[ ! "$output" =~ "create-table-branch" ]] || false
|
||||
[[ "$output" =~ "remotes/rem1/b1" ]] || false
|
||||
|
||||
run dolt sql -q "select * from dolt_branches union select * from dolt_remote_branches"
|
||||
[[ "$output" =~ "main" ]] || false
|
||||
[[ "$output" =~ "create-table-branch" ]] || false
|
||||
[[ "$output" =~ "remotes/rem1/b1" ]] || false
|
||||
|
||||
# make sure table works with no remote branches
|
||||
mkdir noremotes && cd noremotes
|
||||
dolt init
|
||||
dolt sql <<SQL
|
||||
create table t1(a int primary key);
|
||||
SQL
|
||||
dolt commit -Am 'new table';
|
||||
dolt branch b1
|
||||
|
||||
run dolt sql -q "select * from dolt_remote_branches" -r csv
|
||||
[ "$status" -eq 0 ]
|
||||
[ "${#lines[@]}" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "system-tables: query dolt_remotes system table" {
|
||||
run dolt sql -q "select count(*) from dolt_remotes" -r csv
|
||||
[ $status -eq 0 ]
|
||||
[[ "$output" =~ 0 ]] || false
|
||||
|
||||
@@ -1,98 +1,121 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const user = args[0];
|
||||
const port = args[1];
|
||||
const dbName = args[2];
|
||||
import mysql from "mysql2/promise";
|
||||
import { getConfig } from "./helpers";
|
||||
|
||||
async function createTable() {
|
||||
const conn = await getConnection();
|
||||
try {
|
||||
await conn.execute("create table users (name varchar(20))");
|
||||
} catch (err) {
|
||||
console.error(`Error creating table:`, err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
const conn = await getConnection();
|
||||
try {
|
||||
await conn.execute("create table users (name varchar(20))");
|
||||
} catch (err) {
|
||||
console.error(`Error creating table:`, err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
}
|
||||
|
||||
async function commitTable() {
|
||||
const conn = await getConnection();
|
||||
try {
|
||||
await conn.execute(`call dolt_add('.')`);
|
||||
await conn.execute(`call dolt_commit('-am', 'new table')`);
|
||||
} catch (err) {
|
||||
console.error(`Error committing table:`, err);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
const conn = await getConnection();
|
||||
try {
|
||||
await conn.execute(`call dolt_add('.')`);
|
||||
await conn.execute(`call dolt_commit('-am', 'new table')`);
|
||||
} catch (err) {
|
||||
console.error(`Error committing table:`, err);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
}
|
||||
|
||||
const authors = [
|
||||
'bob', 'john', 'mary', 'alice',
|
||||
'bob2', 'john2', 'mary2', 'alice2',
|
||||
'bob3', 'john3', 'mary3', 'alice3',
|
||||
'bob4', 'john4', 'mary4', 'alice4',
|
||||
'bob5', 'john5', 'mary5', 'alice5',
|
||||
'bob6', 'john6', 'mary6', 'alice6',
|
||||
'bob7', 'john7', 'mary7', 'alice7',
|
||||
'bob8', 'john8', 'mary8', 'alice8',
|
||||
'bob9', 'john9', 'mary9', 'alice9'
|
||||
"bob",
|
||||
"john",
|
||||
"mary",
|
||||
"alice",
|
||||
"bob2",
|
||||
"john2",
|
||||
"mary2",
|
||||
"alice2",
|
||||
"bob3",
|
||||
"john3",
|
||||
"mary3",
|
||||
"alice3",
|
||||
"bob4",
|
||||
"john4",
|
||||
"mary4",
|
||||
"alice4",
|
||||
"bob5",
|
||||
"john5",
|
||||
"mary5",
|
||||
"alice5",
|
||||
"bob6",
|
||||
"john6",
|
||||
"mary6",
|
||||
"alice6",
|
||||
"bob7",
|
||||
"john7",
|
||||
"mary7",
|
||||
"alice7",
|
||||
"bob8",
|
||||
"john8",
|
||||
"mary8",
|
||||
"alice8",
|
||||
"bob9",
|
||||
"john9",
|
||||
"mary9",
|
||||
"alice9",
|
||||
];
|
||||
|
||||
|
||||
async function insertAuthor(name) {
|
||||
const conn = await getConnection();
|
||||
try {
|
||||
await conn.execute('start transaction');
|
||||
await conn.execute('INSERT INTO users (name) VALUES(?);', [name]);
|
||||
await conn.execute(`call dolt_commit('-am', concat('created author', ?))`, [name]);
|
||||
} catch (err) {
|
||||
console.error(`Error committing ${name}:`, err);
|
||||
process.exit(1)
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
const conn = await getConnection();
|
||||
try {
|
||||
await conn.execute("start transaction");
|
||||
await conn.execute("INSERT INTO users (name) VALUES(?);", [name]);
|
||||
await conn.execute(`call dolt_commit('-am', concat('created author', ?))`, [
|
||||
name,
|
||||
]);
|
||||
} catch (err) {
|
||||
console.error(`Error committing ${name}:`, err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
}
|
||||
|
||||
async function validateCommits(name) {
|
||||
const conn = await getConnection();
|
||||
var results;
|
||||
try {
|
||||
results = await conn.query(`select count(*) as c from dolt_log where message like 'created author%'`);
|
||||
} catch (err) {
|
||||
console.error(`Error:`, err);
|
||||
process.exit(1)
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
|
||||
const count = results[0][0].c;
|
||||
const expectedCount = authors.length;
|
||||
if (count != expectedCount) {
|
||||
console.error(`Unexpected number of commits: expected ${expectedCount}, was ${count}`);
|
||||
process.exit(1)
|
||||
}
|
||||
const conn = await getConnection();
|
||||
let results;
|
||||
try {
|
||||
results = await conn.query(
|
||||
`select count(*) as c from dolt_log where message like 'created author%'`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(`Error:`, err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
|
||||
const count = results[0][0].c;
|
||||
const expectedCount = authors.length;
|
||||
if (count != expectedCount) {
|
||||
console.error(
|
||||
`Unexpected number of commits: expected ${expectedCount}, was ${count}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function getConnection() {
|
||||
const connection = await mysql.createConnection({
|
||||
host: '127.0.0.1',
|
||||
port: port,
|
||||
user: user,
|
||||
database: dbName,
|
||||
});
|
||||
return connection;
|
||||
const connection = await mysql.createConnection(getConfig());
|
||||
return connection;
|
||||
}
|
||||
|
||||
// Regression test concurrent dolt_commit with node clients
|
||||
// https://github.com/dolthub/dolt/issues/4361
|
||||
async function main() {
|
||||
await createTable();
|
||||
await commitTable();
|
||||
await Promise.all(authors.map(insertAuthor));
|
||||
await validateCommits();
|
||||
await createTable();
|
||||
await commitTable();
|
||||
await Promise.all(authors.map(insertAuthor));
|
||||
await validateCommits();
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import mysql from "mysql";
|
||||
|
||||
export class Database {
|
||||
constructor(config) {
|
||||
this.connection = mysql.createConnection(config);
|
||||
this.connection.connect();
|
||||
}
|
||||
|
||||
query(sql, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connection.query(sql, args, (err, rows) => {
|
||||
if (err) return reject(err);
|
||||
return resolve(rows);
|
||||
});
|
||||
});
|
||||
}
|
||||
close() {
|
||||
this.connection.end((err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
console.log("db connection closed");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
const args = process.argv.slice(2);
|
||||
const user = args[0];
|
||||
const port = args[1];
|
||||
const dbName = args[2];
|
||||
|
||||
export function getArgs() {
|
||||
return { user, port, dbName };
|
||||
}
|
||||
|
||||
export function getConfig() {
|
||||
const { user, port, dbName } = getArgs();
|
||||
return {
|
||||
host: "127.0.0.1",
|
||||
port: port,
|
||||
user: user,
|
||||
database: dbName,
|
||||
};
|
||||
}
|
||||
@@ -1,145 +1,115 @@
|
||||
const mysql = require('mysql');
|
||||
import { Database } from "./database.js";
|
||||
import { getConfig } from "./helpers.js";
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const user = args[0];
|
||||
const port = args[1];
|
||||
const db = args[2];
|
||||
|
||||
const config = {
|
||||
host: '127.0.0.1',
|
||||
user: user,
|
||||
port: port,
|
||||
database: db
|
||||
};
|
||||
|
||||
class Database {
|
||||
constructor( config ) {
|
||||
this.connection = mysql.createConnection( config );
|
||||
this.connection.connect();
|
||||
}
|
||||
|
||||
query( sql, args ) {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
this.connection.query( sql, args, ( err, rows ) => {
|
||||
if ( err )
|
||||
return reject( err );
|
||||
return resolve( rows );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
close() {
|
||||
this.connection.end(err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
} else {
|
||||
console.log("db connection closed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const tests = [
|
||||
{
|
||||
q: "create table test (pk int, `value` int, primary key(pk))",
|
||||
res: {
|
||||
fieldCount: 0,
|
||||
affectedRows: 0,
|
||||
insertId: 0,
|
||||
serverStatus: 2,
|
||||
warningCount: 0,
|
||||
message: "",
|
||||
protocol41: true,
|
||||
changedRows: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
q: "describe test",
|
||||
res: [
|
||||
{
|
||||
Field: "pk",
|
||||
Type: "int",
|
||||
Null: "NO",
|
||||
Key: "PRI",
|
||||
Default: "NULL",
|
||||
Extra: "",
|
||||
},
|
||||
{
|
||||
Field: "value",
|
||||
Type: "int",
|
||||
Null: "YES",
|
||||
Key: "",
|
||||
Default: "NULL",
|
||||
Extra: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ q: "select * from test", res: [] },
|
||||
{
|
||||
q: "insert into test (pk, `value`) values (0,0)",
|
||||
res: {
|
||||
fieldCount: 0,
|
||||
affectedRows: 1,
|
||||
insertId: 0,
|
||||
serverStatus: 2,
|
||||
warningCount: 0,
|
||||
message: "",
|
||||
protocol41: true,
|
||||
changedRows: 0,
|
||||
},
|
||||
},
|
||||
{ q: "select * from test", res: [{ pk: 0, value: 0 }] },
|
||||
{ q: "call dolt_add('-A');", res: [{ status: 0 }] },
|
||||
{ q: "call dolt_commit('-m', 'my commit')", res: [] },
|
||||
{ q: "select COUNT(*) FROM dolt_log", res: [{ "COUNT(*)": 2 }] },
|
||||
{ q: "call dolt_checkout('-b', 'mybranch')", res: [{ status: 0 }] },
|
||||
{
|
||||
q: "insert into test (pk, `value`) values (1,1)",
|
||||
res: {
|
||||
fieldCount: 0,
|
||||
affectedRows: 1,
|
||||
insertId: 0,
|
||||
serverStatus: 2,
|
||||
warningCount: 0,
|
||||
message: "",
|
||||
protocol41: true,
|
||||
changedRows: 0,
|
||||
},
|
||||
},
|
||||
{ q: "call dolt_commit('-a', '-m', 'my commit2')", res: [] },
|
||||
{ q: "call dolt_checkout('main')", res: [{ status: 0 }] },
|
||||
{
|
||||
q: "call dolt_merge('mybranch')",
|
||||
res: [{ fast_forward: 1, conflicts: 0 }],
|
||||
},
|
||||
{ q: "select COUNT(*) FROM dolt_log", res: [{ "COUNT(*)": 3 }] },
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const queries = [
|
||||
"create table test (pk int, `value` int, primary key(pk))",
|
||||
"describe test",
|
||||
"select * from test",
|
||||
"insert into test (pk, `value`) values (0,0)",
|
||||
"select * from test",
|
||||
"call dolt_add('-A');",
|
||||
"call dolt_commit('-m', 'my commit')",
|
||||
"select COUNT(*) FROM dolt_log",
|
||||
"call dolt_checkout('-b', 'mybranch')",
|
||||
"insert into test (pk, `value`) values (1,1)",
|
||||
"call dolt_commit('-a', '-m', 'my commit2')",
|
||||
"call dolt_checkout('main')",
|
||||
"call dolt_merge('mybranch')",
|
||||
"select COUNT(*) FROM dolt_log",
|
||||
];
|
||||
const database = new Database(getConfig());
|
||||
|
||||
const results = [
|
||||
{
|
||||
fieldCount: 0,
|
||||
affectedRows: 0,
|
||||
insertId: 0,
|
||||
serverStatus: 2,
|
||||
warningCount: 0,
|
||||
message: '',
|
||||
protocol41: true,
|
||||
changedRows: 0
|
||||
},
|
||||
[ { Field: 'pk',
|
||||
Type: 'int',
|
||||
Null: 'NO',
|
||||
Key: 'PRI',
|
||||
Default: 'NULL',
|
||||
Extra: '' },
|
||||
{ Field: 'value',
|
||||
Type: 'int',
|
||||
Null: 'YES',
|
||||
Key: '',
|
||||
Default: 'NULL',
|
||||
Extra: '' }
|
||||
],
|
||||
[],
|
||||
{
|
||||
fieldCount: 0,
|
||||
affectedRows: 1,
|
||||
insertId: 0,
|
||||
serverStatus: 2,
|
||||
warningCount: 0,
|
||||
message: '',
|
||||
protocol41: true,
|
||||
changedRows: 0
|
||||
},
|
||||
[ { pk: 0, value: 0 } ],
|
||||
[ { status: 0 } ],
|
||||
[],
|
||||
[ { "COUNT(*)": 2 } ],
|
||||
[ { status: 0 } ],
|
||||
{
|
||||
fieldCount: 0,
|
||||
affectedRows: 1,
|
||||
insertId: 0,
|
||||
serverStatus: 2,
|
||||
warningCount: 0,
|
||||
message: '',
|
||||
protocol41: true,
|
||||
changedRows: 0
|
||||
},
|
||||
[],
|
||||
[ { status: 0 } ],
|
||||
[ { fast_forward: 1, conflicts: 0 } ],
|
||||
[ { "COUNT(*)": 3 } ],
|
||||
];
|
||||
await Promise.all(
|
||||
tests.map((test) => {
|
||||
const expected = test.res;
|
||||
return database
|
||||
.query(test.q)
|
||||
.then((rows) => {
|
||||
const resultStr = JSON.stringify(rows);
|
||||
const result = JSON.parse(resultStr);
|
||||
if (
|
||||
resultStr !== JSON.stringify(expected) &&
|
||||
test.q.includes("dolt_commit") &&
|
||||
!(rows.length === 1 && rows[0].hash.length > 0)
|
||||
) {
|
||||
console.log("Query:", test.q);
|
||||
console.log("Results:", result);
|
||||
console.log("Expected:", expected);
|
||||
throw new Error("Query failed");
|
||||
} else {
|
||||
console.log("Query succeeded:", test.q);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const database = new Database(config);
|
||||
|
||||
await Promise.all(queries.map((query, idx) => {
|
||||
const expected = results[idx];
|
||||
return database.query(query).then(rows => {
|
||||
const resultStr = JSON.stringify(rows);
|
||||
const result = JSON.parse(resultStr);
|
||||
if (resultStr !== JSON.stringify(expected) && !(query.includes("dolt_commit"))) {
|
||||
console.log("Query:", query);
|
||||
console.log("Results:", result);
|
||||
console.log("Expected:", expected);
|
||||
throw new Error("Query failed")
|
||||
} else {
|
||||
console.log("Query succeeded:", query)
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
process.exit(1);
|
||||
});
|
||||
}));
|
||||
|
||||
database.close()
|
||||
process.exit(0)
|
||||
database.close();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,84 +1,79 @@
|
||||
const knex = require("knex");
|
||||
const wtfnode = require("wtfnode")
|
||||
Socket = require('net').Socket;
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const user = args[0];
|
||||
const port = args[1];
|
||||
const dbName = args[2];
|
||||
import knex from "knex";
|
||||
import wtfnode from "wtfnode";
|
||||
import { Socket } from "net";
|
||||
import { getConfig } from "./helpers.js";
|
||||
|
||||
const db = knex({
|
||||
client: "mysql2",
|
||||
connection: {
|
||||
host: "127.0.0.1",
|
||||
port: port,
|
||||
user: user,
|
||||
database: dbName,
|
||||
},
|
||||
client: "mysql2",
|
||||
connection: getConfig(),
|
||||
});
|
||||
|
||||
async function createTable() {
|
||||
let val = await db.schema.createTable('test2', (table) => {
|
||||
table.integer('id').primary()
|
||||
table.integer('foo')
|
||||
});
|
||||
return val
|
||||
const val = await db.schema.createTable("test2", (table) => {
|
||||
table.integer("id").primary();
|
||||
table.integer("foo");
|
||||
});
|
||||
return val;
|
||||
}
|
||||
|
||||
async function upsert(table, data) {
|
||||
let val = await db(table).insert(data).onConflict().merge();
|
||||
return val
|
||||
const val = await db(table).insert(data).onConflict().merge();
|
||||
return val;
|
||||
}
|
||||
|
||||
async function select() {
|
||||
let val = await db.select('id', 'foo').from('test2');
|
||||
return val
|
||||
const val = await db.select("id", "foo").from("test2");
|
||||
return val;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await createTable();
|
||||
await Promise.all([
|
||||
upsert("test2", { id: 1, foo: 1 }),
|
||||
upsert("test2", { id: 2, foo: 2 }),
|
||||
])
|
||||
await createTable();
|
||||
await Promise.all([
|
||||
upsert("test2", { id: 1, foo: 1 }),
|
||||
upsert("test2", { id: 2, foo: 2 }),
|
||||
]);
|
||||
|
||||
let expectedResult = JSON.stringify([ { id: 1, foo: 1 }, { id: 2, foo: 2 } ])
|
||||
let result = await select();
|
||||
if (JSON.stringify(result) !== expectedResult) {
|
||||
console.log("Results:", result);
|
||||
console.log("Expected:", expectedResult);
|
||||
process.exit(1)
|
||||
throw new Error("Query failed")
|
||||
const expectedResult = JSON.stringify([
|
||||
{ id: 1, foo: 1 },
|
||||
{ id: 2, foo: 2 },
|
||||
]);
|
||||
const result = await select();
|
||||
if (JSON.stringify(result) !== expectedResult) {
|
||||
console.log("Results:", result);
|
||||
console.log("Expected:", expectedResult);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await db.destroy();
|
||||
|
||||
// cc: https://github.com/dolthub/dolt/issues/3752
|
||||
setTimeout(async () => {
|
||||
const sockets = await getOpenSockets();
|
||||
|
||||
if (sockets.length > 0) {
|
||||
wtfnode.dump();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await db.destroy();
|
||||
|
||||
// cc: https://github.com/dolthub/dolt/issues/3752
|
||||
setTimeout(async () => {
|
||||
let sockets = await getOpenSockets();
|
||||
|
||||
if (sockets.length > 0) {
|
||||
wtfnode.dump();
|
||||
process.exit(1);
|
||||
throw new Error("Database not properly destroyed. Hanging server connections");
|
||||
}
|
||||
|
||||
}, 3000);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// cc: https://github.com/myndzi/wtfnode/blob/master/index.js#L457
|
||||
async function getOpenSockets() {
|
||||
let sockets = []
|
||||
process._getActiveHandles().forEach(function (h) {
|
||||
// handles can be null now? early exit to guard against this
|
||||
if (!h) { return; }
|
||||
const sockets = [];
|
||||
process._getActiveHandles().forEach(function (h) {
|
||||
// handles can be null now? early exit to guard against this
|
||||
if (!h) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (h instanceof Socket) {
|
||||
if ((h.fd == null) && (h.localAddress) && !(h.destroyed)) { sockets.push(h); }
|
||||
}
|
||||
});
|
||||
if (h instanceof Socket) {
|
||||
if (h.fd == null && h.localAddress && !h.destroyed) {
|
||||
sockets.push(h);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return sockets
|
||||
return sockets;
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
+33
-19
@@ -9,9 +9,10 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"knex": "^1.0.7",
|
||||
"knex": "^2.4.0",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^2.3.3"
|
||||
"mysql2": "^2.3.3",
|
||||
"wtfnode": "^0.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
@@ -23,9 +24,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "2.0.16",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
|
||||
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g=="
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz",
|
||||
"integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ=="
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "9.2.0",
|
||||
@@ -163,11 +164,11 @@
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"node_modules/knex": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/knex/-/knex-1.0.7.tgz",
|
||||
"integrity": "sha512-89jxuRATt4qJMb9ZyyaKBy0pQ4d5h7eOFRqiNFnUvsgU+9WZ2eIaZKrAPG1+F3mgu5UloPUnkVE5Yo2sKZUs6Q==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/knex/-/knex-2.4.0.tgz",
|
||||
"integrity": "sha512-i0GWwqYp1Hs2yvc2rlDO6nzzkLhwdyOZKRdsMTB8ZxOs2IXQyL5rBjSbS1krowCh6V65T4X9CJaKtuIfkaPGSA==",
|
||||
"dependencies": {
|
||||
"colorette": "2.0.16",
|
||||
"colorette": "2.0.19",
|
||||
"commander": "^9.1.0",
|
||||
"debug": "4.3.4",
|
||||
"escalade": "^3.1.1",
|
||||
@@ -189,9 +190,6 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vscode/sqlite3": {
|
||||
"optional": true
|
||||
},
|
||||
"better-sqlite3": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -207,6 +205,9 @@
|
||||
"pg-native": {
|
||||
"optional": true
|
||||
},
|
||||
"sqlite3": {
|
||||
"optional": true
|
||||
},
|
||||
"tedious": {
|
||||
"optional": true
|
||||
}
|
||||
@@ -435,6 +436,14 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"node_modules/wtfnode": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.9.1.tgz",
|
||||
"integrity": "sha512-Ip6C2KeQPl/F3aP1EfOnPoQk14Udd9lffpoqWDNH3Xt78svxPbv53ngtmtfI0q2Te3oTq79XKTnRNXVIn/GsPA==",
|
||||
"bin": {
|
||||
"wtfnode": "proxy.js"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
@@ -448,9 +457,9 @@
|
||||
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
|
||||
},
|
||||
"colorette": {
|
||||
"version": "2.0.16",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
|
||||
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g=="
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz",
|
||||
"integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ=="
|
||||
},
|
||||
"commander": {
|
||||
"version": "9.2.0",
|
||||
@@ -553,11 +562,11 @@
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"knex": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/knex/-/knex-1.0.7.tgz",
|
||||
"integrity": "sha512-89jxuRATt4qJMb9ZyyaKBy0pQ4d5h7eOFRqiNFnUvsgU+9WZ2eIaZKrAPG1+F3mgu5UloPUnkVE5Yo2sKZUs6Q==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/knex/-/knex-2.4.0.tgz",
|
||||
"integrity": "sha512-i0GWwqYp1Hs2yvc2rlDO6nzzkLhwdyOZKRdsMTB8ZxOs2IXQyL5rBjSbS1krowCh6V65T4X9CJaKtuIfkaPGSA==",
|
||||
"requires": {
|
||||
"colorette": "2.0.16",
|
||||
"colorette": "2.0.19",
|
||||
"commander": "^9.1.0",
|
||||
"debug": "4.3.4",
|
||||
"escalade": "^3.1.1",
|
||||
@@ -758,6 +767,11 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"wtfnode": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.9.1.tgz",
|
||||
"integrity": "sha512-Ip6C2KeQPl/F3aP1EfOnPoQk14Udd9lffpoqWDNH3Xt78svxPbv53ngtmtfI0q2Te3oTq79XKTnRNXVIn/GsPA=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
"version": "1.0.0",
|
||||
"description": "A simple node command line utility to show how to connect a node application to a Dolt database using the MySQL connector.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"knex": "^1.0.7",
|
||||
"knex": "^2.4.0",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^2.3.3",
|
||||
"wtfnode": "^0.9.1"
|
||||
|
||||
Reference in New Issue
Block a user