1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-02-06 10:44:43 +00:00

changed store.Store to accept generic key type

This commit is contained in:
Gani Georgiev 2024-12-23 15:44:00 +02:00
parent e18116d859
commit 39df26ee21
12 changed files with 57 additions and 54 deletions

View File

@ -18,6 +18,9 @@
With this change the "multi-match" operators are also normalized in case the targetted colletion doesn't have any records
(_or in other words, `@collection.example.someField != "test"` will result to `true` if `example` collection has no records because it satisfies the condition that all available "example" records mustn't have `someField` equal to "test"_).
- ⚠️ Changed the type definition of `store.Store[T any]` to `store.Store[K comparable, T any]` to allow support for custom store key types.
For most users it should be non-breaking change, BUT if you are creating manually `store.New[any](nil)` instances you'll have to specify the key generic type, aka. `store.New[string, any](nil)`.
## v0.23.12

View File

@ -111,7 +111,7 @@ func checkCollectionRateLimit(e *core.RequestEvent, collection *core.Collection,
//
//nolint:unused
func isClientRateLimited(e *core.RequestEvent, rtId string) bool {
rateLimiters, ok := e.App.Store().Get(rateLimitersStoreKey).(*store.Store[*rateLimiter])
rateLimiters, ok := e.App.Store().Get(rateLimitersStoreKey).(*store.Store[string, *rateLimiter])
if !ok || rateLimiters == nil {
return false
}
@ -146,7 +146,7 @@ func checkRateLimit(e *core.RequestEvent, rtId string, rule core.RateLimitRule)
rateLimiters := e.App.Store().GetOrSet(rateLimitersStoreKey, func() any {
return initRateLimitersStore(e.App)
}).(*store.Store[*rateLimiter])
}).(*store.Store[string, *rateLimiter])
if rateLimiters == nil {
e.App.Logger().Warn("Failed to retrieve app rate limiters store")
return nil
@ -198,9 +198,9 @@ func destroyRateLimitersStore(app core.App) {
app.Store().Remove(rateLimitersStoreKey)
}
func initRateLimitersStore(app core.App) *store.Store[*rateLimiter] {
func initRateLimitersStore(app core.App) *store.Store[string, *rateLimiter] {
app.Cron().Add(rateLimitersCronKey, "2 * * * *", func() { // offset a little since too many cleanup tasks execute at 00
limitersStore, ok := app.Store().Get(rateLimitersStoreKey).(*store.Store[*rateLimiter])
limitersStore, ok := app.Store().Get(rateLimitersStoreKey).(*store.Store[string, *rateLimiter])
if !ok {
return
}
@ -225,7 +225,7 @@ func initRateLimitersStore(app core.App) *store.Store[*rateLimiter] {
},
})
return store.New[*rateLimiter](nil)
return store.New[string, *rateLimiter](nil)
}
func newRateLimiter(maxAllowed int, intervalInSec int64, minDeleteIntervalInSec int64) *rateLimiter {

View File

@ -71,7 +71,7 @@ type App interface {
Settings() *Settings
// Store returns the app runtime store.
Store() *store.Store[any]
Store() *store.Store[string, any]
// Cron returns the app cron instance.
Cron() *cron.Cron

View File

@ -70,7 +70,7 @@ var _ App = (*BaseApp)(nil)
type BaseApp struct {
config *BaseAppConfig
txInfo *txAppInfo
store *store.Store[any]
store *store.Store[string, any]
cron *cron.Cron
settings *Settings
subscriptionsBroker *subscriptions.Broker
@ -194,7 +194,7 @@ type BaseApp struct {
func NewBaseApp(config BaseAppConfig) *BaseApp {
app := &BaseApp{
settings: newDefaultSettings(),
store: store.New[any](nil),
store: store.New[string, any](nil),
cron: cron.New(),
subscriptionsBroker: subscriptions.NewBroker(),
config: &config,
@ -532,7 +532,7 @@ func (app *BaseApp) Settings() *Settings {
}
// Store returns the app runtime store.
func (app *BaseApp) Store() *store.Store[any] {
func (app *BaseApp) Store() *store.Store[string, any] {
return app.store
}

View File

@ -11,7 +11,7 @@ import (
"github.com/spf13/cast"
)
var cachedColors = store.New[*color.Color](nil)
var cachedColors = store.New[string, *color.Color](nil)
// getColor returns [color.Color] object and cache it (if not already).
func getColor(attrs ...color.Attribute) (c *color.Color) {

View File

@ -37,9 +37,9 @@ var (
type Record struct {
collection *Collection
originalData map[string]any
customVisibility *store.Store[bool]
data *store.Store[any]
expand *store.Store[any]
customVisibility *store.Store[string, bool]
data *store.Store[string, any]
expand *store.Store[string, any]
BaseModel
@ -537,8 +537,8 @@ func newRecordsFromNullStringMaps(collection *Collection, rows []dbx.NullStringM
func NewRecord(collection *Collection) *Record {
record := &Record{
collection: collection,
data: store.New[any](nil),
customVisibility: store.New[bool](nil),
data: store.New[string, any](nil),
customVisibility: store.New[string, bool](nil),
originalData: make(map[string]any, len(collection.Fields)),
}
@ -681,7 +681,7 @@ func (m *Record) Expand() map[string]any {
// SetExpand replaces the current Record's expand with the provided expand arg data (shallow copied).
func (m *Record) SetExpand(expand map[string]any) {
if m.expand == nil {
m.expand = store.New[any](nil)
m.expand = store.New[string, any](nil)
}
m.expand.Reset(expand)

View File

@ -9,7 +9,7 @@ import (
"github.com/spf13/cast"
)
var cachedPatterns = store.New[*regexp.Regexp](nil)
var cachedPatterns = store.New[string, *regexp.Regexp](nil)
// SubtractSlice returns a new slice with only the "base" elements
// that don't exist in "subtract".

View File

@ -34,7 +34,7 @@ type Event struct {
hook.Event
data store.Store[any]
data store.Store[string, any]
}
// RWUnwrapper specifies that an http.ResponseWriter could be "unwrapped"

View File

@ -9,15 +9,15 @@ import (
const ShrinkThreshold = 200 // the number is arbitrary chosen
// Store defines a concurrent safe in memory key-value data store.
type Store[T any] struct {
data map[string]T
type Store[K comparable, T any] struct {
data map[K]T
mu sync.RWMutex
deleted int64
}
// New creates a new Store[T] instance with a shallow copy of the provided data (if any).
func New[T any](data map[string]T) *Store[T] {
s := &Store[T]{}
func New[K comparable, T any](data map[K]T) *Store[K, T] {
s := &Store[K, T]{}
s.Reset(data)
@ -26,24 +26,24 @@ func New[T any](data map[string]T) *Store[T] {
// Reset clears the store and replaces the store data with a
// shallow copy of the provided newData.
func (s *Store[T]) Reset(newData map[string]T) {
func (s *Store[K, T]) Reset(newData map[K]T) {
s.mu.Lock()
defer s.mu.Unlock()
if len(newData) > 0 {
s.data = make(map[string]T, len(newData))
s.data = make(map[K]T, len(newData))
for k, v := range newData {
s.data[k] = v
}
} else {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
s.deleted = 0
}
// Length returns the current number of elements in the store.
func (s *Store[T]) Length() int {
func (s *Store[K, T]) Length() int {
s.mu.RLock()
defer s.mu.RUnlock()
@ -51,14 +51,14 @@ func (s *Store[T]) Length() int {
}
// RemoveAll removes all the existing store entries.
func (s *Store[T]) RemoveAll() {
func (s *Store[K, T]) RemoveAll() {
s.Reset(nil)
}
// Remove removes a single entry from the store.
//
// Remove does nothing if key doesn't exist in the store.
func (s *Store[T]) Remove(key string) {
func (s *Store[K, T]) Remove(key K) {
s.mu.Lock()
defer s.mu.Unlock()
@ -69,7 +69,7 @@ func (s *Store[T]) Remove(key string) {
//
// @todo remove after https://github.com/golang/go/issues/20135
if s.deleted >= ShrinkThreshold {
newData := make(map[string]T, len(s.data))
newData := make(map[K]T, len(s.data))
for k, v := range s.data {
newData[k] = v
}
@ -79,7 +79,7 @@ func (s *Store[T]) Remove(key string) {
}
// Has checks if element with the specified key exist or not.
func (s *Store[T]) Has(key string) bool {
func (s *Store[K, T]) Has(key K) bool {
s.mu.RLock()
defer s.mu.RUnlock()
@ -91,7 +91,7 @@ func (s *Store[T]) Has(key string) bool {
// Get returns a single element value from the store.
//
// If key is not set, the zero T value is returned.
func (s *Store[T]) Get(key string) T {
func (s *Store[K, T]) Get(key K) T {
s.mu.RLock()
defer s.mu.RUnlock()
@ -99,7 +99,7 @@ func (s *Store[T]) Get(key string) T {
}
// GetOk is similar to Get but returns also a boolean indicating whether the key exists or not.
func (s *Store[T]) GetOk(key string) (T, bool) {
func (s *Store[K, T]) GetOk(key K) (T, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
@ -109,11 +109,11 @@ func (s *Store[T]) GetOk(key string) (T, bool) {
}
// GetAll returns a shallow copy of the current store data.
func (s *Store[T]) GetAll() map[string]T {
func (s *Store[K, T]) GetAll() map[K]T {
s.mu.RLock()
defer s.mu.RUnlock()
var clone = make(map[string]T, len(s.data))
var clone = make(map[K]T, len(s.data))
for k, v := range s.data {
clone[k] = v
@ -123,7 +123,7 @@ func (s *Store[T]) GetAll() map[string]T {
}
// Values returns a slice with all of the current store values.
func (s *Store[T]) Values() []T {
func (s *Store[K, T]) Values() []T {
s.mu.RLock()
defer s.mu.RUnlock()
@ -137,12 +137,12 @@ func (s *Store[T]) Values() []T {
}
// Set sets (or overwrite if already exist) a new value for key.
func (s *Store[T]) Set(key string, value T) {
func (s *Store[K, T]) Set(key K, value T) {
s.mu.Lock()
defer s.mu.Unlock()
if s.data == nil {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
s.data[key] = value
@ -150,7 +150,7 @@ func (s *Store[T]) Set(key string, value T) {
// GetOrSet retrieves a single existing value for the provided key
// or stores a new one if it doesn't exist.
func (s *Store[T]) GetOrSet(key string, setFunc func() T) T {
func (s *Store[K, T]) GetOrSet(key K, setFunc func() T) T {
// lock only reads to minimize locks contention
s.mu.RLock()
v, ok := s.data[key]
@ -160,7 +160,7 @@ func (s *Store[T]) GetOrSet(key string, setFunc func() T) T {
s.mu.Lock()
v = setFunc()
if s.data == nil {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
s.data[key] = v
s.mu.Unlock()
@ -174,12 +174,12 @@ func (s *Store[T]) GetOrSet(key string, setFunc func() T) T {
// This method is similar to Set() but **it will skip adding new elements**
// to the store if the store length has reached the specified limit.
// false is returned if maxAllowedElements limit is reached.
func (s *Store[T]) SetIfLessThanLimit(key string, value T, maxAllowedElements int) bool {
func (s *Store[K, T]) SetIfLessThanLimit(key K, value T, maxAllowedElements int) bool {
s.mu.Lock()
defer s.mu.Unlock()
if s.data == nil {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
// check for existing item
@ -200,8 +200,8 @@ func (s *Store[T]) SetIfLessThanLimit(key string, value T, maxAllowedElements in
// provided JSON data into the store.
//
// The store entries that match with the ones from the data will be overwritten with the new value.
func (s *Store[T]) UnmarshalJSON(data []byte) error {
raw := map[string]T{}
func (s *Store[K, T]) UnmarshalJSON(data []byte) error {
raw := map[K]T{}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
@ -210,7 +210,7 @@ func (s *Store[T]) UnmarshalJSON(data []byte) error {
defer s.mu.Unlock()
if s.data == nil {
s.data = make(map[string]T)
s.data = make(map[K]T)
}
for k, v := range raw {
@ -222,6 +222,6 @@ func (s *Store[T]) UnmarshalJSON(data []byte) error {
// MarshalJSON implements [json.Marshaler] and export the current
// store data into valid JSON.
func (s *Store[T]) MarshalJSON() ([]byte, error) {
func (s *Store[K, T]) MarshalJSON() ([]byte, error) {
return json.Marshal(s.GetAll())
}

View File

@ -227,7 +227,7 @@ func TestValues(t *testing.T) {
}
func TestSet(t *testing.T) {
s := store.Store[int]{}
s := store.Store[string, int]{}
data := map[string]int{"test1": 0, "test2": 1, "test3": 3}
@ -281,7 +281,7 @@ func TestGetOrSet(t *testing.T) {
}
func TestSetIfLessThanLimit(t *testing.T) {
s := store.Store[int]{}
s := store.Store[string, int]{}
limit := 2
@ -316,7 +316,7 @@ func TestSetIfLessThanLimit(t *testing.T) {
}
func TestUnmarshalJSON(t *testing.T) {
s := store.Store[string]{}
s := store.Store[string, string]{}
s.Set("b", "old") // should be overwritten
s.Set("c", "test3") // ensures that the old values are not removed
@ -339,7 +339,7 @@ func TestUnmarshalJSON(t *testing.T) {
}
func TestMarshalJSON(t *testing.T) {
s := &store.Store[string]{}
s := &store.Store[string, string]{}
s.Set("a", "test1")
s.Set("b", "test2")
@ -356,7 +356,7 @@ func TestMarshalJSON(t *testing.T) {
}
func TestShrink(t *testing.T) {
s := &store.Store[int]{}
s := &store.Store[string, int]{}
total := 1000

View File

@ -9,13 +9,13 @@ import (
// Broker defines a struct for managing subscriptions clients.
type Broker struct {
store *store.Store[Client]
store *store.Store[string, Client]
}
// NewBroker initializes and returns a new Broker instance.
func NewBroker() *Broker {
return &Broker{
store: store.New[Client](nil),
store: store.New[string, Client](nil),
}
}

View File

@ -37,7 +37,7 @@ import (
// Use the Registry.Load* methods to load templates into the registry.
func NewRegistry() *Registry {
return &Registry{
cache: store.New[*Renderer](nil),
cache: store.New[string, *Renderer](nil),
funcs: template.FuncMap{
"raw": func(str string) template.HTML {
return template.HTML(str)
@ -50,7 +50,7 @@ func NewRegistry() *Registry {
//
// Use the Registry.Load* methods to load templates into the registry.
type Registry struct {
cache *store.Store[*Renderer]
cache *store.Store[string, *Renderer]
funcs template.FuncMap
}