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:
parent
e18116d859
commit
39df26ee21
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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".
|
||||
|
@ -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"
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user