v1.2.0: skip unchanged writes, transactional schema/root counters, snapshot replay for meta:events 0-0 (drops boot republish), collect-once search driver
Build and Push Docker Image / build (push) Successful in 7m38s
Build and Push Docker Image / build (push) Successful in 7m38s
This commit is contained in:
+52
-4
@@ -44,20 +44,21 @@ func events(t *testing.T, s *Store) []Entry {
|
||||
}
|
||||
|
||||
// The replacement for keyspace notifications: a change and its event commit
|
||||
// together, with Redis' semantics — `set` even for an unchanged value, `del`
|
||||
// only for a key that existed, and nothing for keys outside the event scope.
|
||||
// together. `set` only for a real change (an unchanged value is a no-op: no
|
||||
// write, no event), `del` only for a key that existed, nothing for keys outside
|
||||
// the event scope.
|
||||
func TestChangesPublishEventsWithRedisSemantics(t *testing.T) {
|
||||
s := openTest(t, "")
|
||||
update(t, s, func(tx *Tx) error {
|
||||
_ = tx.Put("file:A/title", "x")
|
||||
_ = tx.Put("file:A/title", "x") // unchanged value still publishes
|
||||
_ = tx.Put("file:A/title", "x") // unchanged: no write, no event
|
||||
_ = tx.Put("cid:bafk", "A") // outside the event scope
|
||||
_, _ = tx.Delete("file:A/missing")
|
||||
_, _ = tx.Delete("file:A/title")
|
||||
return nil
|
||||
})
|
||||
got := events(t, s)
|
||||
want := []string{"set file:A/title", "set file:A/title", "del file:A/title"}
|
||||
want := []string{"set file:A/title", "del file:A/title"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("events = %+v, want %v", got, want)
|
||||
}
|
||||
@@ -312,3 +313,50 @@ func TestGroupCommitIsolatesFailures(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The change hook sees real changes only, with old and new values, and
|
||||
// counters follow the request's fate (a failed request's deltas are dropped).
|
||||
func TestOnChangeAndCounters(t *testing.T) {
|
||||
type change struct{ k, old, new string }
|
||||
var seen []change
|
||||
s, err := Open(filepath.Join(t.TempDir(), "kv.db"), Options{
|
||||
OnChange: func(tx *Tx, k, old string, hadOld bool, v string, hasNew bool) {
|
||||
seen = append(seen, change{k, old, v})
|
||||
if hasNew {
|
||||
tx.AddCounter("count/"+v, 1)
|
||||
}
|
||||
if hadOld {
|
||||
tx.AddCounter("count/"+old, -1)
|
||||
}
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
update(t, s, func(tx *Tx) error {
|
||||
_ = tx.Put("a", "x")
|
||||
_ = tx.Put("b", "x")
|
||||
_ = tx.Put("a", "x") // no-op
|
||||
_ = tx.Put("a", "y")
|
||||
return nil
|
||||
})
|
||||
_ = s.Update(context.Background(), func(tx *Tx) error {
|
||||
_ = tx.Put("c", "x")
|
||||
return errors.New("fail")
|
||||
})
|
||||
update(t, s, func(tx *Tx) error { _, err := tx.DeletePrefix("b", 0); return err })
|
||||
|
||||
if len(seen) != 5 { // a=x, b=x, a x→y, c=x (rolled back, hook still ran), del b; stats count attempts
|
||||
t.Fatalf("changes = %+v", seen)
|
||||
}
|
||||
for k, want := range map[string]int64{"count/x": 0, "count/y": 1} {
|
||||
got, _, err := s.Counter(context.Background(), k)
|
||||
if err != nil || got != want {
|
||||
t.Errorf("%s = %d (err %v), want %d", k, got, err, want)
|
||||
}
|
||||
}
|
||||
if changed, unchanged := s.WriteStats(); changed != 4 || unchanged != 1 {
|
||||
t.Errorf("write stats = %d changed / %d unchanged", changed, unchanged)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user