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

     1  // Copyright 2022 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  	"strings"
    19  	"sync"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    24  )
    25  
    26  // SessionCache caches various pieces of expensive to compute information to speed up future lookups in the session.
    27  type SessionCache struct {
    28  	indexes  map[doltdb.DataCacheKey]map[string][]sql.Index
    29  	tables   map[doltdb.DataCacheKey]map[TableCacheKey]sql.Table
    30  	views    map[doltdb.DataCacheKey]map[TableCacheKey]sql.ViewDefinition
    31  	triggers map[TableSchemaKey][]sql.TriggerDefinition
    32  
    33  	mu sync.RWMutex
    34  }
    35  
    36  // DatabaseCache stores databases and their initial states, offloading the compute / IO involved in resolving a
    37  // database name to a particular database. This is safe only because the database objects themselves don't have any
    38  // handles to data or state, but always defer to the session. Keys in the secondary map are revision specifier strings
    39  type DatabaseCache struct {
    40  	// revisionDbs caches databases by name. The name is always lower case and revision qualified
    41  	revisionDbs map[revisionDbCacheKey]SqlDatabase
    42  	// initialDbStates caches the initial state of databases by name for a given noms root, which is the primary key.
    43  	// The secondary key is the lower-case revision-qualified database name.
    44  	initialDbStates map[doltdb.DataCacheKey]map[string]InitialDbState
    45  	// sessionVars records a key for the most recently used session vars for each database in the session
    46  	sessionVars map[string]sessionVarCacheKey
    47  
    48  	mu sync.RWMutex
    49  }
    50  
    51  type revisionDbCacheKey struct {
    52  	dbName        string
    53  	requestedName string
    54  }
    55  
    56  type sessionVarCacheKey struct {
    57  	root doltdb.DataCacheKey
    58  	head string
    59  }
    60  
    61  const maxCachedKeys = 64
    62  
    63  func newSessionCache() *SessionCache {
    64  	return &SessionCache{}
    65  }
    66  
    67  func newDatabaseCache() *DatabaseCache {
    68  	return &DatabaseCache{
    69  		sessionVars: make(map[string]sessionVarCacheKey),
    70  	}
    71  }
    72  
    73  // CacheTableIndexes caches all indexes for the table with the name given
    74  func (c *SessionCache) CacheTableIndexes(key doltdb.DataCacheKey, table string, indexes []sql.Index) {
    75  	c.mu.Lock()
    76  	defer c.mu.Unlock()
    77  
    78  	table = strings.ToLower(table)
    79  
    80  	if c.indexes == nil {
    81  		c.indexes = make(map[doltdb.DataCacheKey]map[string][]sql.Index)
    82  	}
    83  	if len(c.indexes) > maxCachedKeys {
    84  		for k := range c.indexes {
    85  			delete(c.indexes, k)
    86  		}
    87  	}
    88  
    89  	tableIndexes, ok := c.indexes[key]
    90  	if !ok {
    91  		tableIndexes = make(map[string][]sql.Index)
    92  		c.indexes[key] = tableIndexes
    93  	}
    94  
    95  	tableIndexes[table] = indexes
    96  }
    97  
    98  // GetTableIndexesCache returns the cached index information for the table named, and whether the cache was present
    99  func (c *SessionCache) GetTableIndexesCache(key doltdb.DataCacheKey, table string) ([]sql.Index, bool) {
   100  	c.mu.RLock()
   101  	defer c.mu.RUnlock()
   102  
   103  	if c.indexes == nil {
   104  		return nil, false
   105  	}
   106  
   107  	tableIndexes, ok := c.indexes[key]
   108  	if !ok {
   109  		return nil, false
   110  	}
   111  	table = strings.ToLower(table)
   112  
   113  	indexes, ok := tableIndexes[table]
   114  	return indexes, ok
   115  }
   116  
   117  // CacheTable caches a sql.Table implementation for the table named
   118  func (c *SessionCache) CacheTable(key doltdb.DataCacheKey, tableName TableCacheKey, table sql.Table) {
   119  	c.mu.Lock()
   120  	defer c.mu.Unlock()
   121  
   122  	if c.tables == nil {
   123  		c.tables = make(map[doltdb.DataCacheKey]map[TableCacheKey]sql.Table)
   124  	}
   125  	if len(c.tables) > maxCachedKeys {
   126  		for k := range c.tables {
   127  			delete(c.tables, k)
   128  		}
   129  	}
   130  
   131  	tablesForKey, ok := c.tables[key]
   132  	if !ok {
   133  		tablesForKey = make(map[TableCacheKey]sql.Table)
   134  		c.tables[key] = tablesForKey
   135  	}
   136  
   137  	tablesForKey[tableName.ToLower()] = table
   138  }
   139  
   140  // ClearTableCache removes all cache info for all tables at all cache keys
   141  func (c *SessionCache) ClearTableCache() {
   142  	c.mu.Lock()
   143  	defer c.mu.Unlock()
   144  
   145  	for k := range c.tables {
   146  		delete(c.tables, k)
   147  	}
   148  }
   149  
   150  type TableCacheKey struct {
   151  	Name   string
   152  	Schema string
   153  }
   154  
   155  func (k TableCacheKey) ToLower() TableCacheKey {
   156  	return TableCacheKey{
   157  		Name:   strings.ToLower(k.Name),
   158  		Schema: strings.ToLower(k.Schema),
   159  	}
   160  }
   161  
   162  // GetCachedTable returns the cached sql.Table for the table named, and whether the cache was present
   163  func (c *SessionCache) GetCachedTable(key doltdb.DataCacheKey, tableName TableCacheKey) (sql.Table, bool) {
   164  	c.mu.RLock()
   165  	defer c.mu.RUnlock()
   166  
   167  	if c.tables == nil {
   168  		return nil, false
   169  	}
   170  
   171  	tablesForKey, ok := c.tables[key]
   172  	if !ok {
   173  		return nil, false
   174  	}
   175  
   176  	table, ok := tablesForKey[tableName.ToLower()]
   177  	return table, ok
   178  }
   179  
   180  // CacheViews caches all views in a database for the cache key given
   181  func (c *SessionCache) CacheViews(key doltdb.DataCacheKey, views []sql.ViewDefinition, schema string) {
   182  	c.mu.Lock()
   183  	defer c.mu.Unlock()
   184  
   185  	if c.views == nil {
   186  		c.views = make(map[doltdb.DataCacheKey]map[TableCacheKey]sql.ViewDefinition)
   187  	}
   188  	if len(c.views) > maxCachedKeys {
   189  		for k := range c.views {
   190  			delete(c.views, k)
   191  		}
   192  	}
   193  
   194  	viewsForKey, ok := c.views[key]
   195  	if !ok {
   196  		viewsForKey = make(map[TableCacheKey]sql.ViewDefinition)
   197  		c.views[key] = viewsForKey
   198  	}
   199  
   200  	for i := range views {
   201  		viewName := TableCacheKey{
   202  			Name:   strings.ToLower(views[i].Name),
   203  			Schema: strings.ToLower(schema),
   204  		}
   205  		viewsForKey[viewName] = views[i]
   206  	}
   207  }
   208  
   209  // ViewsCached returns whether this cache has been initialized with the set of views yet
   210  func (c *SessionCache) ViewsCached(key doltdb.DataCacheKey) bool {
   211  	c.mu.RLock()
   212  	defer c.mu.RUnlock()
   213  
   214  	if c.views == nil {
   215  		return false
   216  	}
   217  
   218  	_, ok := c.views[key]
   219  	return ok
   220  }
   221  
   222  // GetCachedViewDefinition returns the cached view named, and whether the cache was present
   223  func (c *SessionCache) GetCachedViewDefinition(key doltdb.DataCacheKey, viewName TableCacheKey) (sql.ViewDefinition, bool) {
   224  	c.mu.RLock()
   225  	defer c.mu.RUnlock()
   226  
   227  	if c.views == nil {
   228  		return sql.ViewDefinition{}, false
   229  	}
   230  
   231  	viewsForKey, ok := c.views[key]
   232  	if !ok {
   233  		return sql.ViewDefinition{}, false
   234  	}
   235  
   236  	table, ok := viewsForKey[viewName.ToLower()]
   237  	return table, ok
   238  }
   239  
   240  type TableSchemaKey struct {
   241  	key    doltdb.DataCacheKey
   242  	schema string
   243  }
   244  
   245  // CacheTriggers caches all views in a database for the cache key given
   246  func (c *SessionCache) CacheTriggers(key doltdb.DataCacheKey, triggers []sql.TriggerDefinition, schema string) {
   247  	c.mu.Lock()
   248  	defer c.mu.Unlock()
   249  
   250  	if c.triggers == nil {
   251  		c.triggers = make(map[TableSchemaKey][]sql.TriggerDefinition)
   252  	}
   253  	if len(c.triggers) > maxCachedKeys {
   254  		for k := range c.triggers {
   255  			delete(c.triggers, k)
   256  		}
   257  	}
   258  
   259  	schKey := TableSchemaKey{key: key, schema: schema}
   260  	_, ok := c.triggers[schKey]
   261  	if !ok {
   262  		// create backing array to differentiate no triggers/no cache
   263  		c.triggers[schKey] = make([]sql.TriggerDefinition, 0)
   264  	}
   265  
   266  	c.triggers[schKey] = append(c.triggers[schKey], triggers...)
   267  }
   268  
   269  // GetCachedTriggers returns the cached view named, and whether the cache was present
   270  func (c *SessionCache) GetCachedTriggers(key doltdb.DataCacheKey, schema string) ([]sql.TriggerDefinition, bool) {
   271  	c.mu.RLock()
   272  	defer c.mu.RUnlock()
   273  
   274  	schKey := TableSchemaKey{key: key, schema: schema}
   275  
   276  	triggers, ok := c.triggers[schKey]
   277  	return triggers, ok
   278  }
   279  
   280  // GetCachedRevisionDb returns the cached revision database named, and whether the cache was present
   281  func (c *DatabaseCache) GetCachedRevisionDb(revisionDbName string, requestedName string) (SqlDatabase, bool) {
   282  	c.mu.RLock()
   283  	defer c.mu.RUnlock()
   284  
   285  	if c.revisionDbs == nil {
   286  		return nil, false
   287  	}
   288  
   289  	db, ok := c.revisionDbs[revisionDbCacheKey{
   290  		dbName:        revisionDbName,
   291  		requestedName: requestedName,
   292  	}]
   293  	return db, ok
   294  }
   295  
   296  // CacheRevisionDb caches the revision database named
   297  func (c *DatabaseCache) CacheRevisionDb(database SqlDatabase) {
   298  	c.mu.Lock()
   299  	defer c.mu.Unlock()
   300  
   301  	if c.revisionDbs == nil {
   302  		c.revisionDbs = make(map[revisionDbCacheKey]SqlDatabase)
   303  	}
   304  
   305  	if len(c.revisionDbs) > maxCachedKeys {
   306  		for k := range c.revisionDbs {
   307  			delete(c.revisionDbs, k)
   308  		}
   309  	}
   310  
   311  	c.revisionDbs[revisionDbCacheKey{
   312  		dbName:        strings.ToLower(database.RevisionQualifiedName()),
   313  		requestedName: database.RequestedName(),
   314  	}] = database
   315  }
   316  
   317  // GetCachedInitialDbState returns the cached initial state for the revision database named, and whether the cache
   318  // was present
   319  func (c *DatabaseCache) GetCachedInitialDbState(key doltdb.DataCacheKey, revisionDbName string) (InitialDbState, bool) {
   320  	c.mu.RLock()
   321  	defer c.mu.RUnlock()
   322  
   323  	if c.initialDbStates == nil {
   324  		return InitialDbState{}, false
   325  	}
   326  
   327  	dbsForKey, ok := c.initialDbStates[key]
   328  	if !ok {
   329  		return InitialDbState{}, false
   330  	}
   331  
   332  	db, ok := dbsForKey[revisionDbName]
   333  	return db, ok
   334  }
   335  
   336  // CacheInitialDbState caches the initials state for the revision database named
   337  func (c *DatabaseCache) CacheInitialDbState(key doltdb.DataCacheKey, revisionDbName string, state InitialDbState) {
   338  	c.mu.Lock()
   339  	defer c.mu.Unlock()
   340  
   341  	if c.initialDbStates == nil {
   342  		c.initialDbStates = make(map[doltdb.DataCacheKey]map[string]InitialDbState)
   343  	}
   344  
   345  	if len(c.initialDbStates) > maxCachedKeys {
   346  		for k := range c.initialDbStates {
   347  			delete(c.initialDbStates, k)
   348  		}
   349  	}
   350  
   351  	dbsForKey, ok := c.initialDbStates[key]
   352  	if !ok {
   353  		dbsForKey = make(map[string]InitialDbState)
   354  		c.initialDbStates[key] = dbsForKey
   355  	}
   356  
   357  	dbsForKey[revisionDbName] = state
   358  }
   359  
   360  // CacheSessionVars updates the session var cache for the given branch state and transaction and returns whether it
   361  // was updated. If it was updated, session vars need to be set for the state and transaction given. Otherwise they
   362  // haven't changed and can be reused.
   363  func (c *DatabaseCache) CacheSessionVars(branchState *branchState, transaction *DoltTransaction) bool {
   364  	c.mu.Lock()
   365  	defer c.mu.Unlock()
   366  
   367  	dbBaseName := branchState.dbState.dbName
   368  
   369  	existingKey, found := c.sessionVars[dbBaseName]
   370  	root, hasRoot := transaction.GetInitialRoot(dbBaseName)
   371  	if !hasRoot {
   372  		return true
   373  	}
   374  
   375  	newKey := sessionVarCacheKey{
   376  		root: doltdb.DataCacheKey{Hash: root},
   377  		head: strings.ToLower(branchState.head),
   378  	}
   379  
   380  	c.sessionVars[dbBaseName] = newKey
   381  	return !found || existingKey != newKey
   382  }
   383  
   384  func (c *DatabaseCache) Clear() {
   385  	c.mu.Lock()
   386  	defer c.mu.Unlock()
   387  	c.sessionVars = make(map[string]sessionVarCacheKey)
   388  	c.revisionDbs = make(map[revisionDbCacheKey]SqlDatabase)
   389  	c.initialDbStates = make(map[doltdb.DataCacheKey]map[string]InitialDbState)
   390  }