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)