github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/sql/sem/tree/regexp_cache.go (about) 1 // Copyright 2015 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package tree 12 13 import ( 14 "regexp" 15 16 "github.com/cockroachdb/cockroachdb-parser/pkg/util/cache" 17 "github.com/cockroachdb/cockroachdb-parser/pkg/util/syncutil" 18 ) 19 20 // RegexpCacheKey allows cache keys to take the form of different types, 21 // as long as they are comparable and can produce a pattern when needed 22 // for regexp compilation. The pattern method will not be called until 23 // after a cache lookup is performed and the result is a miss. 24 type RegexpCacheKey interface { 25 Pattern() (string, error) 26 } 27 28 // A RegexpCache is a cache used to store compiled regular expressions. 29 // The cache is safe for concurrent use by multiple goroutines. It is also 30 // safe to use the cache through a nil reference, where it will act like a valid 31 // cache with no capacity. 32 type RegexpCache struct { 33 mu syncutil.Mutex 34 cache *cache.UnorderedCache 35 } 36 37 // NewRegexpCache creates a new RegexpCache of the given size. 38 // The underlying cache internally uses a hash map, so lookups 39 // are cheap. 40 func NewRegexpCache(size int) *RegexpCache { 41 return &RegexpCache{ 42 cache: cache.NewUnorderedCache(cache.Config{ 43 Policy: cache.CacheLRU, 44 ShouldEvict: func(s int, key, value interface{}) bool { 45 return s > size 46 }, 47 }), 48 } 49 } 50 51 // GetRegexp consults the cache for the regular expressions stored for 52 // the given key, compiling the key's pattern if it is not already 53 // in the cache. 54 func (rc *RegexpCache) GetRegexp(key RegexpCacheKey) (*regexp.Regexp, error) { 55 if rc != nil { 56 re := rc.lookup(key) 57 if re != nil { 58 return re, nil 59 } 60 } 61 62 pattern, err := key.Pattern() 63 if err != nil { 64 return nil, err 65 } 66 67 re, err := regexp.Compile(pattern) 68 if err != nil { 69 return nil, err 70 } 71 72 if rc != nil { 73 rc.update(key, re) 74 } 75 return re, nil 76 } 77 78 // lookup checks for the regular expression in the cache in a 79 // synchronized manner, returning it if it exists. 80 func (rc *RegexpCache) lookup(key RegexpCacheKey) *regexp.Regexp { 81 rc.mu.Lock() 82 defer rc.mu.Unlock() 83 v, ok := rc.cache.Get(key) 84 if !ok { 85 return nil 86 } 87 return v.(*regexp.Regexp) 88 } 89 90 // update invalidates the regular expression for the given pattern. 91 // If a new regular expression is passed in, it is inserted into the cache. 92 func (rc *RegexpCache) update(key RegexpCacheKey, re *regexp.Regexp) { 93 rc.mu.Lock() 94 defer rc.mu.Unlock() 95 rc.cache.Del(key) 96 if re != nil { 97 rc.cache.Add(key, re) 98 } 99 } 100 101 // Len returns the number of compiled regular expressions in the cache. 102 func (rc *RegexpCache) Len() int { 103 if rc == nil { 104 return 0 105 } 106 rc.mu.Lock() 107 defer rc.mu.Unlock() 108 return rc.cache.Len() 109 }