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 }