better convert

This commit is contained in:
mats committed 2026-06-29 13:17:43 +02:00
1 parent 2a66754f9e
commit b128608316
7 files changed
+591 -20

No files matched your search

+35 -4
View File
@@ -45,9 +45,11 @@ type Store[W, T any] struct {
fallback Fallback[W]
holder *SnapshotHolder[T] // mmap base; atomic-swap + drain-grace
hashKeys bool // build the base + dedup Scan under the hashed key encoding
mu sync.RWMutex
epoch int64 // epoch of the live base
overlay map[string]overlayEntry[T] // same-epoch deltas layered on the base
overlay map[string]overlayEntry[T] // same-epoch deltas layered on the base (keyed by ORIGINAL key)
// in-progress snapshot assembly (one epoch at a time)
building *SnapshotWriter[T]
@@ -60,13 +62,24 @@ type overlayEntry[T any] struct {
deleted bool
}
// StoreOption configures a Store at construction.
type StoreOption func(*storeConfig)
type storeConfig struct{ hashKeys bool }
// WithKeyHashing makes the store build its base under the hashed key encoding,
// so unbounded keys (page paths, query strings) fit the fixed-width index. Get
// still takes the original key (it is hashed internally); Scan yields digests
// (recover the original from the value). Match this to how the producer emits.
func WithKeyHashing() StoreOption { return func(c *storeConfig) { c.hashKeys = true } }
// NewStore opens a store backed by dir/name (its own .bin). decode and
// transform are REQUIRED: decode parses the domain wire schema from a frame
// payload; transform projects each wire value to the stored shape. If a base
// from a previous run is present it is reopened immediately (cold-start ready);
// its epoch is read from the sidecar so same-epoch deltas can resume. fallback
// may be nil. Close() when done.
func NewStore[W, T any](dir, name string, decode Decoder[W], transform Transform[W, T], fallback Fallback[W]) (*Store[W, T], error) {
func NewStore[W, T any](dir, name string, decode Decoder[W], transform Transform[W, T], fallback Fallback[W], opts ...StoreOption) (*Store[W, T], error) {
if dir == "" || name == "" {
return nil, fmt.Errorf("projection: Store needs dir and name")
}
@@ -76,12 +89,17 @@ func NewStore[W, T any](dir, name string, decode Decoder[W], transform Transform
if transform == nil {
return nil, fmt.Errorf("projection: Store needs a transform (declare which facts this consumer stores)")
}
var cfg storeConfig
for _, o := range opts {
o(&cfg)
}
s := &Store[W, T]{
dir: dir,
name: name,
decode: decode,
transform: transform,
fallback: fallback,
hashKeys: cfg.hashKeys,
holder: NewSnapshotHolder[T](0),
overlay: make(map[string]overlayEntry[T]),
}
@@ -127,6 +145,9 @@ func (s *Store[W, T]) beginSnapshot(epoch int64, tenant string) error {
// Start (or restart) assembly for this epoch. A new begin supersedes any
// half-built snapshot (e.g. a producer that aborted mid-stream).
s.building = NewSnapshotWriter[T]().WithTenant(tenant)
if s.hashKeys {
s.building.WithKeyHashing()
}
s.buildingEpoch = epoch
s.buildingSeen = 0
return nil
@@ -271,13 +292,23 @@ func (s *Store[W, T]) Get(sku string) (T, bool) {
func (s *Store[W, T]) Scan(fn func(key string, v T) bool) {
s.mu.RLock()
defer s.mu.RUnlock()
// The base (holder) is keyed by stored key — a digest in hashed mode. To
// dedup overlay against it and emit one consistent key space, project the
// overlay's original keys through the same encoding.
storeKey := func(k string) string {
if s.hashKeys {
return hashKey(k)
}
return k
}
seen := make(map[string]struct{}, len(s.overlay))
for k, e := range s.overlay {
seen[k] = struct{}{}
sk := storeKey(k)
seen[sk] = struct{}{}
if e.deleted {
continue
}
if !fn(k, e.val) {
if !fn(sk, e.val) {
return
}
}