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 }