1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-03-20 06:21:06 +02: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 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"_). (_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 ## v0.23.12

View File

@ -111,7 +111,7 @@ func checkCollectionRateLimit(e *core.RequestEvent, collection *core.Collection,
// //
//nolint:unused //nolint:unused
func isClientRateLimited(e *core.RequestEvent, rtId string) bool { 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 { if !ok || rateLimiters == nil {
return false return false
} }
@ -146,7 +146,7 @@ func checkRateLimit(e *core.RequestEvent, rtId string, rule core.RateLimitRule)
rateLimiters := e.App.Store().GetOrSet(rateLimitersStoreKey, func() any { rateLimiters := e.App.Store().GetOrSet(rateLimitersStoreKey, func() any {
return initRateLimitersStore(e.App) return initRateLimitersStore(e.App)
}).(*store.Store[*rateLimiter]) }).(*store.Store[string, *rateLimiter])
if rateLimiters == nil { if rateLimiters == nil {
e.App.Logger().Warn("Failed to retrieve app rate limiters store") e.App.Logger().Warn("Failed to retrieve app rate limiters store")
return nil return nil
@ -198,9 +198,9 @@ func destroyRateLimitersStore(app core.App) {
app.Store().Remove(rateLimitersStoreKey) 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 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 { if !ok {
return 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 { func newRateLimiter(maxAllowed int, intervalInSec int64, minDeleteIntervalInSec int64) *rateLimiter {

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import (
"github.com/spf13/cast" "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). // getColor returns [color.Color] object and cache it (if not already).
func getColor(attrs ...color.Attribute) (c *color.Color) { func getColor(attrs ...color.Attribute) (c *color.Color) {

View File

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

View File

@ -9,7 +9,7 @@ import (
"github.com/spf13/cast" "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 // SubtractSlice returns a new slice with only the "base" elements
// that don't exist in "subtract". // that don't exist in "subtract".

View File

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

View File

@ -9,15 +9,15 @@ import (
const ShrinkThreshold = 200 // the number is arbitrary chosen const ShrinkThreshold = 200 // the number is arbitrary chosen
// Store defines a concurrent safe in memory key-value data store. // Store defines a concurrent safe in memory key-value data store.
type Store[T any] struct { type Store[K comparable, T any] struct {
data map[string]T data map[K]T
mu sync.RWMutex mu sync.RWMutex
deleted int64 deleted int64
} }
// New creates a new Store[T] instance with a shallow copy of the provided data (if any). // 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] { func New[K comparable, T any](data map[K]T) *Store[K, T] {
s := &Store[T]{} s := &Store[K, T]{}
s.Reset(data) 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 // Reset clears the store and replaces the store data with a
// shallow copy of the provided newData. // 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() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if len(newData) > 0 { 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 { for k, v := range newData {
s.data[k] = v s.data[k] = v
} }
} else { } else {
s.data = make(map[string]T) s.data = make(map[K]T)
} }
s.deleted = 0 s.deleted = 0
} }
// Length returns the current number of elements in the store. // 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() s.mu.RLock()
defer s.mu.RUnlock() defer s.mu.RUnlock()
@ -51,14 +51,14 @@ func (s *Store[T]) Length() int {
} }
// RemoveAll removes all the existing store entries. // RemoveAll removes all the existing store entries.
func (s *Store[T]) RemoveAll() { func (s *Store[K, T]) RemoveAll() {
s.Reset(nil) s.Reset(nil)
} }
// Remove removes a single entry from the store. // Remove removes a single entry from the store.
// //
// Remove does nothing if key doesn't exist in 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() s.mu.Lock()
defer s.mu.Unlock() 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 // @todo remove after https://github.com/golang/go/issues/20135
if s.deleted >= ShrinkThreshold { 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 { for k, v := range s.data {
newData[k] = v 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. // 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() s.mu.RLock()
defer s.mu.RUnlock() 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. // Get returns a single element value from the store.
// //
// If key is not set, the zero T value is returned. // 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() s.mu.RLock()
defer s.mu.RUnlock() 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. // 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() s.mu.RLock()
defer s.mu.RUnlock() 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. // 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() s.mu.RLock()
defer s.mu.RUnlock() 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 { for k, v := range s.data {
clone[k] = v 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. // 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() s.mu.RLock()
defer s.mu.RUnlock() 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. // 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() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.data == nil { if s.data == nil {
s.data = make(map[string]T) s.data = make(map[K]T)
} }
s.data[key] = value 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 // GetOrSet retrieves a single existing value for the provided key
// or stores a new one if it doesn't exist. // 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 // lock only reads to minimize locks contention
s.mu.RLock() s.mu.RLock()
v, ok := s.data[key] v, ok := s.data[key]
@ -160,7 +160,7 @@ func (s *Store[T]) GetOrSet(key string, setFunc func() T) T {
s.mu.Lock() s.mu.Lock()
v = setFunc() v = setFunc()
if s.data == nil { if s.data == nil {
s.data = make(map[string]T) s.data = make(map[K]T)
} }
s.data[key] = v s.data[key] = v
s.mu.Unlock() 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** // 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. // to the store if the store length has reached the specified limit.
// false is returned if maxAllowedElements limit is reached. // 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() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.data == nil { if s.data == nil {
s.data = make(map[string]T) s.data = make(map[K]T)
} }
// check for existing item // 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. // provided JSON data into the store.
// //
// The store entries that match with the ones from the data will be overwritten with the new value. // 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 { func (s *Store[K, T]) UnmarshalJSON(data []byte) error {
raw := map[string]T{} raw := map[K]T{}
if err := json.Unmarshal(data, &raw); err != nil { if err := json.Unmarshal(data, &raw); err != nil {
return err return err
} }
@ -210,7 +210,7 @@ func (s *Store[T]) UnmarshalJSON(data []byte) error {
defer s.mu.Unlock() defer s.mu.Unlock()
if s.data == nil { if s.data == nil {
s.data = make(map[string]T) s.data = make(map[K]T)
} }
for k, v := range raw { 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 // MarshalJSON implements [json.Marshaler] and export the current
// store data into valid JSON. // 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()) return json.Marshal(s.GetAll())
} }

View File

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

View File

@ -9,13 +9,13 @@ import (
// Broker defines a struct for managing subscriptions clients. // Broker defines a struct for managing subscriptions clients.
type Broker struct { type Broker struct {
store *store.Store[Client] store *store.Store[string, Client]
} }
// NewBroker initializes and returns a new Broker instance. // NewBroker initializes and returns a new Broker instance.
func NewBroker() *Broker { func NewBroker() *Broker {
return &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. // Use the Registry.Load* methods to load templates into the registry.
func NewRegistry() *Registry { func NewRegistry() *Registry {
return &Registry{ return &Registry{
cache: store.New[*Renderer](nil), cache: store.New[string, *Renderer](nil),
funcs: template.FuncMap{ funcs: template.FuncMap{
"raw": func(str string) template.HTML { "raw": func(str string) template.HTML {
return template.HTML(str) return template.HTML(str)
@ -50,7 +50,7 @@ func NewRegistry() *Registry {
// //
// Use the Registry.Load* methods to load templates into the registry. // Use the Registry.Load* methods to load templates into the registry.
type Registry struct { type Registry struct {
cache *store.Store[*Renderer] cache *store.Store[string, *Renderer]
funcs template.FuncMap funcs template.FuncMap
} }