go-store/scope.go
Virgil dfbdace985
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
feat: add scoped store transactions
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-04 16:09:14 +00:00

640 lines
27 KiB
Go

package store
import (
"iter"
"regexp"
"time"
core "dappco.re/go/core"
)
// validNamespace.MatchString("tenant-a") is true; validNamespace.MatchString("tenant_a") is false.
var validNamespace = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
const defaultScopedGroupName = "default"
// QuotaConfig sets per-namespace key and group limits.
// Usage example: `quota := store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}`
type QuotaConfig struct {
// Usage example: `store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}` limits a namespace to 100 keys.
MaxKeys int
// Usage example: `store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}` limits a namespace to 10 groups.
MaxGroups int
}
// Usage example: `if err := (store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}).Validate(); err != nil { return }`
func (quotaConfig QuotaConfig) Validate() error {
if quotaConfig.MaxKeys < 0 || quotaConfig.MaxGroups < 0 {
return core.E(
"store.QuotaConfig.Validate",
core.Sprintf("quota values must be zero or positive; got MaxKeys=%d MaxGroups=%d", quotaConfig.MaxKeys, quotaConfig.MaxGroups),
nil,
)
}
return nil
}
// ScopedStoreConfig combines namespace selection with optional quota limits.
// Usage example: `config := store.ScopedStoreConfig{Namespace: "tenant-a", Quota: store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}}`
type ScopedStoreConfig struct {
// Usage example: `config := store.ScopedStoreConfig{Namespace: "tenant-a"}`
Namespace string
// Usage example: `config := store.ScopedStoreConfig{Quota: store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}}`
Quota QuotaConfig
}
// Usage example: `if err := (store.ScopedStoreConfig{Namespace: "tenant-a", Quota: store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}}).Validate(); err != nil { return }`
func (scopedConfig ScopedStoreConfig) Validate() error {
if !validNamespace.MatchString(scopedConfig.Namespace) {
return core.E(
"store.ScopedStoreConfig.Validate",
core.Sprintf("namespace %q is invalid; use names like %q or %q", scopedConfig.Namespace, "tenant-a", "tenant-42"),
nil,
)
}
if err := scopedConfig.Quota.Validate(); err != nil {
return core.E("store.ScopedStoreConfig.Validate", "quota", err)
}
return nil
}
// ScopedStore prefixes group names with namespace + ":" before delegating to Store.
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }; if err := scopedStore.Set("config", "colour", "blue"); err != nil { return }`
type ScopedStore struct {
storeInstance *Store
namespace string
// Usage example: `scopedStore.MaxKeys = 100`
MaxKeys int
// Usage example: `scopedStore.MaxGroups = 10`
MaxGroups int
}
// NewScoped validates a namespace and prefixes groups with namespace + ":".
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }`
func NewScoped(storeInstance *Store, namespace string) (*ScopedStore, error) {
if storeInstance == nil {
return nil, core.E("store.NewScoped", "store instance is nil", nil)
}
if !validNamespace.MatchString(namespace) {
return nil, core.E("store.NewScoped", core.Sprintf("namespace %q is invalid; use names like %q or %q", namespace, "tenant-a", "tenant-42"), nil)
}
scopedStore := &ScopedStore{storeInstance: storeInstance, namespace: namespace}
return scopedStore, nil
}
// NewScopedConfigured validates the namespace and optional quota settings before constructing a ScopedStore.
// Usage example: `scopedStore, err := store.NewScopedConfigured(storeInstance, store.ScopedStoreConfig{Namespace: "tenant-a", Quota: store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}}); if err != nil { return }`
func NewScopedConfigured(storeInstance *Store, scopedConfig ScopedStoreConfig) (*ScopedStore, error) {
if storeInstance == nil {
return nil, core.E("store.NewScopedConfigured", "store instance is nil", nil)
}
if err := scopedConfig.Validate(); err != nil {
return nil, core.E("store.NewScopedConfigured", "validate config", err)
}
scopedStore, err := NewScoped(storeInstance, scopedConfig.Namespace)
if err != nil {
return nil, err
}
scopedStore.MaxKeys = scopedConfig.Quota.MaxKeys
scopedStore.MaxGroups = scopedConfig.Quota.MaxGroups
return scopedStore, nil
}
// NewScopedWithQuota adds per-namespace key and group limits.
// Usage example: `scopedStore, err := store.NewScopedWithQuota(storeInstance, "tenant-a", store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}); if err != nil { return }`
func NewScopedWithQuota(storeInstance *Store, namespace string, quota QuotaConfig) (*ScopedStore, error) {
return NewScopedConfigured(storeInstance, ScopedStoreConfig{
Namespace: namespace,
Quota: quota,
})
}
func (scopedStore *ScopedStore) namespacedGroup(group string) string {
return scopedStore.namespace + ":" + group
}
func (scopedStore *ScopedStore) namespacePrefix() string {
return scopedStore.namespace + ":"
}
func (scopedStore *ScopedStore) defaultGroup() string {
return defaultScopedGroupName
}
func (scopedStore *ScopedStore) trimNamespacePrefix(groupName string) string {
return core.TrimPrefix(groupName, scopedStore.namespacePrefix())
}
// Namespace returns the namespace string.
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }; namespace := scopedStore.Namespace(); fmt.Println(namespace)`
func (scopedStore *ScopedStore) Namespace() string {
return scopedStore.namespace
}
// Usage example: `colourValue, err := scopedStore.Get("colour")`
// Usage example: `colourValue, err := scopedStore.Get("config", "colour")`
func (scopedStore *ScopedStore) Get(arguments ...string) (string, error) {
group, key, err := scopedStore.getArguments(arguments)
if err != nil {
return "", err
}
return scopedStore.storeInstance.Get(scopedStore.namespacedGroup(group), key)
}
// GetFrom reads a key from an explicit namespaced group.
// Usage example: `colourValue, err := scopedStore.GetFrom("config", "colour")`
func (scopedStore *ScopedStore) GetFrom(group, key string) (string, error) {
return scopedStore.Get(group, key)
}
// Usage example: `if err := scopedStore.Set("colour", "blue"); err != nil { return }`
// Usage example: `if err := scopedStore.Set("config", "colour", "blue"); err != nil { return }`
func (scopedStore *ScopedStore) Set(arguments ...string) error {
group, key, value, err := scopedStore.setArguments(arguments)
if err != nil {
return err
}
if err := scopedStore.checkQuota("store.ScopedStore.Set", group, key); err != nil {
return err
}
return scopedStore.storeInstance.Set(scopedStore.namespacedGroup(group), key, value)
}
// SetIn writes a key to an explicit namespaced group.
// Usage example: `if err := scopedStore.SetIn("config", "colour", "blue"); err != nil { return }`
func (scopedStore *ScopedStore) SetIn(group, key, value string) error {
return scopedStore.Set(group, key, value)
}
// Usage example: `if err := scopedStore.SetWithTTL("sessions", "token", "abc123", time.Hour); err != nil { return }`
func (scopedStore *ScopedStore) SetWithTTL(group, key, value string, timeToLive time.Duration) error {
if err := scopedStore.checkQuota("store.ScopedStore.SetWithTTL", group, key); err != nil {
return err
}
return scopedStore.storeInstance.SetWithTTL(scopedStore.namespacedGroup(group), key, value, timeToLive)
}
// Usage example: `if err := scopedStore.Delete("config", "colour"); err != nil { return }`
func (scopedStore *ScopedStore) Delete(group, key string) error {
return scopedStore.storeInstance.Delete(scopedStore.namespacedGroup(group), key)
}
// Usage example: `if err := scopedStore.DeleteGroup("cache"); err != nil { return }`
func (scopedStore *ScopedStore) DeleteGroup(group string) error {
return scopedStore.storeInstance.DeleteGroup(scopedStore.namespacedGroup(group))
}
// Usage example: `if err := scopedStore.DeletePrefix("cache"); err != nil { return }`
// Usage example: `if err := scopedStore.DeletePrefix(""); err != nil { return }`
func (scopedStore *ScopedStore) DeletePrefix(groupPrefix string) error {
return scopedStore.storeInstance.DeletePrefix(scopedStore.namespacedGroup(groupPrefix))
}
// Usage example: `colourEntries, err := scopedStore.GetAll("config")`
func (scopedStore *ScopedStore) GetAll(group string) (map[string]string, error) {
return scopedStore.storeInstance.GetAll(scopedStore.namespacedGroup(group))
}
// Usage example: `for entry, err := range scopedStore.All("config") { if err != nil { break }; fmt.Println(entry.Key, entry.Value) }`
func (scopedStore *ScopedStore) All(group string) iter.Seq2[KeyValue, error] {
return scopedStore.storeInstance.All(scopedStore.namespacedGroup(group))
}
// Usage example: `for entry, err := range scopedStore.AllSeq("config") { if err != nil { break }; fmt.Println(entry.Key, entry.Value) }`
func (scopedStore *ScopedStore) AllSeq(group string) iter.Seq2[KeyValue, error] {
return scopedStore.All(group)
}
// Usage example: `keyCount, err := scopedStore.Count("config")`
func (scopedStore *ScopedStore) Count(group string) (int, error) {
return scopedStore.storeInstance.Count(scopedStore.namespacedGroup(group))
}
// Usage example: `keyCount, err := scopedStore.CountAll("config")`
// Usage example: `keyCount, err := scopedStore.CountAll()`
func (scopedStore *ScopedStore) CountAll(groupPrefix ...string) (int, error) {
return scopedStore.storeInstance.CountAll(scopedStore.namespacedGroup(firstString(groupPrefix)))
}
// Usage example: `groupNames, err := scopedStore.Groups("config")`
// Usage example: `groupNames, err := scopedStore.Groups()`
func (scopedStore *ScopedStore) Groups(groupPrefix ...string) ([]string, error) {
groupNames, err := scopedStore.storeInstance.Groups(scopedStore.namespacedGroup(firstString(groupPrefix)))
if err != nil {
return nil, err
}
for i, groupName := range groupNames {
groupNames[i] = scopedStore.trimNamespacePrefix(groupName)
}
return groupNames, nil
}
// Usage example: `for groupName, err := range scopedStore.GroupsSeq("config") { if err != nil { break }; fmt.Println(groupName) }`
// Usage example: `for groupName, err := range scopedStore.GroupsSeq() { if err != nil { break }; fmt.Println(groupName) }`
func (scopedStore *ScopedStore) GroupsSeq(groupPrefix ...string) iter.Seq2[string, error] {
return func(yield func(string, error) bool) {
namespacePrefix := scopedStore.namespacePrefix()
for groupName, err := range scopedStore.storeInstance.GroupsSeq(scopedStore.namespacedGroup(firstString(groupPrefix))) {
if err != nil {
if !yield("", err) {
return
}
continue
}
if !yield(core.TrimPrefix(groupName, namespacePrefix), nil) {
return
}
}
}
}
// Usage example: `renderedTemplate, err := scopedStore.Render("Hello {{ .name }}", "user")`
func (scopedStore *ScopedStore) Render(templateSource, group string) (string, error) {
return scopedStore.storeInstance.Render(templateSource, scopedStore.namespacedGroup(group))
}
// Usage example: `parts, err := scopedStore.GetSplit("config", "hosts", ","); if err != nil { return }; for part := range parts { fmt.Println(part) }`
func (scopedStore *ScopedStore) GetSplit(group, key, separator string) (iter.Seq[string], error) {
return scopedStore.storeInstance.GetSplit(scopedStore.namespacedGroup(group), key, separator)
}
// Usage example: `fields, err := scopedStore.GetFields("config", "flags"); if err != nil { return }; for field := range fields { fmt.Println(field) }`
func (scopedStore *ScopedStore) GetFields(group, key string) (iter.Seq[string], error) {
return scopedStore.storeInstance.GetFields(scopedStore.namespacedGroup(group), key)
}
// Usage example: `removedRows, err := scopedStore.PurgeExpired(); if err != nil { return }; fmt.Println(removedRows)`
func (scopedStore *ScopedStore) PurgeExpired() (int64, error) {
removedRows, err := scopedStore.storeInstance.purgeExpiredMatchingGroupPrefix(scopedStore.namespacePrefix())
if err != nil {
return 0, core.E("store.ScopedStore.PurgeExpired", "delete expired rows", err)
}
return removedRows, nil
}
// ScopedStoreTransaction exposes namespace-local transaction helpers so callers
// can work inside a scoped namespace without manually prefixing group names.
//
// Usage example: `err := scopedStore.Transaction(func(transaction *store.ScopedStoreTransaction) error { return transaction.Set("theme", "dark") })`
type ScopedStoreTransaction struct {
scopedStore *ScopedStore
storeTransaction *StoreTransaction
}
// Usage example: `err := scopedStore.Transaction(func(transaction *store.ScopedStoreTransaction) error { return transaction.Set("theme", "dark") })`
func (scopedStore *ScopedStore) Transaction(operation func(*ScopedStoreTransaction) error) error {
if scopedStore == nil {
return core.E("store.ScopedStore.Transaction", "scoped store is nil", nil)
}
if operation == nil {
return core.E("store.ScopedStore.Transaction", "operation is nil", nil)
}
return scopedStore.storeInstance.Transaction(func(storeTransaction *StoreTransaction) error {
return operation(&ScopedStoreTransaction{
scopedStore: scopedStore,
storeTransaction: storeTransaction,
})
})
}
func (scopedStoreTransaction *ScopedStoreTransaction) ensureReady(operation string) error {
if scopedStoreTransaction == nil {
return core.E(operation, "scoped transaction is nil", nil)
}
if scopedStoreTransaction.scopedStore == nil {
return core.E(operation, "scoped transaction store is nil", nil)
}
if scopedStoreTransaction.storeTransaction == nil {
return core.E(operation, "scoped transaction database is nil", nil)
}
if err := scopedStoreTransaction.scopedStore.storeInstance.ensureReady(operation); err != nil {
return err
}
return scopedStoreTransaction.storeTransaction.ensureReady(operation)
}
// Usage example: `colourValue, err := scopedStoreTransaction.Get("colour")`
// Usage example: `colourValue, err := scopedStoreTransaction.Get("config", "colour")`
func (scopedStoreTransaction *ScopedStoreTransaction) Get(arguments ...string) (string, error) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.Get"); err != nil {
return "", err
}
group, key, err := scopedStoreTransaction.scopedStore.getArguments(arguments)
if err != nil {
return "", core.E("store.ScopedStoreTransaction.Get", "arguments", err)
}
return scopedStoreTransaction.storeTransaction.Get(scopedStoreTransaction.scopedStore.namespacedGroup(group), key)
}
// Usage example: `colourValue, err := scopedStoreTransaction.GetFrom("config", "colour")`
func (scopedStoreTransaction *ScopedStoreTransaction) GetFrom(group, key string) (string, error) {
return scopedStoreTransaction.Get(group, key)
}
// Usage example: `if err := scopedStoreTransaction.Set("theme", "dark"); err != nil { return err }`
// Usage example: `if err := scopedStoreTransaction.Set("config", "colour", "blue"); err != nil { return err }`
func (scopedStoreTransaction *ScopedStoreTransaction) Set(arguments ...string) error {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.Set"); err != nil {
return err
}
group, key, value, err := scopedStoreTransaction.scopedStore.setArguments(arguments)
if err != nil {
return core.E("store.ScopedStoreTransaction.Set", "arguments", err)
}
if err := scopedStoreTransaction.checkQuota("store.ScopedStoreTransaction.Set", group, key); err != nil {
return err
}
return scopedStoreTransaction.storeTransaction.Set(scopedStoreTransaction.scopedStore.namespacedGroup(group), key, value)
}
// Usage example: `if err := scopedStoreTransaction.SetIn("config", "colour", "blue"); err != nil { return err }`
func (scopedStoreTransaction *ScopedStoreTransaction) SetIn(group, key, value string) error {
return scopedStoreTransaction.Set(group, key, value)
}
// Usage example: `if err := scopedStoreTransaction.SetWithTTL("sessions", "token", "abc123", time.Hour); err != nil { return err }`
func (scopedStoreTransaction *ScopedStoreTransaction) SetWithTTL(group, key, value string, timeToLive time.Duration) error {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.SetWithTTL"); err != nil {
return err
}
if err := scopedStoreTransaction.checkQuota("store.ScopedStoreTransaction.SetWithTTL", group, key); err != nil {
return err
}
return scopedStoreTransaction.storeTransaction.SetWithTTL(scopedStoreTransaction.scopedStore.namespacedGroup(group), key, value, timeToLive)
}
// Usage example: `if err := scopedStoreTransaction.Delete("config", "colour"); err != nil { return err }`
func (scopedStoreTransaction *ScopedStoreTransaction) Delete(group, key string) error {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.Delete"); err != nil {
return err
}
return scopedStoreTransaction.storeTransaction.Delete(scopedStoreTransaction.scopedStore.namespacedGroup(group), key)
}
// Usage example: `if err := scopedStoreTransaction.DeleteGroup("cache"); err != nil { return err }`
func (scopedStoreTransaction *ScopedStoreTransaction) DeleteGroup(group string) error {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.DeleteGroup"); err != nil {
return err
}
return scopedStoreTransaction.storeTransaction.DeleteGroup(scopedStoreTransaction.scopedStore.namespacedGroup(group))
}
// Usage example: `if err := scopedStoreTransaction.DeletePrefix("cache"); err != nil { return err }`
// Usage example: `if err := scopedStoreTransaction.DeletePrefix(""); err != nil { return err }`
func (scopedStoreTransaction *ScopedStoreTransaction) DeletePrefix(groupPrefix string) error {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.DeletePrefix"); err != nil {
return err
}
return scopedStoreTransaction.storeTransaction.DeletePrefix(scopedStoreTransaction.scopedStore.namespacedGroup(groupPrefix))
}
// Usage example: `colourEntries, err := scopedStoreTransaction.GetAll("config")`
func (scopedStoreTransaction *ScopedStoreTransaction) GetAll(group string) (map[string]string, error) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.GetAll"); err != nil {
return nil, err
}
return scopedStoreTransaction.storeTransaction.GetAll(scopedStoreTransaction.scopedStore.namespacedGroup(group))
}
// Usage example: `for entry, err := range scopedStoreTransaction.All("config") { if err != nil { break }; fmt.Println(entry.Key, entry.Value) }`
func (scopedStoreTransaction *ScopedStoreTransaction) All(group string) iter.Seq2[KeyValue, error] {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.All"); err != nil {
return func(yield func(KeyValue, error) bool) {
yield(KeyValue{}, err)
}
}
return scopedStoreTransaction.storeTransaction.All(scopedStoreTransaction.scopedStore.namespacedGroup(group))
}
// Usage example: `for entry, err := range scopedStoreTransaction.AllSeq("config") { if err != nil { break }; fmt.Println(entry.Key, entry.Value) }`
func (scopedStoreTransaction *ScopedStoreTransaction) AllSeq(group string) iter.Seq2[KeyValue, error] {
return scopedStoreTransaction.All(group)
}
// Usage example: `keyCount, err := scopedStoreTransaction.Count("config")`
func (scopedStoreTransaction *ScopedStoreTransaction) Count(group string) (int, error) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.Count"); err != nil {
return 0, err
}
return scopedStoreTransaction.storeTransaction.Count(scopedStoreTransaction.scopedStore.namespacedGroup(group))
}
// Usage example: `keyCount, err := scopedStoreTransaction.CountAll("config")`
// Usage example: `keyCount, err := scopedStoreTransaction.CountAll()`
func (scopedStoreTransaction *ScopedStoreTransaction) CountAll(groupPrefix ...string) (int, error) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.CountAll"); err != nil {
return 0, err
}
return scopedStoreTransaction.storeTransaction.CountAll(scopedStoreTransaction.scopedStore.namespacedGroup(firstString(groupPrefix)))
}
// Usage example: `groupNames, err := scopedStoreTransaction.Groups("config")`
// Usage example: `groupNames, err := scopedStoreTransaction.Groups()`
func (scopedStoreTransaction *ScopedStoreTransaction) Groups(groupPrefix ...string) ([]string, error) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.Groups"); err != nil {
return nil, err
}
groupNames, err := scopedStoreTransaction.storeTransaction.Groups(scopedStoreTransaction.scopedStore.namespacedGroup(firstString(groupPrefix)))
if err != nil {
return nil, err
}
for i, groupName := range groupNames {
groupNames[i] = scopedStoreTransaction.scopedStore.trimNamespacePrefix(groupName)
}
return groupNames, nil
}
// Usage example: `for groupName, err := range scopedStoreTransaction.GroupsSeq("config") { if err != nil { break }; fmt.Println(groupName) }`
// Usage example: `for groupName, err := range scopedStoreTransaction.GroupsSeq() { if err != nil { break }; fmt.Println(groupName) }`
func (scopedStoreTransaction *ScopedStoreTransaction) GroupsSeq(groupPrefix ...string) iter.Seq2[string, error] {
return func(yield func(string, error) bool) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.GroupsSeq"); err != nil {
yield("", err)
return
}
namespacePrefix := scopedStoreTransaction.scopedStore.namespacePrefix()
for groupName, err := range scopedStoreTransaction.storeTransaction.GroupsSeq(scopedStoreTransaction.scopedStore.namespacedGroup(firstString(groupPrefix))) {
if err != nil {
if !yield("", err) {
return
}
continue
}
if !yield(core.TrimPrefix(groupName, namespacePrefix), nil) {
return
}
}
}
}
// Usage example: `renderedTemplate, err := scopedStoreTransaction.Render("Hello {{ .name }}", "user")`
func (scopedStoreTransaction *ScopedStoreTransaction) Render(templateSource, group string) (string, error) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.Render"); err != nil {
return "", err
}
return scopedStoreTransaction.storeTransaction.Render(templateSource, scopedStoreTransaction.scopedStore.namespacedGroup(group))
}
// Usage example: `parts, err := scopedStoreTransaction.GetSplit("config", "hosts", ","); if err != nil { return }; for part := range parts { fmt.Println(part) }`
func (scopedStoreTransaction *ScopedStoreTransaction) GetSplit(group, key, separator string) (iter.Seq[string], error) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.GetSplit"); err != nil {
return nil, err
}
return scopedStoreTransaction.storeTransaction.GetSplit(scopedStoreTransaction.scopedStore.namespacedGroup(group), key, separator)
}
// Usage example: `fields, err := scopedStoreTransaction.GetFields("config", "flags"); if err != nil { return }; for field := range fields { fmt.Println(field) }`
func (scopedStoreTransaction *ScopedStoreTransaction) GetFields(group, key string) (iter.Seq[string], error) {
if err := scopedStoreTransaction.ensureReady("store.ScopedStoreTransaction.GetFields"); err != nil {
return nil, err
}
return scopedStoreTransaction.storeTransaction.GetFields(scopedStoreTransaction.scopedStore.namespacedGroup(group), key)
}
func (scopedStoreTransaction *ScopedStoreTransaction) checkQuota(operation, group, key string) error {
if scopedStoreTransaction.scopedStore.MaxKeys == 0 && scopedStoreTransaction.scopedStore.MaxGroups == 0 {
return nil
}
namespacedGroup := scopedStoreTransaction.scopedStore.namespacedGroup(group)
namespacePrefix := scopedStoreTransaction.scopedStore.namespacePrefix()
_, err := scopedStoreTransaction.storeTransaction.Get(namespacedGroup, key)
if err == nil {
return nil
}
if !core.Is(err, NotFoundError) {
return core.E(operation, "quota check", err)
}
if scopedStoreTransaction.scopedStore.MaxKeys > 0 {
keyCount, err := scopedStoreTransaction.storeTransaction.CountAll(namespacePrefix)
if err != nil {
return core.E(operation, "quota check", err)
}
if keyCount >= scopedStoreTransaction.scopedStore.MaxKeys {
return core.E(operation, core.Sprintf("key limit (%d)", scopedStoreTransaction.scopedStore.MaxKeys), QuotaExceededError)
}
}
if scopedStoreTransaction.scopedStore.MaxGroups > 0 {
existingGroupCount, err := scopedStoreTransaction.storeTransaction.Count(namespacedGroup)
if err != nil {
return core.E(operation, "quota check", err)
}
if existingGroupCount == 0 {
knownGroupCount := 0
for _, iterationErr := range scopedStoreTransaction.storeTransaction.GroupsSeq(namespacePrefix) {
if iterationErr != nil {
return core.E(operation, "quota check", iterationErr)
}
knownGroupCount++
}
if knownGroupCount >= scopedStoreTransaction.scopedStore.MaxGroups {
return core.E(operation, core.Sprintf("group limit (%d)", scopedStoreTransaction.scopedStore.MaxGroups), QuotaExceededError)
}
}
}
return nil
}
// checkQuota("store.ScopedStore.Set", "config", "colour") returns nil when the
// namespace still has quota available and QuotaExceededError when a new key or
// group would exceed the configured limit. Existing keys are treated as
// upserts and do not consume quota.
func (scopedStore *ScopedStore) checkQuota(operation, group, key string) error {
if scopedStore.MaxKeys == 0 && scopedStore.MaxGroups == 0 {
return nil
}
namespacedGroup := scopedStore.namespacedGroup(group)
namespacePrefix := scopedStore.namespacePrefix()
// Check if this is an upsert (key already exists) — upserts never exceed quota.
_, err := scopedStore.storeInstance.Get(namespacedGroup, key)
if err == nil {
// Key exists — this is an upsert, no quota check needed.
return nil
}
if !core.Is(err, NotFoundError) {
// A database error occurred, not just a "not found" result.
return core.E(operation, "quota check", err)
}
// Check MaxKeys quota.
if scopedStore.MaxKeys > 0 {
keyCount, err := scopedStore.storeInstance.CountAll(namespacePrefix)
if err != nil {
return core.E(operation, "quota check", err)
}
if keyCount >= scopedStore.MaxKeys {
return core.E(operation, core.Sprintf("key limit (%d)", scopedStore.MaxKeys), QuotaExceededError)
}
}
// Check MaxGroups quota — only if this would create a new group.
if scopedStore.MaxGroups > 0 {
existingGroupCount, err := scopedStore.storeInstance.Count(namespacedGroup)
if err != nil {
return core.E(operation, "quota check", err)
}
if existingGroupCount == 0 {
// This group is new — check if adding it would exceed the group limit.
knownGroupCount := 0
for _, iterationErr := range scopedStore.storeInstance.GroupsSeq(namespacePrefix) {
if iterationErr != nil {
return core.E(operation, "quota check", iterationErr)
}
knownGroupCount++
}
if knownGroupCount >= scopedStore.MaxGroups {
return core.E(operation, core.Sprintf("group limit (%d)", scopedStore.MaxGroups), QuotaExceededError)
}
}
}
return nil
}
func (scopedStore *ScopedStore) getArguments(arguments []string) (string, string, error) {
switch len(arguments) {
case 1:
return scopedStore.defaultGroup(), arguments[0], nil
case 2:
return arguments[0], arguments[1], nil
default:
return "", "", core.E(
"store.ScopedStore.Get",
core.Sprintf("expected 1 or 2 arguments; got %d", len(arguments)),
nil,
)
}
}
func (scopedStore *ScopedStore) setArguments(arguments []string) (string, string, string, error) {
switch len(arguments) {
case 2:
return scopedStore.defaultGroup(), arguments[0], arguments[1], nil
case 3:
return arguments[0], arguments[1], arguments[2], nil
default:
return "", "", "", core.E(
"store.ScopedStore.Set",
core.Sprintf("expected 2 or 3 arguments; got %d", len(arguments)),
nil,
)
}
}
func firstString(values []string) string {
if len(values) == 0 {
return ""
}
return values[0]
}