1// Package lplist provides a layered proxy implementation for lists that allows transparent2// migration of data between different schema versions.3//4// LayeredProxyList wraps an existing list (source) with a new list (target) and optionally5// applies migrations to source data when it's accessed. This enables schema evolution without6// requiring upfront migration of all data, making it ideal for large datasets or when7// preserving original data is important.8//9// Key features:10// - Lazy migration: Data is only transformed when accessed, not stored in migrated form11// - Append-only source: Source data is treated as immutable to preserve original data12// - Chaining: Multiple LayeredProxyLists can be stacked for multi-step migrations13//14// Example usage:15//16// // Define data types for different schema versions17// type UserV1 struct {18// Name string19// Age int20// }21//22// type UserV2 struct {23// FullName string24// Age int25// Active bool26// }27//28// // Create source list with old schema29// sourceList := ulist.New()30// sourceList.Append(31// UserV1{Name: "Alice", Age: 30},32// UserV1{Name: "Bob", Age: 25},33// )34//35// // Define migration function from V1 to V236// migrateUserV1ToV2 := func(v any) any {37// user := v.(UserV1)38// return UserV2{39// FullName: user.Name, // Name field renamed to FullName40// Age: user.Age,41// Active: true, // New field with default value42// }43// }44//45// // Create layered proxy with migration46// proxy := NewLayeredProxyList(sourceList, migrateUserV1ToV2)47//48// // Add new data directly in V2 format49// proxy.Append(UserV2{FullName: "Charlie", Age: 40, Active: false})50//51// // All access through proxy returns data in V2 format52// for i := 0; i < proxy.Size(); i++ {53// user := proxy.Get(i).(UserV2)54// fmt.Printf("User: %s, Age: %d, Active: %t\n", user.FullName, user.Age, user.Active)55// }56package lplist5758import (59 "errors"6061 "gno.land/p/moul/ulist/v0"62)6364// MigratorFn is a function type that lazily converts values from source to target65type MigratorFn func(any) any6667// LayeredProxyList represents a wrapper around an existing List that handles migration68type LayeredProxyList struct {69 source ulist.IList70 target *ulist.List71 migrator MigratorFn72 sourceHeight int // Store initial source size to optimize lookups73}7475// NewLayeredProxyList creates a new LayeredProxyList instance that wraps an existing List76func NewLayeredProxyList(source ulist.IList, migrator MigratorFn) *LayeredProxyList {77 sourceHeight := source.TotalSize()78 target := ulist.New()79 return &LayeredProxyList{80 source: source,81 target: target,82 migrator: migrator,83 sourceHeight: sourceHeight,84 }85}8687// Get retrieves the value at the specified index88// Uses sourceHeight to efficiently route requests89func (l *LayeredProxyList) Get(index int) any {90 if index < l.sourceHeight {91 // Direct access to source for indices below sourceHeight92 val := l.source.Get(index)93 if val == nil {94 return nil95 }96 // Only apply migrator if it exists97 if l.migrator != nil {98 return l.migrator(val)99 }100 return val101 }102 // For indices >= sourceHeight, adjust index to be relative to target list starting at 0103 targetIndex := index - l.sourceHeight104 return l.target.Get(targetIndex)105}106107// Append adds one or more values to the target list108func (l *LayeredProxyList) Append(values ...any) {109 l.target.Append(values...)110}111112// Delete marks elements as deleted in the appropriate list113func (l *LayeredProxyList) Delete(indices ...int) error {114 for _, index := range indices {115 if index < l.sourceHeight {116 err := l.source.Delete(index)117 if err != nil {118 return err119 }120 }121 }122123 for _, index := range indices {124 targetIndex := index - l.sourceHeight125 err := l.target.Delete(targetIndex)126 if err != nil {127 return err128 }129 }130 return nil131}132133// Size returns the total number of active elements134func (l *LayeredProxyList) Size() int {135 return l.source.Size() + l.target.Size()136}137138// TotalSize returns the total number of elements in the list139func (l *LayeredProxyList) TotalSize() int {140 return l.sourceHeight + l.target.TotalSize()141}142143// MustDelete deletes elements, panicking on error144func (l *LayeredProxyList) MustDelete(indices ...int) {145 if err := l.Delete(indices...); err != nil {146 panic(err)147 }148}149150// MustGet retrieves a value, panicking if not found151func (l *LayeredProxyList) MustGet(index int) any {152 val := l.Get(index)153 if val == nil {154 panic(ulist.ErrDeleted)155 }156 return val157}158159// GetRange returns elements between start and end indices160func (l *LayeredProxyList) GetRange(start, end int) []ulist.Entry {161 var entries []ulist.Entry162 l.Iterator(start, end, func(index int, value any) bool {163 entries = append(entries, ulist.Entry{Index: index, Value: value})164 return false165 })166 return entries167}168169// GetRangeByOffset returns elements starting from offset170func (l *LayeredProxyList) GetRangeByOffset(offset int, count int) []ulist.Entry {171 var entries []ulist.Entry172 l.IteratorByOffset(offset, count, func(index int, value any) bool {173 entries = append(entries, ulist.Entry{Index: index, Value: value})174 return false175 })176 return entries177}178179// Iterator performs iteration between start and end indices180func (l *LayeredProxyList) Iterator(start, end int, cb ulist.IterCbFn) bool {181 // For empty list or invalid range182 if start < 0 && end < 0 {183 return false184 }185186 // Normalize indices187 if start < 0 {188 start = 0189 }190 if end < 0 {191 end = 0192 }193194 totalSize := l.TotalSize()195 if end >= totalSize {196 end = totalSize - 1197 }198 if start >= totalSize {199 start = totalSize - 1200 }201202 // Handle reverse iteration203 if start > end {204 for i := start; i >= end; i-- {205 val := l.Get(i)206 if val != nil {207 if cb(i, val) {208 return true209 }210 }211 }212 return false213 }214215 // Handle forward iteration216 for i := start; i <= end; i++ {217 val := l.Get(i)218 if val != nil {219 if cb(i, val) {220 return true221 }222 }223 }224 return false225}226227// IteratorByOffset performs iteration starting from offset228func (l *LayeredProxyList) IteratorByOffset(offset int, count int, cb ulist.IterCbFn) bool {229 if count == 0 {230 return false231 }232233 // Normalize offset234 if offset < 0 {235 offset = 0236 }237 totalSize := l.TotalSize()238 if offset >= totalSize {239 offset = totalSize - 1240 }241242 // Determine end based on count direction243 var end int244 if count > 0 {245 end = totalSize - 1246 } else {247 end = 0248 }249250 wrapperReturned := false251252 // Calculate absolute value manually instead of using abs function253 remaining := count254 if remaining < 0 {255 remaining = -remaining256 }257258 wrapper := func(index int, value any) bool {259 if remaining <= 0 {260 wrapperReturned = true261 return true262 }263 remaining--264 return cb(index, value)265 }266267 ret := l.Iterator(offset, end, wrapper)268 if wrapperReturned {269 return false270 }271 return ret272}273274// Set updates the value at the specified index275func (l *LayeredProxyList) Set(index int, value any) error {276 if index < l.sourceHeight {277 // Cannot modify source list directly278 return errors.New("cannot modify source list directly")279 }280281 // Adjust index to be relative to target list starting at 0282 targetIndex := index - l.sourceHeight283 return l.target.Set(targetIndex, value)284}285286// MustSet updates the value at the specified index, panicking on error287func (l *LayeredProxyList) MustSet(index int, value any) {288 if err := l.Set(index, value); err != nil {289 panic(err)290 }291}292293// GetByOffset returns elements starting from offset with count determining direction294func (l *LayeredProxyList) GetByOffset(offset int, count int) []ulist.Entry {295 var entries []ulist.Entry296 l.IteratorByOffset(offset, count, func(index int, value any) bool {297 entries = append(entries, ulist.Entry{Index: index, Value: value})298 return false299 })300 return entries301}302303// Verify that LayeredProxyList implements IList304var _ ulist.IList = (*LayeredProxyList)(nil)305Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.