mirror of
https://github.com/dolthub/dolt.git
synced 2026-05-07 11:22:02 -05:00
Merge pull request #3487 from dolthub/fulghum/dolt_branch
Adding support for deleting and renaming branches from SQL with `dolt_branch`
This commit is contained in:
@@ -220,7 +220,7 @@ func moveBranch(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgParseR
|
||||
force := apr.Contains(forceFlag)
|
||||
src := apr.Arg(0)
|
||||
dest := apr.Arg(1)
|
||||
err := actions.RenameBranch(ctx, dEnv, src, apr.Arg(1), force)
|
||||
err := actions.RenameBranch(ctx, dEnv.DbData(), dEnv.Config, src, apr.Arg(1), force)
|
||||
|
||||
var verr errhand.VerboseError
|
||||
if err != nil {
|
||||
@@ -285,7 +285,7 @@ func handleDeleteBranches(ctx context.Context, dEnv *env.DoltEnv, apr *argparser
|
||||
for i := 0; i < apr.NArg(); i++ {
|
||||
brName := apr.Arg(i)
|
||||
|
||||
err := actions.DeleteBranch(ctx, dEnv, brName, actions.DeleteOptions{
|
||||
err := actions.DeleteBranch(ctx, dEnv.DbData(), dEnv.Config, brName, actions.DeleteOptions{
|
||||
Force: force,
|
||||
Remote: apr.Contains(remoteFlag),
|
||||
})
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
||||
_ "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dfunctions"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqlserver"
|
||||
)
|
||||
|
||||
// Serve starts a MySQL-compatible server. Returns any errors that were encountered.
|
||||
@@ -62,6 +63,7 @@ func Serve(
|
||||
}
|
||||
serverController.StopServer()
|
||||
serverController.serverStopped(closeError)
|
||||
sqlserver.SetRunningServer(nil)
|
||||
}()
|
||||
|
||||
if startError = ValidateConfig(serverConfig); startError != nil {
|
||||
@@ -195,6 +197,8 @@ func Serve(
|
||||
if startError != nil {
|
||||
cli.PrintErr(startError)
|
||||
return
|
||||
} else {
|
||||
sqlserver.SetRunningServer(mySQLServer)
|
||||
}
|
||||
|
||||
var metSrv *http.Server
|
||||
|
||||
@@ -154,17 +154,17 @@ func TestServerGoodParams(t *testing.T) {
|
||||
|
||||
tests := []ServerConfig{
|
||||
DefaultServerConfig(),
|
||||
DefaultServerConfig().withHost("127.0.0.1").withPort(15400),
|
||||
DefaultServerConfig().withHost("localhost").withPort(15401),
|
||||
//DefaultServerConfig().withHost("::1").withPort(15402), // Fails on Jenkins, assuming no IPv6 support
|
||||
DefaultServerConfig().withUser("testusername").withPort(15403),
|
||||
DefaultServerConfig().withPassword("hunter2").withPort(15404),
|
||||
DefaultServerConfig().withTimeout(0).withPort(15405),
|
||||
DefaultServerConfig().withTimeout(5).withPort(15406),
|
||||
DefaultServerConfig().withLogLevel(LogLevel_Debug).withPort(15407),
|
||||
DefaultServerConfig().withLogLevel(LogLevel_Info).withPort(15408),
|
||||
DefaultServerConfig().withReadOnly(true).withPort(15409),
|
||||
DefaultServerConfig().withUser("testusernamE").withPassword("hunter2").withTimeout(4).withPort(15410),
|
||||
DefaultServerConfig().withHost("127.0.0.1").WithPort(15400),
|
||||
DefaultServerConfig().withHost("localhost").WithPort(15401),
|
||||
//DefaultServerConfig().withHost("::1").WithPort(15402), // Fails on Jenkins, assuming no IPv6 support
|
||||
DefaultServerConfig().withUser("testusername").WithPort(15403),
|
||||
DefaultServerConfig().withPassword("hunter2").WithPort(15404),
|
||||
DefaultServerConfig().withTimeout(0).WithPort(15405),
|
||||
DefaultServerConfig().withTimeout(5).WithPort(15406),
|
||||
DefaultServerConfig().withLogLevel(LogLevel_Debug).WithPort(15407),
|
||||
DefaultServerConfig().withLogLevel(LogLevel_Info).WithPort(15408),
|
||||
DefaultServerConfig().withReadOnly(true).WithPort(15409),
|
||||
DefaultServerConfig().withUser("testusernamE").withPassword("hunter2").withTimeout(4).WithPort(15410),
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -188,7 +188,7 @@ func TestServerGoodParams(t *testing.T) {
|
||||
|
||||
func TestServerSelect(t *testing.T) {
|
||||
env := dtestutils.CreateEnvWithSeedData(t)
|
||||
serverConfig := DefaultServerConfig().withLogLevel(LogLevel_Fatal).withPort(15300)
|
||||
serverConfig := DefaultServerConfig().withLogLevel(LogLevel_Fatal).WithPort(15300)
|
||||
|
||||
sc := NewServerController()
|
||||
defer sc.StopServer()
|
||||
@@ -263,7 +263,7 @@ func TestServerFailsIfPortInUse(t *testing.T) {
|
||||
|
||||
func TestServerSetDefaultBranch(t *testing.T) {
|
||||
dEnv := dtestutils.CreateEnvWithSeedData(t)
|
||||
serverConfig := DefaultServerConfig().withLogLevel(LogLevel_Fatal).withPort(15302)
|
||||
serverConfig := DefaultServerConfig().withLogLevel(LogLevel_Fatal).WithPort(15302)
|
||||
|
||||
sc := NewServerController()
|
||||
defer sc.StopServer()
|
||||
@@ -413,7 +413,7 @@ func TestReadReplica(t *testing.T) {
|
||||
|
||||
// start server as read replica
|
||||
sc := NewServerController()
|
||||
serverConfig := DefaultServerConfig().withLogLevel(LogLevel_Fatal).withPort(15303)
|
||||
serverConfig := DefaultServerConfig().withLogLevel(LogLevel_Fatal).WithPort(15303)
|
||||
|
||||
func() {
|
||||
os.Chdir(multiSetup.DbPaths[readReplicaDbName])
|
||||
|
||||
@@ -267,8 +267,8 @@ func (cfg *commandLineServerConfig) withHost(host string) *commandLineServerConf
|
||||
return cfg
|
||||
}
|
||||
|
||||
// withPort updates the port and returns the called `*commandLineServerConfig`, which is useful for chaining calls.
|
||||
func (cfg *commandLineServerConfig) withPort(port int) *commandLineServerConfig {
|
||||
// WithPort updates the port and returns the called `*commandLineServerConfig`, which is useful for chaining calls.
|
||||
func (cfg *commandLineServerConfig) WithPort(port int) *commandLineServerConfig {
|
||||
cfg.port = port
|
||||
return cfg
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ func getCommandLineServerConfig(dEnv *env.DoltEnv, apr *argparser.ArgParseResult
|
||||
serverConfig.withHost(host)
|
||||
}
|
||||
if port, ok := apr.GetInt(portFlag); ok {
|
||||
serverConfig.withPort(port)
|
||||
serverConfig.WithPort(port)
|
||||
}
|
||||
if user, ok := apr.GetValue(userFlag); ok {
|
||||
serverConfig.withUser(user)
|
||||
|
||||
+12
-12
@@ -30,17 +30,17 @@ var ErrAlreadyExists = errors.New("already exists")
|
||||
var ErrCOBranchDelete = errors.New("attempted to delete checked out branch")
|
||||
var ErrUnmergedBranchDelete = errors.New("attempted to delete a branch that is not fully merged into its parent; use `-f` to force")
|
||||
|
||||
func RenameBranch(ctx context.Context, dEnv *env.DoltEnv, oldBranch, newBranch string, force bool) error {
|
||||
func RenameBranch(ctx context.Context, dbData env.DbData, config *env.DoltCliConfig, oldBranch, newBranch string, force bool) error {
|
||||
oldRef := ref.NewBranchRef(oldBranch)
|
||||
newRef := ref.NewBranchRef(newBranch)
|
||||
|
||||
err := CopyBranch(ctx, dEnv, oldBranch, newBranch, force)
|
||||
err := CopyBranchOnDB(ctx, dbData.Ddb, oldBranch, newBranch, force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ref.Equals(dEnv.RepoStateReader().CWBHeadRef(), oldRef) {
|
||||
err = dEnv.RepoStateWriter().SetCWBHeadRef(ctx, ref.MarshalableRef{Ref: newRef})
|
||||
if ref.Equals(dbData.Rsr.CWBHeadRef(), oldRef) {
|
||||
err = dbData.Rsw.SetCWBHeadRef(ctx, ref.MarshalableRef{Ref: newRef})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,13 +59,13 @@ func RenameBranch(ctx context.Context, dEnv *env.DoltEnv, oldBranch, newBranch s
|
||||
// We always `force` here, because the CopyBranch up
|
||||
// above created a new branch and it will have a
|
||||
// working set.
|
||||
err = dEnv.DoltDB.CopyWorkingSet(ctx, fromWSRef, toWSRef, true /* force */)
|
||||
err = dbData.Ddb.CopyWorkingSet(ctx, fromWSRef, toWSRef, true /* force */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return DeleteBranch(ctx, dEnv, oldBranch, DeleteOptions{Force: true})
|
||||
return DeleteBranch(ctx, dbData, config, oldBranch, DeleteOptions{Force: true})
|
||||
}
|
||||
|
||||
func CopyBranch(ctx context.Context, dEnv *env.DoltEnv, oldBranch, newBranch string, force bool) error {
|
||||
@@ -111,7 +111,7 @@ type DeleteOptions struct {
|
||||
Remote bool
|
||||
}
|
||||
|
||||
func DeleteBranch(ctx context.Context, dEnv *env.DoltEnv, brName string, opts DeleteOptions) error {
|
||||
func DeleteBranch(ctx context.Context, dbData env.DbData, config *env.DoltCliConfig, brName string, opts DeleteOptions) error {
|
||||
var dref ref.DoltRef
|
||||
if opts.Remote {
|
||||
var err error
|
||||
@@ -121,16 +121,16 @@ func DeleteBranch(ctx context.Context, dEnv *env.DoltEnv, brName string, opts De
|
||||
}
|
||||
} else {
|
||||
dref = ref.NewBranchRef(brName)
|
||||
if ref.Equals(dEnv.RepoStateReader().CWBHeadRef(), dref) {
|
||||
if ref.Equals(dbData.Rsr.CWBHeadRef(), dref) {
|
||||
return ErrCOBranchDelete
|
||||
}
|
||||
}
|
||||
|
||||
return DeleteBranchOnDB(ctx, dEnv, dref, opts)
|
||||
return DeleteBranchOnDB(ctx, dbData, config, dref, opts)
|
||||
}
|
||||
|
||||
func DeleteBranchOnDB(ctx context.Context, dEnv *env.DoltEnv, dref ref.DoltRef, opts DeleteOptions) error {
|
||||
ddb := dEnv.DoltDB
|
||||
func DeleteBranchOnDB(ctx context.Context, dbData env.DbData, config *env.DoltCliConfig, dref ref.DoltRef, opts DeleteOptions) error {
|
||||
ddb := dbData.Ddb
|
||||
hasRef, err := ddb.HasRef(ctx, dref)
|
||||
|
||||
if err != nil {
|
||||
@@ -140,7 +140,7 @@ func DeleteBranchOnDB(ctx context.Context, dEnv *env.DoltEnv, dref ref.DoltRef,
|
||||
}
|
||||
|
||||
if !opts.Force && !opts.Remote {
|
||||
ms, err := doltdb.NewCommitSpec(env.GetDefaultInitBranch(dEnv.Config))
|
||||
ms, err := doltdb.NewCommitSpec(env.GetDefaultInitBranch(config))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -26,8 +26,11 @@ 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/libraries/doltcore/sqlserver"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/argparser"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/filesys"
|
||||
)
|
||||
|
||||
const DoltBranchFuncName = "dolt_branch"
|
||||
@@ -91,50 +94,130 @@ func DoDoltBranch(ctx *sql.Context, args []string) (int, error) {
|
||||
|
||||
switch {
|
||||
case apr.Contains(cli.CopyFlag):
|
||||
err = makeACopyOfBranch(ctx, dbData, apr)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
err = copyBranch(ctx, dbData, apr)
|
||||
case apr.Contains(cli.MoveFlag):
|
||||
return 1, errors.New("Renaming a branch is not supported.")
|
||||
case apr.Contains(cli.DeleteFlag):
|
||||
return 1, errors.New("Deleting branches is not supported.")
|
||||
case apr.Contains(cli.DeleteForceFlag):
|
||||
return 1, errors.New("Deleting branches is not supported.")
|
||||
err = renameBranch(ctx, dbData, apr)
|
||||
case apr.Contains(cli.DeleteFlag), apr.Contains(cli.DeleteForceFlag):
|
||||
err = deleteBranches(ctx, apr, dbData)
|
||||
default:
|
||||
// regular branch - create new branch
|
||||
if apr.NArg() != 1 {
|
||||
return 1, InvalidArgErr
|
||||
}
|
||||
|
||||
branchName := apr.Arg(0)
|
||||
if len(branchName) == 0 {
|
||||
return 1, EmptyBranchNameErr
|
||||
}
|
||||
|
||||
err = createNewBranch(ctx, dbData, branchName)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
err = createNewBranch(ctx, dbData, apr)
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func createNewBranch(ctx *sql.Context, dbData env.DbData, branchName string) error {
|
||||
// Check if the branch already exists.
|
||||
isBranch, err := actions.IsBranch(ctx, dbData.Ddb, branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if isBranch {
|
||||
return errors.New(fmt.Sprintf("fatal: A branch named '%s' already exists.", branchName))
|
||||
return 1, err
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
startPt := fmt.Sprintf("head")
|
||||
return actions.CreateBranchWithStartPt(ctx, dbData, branchName, startPt, false)
|
||||
}
|
||||
|
||||
func makeACopyOfBranch(ctx *sql.Context, dbData env.DbData, apr *argparser.ArgParseResults) error {
|
||||
func renameBranch(ctx *sql.Context, dbData env.DbData, apr *argparser.ArgParseResults) error {
|
||||
if apr.NArg() != 2 {
|
||||
return InvalidArgErr
|
||||
}
|
||||
oldBranchName, newBranchName := apr.Arg(0), apr.Arg(1)
|
||||
if oldBranchName == "" || newBranchName == "" {
|
||||
return EmptyBranchNameErr
|
||||
}
|
||||
force := apr.Contains(cli.ForceFlag)
|
||||
|
||||
if !force {
|
||||
err := validateBranchNotActiveInAnySession(ctx, oldBranchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return actions.RenameBranch(ctx, dbData, loadConfig(ctx), oldBranchName, newBranchName, force)
|
||||
}
|
||||
|
||||
func deleteBranches(ctx *sql.Context, apr *argparser.ArgParseResults, dbData env.DbData) error {
|
||||
if apr.NArg() == 0 {
|
||||
return InvalidArgErr
|
||||
}
|
||||
|
||||
for _, branchName := range apr.Args {
|
||||
if len(branchName) == 0 {
|
||||
return EmptyBranchNameErr
|
||||
}
|
||||
force := apr.Contains(cli.DeleteForceFlag) || apr.Contains(cli.ForceFlag)
|
||||
|
||||
if !force {
|
||||
err := validateBranchNotActiveInAnySession(ctx, branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := actions.DeleteBranch(ctx, dbData, loadConfig(ctx), branchName, actions.DeleteOptions{
|
||||
Force: force,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBranchNotActiveInAnySessions returns an error if the specified branch is currently
|
||||
// selected as the active branch for any active server sessions.
|
||||
func validateBranchNotActiveInAnySession(ctx *sql.Context, branchName string) error {
|
||||
dbName := ctx.GetCurrentDatabase()
|
||||
if dbName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sqlserver.RunningInServerMode() == false {
|
||||
return nil
|
||||
}
|
||||
|
||||
runningServer := sqlserver.GetRunningServer()
|
||||
if runningServer == nil {
|
||||
return nil
|
||||
}
|
||||
sessionManager := runningServer.SessionManager()
|
||||
branchRef := ref.NewBranchRef(branchName)
|
||||
|
||||
return sessionManager.Iter(func(session sql.Session) (bool, error) {
|
||||
dsess, ok := session.(*dsess.DoltSession)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("unexpected session type: %T", session)
|
||||
}
|
||||
|
||||
activeBranchRef, err := dsess.CWBHeadRef(ctx, dbName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if ref.Equals(branchRef, activeBranchRef) {
|
||||
return false, fmt.Errorf("unsafe to delete or rename branches in use in other sessions; " +
|
||||
"use --force to force the change")
|
||||
}
|
||||
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func loadConfig(ctx *sql.Context) *env.DoltCliConfig {
|
||||
// When executing branch actions from SQL, we don't have access to a DoltEnv like we do from
|
||||
// within the CLI. We can fake it here enough to get a DoltCliConfig, but we can't rely on the
|
||||
// DoltEnv because tests and production will run with different settings (e.g. in-mem versus file).
|
||||
dEnv := env.Load(ctx, env.GetCurrentUserHomeDir, filesys.LocalFS, doltdb.LocalDirDoltDB, "")
|
||||
return dEnv.Config
|
||||
}
|
||||
|
||||
func createNewBranch(ctx *sql.Context, dbData env.DbData, apr *argparser.ArgParseResults) error {
|
||||
if apr.NArg() != 1 {
|
||||
return InvalidArgErr
|
||||
}
|
||||
|
||||
branchName := apr.Arg(0)
|
||||
if len(branchName) == 0 {
|
||||
return EmptyBranchNameErr
|
||||
}
|
||||
|
||||
return actions.CreateBranchWithStartPt(ctx, dbData, branchName, "HEAD", apr.Contains(cli.ForceFlag))
|
||||
}
|
||||
|
||||
func copyBranch(ctx *sql.Context, dbData env.DbData, apr *argparser.ArgParseResults) error {
|
||||
if apr.NArg() != 2 {
|
||||
return InvalidArgErr
|
||||
}
|
||||
|
||||
@@ -637,6 +637,12 @@ func TestDoltReset(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoltBranch(t *testing.T) {
|
||||
for _, script := range DoltBranchScripts {
|
||||
enginetest.TestScript(t, newDoltHarness(t), script)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSingleTransactionScript is a convenience method for debugging a single transaction test. Unskip and set to the
|
||||
// desired test.
|
||||
func TestSingleTransactionScript(t *testing.T) {
|
||||
|
||||
@@ -1297,6 +1297,144 @@ var MergeScripts = []queries.ScriptTest{
|
||||
},
|
||||
}
|
||||
|
||||
var DoltBranchScripts = []queries.ScriptTest{
|
||||
{
|
||||
Name: "Create branches from HEAD with dolt_branch procedure",
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('myNewBranch1')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT COUNT(*) FROM DOLT_BRANCHES WHERE NAME='myNewBranch1';",
|
||||
Expected: []sql.Row{{1}},
|
||||
},
|
||||
{
|
||||
// Trying to recreate that branch fails without the force flag
|
||||
Query: "CALL DOLT_BRANCH('myNewBranch1')",
|
||||
ExpectedErrStr: "fatal: A branch named 'myNewBranch1' already exists.",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-f', 'myNewBranch1')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Rename branches with dolt_branch procedure",
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('myNewBranch1')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('myNewBranch2')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
// Renaming to an existing name fails without the force flag
|
||||
Query: "CALL DOLT_BRANCH('-m', 'myNewBranch1', 'myNewBranch2')",
|
||||
ExpectedErrStr: "already exists",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-mf', 'myNewBranch1', 'myNewBranch2')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-m', 'myNewBranch2', 'myNewBranch3')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Copy branches from other branches using dolt_branch procedure",
|
||||
SetUpScript: []string{
|
||||
"CALL DOLT_BRANCH('myNewBranch1')",
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-c')",
|
||||
ExpectedErrStr: "error: invalid usage",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-c', 'myNewBranch1')",
|
||||
ExpectedErrStr: "error: invalid usage",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-c', 'myNewBranch2')",
|
||||
ExpectedErrStr: "error: invalid usage",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-c', '', '')",
|
||||
ExpectedErrStr: "error: cannot branch empty string",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-c', 'myNewBranch1', 'myNewBranch2')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT COUNT(*) FROM DOLT_BRANCHES WHERE NAME='myNewBranch2';",
|
||||
Expected: []sql.Row{{1}},
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-c', 'myNewBranch1', 'myNewBranch2')",
|
||||
ExpectedErrStr: "fatal: A branch named 'myNewBranch2' already exists.",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-cf', 'myNewBranch1', 'myNewBranch2')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Delete branches with dolt_branch procedure",
|
||||
SetUpScript: []string{
|
||||
"CALL DOLT_BRANCH('myNewBranch1')",
|
||||
"CALL DOLT_BRANCH('myNewBranch2')",
|
||||
"CALL DOLT_BRANCH('myNewBranch3')",
|
||||
"CALL DOLT_BRANCH('myNewBranchWithCommit')",
|
||||
"CALL DOLT_CHECKOUT('myNewBranchWithCommit')",
|
||||
"CALL DOLT_COMMIT('--allow-empty', '-am', 'empty commit')",
|
||||
"CALL DOLT_CHECKOUT('main')",
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-d')",
|
||||
ExpectedErrStr: "error: invalid usage",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-d', '')",
|
||||
ExpectedErrStr: "error: cannot branch empty string",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-d', 'branchDoesNotExist')",
|
||||
ExpectedErrStr: "branch not found",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-d', 'myNewBranch1')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "SELECT COUNT(*) FROM DOLT_BRANCHES WHERE NAME='myNewBranch1'",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-d', 'myNewBranch2', 'myNewBranch3')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
// Trying to delete a branch with unpushed changes fails without force option
|
||||
Query: "CALL DOLT_BRANCH('-d', 'myNewBranchWithCommit')",
|
||||
ExpectedErrStr: "attempted to delete a branch that is not fully merged into its parent; use `-f` to force",
|
||||
},
|
||||
{
|
||||
Query: "CALL DOLT_BRANCH('-df', 'myNewBranchWithCommit')",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var DoltReset = []queries.ScriptTest{
|
||||
{
|
||||
Name: "CALL DOLT_RESET('--hard') should reset the merge state after uncommitted merge",
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
// Copyright 2022 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 enginetest
|
||||
|
||||
import (
|
||||
"context"
|
||||
gosql "database/sql"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/enginetest/queries"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/gocraft/dbr/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/commands/sqlserver"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
|
||||
)
|
||||
|
||||
// DoltBranchMultiSessionScriptTests contain tests that need to be run in a multi-session server environment
|
||||
// in order to fully test branch deletion and renaming logic.
|
||||
var DoltBranchMultiSessionScriptTests = []queries.ScriptTest{
|
||||
{
|
||||
Name: "Test multi-session behavior for deleting branches",
|
||||
SetUpScript: []string{
|
||||
"call dolt_branch('branch1');",
|
||||
"call dolt_branch('branch2');",
|
||||
"call dolt_branch('branch3');",
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "/* client a */ CALL DOLT_CHECKOUT('branch1');",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "/* client a */ select active_branch();",
|
||||
Expected: []sql.Row{{"branch1"}},
|
||||
},
|
||||
{
|
||||
Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch1');",
|
||||
ExpectedErrStr: "Error 1105: unsafe to delete or rename branches in use in other sessions; use --force to force the change",
|
||||
},
|
||||
{
|
||||
Query: "/* client a */ CALL DOLT_CHECKOUT('branch2');",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch1');",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch2');",
|
||||
ExpectedErrStr: "Error 1105: unsafe to delete or rename branches in use in other sessions; use --force to force the change",
|
||||
},
|
||||
{
|
||||
Query: "/* client b */ CALL DOLT_BRANCH('-df', 'branch2');",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "/* client b */ CALL DOLT_BRANCH('-d', 'branch3');",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Test multi-session behavior for renaming branches",
|
||||
SetUpScript: []string{
|
||||
"call dolt_branch('branch1');",
|
||||
"call dolt_branch('branch2');",
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "/* client a */ CALL DOLT_CHECKOUT('branch1');",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "/* client a */ select active_branch();",
|
||||
Expected: []sql.Row{{"branch1"}},
|
||||
},
|
||||
{
|
||||
Query: "/* client b */ CALL DOLT_BRANCH('-m', 'branch1', 'movedBranch1');",
|
||||
ExpectedErrStr: "Error 1105: unsafe to delete or rename branches in use in other sessions; use --force to force the change",
|
||||
},
|
||||
{
|
||||
Query: "/* client b */ CALL DOLT_BRANCH('-mf', 'branch1', 'movedBranch1');",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
{
|
||||
Query: "/* client b */ CALL DOLT_BRANCH('-m', 'branch2', 'movedBranch2');",
|
||||
Expected: []sql.Row{{0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TestDoltMultiSessionBehavior runs tests that exercise multi-session logic on a running SQL server. Statements
|
||||
// are sent through the server, from out of process, instead of directly to the in-process engine API.
|
||||
func TestDoltMultiSessionBehavior(t *testing.T) {
|
||||
// When this test runs with the new storage engine format, we get a panic about an unknown message id.
|
||||
// Ex: https://github.com/dolthub/dolt/runs/6679643619?check_suite_focus=true
|
||||
skipNewFormat(t)
|
||||
|
||||
testMultiSessionScriptTests(t, DoltBranchMultiSessionScriptTests)
|
||||
}
|
||||
|
||||
func testMultiSessionScriptTests(t *testing.T, tests []queries.ScriptTest) {
|
||||
sc, serverConfig := startServer(t)
|
||||
defer sc.StopServer()
|
||||
|
||||
for _, test := range tests {
|
||||
conn1, sess1 := newConnection(t, serverConfig)
|
||||
conn2, sess2 := newConnection(t, serverConfig)
|
||||
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
for _, setupStatement := range test.SetUpScript {
|
||||
_, err := sess1.Exec(setupStatement)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
for _, assertion := range test.Assertions {
|
||||
t.Run(assertion.Query, func(t *testing.T) {
|
||||
var activeSession *dbr.Session
|
||||
if strings.Contains(strings.ToLower(assertion.Query), "/* client a */") {
|
||||
activeSession = sess1
|
||||
} else if strings.Contains(strings.ToLower(assertion.Query), "/* client b */") {
|
||||
activeSession = sess2
|
||||
} else {
|
||||
require.Fail(t, "unsupported client specification: "+assertion.Query)
|
||||
}
|
||||
|
||||
rows, err := activeSession.Query(assertion.Query)
|
||||
|
||||
if len(assertion.ExpectedErrStr) > 0 {
|
||||
require.EqualError(t, err, assertion.ExpectedErrStr)
|
||||
} else if assertion.ExpectedErr != nil {
|
||||
require.True(t, assertion.ExpectedErr.Is(err))
|
||||
} else if assertion.Expected != nil {
|
||||
require.NoError(t, err)
|
||||
assertResultsEqual(t, assertion.Expected, rows)
|
||||
} else {
|
||||
require.Fail(t, "unsupported ScriptTestAssertion property: %v", assertion)
|
||||
}
|
||||
if rows != nil {
|
||||
require.NoError(t, rows.Close())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
require.NoError(t, conn1.Close())
|
||||
require.NoError(t, conn2.Close())
|
||||
}
|
||||
}
|
||||
|
||||
func makeDestinationSlice(t *testing.T, columnTypes []*gosql.ColumnType) []interface{} {
|
||||
dest := make([]any, len(columnTypes))
|
||||
for i, columnType := range columnTypes {
|
||||
switch strings.ToLower(columnType.DatabaseTypeName()) {
|
||||
case "int", "tinyint", "bigint":
|
||||
var integer int
|
||||
dest[i] = &integer
|
||||
case "text":
|
||||
var s string
|
||||
dest[i] = &s
|
||||
default:
|
||||
require.Fail(t, "unsupported type: "+columnType.DatabaseTypeName())
|
||||
}
|
||||
}
|
||||
|
||||
return dest
|
||||
}
|
||||
|
||||
func assertResultsEqual(t *testing.T, expected []sql.Row, rows *gosql.Rows) {
|
||||
columnTypes, err := rows.ColumnTypes()
|
||||
require.NoError(t, err)
|
||||
dest := makeDestinationSlice(t, columnTypes)
|
||||
|
||||
for _, expectedRow := range expected {
|
||||
ok := rows.Next()
|
||||
if !ok {
|
||||
require.Fail(t, "Fewer results than expected")
|
||||
}
|
||||
err := rows.Scan(dest...)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(expectedRow), len(dest),
|
||||
"Different number of columns returned than expected")
|
||||
|
||||
for j, expectedValue := range expectedRow {
|
||||
switch strings.ToUpper(columnTypes[j].DatabaseTypeName()) {
|
||||
case "TEXT":
|
||||
actualValue, ok := dest[j].(*string)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedValue, *actualValue)
|
||||
case "INT", "TINYINT", "BIGINT":
|
||||
actualValue, ok := dest[j].(*int)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedValue, *actualValue)
|
||||
default:
|
||||
require.Fail(t, "Unsupported datatype: %s", columnTypes[j].DatabaseTypeName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rows.Next() {
|
||||
require.Fail(t, "More results than expected")
|
||||
}
|
||||
}
|
||||
|
||||
func startServer(t *testing.T) (*sqlserver.ServerController, sqlserver.ServerConfig) {
|
||||
dEnv := dtestutils.CreateEnvWithSeedData(t)
|
||||
port := 15403 + rand.Intn(25)
|
||||
serverConfig := sqlserver.DefaultServerConfig().WithPort(port)
|
||||
|
||||
sc := sqlserver.NewServerController()
|
||||
go func() {
|
||||
_, _ = sqlserver.Serve(context.Background(), "", serverConfig, sc, dEnv)
|
||||
}()
|
||||
err := sc.WaitForStart()
|
||||
require.NoError(t, err)
|
||||
|
||||
return sc, serverConfig
|
||||
}
|
||||
|
||||
func newConnection(t *testing.T, serverConfig sqlserver.ServerConfig) (*dbr.Connection, *dbr.Session) {
|
||||
const dbName = "dolt"
|
||||
conn, err := dbr.Open("mysql", sqlserver.ConnectionString(serverConfig)+dbName, nil)
|
||||
require.NoError(t, err)
|
||||
sess := conn.NewSession(nil)
|
||||
return conn, sess
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2022 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 sqlserver
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/server"
|
||||
)
|
||||
|
||||
var mySQLServer *server.Server
|
||||
var mySQLServerMutex sync.Mutex
|
||||
|
||||
// RunningInServerMode returns true if the current process is running a SQL server.
|
||||
func RunningInServerMode() bool {
|
||||
mySQLServerMutex.Lock()
|
||||
defer mySQLServerMutex.Unlock()
|
||||
return mySQLServer != nil
|
||||
}
|
||||
|
||||
// GetRunningServer returns the Server instance running in this process, or nil if no SQL server is running.
|
||||
func GetRunningServer() *server.Server {
|
||||
mySQLServerMutex.Lock()
|
||||
defer mySQLServerMutex.Unlock()
|
||||
return mySQLServer
|
||||
}
|
||||
|
||||
// SetRunningServer sets the specified Server as the running SQL server for this process.
|
||||
func SetRunningServer(server *server.Server) {
|
||||
mySQLServerMutex.Lock()
|
||||
defer mySQLServerMutex.Unlock()
|
||||
mySQLServer = server
|
||||
}
|
||||
@@ -245,36 +245,48 @@ SQL
|
||||
[ "$output" = "$mainhash" ]
|
||||
}
|
||||
|
||||
@test "sql-branch: asserts unsupported -m, -d, -D flags" {
|
||||
@test "sql-branch: SELECT DOLT_BRANCH to rename and delete" {
|
||||
dolt add . && dolt commit -m "1, 2, and 3 in test table"
|
||||
dolt branch new_branch
|
||||
|
||||
run dolt sql -q "SELECT DOLT_BRANCH('-m', 'new_branch', 'changed');"
|
||||
[ $status -eq 1 ]
|
||||
[[ "$output" =~ "Renaming a branch is not supported." ]] || false
|
||||
[ $status -eq 0 ]
|
||||
|
||||
run dolt sql -q "SELECT DOLT_BRANCH('-d', 'new_branch');"
|
||||
[ $status -eq 1 ]
|
||||
[[ "$output" =~ "Deleting branches is not supported." ]] || false
|
||||
run dolt sql -q "SELECT DOLT_BRANCH('-d', 'changed');"
|
||||
[ $status -eq 0 ]
|
||||
|
||||
run dolt sql -q "SELECT DOLT_BRANCH('-D', 'new_branch');"
|
||||
dolt branch branch_with_unpushed_commit
|
||||
dolt checkout branch_with_unpushed_commit
|
||||
dolt commit --allow-empty -am 'empty commit'
|
||||
dolt checkout main
|
||||
|
||||
run dolt sql -q "SELECT DOLT_BRANCH('-d', 'branch_with_unpushed_commit');"
|
||||
[ $status -eq 1 ]
|
||||
[[ "$output" =~ "Deleting branches is not supported." ]] || false
|
||||
[[ "$output" =~ "attempted to delete a branch that is not fully merged" ]] || false
|
||||
|
||||
run dolt sql -q "SELECT DOLT_BRANCH('-D', 'branch_with_unpushed_commit');"
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
|
||||
@test "sql-branch: asserts unsupported -m, -d, -D flags on CALL" {
|
||||
@test "sql-branch: CALL DOLT_BRANCH to rename and delete" {
|
||||
dolt add . && dolt commit -m "1, 2, and 3 in test table"
|
||||
dolt branch new_branch
|
||||
|
||||
run dolt sql -q "CALL DOLT_BRANCH('-m', 'new_branch', 'changed');"
|
||||
[ $status -eq 1 ]
|
||||
[[ "$output" =~ "Renaming a branch is not supported." ]] || false
|
||||
[ $status -eq 0 ]
|
||||
|
||||
run dolt sql -q "CALL DOLT_BRANCH('-d', 'new_branch');"
|
||||
[ $status -eq 1 ]
|
||||
[[ "$output" =~ "Deleting branches is not supported." ]] || false
|
||||
run dolt sql -q "CALL DOLT_BRANCH('-d', 'changed');"
|
||||
[ $status -eq 0 ]
|
||||
|
||||
run dolt sql -q "CALL DOLT_BRANCH('-D', 'new_branch');"
|
||||
dolt branch branch_with_unpushed_commit
|
||||
dolt checkout branch_with_unpushed_commit
|
||||
dolt commit --allow-empty -am 'empty commit'
|
||||
dolt checkout main
|
||||
|
||||
run dolt sql -q "CALL DOLT_BRANCH('-d', 'branch_with_unpushed_commit');"
|
||||
[ $status -eq 1 ]
|
||||
[[ "$output" =~ "Deleting branches is not supported." ]] || false
|
||||
[[ "$output" =~ "attempted to delete a branch that is not fully merged" ]] || false
|
||||
|
||||
run dolt sql -q "CALL DOLT_BRANCH('-D', 'branch_with_unpushed_commit');"
|
||||
[ $status -eq 0 ]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user