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 }