2026-02-20 08:19:11 +00:00
package store
import (
2026-04-04 19:12:24 +00:00
"database/sql"
2026-02-23 05:21:39 +00:00
"iter"
2026-02-20 08:19:11 +00:00
"regexp"
2026-04-04 20:34:25 +00:00
"sync"
2026-02-20 08:19:11 +00:00
"time"
2026-03-16 21:29:42 +00:00
2026-03-26 13:58:50 +00:00
core "dappco.re/go/core"
2026-02-20 08:19:11 +00:00
)
2026-03-30 17:37:50 +00:00
// validNamespace.MatchString("tenant-a") is true; validNamespace.MatchString("tenant_a") is false.
2026-02-20 08:19:11 +00:00
var validNamespace = regexp . MustCompile ( ` ^[a-zA-Z0-9-]+$ ` )
2026-03-30 20:46:43 +00:00
const defaultScopedGroupName = "default"
2026-04-04 16:06:43 +00:00
// QuotaConfig sets per-namespace key and group limits.
2026-03-26 19:17:11 +00:00
// Usage example: `quota := store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}`
2026-02-20 08:19:11 +00:00
type QuotaConfig struct {
2026-03-30 16:54:02 +00:00
// 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
2026-02-20 08:19:11 +00:00
}
2026-04-04 15:47:56 +00:00
// 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
}
2026-04-04 16:06:43 +00:00
// ScopedStoreConfig combines namespace selection with optional quota limits.
2026-04-04 09:34:54 +00:00
// 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
}
2026-04-04 10:46:46 +00:00
// Usage example: `if err := (store.ScopedStoreConfig{Namespace: "tenant-a", Quota: store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}}).Validate(); err != nil { return }`
2026-04-04 11:26:16 +00:00
func ( scopedConfig ScopedStoreConfig ) Validate ( ) error {
if ! validNamespace . MatchString ( scopedConfig . Namespace ) {
2026-04-04 10:46:46 +00:00
return core . E (
"store.ScopedStoreConfig.Validate" ,
2026-04-04 11:26:16 +00:00
core . Sprintf ( "namespace %q is invalid; use names like %q or %q" , scopedConfig . Namespace , "tenant-a" , "tenant-42" ) ,
2026-04-04 10:46:46 +00:00
nil ,
)
}
2026-04-04 15:47:56 +00:00
if err := scopedConfig . Quota . Validate ( ) ; err != nil {
return core . E ( "store.ScopedStoreConfig.Validate" , "quota" , err )
2026-04-04 10:46:46 +00:00
}
return nil
}
2026-04-04 21:29:27 +00:00
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }`
// Usage example: `if err := scopedStore.Set("colour", "blue"); err != nil { return } // writes tenant-a:default/colour`
// Usage example: `if err := scopedStore.SetIn("config", "colour", "blue"); err != nil { return } // writes tenant-a:config/colour`
2026-04-04 16:06:43 +00:00
type ScopedStore struct {
2026-04-04 20:11:54 +00:00
store * Store
namespace string
2026-04-04 16:06:43 +00:00
// Usage example: `scopedStore.MaxKeys = 100`
MaxKeys int
// Usage example: `scopedStore.MaxGroups = 10`
MaxGroups int
2026-04-04 20:34:25 +00:00
2026-04-04 21:09:20 +00:00
watcherBridgeLock sync . Mutex
watcherBridges map [ uintptr ] scopedWatcherBridge
2026-04-04 20:34:25 +00:00
}
type scopedWatcherBridge struct {
sourceGroup string
sourceEvents <- chan Event
done chan struct { }
2026-04-03 06:31:35 +00:00
}
2026-04-04 16:06:43 +00:00
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }`
2026-04-04 21:29:27 +00:00
// Prefer `NewScopedConfigured(storeInstance, store.ScopedStoreConfig{Namespace: "tenant-a"})`
// when the namespace and quota are already known at the call site.
2026-04-04 16:06:43 +00:00
func NewScoped ( storeInstance * Store , namespace string ) ( * ScopedStore , error ) {
2026-03-30 19:29:48 +00:00
if storeInstance == nil {
2026-04-04 16:06:43 +00:00
return nil , core . E ( "store.NewScoped" , "store instance is nil" , nil )
2026-03-30 19:29:48 +00:00
}
2026-02-20 08:19:11 +00:00
if ! validNamespace . MatchString ( namespace ) {
2026-04-04 16:06:43 +00:00
return nil , core . E ( "store.NewScoped" , core . Sprintf ( "namespace %q is invalid; use names like %q or %q" , namespace , "tenant-a" , "tenant-42" ) , nil )
2026-02-20 08:19:11 +00:00
}
2026-04-04 20:34:25 +00:00
scopedStore := & ScopedStore {
store : storeInstance ,
namespace : namespace ,
watcherBridges : make ( map [ uintptr ] scopedWatcherBridge ) ,
}
2026-04-04 16:06:43 +00:00
return scopedStore , nil
2026-02-20 08:19:11 +00:00
}
2026-04-04 09:34:54 +00:00
// Usage example: `scopedStore, err := store.NewScopedConfigured(storeInstance, store.ScopedStoreConfig{Namespace: "tenant-a", Quota: store.QuotaConfig{MaxKeys: 100, MaxGroups: 10}}); if err != nil { return }`
2026-04-04 21:29:27 +00:00
// This keeps the namespace and quota in one declarative literal instead of an
// option chain.
2026-04-04 11:26:16 +00:00
func NewScopedConfigured ( storeInstance * Store , scopedConfig ScopedStoreConfig ) ( * ScopedStore , error ) {
2026-04-04 10:46:46 +00:00
if storeInstance == nil {
return nil , core . E ( "store.NewScopedConfigured" , "store instance is nil" , nil )
2026-02-20 08:19:11 +00:00
}
2026-04-04 11:26:16 +00:00
if err := scopedConfig . Validate ( ) ; err != nil {
2026-04-04 10:46:46 +00:00
return nil , core . E ( "store.NewScopedConfigured" , "validate config" , err )
2026-03-30 20:03:19 +00:00
}
2026-04-04 16:06:43 +00:00
scopedStore , err := NewScoped ( storeInstance , scopedConfig . Namespace )
if err != nil {
return nil , err
}
2026-04-04 11:26:16 +00:00
scopedStore . MaxKeys = scopedConfig . Quota . MaxKeys
scopedStore . MaxGroups = scopedConfig . Quota . MaxGroups
2026-03-30 14:54:34 +00:00
return scopedStore , nil
2026-02-20 08:19:11 +00:00
}
2026-03-30 15:02:28 +00:00
func ( scopedStore * ScopedStore ) namespacedGroup ( group string ) string {
return scopedStore . namespace + ":" + group
2026-02-20 08:19:11 +00:00
}
2026-03-30 19:14:09 +00:00
func ( scopedStore * ScopedStore ) namespacePrefix ( ) string {
return scopedStore . namespace + ":"
}
2026-04-04 16:06:43 +00:00
func ( scopedStore * ScopedStore ) defaultGroup ( ) string {
return defaultScopedGroupName
}
2026-03-30 19:14:09 +00:00
func ( scopedStore * ScopedStore ) trimNamespacePrefix ( groupName string ) string {
return core . TrimPrefix ( groupName , scopedStore . namespacePrefix ( ) )
}
2026-04-04 21:19:59 +00:00
func ( scopedStore * ScopedStore ) ensureReady ( operation string ) error {
if scopedStore == nil {
return core . E ( operation , "scoped store is nil" , nil )
}
if scopedStore . store == nil {
return core . E ( operation , "scoped store store is nil" , nil )
}
if err := scopedStore . store . ensureReady ( operation ) ; err != nil {
return err
}
return nil
}
2026-04-04 16:06:43 +00:00
// Namespace returns the namespace string.
// Usage example: `scopedStore, err := store.NewScoped(storeInstance, "tenant-a"); if err != nil { return }; namespace := scopedStore.Namespace(); fmt.Println(namespace)`
2026-03-30 15:02:28 +00:00
func ( scopedStore * ScopedStore ) Namespace ( ) string {
return scopedStore . namespace
2026-02-20 08:19:11 +00:00
}
2026-04-04 20:54:45 +00:00
// Config returns the namespace and quota settings as a single declarative struct.
// Usage example: `config := scopedStore.Config(); fmt.Println(config.Namespace, config.Quota.MaxKeys, config.Quota.MaxGroups)`
func ( scopedStore * ScopedStore ) Config ( ) ScopedStoreConfig {
if scopedStore == nil {
return ScopedStoreConfig { }
}
return ScopedStoreConfig {
Namespace : scopedStore . namespace ,
Quota : QuotaConfig {
MaxKeys : scopedStore . MaxKeys ,
MaxGroups : scopedStore . MaxGroups ,
} ,
}
}
2026-04-05 08:58:26 +01:00
// Usage example: `exists, err := scopedStore.Exists("colour")`
// Usage example: `if exists, _ := scopedStore.Exists("token"); !exists { fmt.Println("session expired") }`
func ( scopedStore * ScopedStore ) Exists ( key string ) ( bool , error ) {
if err := scopedStore . ensureReady ( "store.Exists" ) ; err != nil {
return false , err
}
return scopedStore . store . Exists ( scopedStore . namespacedGroup ( scopedStore . defaultGroup ( ) ) , key )
}
// Usage example: `exists, err := scopedStore.ExistsIn("config", "colour")`
// Usage example: `if exists, _ := scopedStore.ExistsIn("session", "token"); !exists { fmt.Println("session expired") }`
func ( scopedStore * ScopedStore ) ExistsIn ( group , key string ) ( bool , error ) {
if err := scopedStore . ensureReady ( "store.Exists" ) ; err != nil {
return false , err
}
return scopedStore . store . Exists ( scopedStore . namespacedGroup ( group ) , key )
}
// Usage example: `exists, err := scopedStore.GroupExists("config")`
// Usage example: `if exists, _ := scopedStore.GroupExists("cache"); !exists { fmt.Println("group is empty") }`
func ( scopedStore * ScopedStore ) GroupExists ( group string ) ( bool , error ) {
if err := scopedStore . ensureReady ( "store.GroupExists" ) ; err != nil {
return false , err
}
return scopedStore . store . GroupExists ( scopedStore . namespacedGroup ( group ) )
}
2026-03-30 20:46:43 +00:00
// Usage example: `colourValue, err := scopedStore.Get("colour")`
2026-04-04 16:37:56 +00:00
func ( scopedStore * ScopedStore ) Get ( key string ) ( string , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.Get" ) ; err != nil {
return "" , err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . Get ( scopedStore . namespacedGroup ( scopedStore . defaultGroup ( ) ) , key )
2026-04-03 05:13:46 +00:00
}
2026-04-04 16:06:43 +00:00
// GetFrom reads a key from an explicit namespaced group.
2026-04-03 05:13:46 +00:00
// Usage example: `colourValue, err := scopedStore.GetFrom("config", "colour")`
func ( scopedStore * ScopedStore ) GetFrom ( group , key string ) ( string , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.Get" ) ; err != nil {
return "" , err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . Get ( scopedStore . namespacedGroup ( group ) , key )
2026-02-20 08:19:11 +00:00
}
2026-03-30 20:46:43 +00:00
// Usage example: `if err := scopedStore.Set("colour", "blue"); err != nil { return }`
2026-04-04 16:37:56 +00:00
func ( scopedStore * ScopedStore ) Set ( key , value string ) error {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.Set" ) ; err != nil {
return err
}
2026-04-04 16:37:56 +00:00
defaultGroup := scopedStore . defaultGroup ( )
if err := scopedStore . checkQuota ( "store.ScopedStore.Set" , defaultGroup , key ) ; err != nil {
2026-04-03 06:31:35 +00:00
return err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . Set ( scopedStore . namespacedGroup ( defaultGroup ) , key , value )
2026-04-04 16:06:43 +00:00
}
// 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 {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.Set" ) ; err != nil {
return err
}
2026-04-04 16:37:56 +00:00
if err := scopedStore . checkQuota ( "store.ScopedStore.SetIn" , group , key ) ; err != nil {
return err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . Set ( scopedStore . namespacedGroup ( group ) , key , value )
2026-02-20 08:19:11 +00:00
}
2026-03-30 16:41:56 +00:00
// Usage example: `if err := scopedStore.SetWithTTL("sessions", "token", "abc123", time.Hour); err != nil { return }`
2026-03-30 18:37:07 +00:00
func ( scopedStore * ScopedStore ) SetWithTTL ( group , key , value string , timeToLive time . Duration ) error {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.SetWithTTL" ) ; err != nil {
return err
}
2026-03-30 16:27:54 +00:00
if err := scopedStore . checkQuota ( "store.ScopedStore.SetWithTTL" , group , key ) ; err != nil {
2026-02-20 08:19:11 +00:00
return err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . SetWithTTL ( scopedStore . namespacedGroup ( group ) , key , value , timeToLive )
2026-02-20 08:19:11 +00:00
}
2026-03-30 18:49:17 +00:00
// Usage example: `if err := scopedStore.Delete("config", "colour"); err != nil { return }`
2026-03-30 15:02:28 +00:00
func ( scopedStore * ScopedStore ) Delete ( group , key string ) error {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.Delete" ) ; err != nil {
return err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . Delete ( scopedStore . namespacedGroup ( group ) , key )
2026-02-20 08:19:11 +00:00
}
2026-03-30 16:41:56 +00:00
// Usage example: `if err := scopedStore.DeleteGroup("cache"); err != nil { return }`
2026-03-30 15:02:28 +00:00
func ( scopedStore * ScopedStore ) DeleteGroup ( group string ) error {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.DeleteGroup" ) ; err != nil {
return err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . DeleteGroup ( scopedStore . namespacedGroup ( group ) )
2026-04-03 07:14:22 +00:00
}
2026-04-04 16:09:14 +00:00
// 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 {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.DeletePrefix" ) ; err != nil {
return err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . DeletePrefix ( scopedStore . namespacedGroup ( groupPrefix ) )
2026-04-04 16:09:14 +00:00
}
2026-03-30 18:49:17 +00:00
// Usage example: `colourEntries, err := scopedStore.GetAll("config")`
2026-03-30 15:02:28 +00:00
func ( scopedStore * ScopedStore ) GetAll ( group string ) ( map [ string ] string , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.GetAll" ) ; err != nil {
return nil , err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . GetAll ( scopedStore . namespacedGroup ( group ) )
2026-04-03 08:32:35 +00:00
}
2026-04-04 17:53:21 +00:00
// Usage example: `page, err := scopedStore.GetPage("config", 0, 25); if err != nil { return }; for _, entry := range page { fmt.Println(entry.Key, entry.Value) }`
func ( scopedStore * ScopedStore ) GetPage ( group string , offset , limit int ) ( [ ] KeyValue , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.GetPage" ) ; err != nil {
return nil , err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . GetPage ( scopedStore . namespacedGroup ( group ) , offset , limit )
2026-04-04 17:53:21 +00:00
}
2026-03-30 17:32:09 +00:00
// Usage example: `for entry, err := range scopedStore.All("config") { if err != nil { break }; fmt.Println(entry.Key, entry.Value) }`
2026-03-30 15:02:28 +00:00
func ( scopedStore * ScopedStore ) All ( group string ) iter . Seq2 [ KeyValue , error ] {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.All" ) ; err != nil {
return func ( yield func ( KeyValue , error ) bool ) {
yield ( KeyValue { } , err )
}
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . All ( scopedStore . namespacedGroup ( group ) )
2026-02-23 05:21:39 +00:00
}
2026-04-03 05:13:46 +00:00
// 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 ] {
2026-04-04 16:06:43 +00:00
return scopedStore . All ( group )
2026-04-03 05:13:46 +00:00
}
2026-03-30 16:13:55 +00:00
// Usage example: `keyCount, err := scopedStore.Count("config")`
2026-03-30 15:02:28 +00:00
func ( scopedStore * ScopedStore ) Count ( group string ) ( int , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.Count" ) ; err != nil {
return 0 , err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . Count ( scopedStore . namespacedGroup ( group ) )
2026-02-20 08:19:11 +00:00
}
2026-03-30 19:14:09 +00:00
// Usage example: `keyCount, err := scopedStore.CountAll("config")`
2026-03-30 20:46:43 +00:00
// Usage example: `keyCount, err := scopedStore.CountAll()`
func ( scopedStore * ScopedStore ) CountAll ( groupPrefix ... string ) ( int , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.CountAll" ) ; err != nil {
return 0 , err
}
2026-04-04 21:29:27 +00:00
return scopedStore . store . CountAll ( scopedStore . namespacedGroup ( firstStringOrEmpty ( groupPrefix ) ) )
2026-03-30 19:14:09 +00:00
}
// Usage example: `groupNames, err := scopedStore.Groups("config")`
2026-03-30 20:46:43 +00:00
// Usage example: `groupNames, err := scopedStore.Groups()`
func ( scopedStore * ScopedStore ) Groups ( groupPrefix ... string ) ( [ ] string , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.Groups" ) ; err != nil {
return nil , err
}
2026-04-04 21:29:27 +00:00
groupNames , err := scopedStore . store . Groups ( scopedStore . namespacedGroup ( firstStringOrEmpty ( groupPrefix ) ) )
2026-03-30 19:14:09 +00:00
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) }`
2026-03-30 20:46:43 +00:00
// 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 ] {
2026-03-30 19:14:09 +00:00
return func ( yield func ( string , error ) bool ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.GroupsSeq" ) ; err != nil {
yield ( "" , err )
return
}
2026-03-30 19:14:09 +00:00
namespacePrefix := scopedStore . namespacePrefix ( )
2026-04-04 21:29:27 +00:00
for groupName , err := range scopedStore . store . GroupsSeq ( scopedStore . namespacedGroup ( firstStringOrEmpty ( groupPrefix ) ) ) {
2026-03-30 19:14:09 +00:00
if err != nil {
if ! yield ( "" , err ) {
return
}
continue
}
if ! yield ( core . TrimPrefix ( groupName , namespacePrefix ) , nil ) {
return
}
}
}
}
2026-03-30 16:13:55 +00:00
// Usage example: `renderedTemplate, err := scopedStore.Render("Hello {{ .name }}", "user")`
2026-03-30 15:02:28 +00:00
func ( scopedStore * ScopedStore ) Render ( templateSource , group string ) ( string , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.Render" ) ; err != nil {
return "" , err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . Render ( templateSource , scopedStore . namespacedGroup ( group ) )
2026-02-20 08:19:11 +00:00
}
2026-03-30 19:14:09 +00:00
// 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 ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.GetSplit" ) ; err != nil {
return nil , err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . GetSplit ( scopedStore . namespacedGroup ( group ) , key , separator )
2026-03-30 19:14:09 +00:00
}
// 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 ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.GetFields" ) ; err != nil {
return nil , err
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . GetFields ( scopedStore . namespacedGroup ( group ) , key )
2026-04-03 06:55:37 +00:00
}
2026-03-30 19:14:09 +00:00
// Usage example: `removedRows, err := scopedStore.PurgeExpired(); if err != nil { return }; fmt.Println(removedRows)`
func ( scopedStore * ScopedStore ) PurgeExpired ( ) ( int64 , error ) {
2026-04-04 21:19:59 +00:00
if err := scopedStore . ensureReady ( "store.PurgeExpired" ) ; err != nil {
2026-04-04 18:20:52 +00:00
return 0 , err
}
2026-04-04 20:11:54 +00:00
removedRows , err := purgeExpiredMatchingGroupPrefix ( scopedStore . store . sqliteDatabase , scopedStore . namespacePrefix ( ) )
2026-03-30 20:03:19 +00:00
if err != nil {
return 0 , core . E ( "store.ScopedStore.PurgeExpired" , "delete expired rows" , err )
}
return removedRows , nil
2026-03-30 19:14:09 +00:00
}
2026-04-04 20:34:25 +00:00
// Usage example: `events := scopedStore.Watch("config")`
// Usage example: `events := scopedStore.Watch("*")`
2026-04-04 21:29:27 +00:00
// A write to `tenant-a:config` is delivered back to this scoped watcher as
// `config`, so callers never have to strip the namespace themselves.
2026-04-04 20:34:25 +00:00
func ( scopedStore * ScopedStore ) Watch ( group string ) <- chan Event {
if scopedStore == nil || scopedStore . store == nil {
return closedEventChannel ( )
}
sourceGroup := scopedStore . namespacedGroup ( group )
if group == "*" {
sourceGroup = "*"
}
sourceEvents := scopedStore . store . Watch ( sourceGroup )
localEvents := make ( chan Event , watcherEventBufferCapacity )
done := make ( chan struct { } )
localEventsPointer := channelPointer ( localEvents )
2026-04-04 21:09:20 +00:00
scopedStore . watcherBridgeLock . Lock ( )
2026-04-04 20:34:25 +00:00
if scopedStore . watcherBridges == nil {
scopedStore . watcherBridges = make ( map [ uintptr ] scopedWatcherBridge )
}
scopedStore . watcherBridges [ localEventsPointer ] = scopedWatcherBridge {
sourceGroup : sourceGroup ,
sourceEvents : sourceEvents ,
done : done ,
}
2026-04-04 21:09:20 +00:00
scopedStore . watcherBridgeLock . Unlock ( )
2026-04-04 20:34:25 +00:00
go func ( ) {
defer close ( localEvents )
defer scopedStore . removeWatcherBridge ( localEventsPointer )
for {
select {
case <- done :
return
case event , ok := <- sourceEvents :
if ! ok {
return
}
localEvent , allowed := scopedStore . localiseWatchedEvent ( event )
if ! allowed {
continue
}
select {
case localEvents <- localEvent :
default :
}
}
}
} ( )
return localEvents
}
// Usage example: `events := scopedStore.Watch("config"); scopedStore.Unwatch("config", events)`
// Usage example: `events := scopedStore.Watch("*"); scopedStore.Unwatch("*", events)`
func ( scopedStore * ScopedStore ) Unwatch ( group string , events <- chan Event ) {
if scopedStore == nil || events == nil {
return
}
2026-04-04 21:09:20 +00:00
scopedStore . watcherBridgeLock . Lock ( )
2026-04-04 20:34:25 +00:00
watcherBridge , ok := scopedStore . watcherBridges [ channelPointer ( events ) ]
if ok {
delete ( scopedStore . watcherBridges , channelPointer ( events ) )
}
2026-04-04 21:09:20 +00:00
scopedStore . watcherBridgeLock . Unlock ( )
2026-04-04 20:34:25 +00:00
if ! ok {
return
}
close ( watcherBridge . done )
scopedStore . store . Unwatch ( watcherBridge . sourceGroup , watcherBridge . sourceEvents )
}
func ( scopedStore * ScopedStore ) removeWatcherBridge ( pointer uintptr ) {
if scopedStore == nil {
return
}
2026-04-04 21:09:20 +00:00
scopedStore . watcherBridgeLock . Lock ( )
2026-04-04 20:34:25 +00:00
delete ( scopedStore . watcherBridges , pointer )
2026-04-04 21:09:20 +00:00
scopedStore . watcherBridgeLock . Unlock ( )
2026-04-04 20:34:25 +00:00
}
func ( scopedStore * ScopedStore ) localiseWatchedEvent ( event Event ) ( Event , bool ) {
if scopedStore == nil {
return Event { } , false
}
namespacePrefix := scopedStore . namespacePrefix ( )
if event . Group == "*" {
return event , true
}
if ! core . HasPrefix ( event . Group , namespacePrefix ) {
return Event { } , false
}
event . Group = core . TrimPrefix ( event . Group , namespacePrefix )
return event , true
}
2026-04-04 19:24:47 +00:00
// Usage example: `unregister := scopedStore.OnChange(func(event store.Event) { fmt.Println(event.Group, event.Key, event.Value) })`
2026-04-04 21:29:27 +00:00
// A callback registered on `tenant-a` receives `config` rather than
// `tenant-a:config`.
2026-04-04 19:24:47 +00:00
func ( scopedStore * ScopedStore ) OnChange ( callback func ( Event ) ) func ( ) {
if scopedStore == nil || callback == nil {
return func ( ) { }
}
2026-04-04 20:11:54 +00:00
if scopedStore . store == nil {
2026-04-04 19:24:47 +00:00
return func ( ) { }
}
namespacePrefix := scopedStore . namespacePrefix ( )
2026-04-04 20:11:54 +00:00
return scopedStore . store . OnChange ( func ( event Event ) {
2026-04-04 19:24:47 +00:00
if ! core . HasPrefix ( event . Group , namespacePrefix ) {
return
}
event . Group = core . TrimPrefix ( event . Group , namespacePrefix )
callback ( event )
} )
}
2026-04-04 16:09:14 +00:00
// 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 )
}
2026-04-04 20:11:54 +00:00
return scopedStore . store . Transaction ( func ( storeTransaction * StoreTransaction ) error {
2026-04-04 16:09:14 +00:00
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 )
}
2026-04-04 20:11:54 +00:00
if err := scopedStoreTransaction . scopedStore . store . ensureReady ( operation ) ; err != nil {
2026-04-04 16:09:14 +00:00
return err
}
return scopedStoreTransaction . storeTransaction . ensureReady ( operation )
}
2026-04-05 08:58:26 +01:00
// Usage example: `exists, err := scopedStoreTransaction.Exists("colour")`
func ( scopedStoreTransaction * ScopedStoreTransaction ) Exists ( key string ) ( bool , error ) {
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.Exists" ) ; err != nil {
return false , err
}
return scopedStoreTransaction . storeTransaction . Exists (
scopedStoreTransaction . scopedStore . namespacedGroup ( scopedStoreTransaction . scopedStore . defaultGroup ( ) ) ,
key ,
)
}
// Usage example: `exists, err := scopedStoreTransaction.ExistsIn("config", "colour")`
func ( scopedStoreTransaction * ScopedStoreTransaction ) ExistsIn ( group , key string ) ( bool , error ) {
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.ExistsIn" ) ; err != nil {
return false , err
}
return scopedStoreTransaction . storeTransaction . Exists ( scopedStoreTransaction . scopedStore . namespacedGroup ( group ) , key )
}
// Usage example: `exists, err := scopedStoreTransaction.GroupExists("config")`
func ( scopedStoreTransaction * ScopedStoreTransaction ) GroupExists ( group string ) ( bool , error ) {
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.GroupExists" ) ; err != nil {
return false , err
}
return scopedStoreTransaction . storeTransaction . GroupExists ( scopedStoreTransaction . scopedStore . namespacedGroup ( group ) )
}
2026-04-04 16:09:14 +00:00
// Usage example: `colourValue, err := scopedStoreTransaction.Get("colour")`
2026-04-04 16:37:56 +00:00
func ( scopedStoreTransaction * ScopedStoreTransaction ) Get ( key string ) ( string , error ) {
2026-04-04 16:09:14 +00:00
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.Get" ) ; err != nil {
return "" , err
}
2026-04-04 16:37:56 +00:00
return scopedStoreTransaction . storeTransaction . Get (
scopedStoreTransaction . scopedStore . namespacedGroup ( scopedStoreTransaction . scopedStore . defaultGroup ( ) ) ,
key ,
)
2026-04-04 16:09:14 +00:00
}
// Usage example: `colourValue, err := scopedStoreTransaction.GetFrom("config", "colour")`
func ( scopedStoreTransaction * ScopedStoreTransaction ) GetFrom ( group , key string ) ( string , error ) {
2026-04-04 16:37:56 +00:00
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.GetFrom" ) ; err != nil {
return "" , err
}
return scopedStoreTransaction . storeTransaction . Get ( scopedStoreTransaction . scopedStore . namespacedGroup ( group ) , key )
2026-04-04 16:09:14 +00:00
}
// Usage example: `if err := scopedStoreTransaction.Set("theme", "dark"); err != nil { return err }`
2026-04-04 16:37:56 +00:00
func ( scopedStoreTransaction * ScopedStoreTransaction ) Set ( key , value string ) error {
2026-04-04 16:09:14 +00:00
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.Set" ) ; err != nil {
return err
}
2026-04-04 16:37:56 +00:00
defaultGroup := scopedStoreTransaction . scopedStore . defaultGroup ( )
if err := scopedStoreTransaction . checkQuota ( "store.ScopedStoreTransaction.Set" , defaultGroup , key ) ; err != nil {
2026-04-04 16:09:14 +00:00
return err
}
2026-04-04 16:37:56 +00:00
return scopedStoreTransaction . storeTransaction . Set (
scopedStoreTransaction . scopedStore . namespacedGroup ( defaultGroup ) ,
key ,
value ,
)
2026-04-04 16:09:14 +00:00
}
// Usage example: `if err := scopedStoreTransaction.SetIn("config", "colour", "blue"); err != nil { return err }`
func ( scopedStoreTransaction * ScopedStoreTransaction ) SetIn ( group , key , value string ) error {
2026-04-04 16:37:56 +00:00
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.SetIn" ) ; err != nil {
return err
}
if err := scopedStoreTransaction . checkQuota ( "store.ScopedStoreTransaction.SetIn" , group , key ) ; err != nil {
return err
}
return scopedStoreTransaction . storeTransaction . Set ( scopedStoreTransaction . scopedStore . namespacedGroup ( group ) , key , value )
2026-04-04 16:09:14 +00:00
}
// 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 ) )
}
2026-04-04 17:53:21 +00:00
// Usage example: `page, err := scopedStoreTransaction.GetPage("config", 0, 25); if err != nil { return }; for _, entry := range page { fmt.Println(entry.Key, entry.Value) }`
func ( scopedStoreTransaction * ScopedStoreTransaction ) GetPage ( group string , offset , limit int ) ( [ ] KeyValue , error ) {
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.GetPage" ) ; err != nil {
return nil , err
}
return scopedStoreTransaction . storeTransaction . GetPage ( scopedStoreTransaction . scopedStore . namespacedGroup ( group ) , offset , limit )
}
2026-04-04 16:09:14 +00:00
// 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
}
2026-04-04 21:29:27 +00:00
return scopedStoreTransaction . storeTransaction . CountAll ( scopedStoreTransaction . scopedStore . namespacedGroup ( firstStringOrEmpty ( groupPrefix ) ) )
2026-04-04 16:09:14 +00:00
}
// 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
}
2026-04-04 21:29:27 +00:00
groupNames , err := scopedStoreTransaction . storeTransaction . Groups ( scopedStoreTransaction . scopedStore . namespacedGroup ( firstStringOrEmpty ( groupPrefix ) ) )
2026-04-04 16:09:14 +00:00
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 ( )
2026-04-04 21:29:27 +00:00
for groupName , err := range scopedStoreTransaction . storeTransaction . GroupsSeq ( scopedStoreTransaction . scopedStore . namespacedGroup ( firstStringOrEmpty ( groupPrefix ) ) ) {
2026-04-04 16:09:14 +00:00
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 )
}
2026-04-04 18:20:52 +00:00
// Usage example: `removedRows, err := scopedStoreTransaction.PurgeExpired(); if err != nil { return err }; fmt.Println(removedRows)`
func ( scopedStoreTransaction * ScopedStoreTransaction ) PurgeExpired ( ) ( int64 , error ) {
if err := scopedStoreTransaction . ensureReady ( "store.ScopedStoreTransaction.PurgeExpired" ) ; err != nil {
return 0 , err
}
removedRows , err := purgeExpiredMatchingGroupPrefix ( scopedStoreTransaction . storeTransaction . sqliteTransaction , scopedStoreTransaction . scopedStore . namespacePrefix ( ) )
if err != nil {
return 0 , core . E ( "store.ScopedStoreTransaction.PurgeExpired" , "delete expired rows" , err )
}
return removedRows , nil
}
2026-04-04 16:09:14 +00:00
func ( scopedStoreTransaction * ScopedStoreTransaction ) checkQuota ( operation , group , key string ) error {
2026-04-04 20:07:33 +00:00
return enforceQuota (
operation ,
group ,
key ,
scopedStoreTransaction . scopedStore . namespacePrefix ( ) ,
scopedStoreTransaction . scopedStore . namespacedGroup ( group ) ,
scopedStoreTransaction . scopedStore . MaxKeys ,
scopedStoreTransaction . scopedStore . MaxGroups ,
scopedStoreTransaction . storeTransaction . sqliteTransaction ,
scopedStoreTransaction . storeTransaction ,
)
2026-04-04 16:09:14 +00:00
}
2026-03-30 18:49:17 +00:00
// checkQuota("store.ScopedStore.Set", "config", "colour") returns nil when the
2026-03-30 17:37:50 +00:00
// 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.
2026-03-30 16:27:54 +00:00
func ( scopedStore * ScopedStore ) checkQuota ( operation , group , key string ) error {
2026-04-04 20:07:33 +00:00
return enforceQuota (
operation ,
group ,
key ,
scopedStore . namespacePrefix ( ) ,
scopedStore . namespacedGroup ( group ) ,
scopedStore . MaxKeys ,
scopedStore . MaxGroups ,
2026-04-04 20:11:54 +00:00
scopedStore . store . sqliteDatabase ,
scopedStore . store ,
2026-04-04 20:07:33 +00:00
)
}
type quotaCounter interface {
CountAll ( groupPrefix string ) ( int , error )
Count ( group string ) ( int , error )
GroupsSeq ( groupPrefix ... string ) iter . Seq2 [ string , error ]
}
func enforceQuota (
operation , group , key , namespacePrefix , namespacedGroup string ,
maxKeys , maxGroups int ,
queryable keyExistenceQuery ,
counter quotaCounter ,
) error {
if maxKeys == 0 && maxGroups == 0 {
2026-02-20 08:19:11 +00:00
return nil
}
2026-04-04 20:07:33 +00:00
exists , err := liveEntryExists ( queryable , namespacedGroup , key )
2026-04-04 19:12:24 +00:00
if err != nil {
2026-04-04 16:06:43 +00:00
// A database error occurred, not just a "not found" result.
2026-03-30 16:27:54 +00:00
return core . E ( operation , "quota check" , err )
2026-03-09 08:20:38 +00:00
}
2026-04-04 19:12:24 +00:00
if exists {
2026-04-04 20:07:33 +00:00
// Key exists - this is an upsert, no quota check needed.
2026-04-04 19:12:24 +00:00
return nil
}
2026-02-20 08:19:11 +00:00
2026-04-04 20:07:33 +00:00
if maxKeys > 0 {
keyCount , err := counter . CountAll ( namespacePrefix )
2026-02-20 08:19:11 +00:00
if err != nil {
2026-03-30 16:27:54 +00:00
return core . E ( operation , "quota check" , err )
2026-02-20 08:19:11 +00:00
}
2026-04-04 20:07:33 +00:00
if keyCount >= maxKeys {
return core . E ( operation , core . Sprintf ( "key limit (%d)" , maxKeys ) , QuotaExceededError )
2026-02-20 08:19:11 +00:00
}
}
2026-04-04 20:07:33 +00:00
if maxGroups > 0 {
existingGroupCount , err := counter . Count ( namespacedGroup )
2026-02-20 08:19:11 +00:00
if err != nil {
2026-03-30 16:27:54 +00:00
return core . E ( operation , "quota check" , err )
2026-02-20 08:19:11 +00:00
}
2026-03-30 14:54:34 +00:00
if existingGroupCount == 0 {
2026-04-04 16:06:43 +00:00
knownGroupCount := 0
2026-04-04 20:07:33 +00:00
for _ , iterationErr := range counter . GroupsSeq ( namespacePrefix ) {
2026-04-04 16:06:43 +00:00
if iterationErr != nil {
return core . E ( operation , "quota check" , iterationErr )
}
knownGroupCount ++
2026-02-20 08:19:11 +00:00
}
2026-04-04 20:07:33 +00:00
if knownGroupCount >= maxGroups {
return core . E ( operation , core . Sprintf ( "group limit (%d)" , maxGroups ) , QuotaExceededError )
2026-02-20 08:19:11 +00:00
}
}
}
return nil
}
2026-04-04 19:12:24 +00:00
func liveEntryExists ( queryable keyExistenceQuery , group , key string ) ( bool , error ) {
var exists int
err := queryable . QueryRow (
"SELECT 1 FROM " + entriesTableName + " WHERE " + entryGroupColumn + " = ? AND " + entryKeyColumn + " = ? AND (expires_at IS NULL OR expires_at > ?) LIMIT 1" ,
group ,
key ,
time . Now ( ) . UnixMilli ( ) ,
) . Scan ( & exists )
if err == nil {
return true , nil
}
if err == sql . ErrNoRows {
return false , nil
}
return false , err
}
type keyExistenceQuery interface {
QueryRow ( query string , args ... any ) * sql . Row
}