[agent/codex:gpt-5.4-mini] Read docs/RFC-STORE.md and docs/specs/core/go/RFC.md fully. ... #163
2 changed files with 69 additions and 12 deletions
45
scope.go
45
scope.go
|
|
@ -1,6 +1,7 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"iter"
|
||||
"regexp"
|
||||
"time"
|
||||
|
|
@ -537,13 +538,13 @@ func (scopedStoreTransaction *ScopedStoreTransaction) checkQuota(operation, grou
|
|||
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) {
|
||||
exists, err := liveEntryExists(scopedStoreTransaction.storeTransaction.sqliteTransaction, namespacedGroup, key)
|
||||
if err != nil {
|
||||
return core.E(operation, "quota check", err)
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if scopedStoreTransaction.scopedStore.MaxKeys > 0 {
|
||||
keyCount, err := scopedStoreTransaction.storeTransaction.CountAll(namespacePrefix)
|
||||
|
|
@ -589,16 +590,15 @@ func (scopedStore *ScopedStore) checkQuota(operation, group, key string) error {
|
|||
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) {
|
||||
exists, err := liveEntryExists(scopedStore.storeInstance.sqliteDatabase, namespacedGroup, key)
|
||||
if err != nil {
|
||||
// A database error occurred, not just a "not found" result.
|
||||
return core.E(operation, "quota check", err)
|
||||
}
|
||||
if exists {
|
||||
// Key exists — this is an upsert, no quota check needed.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check MaxKeys quota.
|
||||
if scopedStore.MaxKeys > 0 {
|
||||
|
|
@ -634,3 +634,24 @@ func (scopedStore *ScopedStore) checkQuota(operation, group, key string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -608,6 +608,42 @@ func TestScope_Quota_Good_UpsertDoesNotCount(t *testing.T) {
|
|||
assert.Equal(t, "updated", value)
|
||||
}
|
||||
|
||||
func TestScope_Quota_Good_ExpiredUpsertDoesNotEmitDeleteEvent(t *testing.T) {
|
||||
storeInstance, _ := New(":memory:")
|
||||
defer storeInstance.Close()
|
||||
|
||||
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 1})
|
||||
|
||||
events := storeInstance.Watch("tenant-a:g")
|
||||
defer storeInstance.Unwatch("tenant-a:g", events)
|
||||
|
||||
require.NoError(t, scopedStore.SetWithTTL("g", "token", "old", 1*time.Millisecond))
|
||||
select {
|
||||
case event := <-events:
|
||||
assert.Equal(t, EventSet, event.Type)
|
||||
assert.Equal(t, "old", event.Value)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out waiting for initial set event")
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
require.NoError(t, scopedStore.SetIn("g", "token", "new"))
|
||||
|
||||
select {
|
||||
case event := <-events:
|
||||
assert.Equal(t, EventSet, event.Type)
|
||||
assert.Equal(t, "new", event.Value)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out waiting for upsert event")
|
||||
}
|
||||
|
||||
select {
|
||||
case event := <-events:
|
||||
t.Fatalf("unexpected extra event: %#v", event)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
func TestScope_Quota_Good_DeleteAndReInsert(t *testing.T) {
|
||||
storeInstance, _ := New(":memory:")
|
||||
defer storeInstance.Close()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue