github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/dsess/transactions.go (about)

     1  // Copyright 2021 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dsess
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/dolthub/go-mysql-server/sql"
    27  	"github.com/dolthub/vitess/go/mysql"
    28  	"github.com/sirupsen/logrus"
    29  
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
    32  	"github.com/dolthub/dolt/go/libraries/doltcore/merge"
    33  	"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
    34  	"github.com/dolthub/dolt/go/store/datas"
    35  	"github.com/dolthub/dolt/go/store/hash"
    36  	"github.com/dolthub/dolt/go/store/prolly"
    37  )
    38  
    39  const (
    40  	maxTxCommitRetries = 5
    41  )
    42  
    43  var ErrRetryTransaction = errors.New("this transaction conflicts with a committed transaction from another client")
    44  
    45  var ErrUnresolvedConflictsCommit = errors.New("Merge conflict detected, transaction rolled back. Merge conflicts must be resolved using the dolt_conflicts and dolt_schema_conflicts tables before committing a transaction. To commit transactions with merge conflicts, set @@dolt_allow_commit_conflicts = 1")
    46  
    47  var ErrUnresolvedConflictsAutoCommit = errors.New("Merge conflict detected, @autocommit transaction rolled back. @autocommit must be disabled so that merge conflicts can be resolved using the dolt_conflicts and dolt_schema_conflicts tables before manually committing the transaction. Alternatively, to commit transactions with merge conflicts, set @@dolt_allow_commit_conflicts = 1")
    48  
    49  var ErrUnresolvedConstraintViolationsCommit = errors.New("Committing this transaction resulted in a working set with constraint violations, transaction rolled back. " +
    50  	"This constraint violation may be the result of a previous merge or the result of transaction sequencing. " +
    51  	"Constraint violations from a merge can be resolved using the dolt_constraint_violations table before committing the transaction. " +
    52  	"To allow transactions to be committed with constraint violations from a merge or transaction sequencing set @@dolt_force_transaction_commit=1.")
    53  
    54  // TODO: remove this
    55  func TransactionsDisabled(ctx *sql.Context) bool {
    56  	enabled, err := ctx.GetSessionVariable(ctx, TransactionsDisabledSysVar)
    57  	if err != nil {
    58  		panic(err)
    59  	}
    60  
    61  	switch enabled.(int8) {
    62  	case 0:
    63  		return false
    64  	case 1:
    65  		return true
    66  	default:
    67  		panic(fmt.Sprintf("Unexpected value %v", enabled))
    68  	}
    69  }
    70  
    71  // DisabledTransaction is a no-op transaction type that lets us feature-gate transaction logic changes
    72  type DisabledTransaction struct{}
    73  
    74  func (d DisabledTransaction) String() string {
    75  	return "Disabled transaction"
    76  }
    77  
    78  func (d DisabledTransaction) IsReadOnly() bool {
    79  	return false
    80  }
    81  
    82  type DoltTransaction struct {
    83  	dbStartPoints   map[string]dbRoot
    84  	savepoints      []savepoint
    85  	tCharacteristic sql.TransactionCharacteristic
    86  }
    87  
    88  type dbRoot struct {
    89  	dbName   string
    90  	rootHash hash.Hash
    91  	db       *doltdb.DoltDB
    92  }
    93  
    94  type savepoint struct {
    95  	name string
    96  	// TODO: we need a root value per DB here
    97  	roots map[string]doltdb.RootValue
    98  }
    99  
   100  func NewDoltTransaction(
   101  	ctx *sql.Context,
   102  	dbs []SqlDatabase,
   103  	tCharacteristic sql.TransactionCharacteristic,
   104  ) (*DoltTransaction, error) {
   105  
   106  	startPoints := make(map[string]dbRoot)
   107  	for _, db := range dbs {
   108  		nomsRoot, err := db.DbData().Ddb.NomsRoot(ctx)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  
   113  		baseName, _ := SplitRevisionDbName(db.Name())
   114  		startPoints[strings.ToLower(baseName)] = dbRoot{
   115  			dbName:   baseName,
   116  			rootHash: nomsRoot,
   117  			db:       db.DbData().Ddb,
   118  		}
   119  	}
   120  
   121  	return &DoltTransaction{
   122  		dbStartPoints:   startPoints,
   123  		tCharacteristic: tCharacteristic,
   124  	}, nil
   125  }
   126  
   127  // AddDb adds the database named to the transaction. Only necessary in the case when new databases are added to an
   128  // existing transaction (as when cloning a database on a read replica when it is first referenced).
   129  func (tx DoltTransaction) AddDb(ctx *sql.Context, db SqlDatabase) error {
   130  	nomsRoot, err := db.DbData().Ddb.NomsRoot(ctx)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	tx.dbStartPoints[strings.ToLower(db.Name())] = dbRoot{
   136  		dbName:   db.Name(),
   137  		rootHash: nomsRoot,
   138  		db:       db.DbData().Ddb,
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  func (tx DoltTransaction) String() string {
   145  	// TODO: return more info (hashes need caching)
   146  	return "DoltTransaction"
   147  }
   148  
   149  func (tx DoltTransaction) IsReadOnly() bool {
   150  	return tx.tCharacteristic == sql.ReadOnly
   151  }
   152  
   153  // GetInitialRoot returns the noms root hash for the db named, established when the transaction began. The dbName here
   154  // is always the base name of the database, not the revision qualified one.
   155  func (tx DoltTransaction) GetInitialRoot(dbName string) (hash.Hash, bool) {
   156  	dbName, _ = SplitRevisionDbName(dbName)
   157  	startPoint, ok := tx.dbStartPoints[strings.ToLower(dbName)]
   158  	return startPoint.rootHash, ok
   159  }
   160  
   161  var txLock sync.Mutex
   162  
   163  // Commit attempts to merge the working set given into the current working set.
   164  // Uses the same algorithm as merge.RootMerger:
   165  // |current working set working root| is the root
   166  // |workingSet.workingRoot| is the mergeRoot
   167  // |tx.startRoot| is ancRoot
   168  // if workingSet.workingRoot == ancRoot, attempt a fast-forward merge
   169  // TODO: Non-working roots aren't merged into the working set and just stomp any changes made there. We need merge
   170  // strategies for staged as well as merge state.
   171  func (tx *DoltTransaction) Commit(ctx *sql.Context, workingSet *doltdb.WorkingSet, dbName string) (*doltdb.WorkingSet, error) {
   172  	ws, _, err := tx.doCommit(ctx, workingSet, nil, txCommit, dbName)
   173  	return ws, err
   174  }
   175  
   176  // transactionWrite is the logic to write an updated working set (and optionally a commit) to the database
   177  type transactionWrite func(ctx *sql.Context,
   178  	tx *DoltTransaction, // the transaction being written
   179  	doltDb *doltdb.DoltDB, // the database to write to
   180  	startState *doltdb.WorkingSet, // the starting working set
   181  	commit *doltdb.PendingCommit, // optional
   182  	workingSet *doltdb.WorkingSet, // must be provided
   183  	hash hash.Hash, // hash of the current working set to be written
   184  	mergeOps editor.Options, // editor options for merges
   185  ) (*doltdb.WorkingSet, *doltdb.Commit, error)
   186  
   187  // doltCommit is a transactionWrite function that updates the working set and commits a pending commit atomically
   188  func doltCommit(ctx *sql.Context,
   189  	tx *DoltTransaction, // the transaction being written
   190  	doltDb *doltdb.DoltDB, // the database to write to
   191  	startState *doltdb.WorkingSet, // the starting working set
   192  	commit *doltdb.PendingCommit, // optional
   193  	workingSet *doltdb.WorkingSet, // must be provided
   194  	currHash hash.Hash, // hash of the current working set to be written
   195  	mergeOpts editor.Options, // editor options for merges
   196  ) (*doltdb.WorkingSet, *doltdb.Commit, error) {
   197  	pending := *commit
   198  
   199  	headRef, err := workingSet.Ref().ToHeadRef()
   200  	if err != nil {
   201  		return nil, nil, err
   202  	}
   203  
   204  	headSpec, _ := doltdb.NewCommitSpec("HEAD")
   205  	optCmt, err := doltDb.Resolve(ctx, headSpec, headRef)
   206  	if err != nil {
   207  		return nil, nil, err
   208  	}
   209  	curHead, ok := optCmt.ToCommit()
   210  	if !ok {
   211  		return nil, nil, doltdb.ErrGhostCommitRuntimeFailure
   212  	}
   213  
   214  	// We already got a new staged root via merge or ff via the doCommit method, so now apply it to the STAGED value
   215  	// we're about to commit.
   216  	pending.Roots.Staged = workingSet.StagedRoot()
   217  
   218  	// We check if the branch HEAD has changed since our transaction started and perform an additional merge if so. The
   219  	// non-dolt-commit transaction logic only merges working sets and doesn't consider the HEAD value.
   220  	if curHead != nil {
   221  		curRootVal, err := curHead.ResolveRootValue(ctx)
   222  		if err != nil {
   223  			return nil, nil, err
   224  		}
   225  		curRootValHash, err := curRootVal.HashOf()
   226  		if err != nil {
   227  			return nil, nil, err
   228  		}
   229  		headRootValHash, err := pending.Roots.Head.HashOf()
   230  		if err != nil {
   231  			return nil, nil, err
   232  		}
   233  
   234  		if curRootValHash != headRootValHash {
   235  			// If the branch head changed since our transaction started, then we merge
   236  			// the existing branch head (curRootVal) into our staged root value. We
   237  			// treat the HEAD of the branch when our transaction started as the common
   238  			// ancestor (TODO: This will not be true in the case of destructive branch
   239  			// updates). The merged root value becomes our new Staged root value which
   240  			// is the value which we are trying to commit.
   241  			start := time.Now()
   242  
   243  			result, err := merge.MergeRoots(
   244  				ctx,
   245  				pending.Roots.Staged,
   246  				curRootVal,
   247  				pending.Roots.Head,
   248  				curHead,
   249  				startState,
   250  				mergeOpts,
   251  				merge.MergeOpts{})
   252  			if err != nil {
   253  				return nil, nil, err
   254  			}
   255  			pending.Roots.Staged = result.Root
   256  
   257  			// We also need to update the working set to reflect the new staged root value
   258  			workingSet = workingSet.WithStagedRoot(pending.Roots.Staged)
   259  
   260  			logrus.Tracef("staged and HEAD merge took %s", time.Since(start))
   261  		}
   262  	}
   263  
   264  	workingSet = workingSet.ClearMerge()
   265  
   266  	var rsc doltdb.ReplicationStatusController
   267  	newCommit, err := doltDb.CommitWithWorkingSet(ctx, headRef, workingSet.Ref(), &pending, workingSet, currHash, tx.WorkingSetMeta(ctx), &rsc)
   268  	WaitForReplicationController(ctx, rsc)
   269  	return workingSet, newCommit, err
   270  }
   271  
   272  // txCommit is a transactionWrite function that updates the working set
   273  func txCommit(ctx *sql.Context,
   274  	tx *DoltTransaction, // the transaction being written
   275  	doltDb *doltdb.DoltDB, // the database to write to
   276  	_ *doltdb.WorkingSet, // the starting working set
   277  	_ *doltdb.PendingCommit, // optional
   278  	workingSet *doltdb.WorkingSet, // must be provided
   279  	hash hash.Hash, // hash of the current working set to be written
   280  	_ editor.Options, // editor options for merges
   281  ) (*doltdb.WorkingSet, *doltdb.Commit, error) {
   282  	var rsc doltdb.ReplicationStatusController
   283  	err := doltDb.UpdateWorkingSet(ctx, workingSet.Ref(), workingSet, hash, tx.WorkingSetMeta(ctx), &rsc)
   284  	WaitForReplicationController(ctx, rsc)
   285  	return workingSet, nil, err
   286  }
   287  
   288  // DoltCommit commits the working set and creates a new DoltCommit as specified, in one atomic write
   289  func (tx *DoltTransaction) DoltCommit(
   290  	ctx *sql.Context,
   291  	workingSet *doltdb.WorkingSet,
   292  	commit *doltdb.PendingCommit,
   293  	dbName string,
   294  ) (*doltdb.WorkingSet, *doltdb.Commit, error) {
   295  	return tx.doCommit(ctx, workingSet, commit, doltCommit, dbName)
   296  }
   297  
   298  func WaitForReplicationController(ctx *sql.Context, rsc doltdb.ReplicationStatusController) {
   299  	if len(rsc.Wait) == 0 {
   300  		return
   301  	}
   302  	_, timeout, ok := sql.SystemVariables.GetGlobal(DoltClusterAckWritesTimeoutSecs)
   303  	if !ok {
   304  		return
   305  	}
   306  	timeoutI := timeout.(int64)
   307  	if timeoutI == 0 {
   308  		return
   309  	}
   310  
   311  	cCtx, cancel := context.WithCancelCause(ctx)
   312  	var wg sync.WaitGroup
   313  	wg.Add(len(rsc.Wait))
   314  	for i, f := range rsc.Wait {
   315  		f := f
   316  		i := i
   317  		go func() {
   318  			defer wg.Done()
   319  			err := f(cCtx)
   320  			if err == nil {
   321  				rsc.Wait[i] = nil
   322  			}
   323  		}()
   324  	}
   325  
   326  	done := make(chan struct{})
   327  	go func() {
   328  		wg.Wait()
   329  		close(done)
   330  	}()
   331  
   332  	waitFailed := false
   333  	select {
   334  	case <-time.After(time.Duration(timeoutI) * time.Second):
   335  		// We timed out before all the waiters were done.
   336  		// First we make certain to finalize everything.
   337  		cancel(doltdb.ErrReplicationWaitFailed)
   338  		<-done
   339  		waitFailed = true
   340  	case <-done:
   341  		cancel(context.Canceled)
   342  	}
   343  
   344  	// Just because our waiters all completed does not mean they all
   345  	// returned nil errors. Any non-nil entries in rsc.Wait returned an
   346  	// error. We turn those into warnings here.
   347  	numFailed := 0
   348  	for i, f := range rsc.Wait {
   349  		if f != nil {
   350  			numFailed += 1
   351  			if waitFailed {
   352  				rsc.NotifyWaitFailed[i]()
   353  			}
   354  		}
   355  	}
   356  	if numFailed > 0 {
   357  		ctx.Session.Warn(&sql.Warning{
   358  			Level:   "Warning",
   359  			Code:    mysql.ERQueryTimeout,
   360  			Message: fmt.Sprintf("Timed out replication of commit to %d out of %d replicas.", numFailed, len(rsc.Wait)),
   361  		})
   362  	}
   363  }
   364  
   365  // doCommit commits this transaction with the write function provided. It takes the same params as DoltCommit
   366  func (tx *DoltTransaction) doCommit(
   367  	ctx *sql.Context,
   368  	workingSet *doltdb.WorkingSet,
   369  	commit *doltdb.PendingCommit,
   370  	writeFn transactionWrite,
   371  	dbName string,
   372  ) (*doltdb.WorkingSet, *doltdb.Commit, error) {
   373  	sess := DSessFromSess(ctx.Session)
   374  	branchState, ok, err := sess.lookupDbState(ctx, dbName)
   375  	if err != nil {
   376  		return nil, nil, err
   377  	}
   378  	if !ok {
   379  		return nil, nil, fmt.Errorf("database %s unknown to transaction, this is a bug", dbName)
   380  	}
   381  
   382  	// Load the start state for this working set from the noms root at tx start
   383  	// Get the base DB name from the db state, not the branch state
   384  	startPoint, ok := tx.dbStartPoints[strings.ToLower(branchState.dbState.dbName)]
   385  	if !ok {
   386  		return nil, nil, fmt.Errorf("database %s unknown to transaction, this is a bug", dbName)
   387  	}
   388  
   389  	startState, err := startPoint.db.ResolveWorkingSetAtRoot(ctx, workingSet.Ref(), startPoint.rootHash)
   390  	if err != nil {
   391  		return nil, nil, err
   392  	}
   393  
   394  	// TODO: no-op if the working set hasn't changed since the transaction started
   395  
   396  	mergeOpts := branchState.EditOpts()
   397  
   398  	for i := 0; i < maxTxCommitRetries; i++ {
   399  		updatedWs, newCommit, err := func() (*doltdb.WorkingSet, *doltdb.Commit, error) {
   400  			// Serialize commits, since only one can possibly succeed at a time anyway
   401  			txLock.Lock()
   402  			defer txLock.Unlock()
   403  
   404  			newWorkingSet := false
   405  
   406  			existingWs, err := startPoint.db.ResolveWorkingSet(ctx, workingSet.Ref())
   407  			if err == doltdb.ErrWorkingSetNotFound {
   408  				// This is to handle the case where an existing DB pre working sets is committing to this HEAD for the
   409  				// first time. Can be removed and called an error post 1.0
   410  				existingWs = doltdb.EmptyWorkingSet(workingSet.Ref())
   411  				newWorkingSet = true
   412  			} else if err != nil {
   413  				return nil, nil, err
   414  			}
   415  
   416  			existingWSHash, err := existingWs.HashOf()
   417  			if err != nil {
   418  				return nil, nil, err
   419  			}
   420  
   421  			if newWorkingSet || workingAndStagedEqual(existingWs, startState) {
   422  				// ff merge
   423  				err = tx.validateWorkingSetForCommit(ctx, workingSet, isFfMerge)
   424  				if err != nil {
   425  					return nil, nil, err
   426  				}
   427  
   428  				var newCommit *doltdb.Commit
   429  				workingSet, newCommit, err = writeFn(ctx, tx, startPoint.db, startState, commit, workingSet, existingWSHash, mergeOpts)
   430  				if err == datas.ErrOptimisticLockFailed {
   431  					// this is effectively a `continue` in the loop
   432  					return nil, nil, nil
   433  				} else if err != nil {
   434  					return nil, nil, err
   435  				}
   436  
   437  				return workingSet, newCommit, nil
   438  			}
   439  
   440  			// otherwise (not a ff), merge the working sets together
   441  			start := time.Now()
   442  			mergedWorkingSet, err := tx.mergeRoots(ctx, startState, existingWs, workingSet, mergeOpts)
   443  			if err != nil {
   444  				return nil, nil, err
   445  			}
   446  			logrus.Tracef("working set merge took %s", time.Since(start))
   447  
   448  			err = tx.validateWorkingSetForCommit(ctx, mergedWorkingSet, notFfMerge)
   449  			if err != nil {
   450  				return nil, nil, err
   451  			}
   452  
   453  			var newCommit *doltdb.Commit
   454  			mergedWorkingSet, newCommit, err = writeFn(ctx, tx, startPoint.db, startState, commit, mergedWorkingSet, existingWSHash, mergeOpts)
   455  			if err == datas.ErrOptimisticLockFailed {
   456  				// this is effectively a `continue` in the loop
   457  				return nil, nil, nil
   458  			} else if err != nil {
   459  				return nil, nil, err
   460  			}
   461  
   462  			return mergedWorkingSet, newCommit, nil
   463  		}()
   464  
   465  		if err != nil {
   466  			return nil, nil, err
   467  		} else if updatedWs != nil {
   468  			return updatedWs, newCommit, nil
   469  		}
   470  	}
   471  
   472  	// TODO: different error type for retries exhausted
   473  	return nil, nil, datas.ErrOptimisticLockFailed
   474  }
   475  
   476  // mergeRoots merges the roots in the existing working set with the one being committed and returns the resulting
   477  // working set. Conflicts are automatically resolved with "accept ours" if the session settings dictate it.
   478  // Currently merges working and staged roots as necessary. HEAD root is only handled by the DoltCommit function.
   479  func (tx *DoltTransaction) mergeRoots(
   480  	ctx *sql.Context,
   481  	startState *doltdb.WorkingSet,
   482  	existingWorkingSet *doltdb.WorkingSet,
   483  	workingSet *doltdb.WorkingSet,
   484  	mergeOpts editor.Options,
   485  ) (*doltdb.WorkingSet, error) {
   486  
   487  	if !rootsEqual(existingWorkingSet.WorkingRoot(), workingSet.WorkingRoot()) {
   488  		result, err := merge.MergeRoots(
   489  			ctx,
   490  			existingWorkingSet.WorkingRoot(),
   491  			workingSet.WorkingRoot(),
   492  			startState.WorkingRoot(),
   493  			workingSet,
   494  			startState,
   495  			mergeOpts,
   496  			merge.MergeOpts{})
   497  		if err != nil {
   498  			return nil, err
   499  		}
   500  		workingSet = workingSet.WithWorkingRoot(result.Root)
   501  	}
   502  
   503  	if !rootsEqual(existingWorkingSet.StagedRoot(), workingSet.StagedRoot()) {
   504  		result, err := merge.MergeRoots(
   505  			ctx,
   506  			existingWorkingSet.StagedRoot(),
   507  			workingSet.StagedRoot(),
   508  			startState.StagedRoot(),
   509  			workingSet,
   510  			startState,
   511  			mergeOpts,
   512  			merge.MergeOpts{})
   513  		if err != nil {
   514  			return nil, err
   515  		}
   516  		workingSet = workingSet.WithStagedRoot(result.Root)
   517  	}
   518  
   519  	return workingSet, nil
   520  }
   521  
   522  // rollback attempts a transaction rollback
   523  func (tx *DoltTransaction) rollback(ctx *sql.Context) error {
   524  	sess := DSessFromSess(ctx.Session)
   525  	rollbackErr := sess.Rollback(ctx, tx)
   526  	if rollbackErr != nil {
   527  		return rollbackErr
   528  	}
   529  
   530  	// We also need to cancel out the transaction here so that a new one will begin on the next statement
   531  	// TODO: it would be better for the engine to handle these details probably, this code is duplicated from the
   532  	//  rollback statement implementation in the engine.
   533  	ctx.SetTransaction(nil)
   534  	ctx.SetIgnoreAutoCommit(false)
   535  
   536  	return nil
   537  }
   538  
   539  type ffMerge bool
   540  
   541  const (
   542  	isFfMerge  = ffMerge(true)
   543  	notFfMerge = ffMerge(false)
   544  )
   545  
   546  // validateWorkingSetForCommit validates that the working set given is legal to
   547  // commit according to the session settings. Returns an error if the given
   548  // working set has conflicts or constraint violations and the session settings
   549  // do not allow them.
   550  //
   551  // If dolt_allow_commit_conflicts = 0 and dolt_force_transaction_commit = 0, and
   552  // a transaction's post-commit working set contains a documented conflict
   553  // ( either as a result of a merge that occurred inside the transaction, or a
   554  // result of a transaction merge) that transaction will be rolled back.
   555  //
   556  // The justification for this behavior is that we want to protect the working
   557  // set from conflicts with the above settings.
   558  //
   559  // If dolt_force_transaction_commit = 0, and a transaction's post-commit working
   560  // set contains a documented constraint violation ( either as a result of a merge
   561  // that occurred inside the transaction, or a result of a transaction merge)
   562  // that transaction will be rolled back.
   563  //
   564  // The justification for this behavior is that we want to protect the working
   565  // set from constraint violations with the above settings.
   566  // TODO: should this validate staged as well?
   567  func (tx *DoltTransaction) validateWorkingSetForCommit(ctx *sql.Context, workingSet *doltdb.WorkingSet, isFf ffMerge) error {
   568  	forceTransactionCommit, err := ctx.GetSessionVariable(ctx, ForceTransactionCommit)
   569  	if err != nil {
   570  		return err
   571  	}
   572  
   573  	allowCommitConflicts, err := ctx.GetSessionVariable(ctx, AllowCommitConflicts)
   574  	if err != nil {
   575  		return err
   576  	}
   577  
   578  	hasSchemaConflicts := false
   579  	if workingSet.MergeState() != nil {
   580  		hasSchemaConflicts = workingSet.MergeState().HasSchemaConflicts()
   581  	}
   582  
   583  	workingRoot := workingSet.WorkingRoot()
   584  	hasDataConflicts, err := doltdb.HasConflicts(ctx, workingRoot)
   585  	if err != nil {
   586  		return err
   587  	}
   588  	hasConstraintViolations, err := doltdb.HasConstraintViolations(ctx, workingRoot)
   589  	if err != nil {
   590  		return err
   591  	}
   592  
   593  	if hasDataConflicts || hasSchemaConflicts {
   594  		// TODO: Sometimes this returns the wrong error. Define an internal
   595  		// merge to be a merge that occurs inside a transaction. Define a
   596  		// transaction merge to be the merge that resolves changes between two
   597  		// transactions. If an internal merge creates a documented conflict and
   598  		// the transaction merge is not a fast-forward, a retry transaction
   599  		// error will be returned. Instead, an ErrUnresolvedConflictsCommit should
   600  		// be returned.
   601  
   602  		// Conflicts are never acceptable when they resulted from a merge with the existing working set -- it's equivalent
   603  		// to hitting a write lock (which we didn't take). Always roll back and return an error in this case.
   604  		if !isFf {
   605  			rollbackErr := tx.rollback(ctx)
   606  			if rollbackErr != nil {
   607  				return rollbackErr
   608  			}
   609  
   610  			return sql.ErrLockDeadlock.New(ErrRetryTransaction.Error())
   611  		}
   612  
   613  		// If there were conflicts before merge with the persisted working set, whether we allow it to be committed is a
   614  		// session setting
   615  		if !(allowCommitConflicts.(int8) == 1 || forceTransactionCommit.(int8) == 1) {
   616  			rollbackErr := tx.rollback(ctx)
   617  			if rollbackErr != nil {
   618  				return rollbackErr
   619  			}
   620  
   621  			// Return a different error message depending on if @autocommit is enabled or not, to help
   622  			// users understand what steps to take
   623  			autocommit, err := isSessionAutocommit(ctx)
   624  			if err != nil {
   625  				return err
   626  			}
   627  			if autocommit {
   628  				return ErrUnresolvedConflictsAutoCommit
   629  			} else {
   630  				return ErrUnresolvedConflictsCommit
   631  			}
   632  		}
   633  	}
   634  
   635  	if hasConstraintViolations {
   636  		// Constraint violations are acceptable in the working set if force
   637  		// transaction commit is enabled, regardless if an internal merge ( a
   638  		// merge that occurs inside a transaction) or a transaction merge
   639  		// created them.
   640  
   641  		// TODO: We need to add more granularity in terms of what types of constraint violations can be committed. For example,
   642  		// in the case of foreign_key_checks=0 you should be able to commit foreign key violations.
   643  		if forceTransactionCommit.(int8) != 1 {
   644  			badTbls, err := doltdb.TablesWithConstraintViolations(ctx, workingRoot)
   645  			if err != nil {
   646  				return err
   647  			}
   648  
   649  			violations := make([]string, len(badTbls))
   650  			for i, name := range badTbls {
   651  				tbl, _, err := workingRoot.GetTable(ctx, doltdb.TableName{Name: name})
   652  				if err != nil {
   653  					return err
   654  				}
   655  
   656  				artIdx, err := tbl.GetArtifacts(ctx)
   657  				if err != nil {
   658  					return err
   659  				}
   660  
   661  				m := durable.ProllyMapFromArtifactIndex(artIdx)
   662  				itr, err := m.IterAllCVs(ctx)
   663  
   664  				for {
   665  					art, err := itr.Next(ctx)
   666  					if err != nil {
   667  						break
   668  					}
   669  
   670  					var meta prolly.ConstraintViolationMeta
   671  					err = json.Unmarshal(art.Metadata, &meta)
   672  					if err != nil {
   673  						return err
   674  					}
   675  
   676  					s := ""
   677  					switch art.ArtType {
   678  					case prolly.ArtifactTypeForeignKeyViol:
   679  						var m merge.FkCVMeta
   680  						err = json.Unmarshal(meta.VInfo, &m)
   681  						if err != nil {
   682  							return err
   683  						}
   684  						s = fmt.Sprintf("\n"+
   685  							"Type: Foreign Key Constraint Violation\n"+
   686  							"\tForeignKey: %s,\n"+
   687  							"\tTable: %s,\n"+
   688  							"\tReferencedTable: %s,\n"+
   689  							"\tIndex: %s,\n"+
   690  							"\tReferencedIndex: %s", m.ForeignKey, m.Table, m.ReferencedIndex, m.Index, m.ReferencedIndex)
   691  
   692  					case prolly.ArtifactTypeUniqueKeyViol:
   693  						var m merge.UniqCVMeta
   694  						err = json.Unmarshal(meta.VInfo, &m)
   695  						if err != nil {
   696  							return err
   697  						}
   698  						s = fmt.Sprintf("\n"+
   699  							"Type: Unique Key Constraint Violation,\n"+
   700  							"\tName: %s,\n"+
   701  							"\tColumns: %v", m.Name, m.Columns)
   702  
   703  					case prolly.ArtifactTypeNullViol:
   704  						var m merge.NullViolationMeta
   705  						err = json.Unmarshal(meta.VInfo, &m)
   706  						if err != nil {
   707  							return err
   708  						}
   709  						s = fmt.Sprintf("\n"+
   710  							"Type: Null Constraint Violation,\n"+
   711  							"\tColumns: %v", m.Columns)
   712  
   713  					case prolly.ArtifactTypeChkConsViol:
   714  						var m merge.CheckCVMeta
   715  						err = json.Unmarshal(meta.VInfo, &m)
   716  						if err != nil {
   717  							return err
   718  						}
   719  						s = fmt.Sprintf("\n"+
   720  							"Type: Check Constraint Violation,\n"+
   721  							"\tName: %s,\n"+
   722  							"\tExpression: %v", m.Name, m.Expression)
   723  					}
   724  					if err != nil {
   725  						return err
   726  					}
   727  
   728  					violations[i] = s
   729  				}
   730  			}
   731  
   732  			rollbackErr := tx.rollback(ctx)
   733  			if rollbackErr != nil {
   734  				return rollbackErr
   735  			}
   736  
   737  			return fmt.Errorf("%s\n"+
   738  				"Constraint violations: %s", ErrUnresolvedConstraintViolationsCommit, strings.Join(violations, ", "))
   739  		}
   740  	}
   741  
   742  	return nil
   743  }
   744  
   745  // CreateSavepoint creates a new savepoint with the name and roots given. If a savepoint with the name given
   746  // already exists, it's overwritten.
   747  func (tx *DoltTransaction) CreateSavepoint(name string, roots map[string]doltdb.RootValue) {
   748  	existing := tx.findSavepoint(name)
   749  	if existing >= 0 {
   750  		tx.savepoints = append(tx.savepoints[:existing], tx.savepoints[existing+1:]...)
   751  	}
   752  	tx.savepoints = append(tx.savepoints, savepoint{name, roots})
   753  }
   754  
   755  // findSavepoint returns the index of the savepoint with the name given, or -1 if it doesn't exist
   756  func (tx *DoltTransaction) findSavepoint(name string) int {
   757  	for i, s := range tx.savepoints {
   758  		if strings.ToLower(s.name) == strings.ToLower(name) {
   759  			return i
   760  		}
   761  	}
   762  	return -1
   763  }
   764  
   765  // RollbackToSavepoint returns the root values for all applicable databases associated with the savepoint name given, or nil if no such savepoint can
   766  // be found. All savepoints created after the one being rolled back to are no longer accessible.
   767  func (tx *DoltTransaction) RollbackToSavepoint(name string) map[string]doltdb.RootValue {
   768  	existing := tx.findSavepoint(name)
   769  	if existing >= 0 {
   770  		// Clear out any savepoints past this one
   771  		tx.savepoints = tx.savepoints[:existing+1]
   772  		return tx.savepoints[existing].roots
   773  	}
   774  	return nil
   775  }
   776  
   777  // ClearSavepoint removes the savepoint with the name given and returns whether a savepoint had that name
   778  func (tx *DoltTransaction) ClearSavepoint(name string) bool {
   779  	existing := tx.findSavepoint(name)
   780  	if existing >= 0 {
   781  		tx.savepoints = append(tx.savepoints[:existing], tx.savepoints[existing+1:]...)
   782  		return true
   783  	}
   784  	return false
   785  }
   786  
   787  // WorkingSetMeta returns the metadata to use for a commit of this transaction
   788  func (tx DoltTransaction) WorkingSetMeta(ctx *sql.Context) *datas.WorkingSetMeta {
   789  	sess := DSessFromSess(ctx.Session)
   790  	return &datas.WorkingSetMeta{
   791  		Name:        sess.Username(),
   792  		Email:       sess.Email(),
   793  		Timestamp:   uint64(time.Now().Unix()),
   794  		Description: "sql transaction",
   795  	}
   796  }
   797  
   798  func rootsEqual(left, right doltdb.RootValue) bool {
   799  	if left == nil || right == nil {
   800  		return false
   801  	}
   802  
   803  	lh, err := left.HashOf()
   804  	if err != nil {
   805  		return false
   806  	}
   807  
   808  	rh, err := right.HashOf()
   809  	if err != nil {
   810  		return false
   811  	}
   812  
   813  	return lh == rh
   814  }
   815  
   816  func workingAndStagedEqual(left, right *doltdb.WorkingSet) bool {
   817  	return rootsEqual(left.WorkingRoot(), right.WorkingRoot()) && rootsEqual(left.StagedRoot(), right.StagedRoot())
   818  }
   819  
   820  // isSessionAutocommit returns true if @autocommit is enabled.
   821  func isSessionAutocommit(ctx *sql.Context) (bool, error) {
   822  	autoCommitSessionVar, err := ctx.GetSessionVariable(ctx, sql.AutoCommitSessionVar)
   823  	if err != nil {
   824  		return false, err
   825  	}
   826  	return sql.ConvertToBool(ctx, autoCommitSessionVar)
   827  }