Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

lplist.gno

7.70 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/nym-moultest123/ulist/v0"
 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)