github.com/ethersphere/bee/v2@v2.2.0/pkg/storage/migration/index.go (about) 1 // Copyright 2022 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package migration 6 7 import ( 8 "errors" 9 "fmt" 10 11 storage "github.com/ethersphere/bee/v2/pkg/storage" 12 ) 13 14 var ErrItemIDShouldntChange = errors.New("item.ID shouldn't be changing after update") 15 16 type ( 17 // ItemDeleteFn is callback function called in migration step 18 // to check if Item should be removed in this step. 19 ItemDeleteFn func(storage.Item) (deleted bool) 20 21 // ItemUpdateFn is callback function called in migration step 22 // to check if Item should be updated in this step. 23 ItemUpdateFn func(storage.Item) (updatedItem storage.Item, hasChanged bool) 24 ) 25 26 // WithItemDeleteFn return option with ItemDeleteFn set. 27 func WithItemDeleteFn(fn ItemDeleteFn) option { 28 return func(o *options) { 29 o.deleteFn = fn 30 } 31 } 32 33 // WithItemUpdaterFn return option with ItemUpdateFn set. 34 func WithItemUpdaterFn(fn ItemUpdateFn) option { 35 return func(o *options) { 36 o.updateFn = fn 37 } 38 } 39 40 type option func(*options) 41 42 type options struct { 43 deleteFn ItemDeleteFn 44 updateFn ItemUpdateFn 45 opPerBatch int 46 } 47 48 func defaultOptions() *options { 49 return &options{ 50 deleteFn: func(storage.Item) bool { return false }, 51 updateFn: func(i storage.Item) (storage.Item, bool) { return i, false }, 52 opPerBatch: 100, 53 } 54 } 55 56 func (o *options) applyAll(opts []option) { 57 for _, opt := range opts { 58 opt(o) 59 } 60 } 61 62 // NewStepOnIndex creates new migration step with update and/or delete operation. 63 // Migration will iterate on all elements selected by query and delete or update items 64 // based on supplied callback functions. 65 func NewStepOnIndex(s storage.BatchStore, query storage.Query, opts ...option) StepFn { 66 o := defaultOptions() 67 o.applyAll(opts) 68 69 return func() error { 70 return stepOnIndex(s, query, o) 71 } 72 } 73 74 func stepOnIndex(s storage.Store, query storage.Query, o *options) error { 75 var itemsForDelete, itemsForUpdate []storage.Item 76 last := 0 77 78 for { 79 itemsForDelete = itemsForDelete[:0] 80 itemsForUpdate = itemsForUpdate[:0] 81 i := 0 82 83 err := s.Iterate(query, func(r storage.Result) (bool, error) { 84 if len(itemsForDelete)+len(itemsForUpdate) == o.opPerBatch { 85 return true, nil 86 } 87 88 i++ 89 if i <= last { 90 return false, nil 91 } 92 93 item := r.Entry 94 95 if deleteItem := o.deleteFn(item); deleteItem { 96 itemsForDelete = append(itemsForDelete, newKey(item)) 97 i-- 98 return false, nil 99 } 100 101 oldID := item.ID() 102 if updatedItem, hadChanged := o.updateFn(item); hadChanged { 103 if oldID != updatedItem.ID() { 104 return true, ErrItemIDShouldntChange 105 } 106 107 itemsForUpdate = append(itemsForUpdate, updatedItem) 108 return false, nil 109 } 110 111 return false, nil 112 }) 113 if err != nil { 114 return err 115 } 116 117 if err := deleteAll(s, itemsForDelete); err != nil { 118 return err 119 } 120 121 if err := putAll(s, itemsForUpdate); err != nil { 122 return err 123 } 124 125 if len(itemsForDelete) == 0 && len(itemsForUpdate) == 0 { 126 break 127 } 128 129 last = i 130 } 131 132 return nil 133 } 134 135 func deleteAll(s storage.Store, items []storage.Item) error { 136 for _, item := range items { 137 if err := s.Delete(item); err != nil { 138 return err 139 } 140 } 141 142 return nil 143 } 144 145 func putAll(s storage.Store, items []storage.Item) error { 146 for _, item := range items { 147 if err := s.Put(item); err != nil { 148 return err 149 } 150 } 151 152 return nil 153 } 154 155 type key struct { 156 storage.Marshaler 157 storage.Unmarshaler 158 storage.Cloner 159 fmt.Stringer 160 161 id string 162 namespace string 163 } 164 165 func newKey(k storage.Key) *key { 166 return &key{ 167 id: k.ID(), 168 namespace: k.Namespace(), 169 } 170 } 171 172 func (k *key) ID() string { return k.id } 173 func (k *key) Namespace() string { return k.namespace }