lplist.gno

7.69 Kb ยท 304 lines
  1// Package lplist provides a layered proxy implementation for lists that allows transparent
  2// migration of data between different schema versions.
  3//
  4// LayeredProxyList wraps an existing list (source) with a new list (target) and optionally
  5// applies migrations to source data when it's accessed. This enables schema evolution without
  6// requiring upfront migration of all data, making it ideal for large datasets or when
  7// preserving original data is important.
  8//
  9// Key features:
 10// - Lazy migration: Data is only transformed when accessed, not stored in migrated form
 11// - Append-only source: Source data is treated as immutable to preserve original data
 12// - Chaining: Multiple LayeredProxyLists can be stacked for multi-step migrations
 13//
 14// Example usage:
 15//
 16//	// Define data types for different schema versions
 17//	type UserV1 struct {
 18//	    Name string
 19//	    Age int
 20//	}
 21//
 22//	type UserV2 struct {
 23//	    FullName string
 24//	    Age int
 25//	    Active bool
 26//	}
 27//
 28//	// Create source list with old schema
 29//	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 V2
 36//	migrateUserV1ToV2 := func(v any) any {
 37//	    user := v.(UserV1)
 38//	    return UserV2{
 39//	        FullName: user.Name,  // Name field renamed to FullName
 40//	        Age: user.Age,
 41//	        Active: true,         // New field with default value
 42//	    }
 43//	}
 44//
 45//	// Create layered proxy with migration
 46//	proxy := NewLayeredProxyList(sourceList, migrateUserV1ToV2)
 47//
 48//	// Add new data directly in V2 format
 49//	proxy.Append(UserV2{FullName: "Charlie", Age: 40, Active: false})
 50//
 51//	// All access through proxy returns data in V2 format
 52//	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 lplist
 57
 58import (
 59	"errors"
 60
 61	"gno.land/p/moul/ulist"
 62)
 63
 64// MigratorFn is a function type that lazily converts values from source to target
 65type MigratorFn func(any) any
 66
 67// LayeredProxyList represents a wrapper around an existing List that handles migration
 68type LayeredProxyList struct {
 69	source       ulist.IList
 70	target       *ulist.List
 71	migrator     MigratorFn
 72	sourceHeight int // Store initial source size to optimize lookups
 73}
 74
 75// NewLayeredProxyList creates a new LayeredProxyList instance that wraps an existing List
 76func 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}
 86
 87// Get retrieves the value at the specified index
 88// Uses sourceHeight to efficiently route requests
 89func (l *LayeredProxyList) Get(index int) any {
 90	if index < l.sourceHeight {
 91		// Direct access to source for indices below sourceHeight
 92		val := l.source.Get(index)
 93		if val == nil {
 94			return nil
 95		}
 96		// Only apply migrator if it exists
 97		if l.migrator != nil {
 98			return l.migrator(val)
 99		}
100		return val
101	}
102	// For indices >= sourceHeight, adjust index to be relative to target list starting at 0
103	targetIndex := index - l.sourceHeight
104	return l.target.Get(targetIndex)
105}
106
107// Append adds one or more values to the target list
108func (l *LayeredProxyList) Append(values ...any) {
109	l.target.Append(values...)
110}
111
112// Delete marks elements as deleted in the appropriate list
113func (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 err
119			}
120		}
121	}
122
123	for _, index := range indices {
124		targetIndex := index - l.sourceHeight
125		err := l.target.Delete(targetIndex)
126		if err != nil {
127			return err
128		}
129	}
130	return nil
131}
132
133// Size returns the total number of active elements
134func (l *LayeredProxyList) Size() int {
135	return l.source.Size() + l.target.Size()
136}
137
138// TotalSize returns the total number of elements in the list
139func (l *LayeredProxyList) TotalSize() int {
140	return l.sourceHeight + l.target.TotalSize()
141}
142
143// MustDelete deletes elements, panicking on error
144func (l *LayeredProxyList) MustDelete(indices ...int) {
145	if err := l.Delete(indices...); err != nil {
146		panic(err)
147	}
148}
149
150// MustGet retrieves a value, panicking if not found
151func (l *LayeredProxyList) MustGet(index int) any {
152	val := l.Get(index)
153	if val == nil {
154		panic(ulist.ErrDeleted)
155	}
156	return val
157}
158
159// GetRange returns elements between start and end indices
160func (l *LayeredProxyList) GetRange(start, end int) []ulist.Entry {
161	var entries []ulist.Entry
162	l.Iterator(start, end, func(index int, value any) bool {
163		entries = append(entries, ulist.Entry{Index: index, Value: value})
164		return false
165	})
166	return entries
167}
168
169// GetRangeByOffset returns elements starting from offset
170func (l *LayeredProxyList) GetRangeByOffset(offset int, count int) []ulist.Entry {
171	var entries []ulist.Entry
172	l.IteratorByOffset(offset, count, func(index int, value any) bool {
173		entries = append(entries, ulist.Entry{Index: index, Value: value})
174		return false
175	})
176	return entries
177}
178
179// Iterator performs iteration between start and end indices
180func (l *LayeredProxyList) Iterator(start, end int, cb ulist.IterCbFn) bool {
181	// For empty list or invalid range
182	if start < 0 && end < 0 {
183		return false
184	}
185
186	// Normalize indices
187	if start < 0 {
188		start = 0
189	}
190	if end < 0 {
191		end = 0
192	}
193
194	totalSize := l.TotalSize()
195	if end >= totalSize {
196		end = totalSize - 1
197	}
198	if start >= totalSize {
199		start = totalSize - 1
200	}
201
202	// Handle reverse iteration
203	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 true
209				}
210			}
211		}
212		return false
213	}
214
215	// Handle forward iteration
216	for i := start; i <= end; i++ {
217		val := l.Get(i)
218		if val != nil {
219			if cb(i, val) {
220				return true
221			}
222		}
223	}
224	return false
225}
226
227// IteratorByOffset performs iteration starting from offset
228func (l *LayeredProxyList) IteratorByOffset(offset int, count int, cb ulist.IterCbFn) bool {
229	if count == 0 {
230		return false
231	}
232
233	// Normalize offset
234	if offset < 0 {
235		offset = 0
236	}
237	totalSize := l.TotalSize()
238	if offset >= totalSize {
239		offset = totalSize - 1
240	}
241
242	// Determine end based on count direction
243	var end int
244	if count > 0 {
245		end = totalSize - 1
246	} else {
247		end = 0
248	}
249
250	wrapperReturned := false
251
252	// Calculate absolute value manually instead of using abs function
253	remaining := count
254	if remaining < 0 {
255		remaining = -remaining
256	}
257
258	wrapper := func(index int, value any) bool {
259		if remaining <= 0 {
260			wrapperReturned = true
261			return true
262		}
263		remaining--
264		return cb(index, value)
265	}
266
267	ret := l.Iterator(offset, end, wrapper)
268	if wrapperReturned {
269		return false
270	}
271	return ret
272}
273
274// Set updates the value at the specified index
275func (l *LayeredProxyList) Set(index int, value any) error {
276	if index < l.sourceHeight {
277		// Cannot modify source list directly
278		return errors.New("cannot modify source list directly")
279	}
280
281	// Adjust index to be relative to target list starting at 0
282	targetIndex := index - l.sourceHeight
283	return l.target.Set(targetIndex, value)
284}
285
286// MustSet updates the value at the specified index, panicking on error
287func (l *LayeredProxyList) MustSet(index int, value any) {
288	if err := l.Set(index, value); err != nil {
289		panic(err)
290	}
291}
292
293// GetByOffset returns elements starting from offset with count determining direction
294func (l *LayeredProxyList) GetByOffset(offset int, count int) []ulist.Entry {
295	var entries []ulist.Entry
296	l.IteratorByOffset(offset, count, func(index int, value any) bool {
297		entries = append(entries, ulist.Entry{Index: index, Value: value})
298		return false
299	})
300	return entries
301}
302
303// Verify that LayeredProxyList implements IList
304var _ ulist.IList = (*LayeredProxyList)(nil)