jldb/mdb/snapshot.go

108 lines
2.2 KiB
Go

package mdb
import (
"bytes"
"encoding/json"
"sync/atomic"
"git.crumpington.com/public/jldb/mdb/change"
)
type Snapshot struct {
parent atomic.Pointer[Snapshot]
// The Snapshot's version is incremented each time it's cloned.
version uint64
// The snapshot's seqNum is set when it becomes active (read-only).]
seqNum int64
timestampMS int64
collections map[uint64]any // Map from collection ID to *collectionState[T].
changes []change.Change
}
func newSnapshot() *Snapshot {
return &Snapshot{
collections: map[uint64]any{},
changes: []change.Change{},
}
}
func (s *Snapshot) addCollection(id uint64, c any) {
s.collections[id] = c
}
func (s *Snapshot) writable() bool {
return s.parent.Load() != nil
}
func (s *Snapshot) setReadOnly() {
s.parent.Store(nil)
s.changes = s.changes[:0]
}
func (s *Snapshot) store(cID, iID uint64, item any) {
change := s.appendChange(cID, iID)
change.Store = true
buf := bytes.NewBuffer(change.Data[:0])
if err := json.NewEncoder(buf).Encode(item); err != nil {
panic(err)
}
change.Data = buf.Bytes()
}
func (s *Snapshot) delete(cID, iID uint64) {
change := s.appendChange(cID, iID)
change.Store = false
}
func (s *Snapshot) appendChange(cID, iID uint64) *change.Change {
if len(s.changes) == cap(s.changes) {
s.changes = append(s.changes, change.Change{})
} else {
s.changes = s.changes[:len(s.changes)+1]
}
change := &s.changes[len(s.changes)-1]
change.CollectionID = cID
change.ItemID = iID
change.Store = false
change.ClearPageIDs = change.ClearPageIDs[:0]
change.WritePageIDs = change.WritePageIDs[:0]
change.Data = change.Data[:0]
return change
}
func (s *Snapshot) begin() *Snapshot {
c := s.clone()
c.changes = c.changes[:0]
return c
}
func (s *Snapshot) clone() *Snapshot {
collections := make(map[uint64]any, len(s.collections))
for k, v := range s.collections {
collections[k] = v
}
c := &Snapshot{
version: s.version + 1,
collections: collections,
changes: s.changes[:],
}
c.parent.Store(s)
return c
}
func (s *Snapshot) rollback() *Snapshot {
parent := s.parent.Load()
if parent == nil {
return nil
}
// Don't throw away allocated changes.
parent.changes = s.changes[:len(parent.changes)]
return parent
}