github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/sqle/dolt_session.go (about)

     1  // Copyright 2020 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 sqle
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"os"
    21  	"strings"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/env"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/ref"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
    30  	"github.com/dolthub/dolt/go/store/hash"
    31  )
    32  
    33  type dbRoot struct {
    34  	hashStr string
    35  	root    *doltdb.RootValue
    36  }
    37  
    38  const (
    39  	HeadKeySuffix    = "_head"
    40  	HeadRefKeySuffix = "_head_ref"
    41  	WorkingKeySuffix = "_working"
    42  )
    43  
    44  const (
    45  	EnableTransactionsEnvKey      = "DOLT_ENABLE_TRANSACTIONS"
    46  	DoltCommitOnTransactionCommit = "dolt_transaction_commit"
    47  )
    48  
    49  type batchMode int8
    50  
    51  const (
    52  	single batchMode = iota
    53  	batched
    54  )
    55  
    56  const TransactionsEnabledSysVar = "dolt_transactions_enabled"
    57  
    58  func init() {
    59  	txEnabledSessionVar := int8(0)
    60  	enableTx, ok := os.LookupEnv(EnableTransactionsEnvKey)
    61  	if ok {
    62  		if strings.ToLower(enableTx) == "true" {
    63  			txEnabledSessionVar = int8(1)
    64  		}
    65  	}
    66  	sql.SystemVariables.AddSystemVariables([]sql.SystemVariable{
    67  		{
    68  			Name:              DoltCommitOnTransactionCommit,
    69  			Scope:             sql.SystemVariableScope_Session,
    70  			Dynamic:           true,
    71  			SetVarHintApplies: false,
    72  			Type:              sql.NewSystemBoolType(DoltCommitOnTransactionCommit),
    73  			Default:           int8(0),
    74  		},
    75  		{
    76  			Name:              TransactionsEnabledSysVar,
    77  			Scope:             sql.SystemVariableScope_Session,
    78  			Dynamic:           true,
    79  			SetVarHintApplies: false,
    80  			Type:              sql.NewSystemBoolType(TransactionsEnabledSysVar),
    81  			Default:           txEnabledSessionVar,
    82  		},
    83  	})
    84  }
    85  
    86  func TransactionsEnabled(ctx *sql.Context) bool {
    87  	enabled, err := ctx.GetSessionVariable(ctx, TransactionsEnabledSysVar)
    88  	if err != nil {
    89  		panic(err)
    90  	}
    91  
    92  	switch enabled.(int8) {
    93  	case 0:
    94  		return false
    95  	case 1:
    96  		return true
    97  	default:
    98  		panic(fmt.Sprintf("Unexpected value %v", enabled))
    99  	}
   100  }
   101  
   102  func IsHeadKey(key string) (bool, string) {
   103  	if strings.HasSuffix(key, HeadKeySuffix) {
   104  		return true, key[:len(key)-len(HeadKeySuffix)]
   105  	}
   106  
   107  	return false, ""
   108  }
   109  
   110  func IsWorkingKey(key string) (bool, string) {
   111  	if strings.HasSuffix(key, WorkingKeySuffix) {
   112  		return true, key[:len(key)-len(WorkingKeySuffix)]
   113  	}
   114  
   115  	return false, ""
   116  }
   117  
   118  // DoltSession is the sql.Session implementation used by dolt.  It is accessible through a *sql.Context instance
   119  type DoltSession struct {
   120  	sql.Session
   121  	roots                 map[string]dbRoot
   122  	workingSets           map[string]ref.WorkingSetRef
   123  	dbDatas               map[string]env.DbData
   124  	editSessions          map[string]*editor.TableEditSession
   125  	dirty                 map[string]bool
   126  	batchMode             batchMode
   127  	Username              string
   128  	Email                 string
   129  	tempTableRoots        map[string]*doltdb.RootValue
   130  	tempTableEditSessions map[string]*editor.TableEditSession
   131  }
   132  
   133  var _ sql.Session = &DoltSession{}
   134  
   135  // DefaultDoltSession creates a DoltSession object with default values
   136  func DefaultDoltSession() *DoltSession {
   137  	sess := &DoltSession{
   138  		Session:               sql.NewBaseSession(),
   139  		roots:                 make(map[string]dbRoot),
   140  		dbDatas:               make(map[string]env.DbData),
   141  		editSessions:          make(map[string]*editor.TableEditSession),
   142  		dirty:                 make(map[string]bool),
   143  		workingSets:           make(map[string]ref.WorkingSetRef),
   144  		Username:              "",
   145  		Email:                 "",
   146  		tempTableRoots:        make(map[string]*doltdb.RootValue),
   147  		tempTableEditSessions: make(map[string]*editor.TableEditSession),
   148  	}
   149  	return sess
   150  }
   151  
   152  // NewDoltSession creates a DoltSession object from a standard sql.Session and 0 or more Database objects.
   153  func NewDoltSession(ctx *sql.Context, sqlSess sql.Session, username, email string, dbs ...Database) (*DoltSession, error) {
   154  	dbDatas := make(map[string]env.DbData)
   155  	editSessions := make(map[string]*editor.TableEditSession)
   156  
   157  	for _, db := range dbs {
   158  		dbDatas[db.Name()] = env.DbData{Rsw: db.rsw, Ddb: db.ddb, Rsr: db.rsr, Drw: db.drw}
   159  		editSessions[db.Name()] = editor.CreateTableEditSession(nil, editor.TableEditSessionProps{})
   160  	}
   161  
   162  	sess := &DoltSession{
   163  		Session:               sqlSess,
   164  		dbDatas:               dbDatas,
   165  		editSessions:          editSessions,
   166  		dirty:                 make(map[string]bool),
   167  		roots:                 make(map[string]dbRoot),
   168  		workingSets:           make(map[string]ref.WorkingSetRef),
   169  		Username:              username,
   170  		Email:                 email,
   171  		tempTableRoots:        make(map[string]*doltdb.RootValue),
   172  		tempTableEditSessions: make(map[string]*editor.TableEditSession),
   173  	}
   174  	for _, db := range dbs {
   175  		err := sess.AddDB(ctx, db, db.DbData())
   176  
   177  		if err != nil {
   178  			return nil, err
   179  		}
   180  	}
   181  
   182  	return sess, nil
   183  }
   184  
   185  // EnableBatchedMode enables batched mode for this session. This is only safe to do during initialization.
   186  // Sessions operating in batched mode don't flush any edit buffers except when told to do so explicitly, or when a
   187  // transaction commits. Disable @@autocommit to prevent edit buffers from being flushed prematurely in this mode.
   188  func (sess *DoltSession) EnableBatchedMode() {
   189  	sess.batchMode = batched
   190  }
   191  
   192  // DSessFromSess retrieves a dolt session from a standard sql.Session
   193  func DSessFromSess(sess sql.Session) *DoltSession {
   194  	return sess.(*DoltSession)
   195  }
   196  
   197  // Flush flushes all changes sitting in edit sessions to the session root for the database named. This normally
   198  // happens automatically as part of statement execution, and is only necessary when the session is manually batched (as
   199  // for bulk SQL import)
   200  func (sess *DoltSession) Flush(ctx *sql.Context, dbName string) error {
   201  	editSession := sess.editSessions[dbName]
   202  	newRoot, err := editSession.Flush(ctx)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	return sess.SetRoot(ctx, dbName, newRoot)
   208  }
   209  
   210  // CommitTransaction commits the in-progress transaction for the database named
   211  func (sess *DoltSession) CommitTransaction(ctx *sql.Context, dbName string, tx sql.Transaction) error {
   212  	if sess.batchMode == batched {
   213  		err := sess.Flush(ctx, dbName)
   214  		if err != nil {
   215  			return err
   216  		}
   217  	}
   218  
   219  	if !sess.dirty[dbName] {
   220  		return nil
   221  	}
   222  
   223  	// This is triggered when certain commands are sent to the server (ex. commit) when a database is not selected.
   224  	// These commands should not error.
   225  	if dbName == "" {
   226  		return nil
   227  	}
   228  
   229  	dbRoot, ok := sess.roots[dbName]
   230  	// It's possible that this returns false if the user has created an in-Memory database. Moreover,
   231  	// the analyzer will check for us whether a db exists or not.
   232  	if !ok {
   233  		return nil
   234  	}
   235  
   236  	// Old "commit" path, which just writes whatever the root for this session is to the repo state file with no care
   237  	// for concurrency. Over time we will disable this path.
   238  	if !TransactionsEnabled(ctx) {
   239  		dbData := sess.dbDatas[dbName]
   240  
   241  		h, err := dbData.Ddb.WriteRootValue(ctx, dbRoot.root)
   242  		if err != nil {
   243  			return err
   244  		}
   245  
   246  		sess.dirty[dbName] = false
   247  		return dbData.Rsw.SetWorkingHash(ctx, h)
   248  	}
   249  
   250  	// Newer commit path does a concurrent merge of the current root with the one other clients are editing, then
   251  	// updates the session with this new root.
   252  	// TODO: validate that the transaction belongs to the DB named
   253  	dtx, ok := tx.(*DoltTransaction)
   254  	if !ok {
   255  		return fmt.Errorf("expected a DoltTransaction")
   256  	}
   257  
   258  	mergedRoot, err := dtx.Commit(ctx, dbRoot.root)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	err = sess.SetRoot(ctx, dbName, mergedRoot)
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	err = sess.CommitWorkingSetToDolt(ctx, dtx.dbData, dbName)
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	sess.dirty[dbName] = false
   274  	return nil
   275  }
   276  
   277  // CommitWorkingSetToDolt stages the working set and then immediately commits the staged changes. This is a Dolt commit
   278  // rather than a transaction commit. If there are no changes to be staged, then no commit is created.
   279  func (sess *DoltSession) CommitWorkingSetToDolt(ctx *sql.Context, dbData env.DbData, dbName string) error {
   280  	if commitBool, err := sess.Session.GetSessionVariable(ctx, DoltCommitOnTransactionCommit); err != nil {
   281  		return err
   282  	} else if commitBool.(int8) == 1 {
   283  		fkChecks, err := sess.Session.GetSessionVariable(ctx, "foreign_key_checks")
   284  		if err != nil {
   285  			return err
   286  		}
   287  		err = actions.StageAllTables(ctx, dbData)
   288  		if err != nil {
   289  			return err
   290  		}
   291  		queryTime := ctx.QueryTime()
   292  		_, err = actions.CommitStaged(ctx, dbData, actions.CommitStagedProps{
   293  			Message:          fmt.Sprintf("Transaction commit at %s", queryTime.UTC().Format("2006-01-02T15:04:05Z")),
   294  			Date:             queryTime,
   295  			AllowEmpty:       false,
   296  			CheckForeignKeys: fkChecks.(int8) == 1,
   297  			Name:             sess.Username,
   298  			Email:            sess.Email,
   299  		})
   300  		if _, ok := err.(actions.NothingStaged); err != nil && !ok {
   301  			return err
   302  		}
   303  
   304  		headCommit, err := dbData.Ddb.Resolve(ctx, dbData.Rsr.CWBHeadSpec(), dbData.Rsr.CWBHeadRef())
   305  		if err != nil {
   306  			return err
   307  		}
   308  		headHash, err := headCommit.HashOf()
   309  		if err != nil {
   310  			return err
   311  		}
   312  		err = sess.Session.SetSessionVariable(ctx, HeadKey(dbName), headHash.String())
   313  		if err != nil {
   314  			return err
   315  		}
   316  	}
   317  	return nil
   318  }
   319  
   320  // RollbackTransaction rolls the given transaction back
   321  func (sess *DoltSession) RollbackTransaction(ctx *sql.Context, dbName string, tx sql.Transaction) error {
   322  	if !TransactionsEnabled(ctx) || dbName == "" {
   323  		return nil
   324  	}
   325  
   326  	if !sess.dirty[dbName] {
   327  		return nil
   328  	}
   329  
   330  	dtx, ok := tx.(*DoltTransaction)
   331  	if !ok {
   332  		return fmt.Errorf("expected a DoltTransaction")
   333  	}
   334  
   335  	err := sess.SetRoot(ctx, dbName, dtx.startRoot)
   336  	if err != nil {
   337  		return err
   338  	}
   339  
   340  	sess.dirty[dbName] = false
   341  	return nil
   342  }
   343  
   344  // CreateSavepoint creates a new savepoint for this transaction with the name given. A previously created savepoint
   345  // with the same name will be overwritten.
   346  func (sess *DoltSession) CreateSavepoint(ctx *sql.Context, savepointName, dbName string, tx sql.Transaction) error {
   347  	if !TransactionsEnabled(ctx) || dbName == "" {
   348  		return nil
   349  	}
   350  
   351  	dtx, ok := tx.(*DoltTransaction)
   352  	if !ok {
   353  		return fmt.Errorf("expected a DoltTransaction")
   354  	}
   355  
   356  	dtx.CreateSavepoint(savepointName, sess.roots[dbName].root)
   357  	return nil
   358  }
   359  
   360  // RollbackToSavepoint sets this session's root to the one saved in the savepoint name. It's an error if no savepoint
   361  // with that name exists.
   362  func (sess *DoltSession) RollbackToSavepoint(ctx *sql.Context, savepointName, dbName string, tx sql.Transaction) error {
   363  	if !TransactionsEnabled(ctx) || dbName == "" {
   364  		return nil
   365  	}
   366  
   367  	dtx, ok := tx.(*DoltTransaction)
   368  	if !ok {
   369  		return fmt.Errorf("expected a DoltTransaction")
   370  	}
   371  
   372  	root := dtx.RollbackToSavepoint(savepointName)
   373  	if root == nil {
   374  		return sql.ErrSavepointDoesNotExist.New(savepointName)
   375  	}
   376  
   377  	err := sess.SetRoot(ctx, dbName, root)
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	return nil
   383  }
   384  
   385  // ReleaseSavepoint removes the savepoint name from the transaction. It's an error if no savepoint with that name
   386  // exists.
   387  func (sess *DoltSession) ReleaseSavepoint(ctx *sql.Context, savepointName, dbName string, tx sql.Transaction) error {
   388  	if !TransactionsEnabled(ctx) || dbName == "" {
   389  		return nil
   390  	}
   391  
   392  	dtx, ok := tx.(*DoltTransaction)
   393  	if !ok {
   394  		return fmt.Errorf("expected a DoltTransaction")
   395  	}
   396  
   397  	root := dtx.ClearSavepoint(savepointName)
   398  	if root == nil {
   399  		return sql.ErrSavepointDoesNotExist.New(savepointName)
   400  	}
   401  
   402  	return nil
   403  }
   404  
   405  // GetDoltDB returns the *DoltDB for a given database by name
   406  func (sess *DoltSession) GetDoltDB(dbName string) (*doltdb.DoltDB, bool) {
   407  	d, ok := sess.dbDatas[dbName]
   408  
   409  	if !ok {
   410  		return nil, false
   411  	}
   412  
   413  	return d.Ddb, true
   414  }
   415  
   416  func (sess *DoltSession) GetDoltDBRepoStateWriter(dbName string) (env.RepoStateWriter, bool) {
   417  	d, ok := sess.dbDatas[dbName]
   418  
   419  	if !ok {
   420  		return nil, false
   421  	}
   422  
   423  	return d.Rsw, true
   424  }
   425  
   426  func (sess *DoltSession) GetDoltDBRepoStateReader(dbName string) (env.RepoStateReader, bool) {
   427  	d, ok := sess.dbDatas[dbName]
   428  
   429  	if !ok {
   430  		return nil, false
   431  	}
   432  
   433  	return d.Rsr, true
   434  }
   435  
   436  func (sess *DoltSession) GetDoltDBDocsReadWriter(dbName string) (env.DocsReadWriter, bool) {
   437  	d, ok := sess.dbDatas[dbName]
   438  
   439  	if !ok {
   440  		return nil, false
   441  	}
   442  
   443  	return d.Drw, true
   444  }
   445  
   446  func (sess *DoltSession) GetDbData(dbName string) (env.DbData, bool) {
   447  	ddb, ok := sess.GetDoltDB(dbName)
   448  
   449  	if !ok {
   450  		return env.DbData{}, false
   451  	}
   452  
   453  	rsr, ok := sess.GetDoltDBRepoStateReader(dbName)
   454  
   455  	if !ok {
   456  		return env.DbData{}, false
   457  	}
   458  
   459  	rsw, ok := sess.GetDoltDBRepoStateWriter(dbName)
   460  
   461  	if !ok {
   462  		return env.DbData{}, false
   463  	}
   464  
   465  	drw, ok := sess.GetDoltDBDocsReadWriter(dbName)
   466  
   467  	if !ok {
   468  		return env.DbData{}, false
   469  	}
   470  
   471  	return env.DbData{
   472  		Ddb: ddb,
   473  		Rsr: rsr,
   474  		Rsw: rsw,
   475  		Drw: drw,
   476  	}, true
   477  }
   478  
   479  // GetRoot returns the current *RootValue for a given database associated with the session
   480  func (sess *DoltSession) GetRoot(dbName string) (*doltdb.RootValue, bool) {
   481  	dbRoot, ok := sess.roots[dbName]
   482  
   483  	if !ok {
   484  		return nil, false
   485  	}
   486  
   487  	return dbRoot.root, true
   488  }
   489  
   490  // SetRoot sets a new root value for the session for the database named. This is the primary mechanism by which data
   491  // changes are communicated to the engine and persisted back to disk. All data changes should be followed by a call to
   492  // update the session's root value via this method.
   493  // Data changes contained in the |newRoot| aren't persisted until this session is committed.
   494  func (sess *DoltSession) SetRoot(ctx *sql.Context, dbName string, newRoot *doltdb.RootValue) error {
   495  	if rootsEqual(sess.roots[dbName].root, newRoot) {
   496  		return nil
   497  	}
   498  
   499  	h, err := newRoot.HashOf()
   500  	if err != nil {
   501  		return err
   502  	}
   503  
   504  	hashStr := h.String()
   505  	err = sess.Session.SetSessionVariable(ctx, WorkingKey(dbName), hashStr)
   506  	if err != nil {
   507  		return err
   508  	}
   509  
   510  	sess.roots[dbName] = dbRoot{hashStr, newRoot}
   511  
   512  	err = sess.editSessions[dbName].SetRoot(ctx, newRoot)
   513  	if err != nil {
   514  		return err
   515  	}
   516  
   517  	sess.dirty[dbName] = true
   518  	return nil
   519  }
   520  
   521  func (sess *DoltSession) GetTempTableRootValue(ctx *sql.Context, dbName string) (*doltdb.RootValue, bool) {
   522  	tempTableRoot, ok := sess.tempTableRoots[dbName]
   523  
   524  	if !ok {
   525  		return nil, false
   526  	}
   527  
   528  	return tempTableRoot, true
   529  }
   530  
   531  func (sess *DoltSession) SetTempTableRoot(ctx *sql.Context, dbName string, newRoot *doltdb.RootValue) error {
   532  	sess.tempTableRoots[dbName] = newRoot
   533  	return sess.tempTableEditSessions[dbName].SetRoot(ctx, newRoot)
   534  }
   535  
   536  // GetHeadCommit returns the parent commit of the current session.
   537  func (sess *DoltSession) GetHeadCommit(ctx *sql.Context, dbName string) (*doltdb.Commit, hash.Hash, error) {
   538  	dbd, dbFound := sess.dbDatas[dbName]
   539  
   540  	if !dbFound {
   541  		return nil, hash.Hash{}, sql.ErrDatabaseNotFound.New(dbName)
   542  	}
   543  
   544  	value, err := sess.Session.GetSessionVariable(ctx, dbName+HeadKeySuffix)
   545  	if err != nil {
   546  		return nil, hash.Hash{}, err
   547  	}
   548  
   549  	valStr, isStr := value.(string)
   550  
   551  	if !isStr || !hash.IsValid(valStr) {
   552  		return nil, hash.Hash{}, doltdb.ErrInvalidHash
   553  	}
   554  
   555  	h := hash.Parse(valStr)
   556  	cs, err := doltdb.NewCommitSpec(valStr)
   557  
   558  	if err != nil {
   559  		return nil, hash.Hash{}, err
   560  	}
   561  
   562  	cm, err := dbd.Ddb.Resolve(ctx, cs, nil)
   563  
   564  	if err != nil {
   565  		return nil, hash.Hash{}, err
   566  	}
   567  
   568  	return cm, h, nil
   569  }
   570  
   571  // SetSessionVariable is defined on sql.Session. We intercept it here to interpret the special semantics of the system
   572  // vars that we define. Otherwise we pass it on to the base implementation.
   573  func (sess *DoltSession) SetSessionVariable(ctx *sql.Context, key string, value interface{}) error {
   574  	// TODO: is working head ref
   575  
   576  	if isHead, dbName := IsHeadKey(key); isHead {
   577  		return sess.setHeadSessionVar(ctx, key, value, dbName)
   578  	}
   579  
   580  	if isWorking, dbName := IsWorkingKey(key); isWorking {
   581  		return sess.setWorkingSessionVar(ctx, value, dbName)
   582  	}
   583  
   584  	if strings.ToLower(key) == "foreign_key_checks" {
   585  		return sess.setForeignKeyChecksSessionVar(ctx, key, value)
   586  	}
   587  
   588  	return sess.Session.SetSessionVariable(ctx, key, value)
   589  }
   590  
   591  func (sess *DoltSession) setForeignKeyChecksSessionVar(ctx *sql.Context, key string, value interface{}) error {
   592  	convertedVal, err := sql.Int64.Convert(value)
   593  	if err != nil {
   594  		return err
   595  	}
   596  	intVal := int64(0)
   597  	if convertedVal != nil {
   598  		intVal = convertedVal.(int64)
   599  	}
   600  	if intVal == 0 {
   601  		for _, tableEditSession := range sess.editSessions {
   602  			tableEditSession.Props.ForeignKeyChecksDisabled = true
   603  		}
   604  	} else if intVal == 1 {
   605  		for _, tableEditSession := range sess.editSessions {
   606  			tableEditSession.Props.ForeignKeyChecksDisabled = false
   607  		}
   608  	} else {
   609  		return fmt.Errorf("variable 'foreign_key_checks' can't be set to the value of '%d'", intVal)
   610  	}
   611  
   612  	return sess.Session.SetSessionVariable(ctx, key, value)
   613  }
   614  
   615  func (sess *DoltSession) setWorkingSessionVar(ctx *sql.Context, value interface{}, dbName string) error {
   616  	valStr, isStr := value.(string) // valStr represents a root val hash
   617  	if !isStr || !hash.IsValid(valStr) {
   618  		return doltdb.ErrInvalidHash
   619  	}
   620  
   621  	// If there's a Root Value that's associated with this hash update dbRoots to include it
   622  	dbd, dbFound := sess.dbDatas[dbName]
   623  	if !dbFound {
   624  		return sql.ErrDatabaseNotFound.New(dbName)
   625  	}
   626  
   627  	root, err := dbd.Ddb.ReadRootValue(ctx, hash.Parse(valStr))
   628  	if errors.Is(doltdb.ErrNoRootValAtHash, err) {
   629  		return nil
   630  	} else if err != nil {
   631  		return err
   632  	}
   633  
   634  	return sess.SetRoot(ctx, dbName, root)
   635  }
   636  
   637  func (sess *DoltSession) setHeadSessionVar(ctx *sql.Context, key string, value interface{}, dbName string) error {
   638  	dbd, dbFound := sess.dbDatas[dbName]
   639  
   640  	if !dbFound {
   641  		return sql.ErrDatabaseNotFound.New(dbName)
   642  	}
   643  
   644  	valStr, isStr := value.(string)
   645  
   646  	if !isStr || !hash.IsValid(valStr) {
   647  		return doltdb.ErrInvalidHash
   648  	}
   649  
   650  	cs, err := doltdb.NewCommitSpec(valStr)
   651  	if err != nil {
   652  		return err
   653  	}
   654  
   655  	cm, err := dbd.Ddb.Resolve(ctx, cs, nil)
   656  	if err != nil {
   657  		return err
   658  	}
   659  
   660  	root, err := cm.GetRootValue()
   661  	if err != nil {
   662  		return err
   663  	}
   664  
   665  	err = sess.Session.SetSessionVariable(ctx, HeadKey(dbName), value)
   666  	if err != nil {
   667  		return err
   668  	}
   669  
   670  	// TODO: preserve working set changes?
   671  	return sess.SetRoot(ctx, dbName, root)
   672  }
   673  
   674  // SetSessionVarDirectly directly updates sess.Session. This is useful in the context of the sql shell where
   675  // the working and head session variable may be updated at different times.
   676  func (sess *DoltSession) SetSessionVarDirectly(ctx *sql.Context, key string, value interface{}) error {
   677  	return sess.Session.SetSessionVariable(ctx, key, value)
   678  }
   679  
   680  // AddDB adds the database given to this session. This establishes a starting root value for this session, as well as
   681  // other state tracking metadata.
   682  func (sess *DoltSession) AddDB(ctx *sql.Context, db sql.Database, dbData env.DbData) error {
   683  	defineSystemVariables(db.Name())
   684  
   685  	rsr := dbData.Rsr
   686  	ddb := dbData.Ddb
   687  
   688  	sess.dbDatas[db.Name()] = dbData
   689  	sess.editSessions[db.Name()] = editor.CreateTableEditSession(nil, editor.TableEditSessionProps{})
   690  
   691  	cs := rsr.CWBHeadSpec()
   692  	headRef := rsr.CWBHeadRef()
   693  
   694  	workingHashInRepoState := rsr.WorkingHash()
   695  	workingHashInWsRef := hash.Hash{}
   696  
   697  	// TODO: this resolve isn't necessary in all cases and slows things down
   698  	cm, err := ddb.Resolve(ctx, cs, headRef)
   699  	if err != nil {
   700  		return err
   701  	}
   702  
   703  	headCommitHash, err := cm.HashOf()
   704  	if err != nil {
   705  		return err
   706  	}
   707  
   708  	var workingRoot *doltdb.RootValue
   709  	// Get a working root to use for this session. This could come from the an independent working set not associated
   710  	// with any commit, or from the head commit itself in some use cases. Some implementors of RepoStateReader use the
   711  	// current HEAD hash as the working set hash, and in fact they have to -- there's not always an independently
   712  	// addressable root value available, only one persisted as a value in a Commit object.
   713  	if headCommitHash == workingHashInRepoState {
   714  		workingRoot, err = cm.GetRootValue()
   715  		if err != nil {
   716  			return err
   717  		}
   718  	}
   719  
   720  	if workingRoot == nil {
   721  		// If the root isn't a head commit value, assume it's a standalone value and look it up
   722  		workingRoot, err = ddb.ReadRootValue(ctx, workingHashInRepoState)
   723  		if err != nil {
   724  			return err
   725  		}
   726  	}
   727  
   728  	if TransactionsEnabled(ctx) {
   729  		// Not all dolt commands update the working set ref yet. So until that's true, we update it here with the contents
   730  		// of the repo_state.json file
   731  		workingSetRef, err := ref.WorkingSetRefForHead(headRef)
   732  		if err != nil {
   733  			return err
   734  		}
   735  		sess.workingSets[db.Name()] = workingSetRef
   736  
   737  		workingSet, err := ddb.ResolveWorkingSet(ctx, workingSetRef)
   738  		if err == doltdb.ErrWorkingSetNotFound {
   739  			// no working set ref established yet
   740  		} else if err != nil {
   741  			return err
   742  		} else {
   743  			workingHashInWsRef, err = workingSet.Struct().Hash(ddb.Format())
   744  			if err != nil {
   745  				return err
   746  			}
   747  		}
   748  
   749  		// TODO: there's a race here if more than one client connects at the same time. We need a retry
   750  		err = ddb.UpdateWorkingSet(ctx, workingSetRef, workingRoot, workingHashInWsRef)
   751  		if err != nil {
   752  			return err
   753  		}
   754  
   755  		err = sess.Session.SetSessionVariable(ctx, HeadRefKey(db.Name()), workingSetRef.GetPath())
   756  		if err != nil {
   757  			return err
   758  		}
   759  	}
   760  
   761  	err = sess.SetRoot(ctx, db.Name(), workingRoot)
   762  	if err != nil {
   763  		return err
   764  	}
   765  
   766  	err = sess.Session.SetSessionVariable(ctx, HeadKey(db.Name()), headCommitHash.String())
   767  	if err != nil {
   768  		return err
   769  	}
   770  
   771  	// After setting the initial root we have no state to commit
   772  	sess.dirty[db.Name()] = false
   773  
   774  	return nil
   775  }
   776  
   777  // CreateTemporaryTablesRoot creates an empty root value and a table edit session for the purposes of storing
   778  // temporary tables. This should only be used on demand. That is only when a temporary table is created should we
   779  // create the root map and edit session map.
   780  func (sess *DoltSession) CreateTemporaryTablesRoot(ctx *sql.Context, dbName string, ddb *doltdb.DoltDB) error {
   781  	newRoot, err := doltdb.EmptyRootValue(ctx, ddb.ValueReadWriter())
   782  	if err != nil {
   783  		return err
   784  	}
   785  
   786  	sess.tempTableEditSessions[dbName] = editor.CreateTableEditSession(newRoot, editor.TableEditSessionProps{})
   787  
   788  	return sess.SetTempTableRoot(ctx, dbName, newRoot)
   789  }
   790  
   791  // defineSystemVariables defines dolt-session variables in the engine as necessary
   792  func defineSystemVariables(name string) {
   793  	if _, _, ok := sql.SystemVariables.GetGlobal(name + HeadKeySuffix); !ok {
   794  		sql.SystemVariables.AddSystemVariables([]sql.SystemVariable{
   795  			{
   796  				Name:              HeadRefKey(name),
   797  				Scope:             sql.SystemVariableScope_Session,
   798  				Dynamic:           true,
   799  				SetVarHintApplies: false,
   800  				Type:              sql.NewSystemStringType(HeadRefKey(name)),
   801  				Default:           "",
   802  			},
   803  			{
   804  				Name:              HeadKey(name),
   805  				Scope:             sql.SystemVariableScope_Session,
   806  				Dynamic:           true,
   807  				SetVarHintApplies: false,
   808  				Type:              sql.NewSystemStringType(HeadKey(name)),
   809  				Default:           "",
   810  			},
   811  			{
   812  				Name:              WorkingKey(name),
   813  				Scope:             sql.SystemVariableScope_Session,
   814  				Dynamic:           true,
   815  				SetVarHintApplies: false,
   816  				Type:              sql.NewSystemStringType(WorkingKey(name)),
   817  				Default:           "",
   818  			},
   819  		})
   820  	}
   821  }