github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/graveler/committed/compare_iterator.go (about)

     1  package committed
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  
     8  	"github.com/treeverse/lakefs/pkg/graveler"
     9  )
    10  
    11  type compareIterator struct {
    12  	ctx             context.Context
    13  	errorOnConflict bool
    14  	diffIt          DiffIterator
    15  	val             *graveler.Diff
    16  	rng             *RangeDiff
    17  	base            Iterator
    18  	err             error
    19  }
    20  
    21  var ErrUnsupportedRangeDiffType = errors.New("range diff type unsupported - supports only added and removed")
    22  
    23  // NewCompareIterator accepts an iterator describing a diff from the merge destination to the source.
    24  // It returns a DiffIterator with the changes to perform on the destination branch, in order to merge the source into it,
    25  // relative to base as the merge base.
    26  // When reaching a conflict, the returned Diff will be of type graveler.DiffTypeConflict.
    27  func NewCompareIterator(ctx context.Context, diffDestToSource DiffIterator, base Iterator) *compareIterator {
    28  	return &compareIterator{
    29  		ctx:             ctx,
    30  		diffIt:          diffDestToSource,
    31  		base:            base,
    32  		errorOnConflict: false,
    33  	}
    34  }
    35  
    36  // baseGE returns value ( and its range) from base iterator, which is greater or equal than the given key
    37  func (d *compareIterator) baseGE(key graveler.Key) (*graveler.ValueRecord, *Range, error) {
    38  	d.base.SeekGE(key)
    39  	if !d.base.Next() {
    40  		return nil, nil, d.err
    41  	}
    42  	baseValue, baseRange := d.base.Value()
    43  	return baseValue, baseRange, nil
    44  }
    45  
    46  func (d *compareIterator) valueFromBase(key graveler.Key) (*graveler.ValueRecord, error) {
    47  	d.base.SeekGE(key)
    48  	var val *graveler.ValueRecord
    49  	for d.base.Next() && val == nil {
    50  		val, _ = d.base.Value()
    51  	}
    52  	if err := d.base.Err(); err != nil {
    53  		return nil, err
    54  	}
    55  	if val == nil || !bytes.Equal(val.Key, key) {
    56  		return nil, nil
    57  	}
    58  	return val, nil
    59  }
    60  
    61  func (d *compareIterator) handleConflict() bool {
    62  	if d.errorOnConflict {
    63  		d.err = graveler.ErrConflictFound
    64  		return false
    65  	}
    66  	val, rng := d.diffIt.Value()
    67  	if val != nil {
    68  		d.val = val.Copy()
    69  	}
    70  	d.setRangeDiff(rng)
    71  	d.val.Type = graveler.DiffTypeConflict
    72  	return true
    73  }
    74  
    75  func (d *compareIterator) setRangeDiff(r *RangeDiff) {
    76  	if r != nil {
    77  		d.rng = r.Copy()
    78  		return
    79  	}
    80  	d.rng = nil
    81  }
    82  
    83  // stepNext is called after diffIt Next or NextRange and iterates over diff iterator until the compare iterator has a value
    84  func (d *compareIterator) stepNext() bool {
    85  	for {
    86  		select {
    87  		case <-d.ctx.Done():
    88  			d.err = d.ctx.Err()
    89  			return false
    90  		default:
    91  		}
    92  		var hasNext bool
    93  		var done bool
    94  		val, _ := d.diffIt.Value()
    95  		if val == nil {
    96  			// range header
    97  			hasNext, done = d.stepRange()
    98  		} else {
    99  			hasNext, done = d.stepValue()
   100  		}
   101  		if done {
   102  			return hasNext
   103  		}
   104  		if !hasNext {
   105  			break
   106  		}
   107  	}
   108  	if d.err != nil {
   109  		d.err = d.diffIt.Err()
   110  	}
   111  	return false
   112  }
   113  
   114  // stepValue moves one step according to current value
   115  // returns hasMore if iterator has more, and done if the step is over  (got to a value, end of iterator, or error)
   116  func (d *compareIterator) stepValue() (hasNext, done bool) {
   117  	val, rngDiff := d.diffIt.Value()
   118  	key := val.Key
   119  	typ := val.Type
   120  	baseVal, err := d.valueFromBase(key)
   121  	if err != nil {
   122  		d.err = err
   123  		return false, true
   124  	}
   125  	switch typ {
   126  	case graveler.DiffTypeAdded:
   127  		// exists on source, but not on dest
   128  		if baseVal == nil {
   129  			// added only on source
   130  			d.val = val.Copy()
   131  			d.setRangeDiff(rngDiff)
   132  			return true, true
   133  		}
   134  		if !bytes.Equal(baseVal.Identity, val.Value.Identity) {
   135  			// removed on dest, but changed on source
   136  			return d.handleConflict(), true
   137  		}
   138  	case graveler.DiffTypeChanged:
   139  		if baseVal == nil {
   140  			// added on dest and source, with different identities
   141  			return d.handleConflict(), true
   142  		}
   143  		if bytes.Equal(baseVal.Identity, val.Value.Identity) {
   144  			// changed on dest, but not on source
   145  			return d.diffIt.Next(), false
   146  		}
   147  		if !bytes.Equal(baseVal.Identity, val.LeftIdentity) {
   148  			// changed on dest and source, to different identities
   149  			return d.handleConflict(), true
   150  		}
   151  		// changed only on source
   152  		d.val = val.Copy()
   153  		d.setRangeDiff(rngDiff)
   154  		return true, true
   155  	case graveler.DiffTypeRemoved:
   156  		// exists on dest, but not on source
   157  		if baseVal != nil {
   158  			if bytes.Equal(baseVal.Identity, val.LeftIdentity) {
   159  				// removed on source, not changed on dest
   160  				d.val = val.Copy()
   161  				d.setRangeDiff(rngDiff)
   162  				return true, true
   163  			}
   164  			// changed on dest, removed on source
   165  			return d.handleConflict(), true
   166  		}
   167  		// added on dest, but not on source - next value
   168  	}
   169  	return d.diffIt.Next(), false
   170  }
   171  
   172  // stepRange moves one step according to current range
   173  // returns hasMore if iterator has more, and done if the step is over (got to a value, end of iterator, or error)
   174  func (d *compareIterator) stepRange() (hasMore bool, done bool) {
   175  	_, rngDiff := d.diffIt.Value()
   176  	typ := rngDiff.Type
   177  	rng := rngDiff.Range
   178  	leftID := rngDiff.LeftIdentity
   179  	baseValue, baseRange, err := d.baseGE(graveler.Key(rng.MinKey))
   180  	if err != nil {
   181  		d.err = err
   182  		return false, true
   183  	}
   184  	switch typ {
   185  	case graveler.DiffTypeAdded:
   186  		// exists on source, but not on dest
   187  		if baseRange != nil && rng.ID == baseRange.ID {
   188  			// removed only on dest -> skip range
   189  			return d.diffIt.NextRange(), false
   190  		}
   191  		if baseValue == nil || bytes.Compare(rng.MaxKey, baseValue.Key) < 0 {
   192  			// added only on source
   193  			d.setRangeDiff(rngDiff)
   194  			d.val = nil
   195  			return true, true
   196  		}
   197  		// overlapping base and diff, must step into range
   198  		return d.diffIt.Next(), false
   199  	case graveler.DiffTypeRemoved:
   200  		if baseRange != nil && rng.ID == baseRange.ID {
   201  			// removed on source, not changed on dest
   202  			d.setRangeDiff(rngDiff)
   203  			d.val = nil
   204  			return true, true
   205  		}
   206  		if (baseRange != nil && bytes.Compare(rng.MaxKey, baseRange.MinKey) < 0) || baseValue == nil || bytes.Compare(rng.MaxKey, baseValue.Key) < 0 {
   207  			// added on dest, but not on source, skip range
   208  			return d.diffIt.NextRange(), false
   209  		}
   210  
   211  		// overlapping base and diff, must step into range
   212  		return d.diffIt.Next(), false
   213  
   214  	case graveler.DiffTypeChanged:
   215  		if baseRange != nil && leftID == baseRange.ID {
   216  			// changed only in source
   217  			d.setRangeDiff(rngDiff)
   218  			d.val = nil
   219  			return true, true
   220  		}
   221  		if baseRange != nil && bytes.Compare(rng.MaxKey, baseRange.MinKey) < 0 {
   222  			// conflict, added on dest and source
   223  			return d.handleConflict(), true
   224  		}
   225  		if baseRange != nil && rng.ID == baseRange.ID {
   226  			// changed on dest, but not on source, skip range
   227  			return d.diffIt.NextRange(), false
   228  		}
   229  		return d.diffIt.Next(), false
   230  	default:
   231  		d.err = ErrUnsupportedRangeDiffType
   232  	}
   233  	return false, true
   234  }
   235  
   236  func (d *compareIterator) Next() bool {
   237  	if d.diffIt.Next() {
   238  		return d.stepNext()
   239  	}
   240  	d.err = d.diffIt.Err()
   241  	return false
   242  }
   243  
   244  func (d *compareIterator) NextRange() bool {
   245  	if !d.diffIt.NextRange() {
   246  		d.err = d.diffIt.Err()
   247  		return false
   248  	}
   249  	return d.stepNext()
   250  }
   251  
   252  func (d *compareIterator) SeekGE(id graveler.Key) {
   253  	d.val = nil
   254  	d.err = nil
   255  	d.diffIt.SeekGE(id)
   256  }
   257  
   258  func (d *compareIterator) Value() (*graveler.Diff, *RangeDiff) {
   259  	if d.err != nil {
   260  		return nil, nil
   261  	}
   262  	return d.val, d.rng
   263  }
   264  
   265  func (d *compareIterator) Err() error {
   266  	return d.err
   267  }
   268  
   269  func (d *compareIterator) Close() {
   270  	d.diffIt.Close()
   271  	d.base.Close()
   272  }