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  }