github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/merge/three_way_list.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // This file incorporates work covered by the following copyright and
    16  // permission notice:
    17  //
    18  // Copyright 2016 Attic Labs, Inc. All rights reserved.
    19  // Licensed under the Apache License, version 2.0:
    20  // http://www.apache.org/licenses/LICENSE-2.0
    21  
    22  package merge
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  
    28  	"golang.org/x/sync/errgroup"
    29  
    30  	"github.com/dolthub/dolt/go/store/d"
    31  	"github.com/dolthub/dolt/go/store/types"
    32  )
    33  
    34  func threeWayListMerge(ctx context.Context, a, b, parent types.List) (merged types.List, err error) {
    35  	aSpliceChan, bSpliceChan := make(chan types.Splice), make(chan types.Splice)
    36  
    37  	eg, ctx := errgroup.WithContext(ctx)
    38  	eg.Go(func() error {
    39  		defer close(aSpliceChan)
    40  		return a.Diff(ctx, parent, aSpliceChan)
    41  	})
    42  	eg.Go(func() error {
    43  		defer close(bSpliceChan)
    44  		return b.Diff(ctx, parent, bSpliceChan)
    45  	})
    46  
    47  	// The algorithm below relies on determining whether one splice "comes
    48  	// before" another, and whether the splices coming from the two diffs
    49  	// remove/add precisely the same elements. Unfortunately, the Golang
    50  	// zero-value for types.Splice (which is what gets read out of
    51  	// a/bSpliceChan when they're closed) is actaually a valid splice,
    52  	// albeit a meaningless one that indicates a no-op. It "comes before"
    53  	// any other splice, so having it in play really gums up the logic
    54  	// below. Rather than specifically checking for it all over the place,
    55  	// swap the zero-splice out for one full of SPLICE_UNASSIGNED, which is
    56  	// really the proper invalid splice value. That splice doesn't come
    57  	// before ANY valid splice, so the logic below can flow more clearly.
    58  	zeroSplice := types.Splice{}
    59  	zeroToInvalid := func(sp types.Splice) types.Splice {
    60  		if sp == zeroSplice {
    61  			return types.Splice{SpAt: types.SPLICE_UNASSIGNED, SpRemoved: types.SPLICE_UNASSIGNED, SpAdded: types.SPLICE_UNASSIGNED, SpFrom: types.SPLICE_UNASSIGNED}
    62  		}
    63  		return sp
    64  	}
    65  	invalidSplice := zeroToInvalid(types.Splice{})
    66  
    67  	merged = parent
    68  	eg.Go(func() error {
    69  		offset := uint64(0)
    70  		aSplice, bSplice := invalidSplice, invalidSplice
    71  		for {
    72  			// Get the next splice from both a and b. If either diff(a,
    73  			// parent) or diff(b, parent) is complete, aSplice or bSplice
    74  			// will get an invalid types.Splice. Generally, though, this
    75  			// allows us to proceed through both diffs in (index) order,
    76  			// considering the "current" splice from both diffs at the same
    77  			// time.
    78  			if aSplice == invalidSplice {
    79  				select {
    80  				case a := <-aSpliceChan:
    81  					aSplice = zeroToInvalid(a)
    82  				case <-ctx.Done():
    83  					return ctx.Err()
    84  				}
    85  			}
    86  			if bSplice == invalidSplice {
    87  				select {
    88  				case b := <-bSpliceChan:
    89  					bSplice = zeroToInvalid(b)
    90  				case <-ctx.Done():
    91  					return ctx.Err()
    92  				}
    93  			}
    94  			// Both channels are producing zero values, so we're done.
    95  			if aSplice == invalidSplice && bSplice == invalidSplice {
    96  				break
    97  			}
    98  			if overlap(aSplice, bSplice) {
    99  				if mergeable, err := canMerge(ctx, a, b, aSplice, bSplice); err != nil {
   100  					return err
   101  				} else if mergeable {
   102  					splice := merge(aSplice, bSplice)
   103  					merged, err = apply(ctx, a, merged, offset, splice)
   104  					if err != nil {
   105  						return err
   106  					}
   107  
   108  					offset += splice.SpAdded - splice.SpRemoved
   109  					aSplice, bSplice = invalidSplice, invalidSplice
   110  					continue
   111  				}
   112  				return newMergeConflict("Overlapping splices: %s vs %s", describeSplice(aSplice), describeSplice(bSplice))
   113  			}
   114  			if aSplice.SpAt < bSplice.SpAt {
   115  				merged, err = apply(ctx, a, merged, offset, aSplice)
   116  				if err != nil {
   117  					return err
   118  				}
   119  
   120  				offset += aSplice.SpAdded - aSplice.SpRemoved
   121  				aSplice = invalidSplice
   122  				continue
   123  			}
   124  			merged, err = apply(ctx, b, merged, offset, bSplice)
   125  			if err != nil {
   126  				return err
   127  			}
   128  
   129  			offset += bSplice.SpAdded - bSplice.SpRemoved
   130  			bSplice = invalidSplice
   131  		}
   132  		return nil
   133  	})
   134  
   135  	if err := eg.Wait(); err != nil {
   136  		return types.EmptyList, err
   137  	}
   138  
   139  	return merged, nil
   140  }
   141  
   142  func overlap(s1, s2 types.Splice) bool {
   143  	earlier, later := s1, s2
   144  	if s2.SpAt < s1.SpAt {
   145  		earlier, later = s2, s1
   146  	}
   147  	return s1.SpAt == s2.SpAt || earlier.SpAt+earlier.SpRemoved > later.SpAt
   148  }
   149  
   150  // canMerge returns whether aSplice and bSplice can be merged into a single splice that can be applied to parent. Currently, we're only willing to do this if the two splices do _precisely_ the same thing -- that is, remove the same number of elements from the same starting index and insert the exact same list of new elements.
   151  func canMerge(ctx context.Context, a, b types.List, aSplice, bSplice types.Splice) (bool, error) {
   152  	if aSplice != bSplice {
   153  		return false, nil
   154  	}
   155  	aIter, err := a.IteratorAt(ctx, aSplice.SpFrom)
   156  
   157  	if err != nil {
   158  		return false, err
   159  	}
   160  
   161  	bIter, err := b.IteratorAt(ctx, bSplice.SpFrom)
   162  
   163  	if err != nil {
   164  		return false, err
   165  	}
   166  
   167  	for count := uint64(0); count < aSplice.SpAdded; count++ {
   168  		aVal, err := aIter.Next(ctx)
   169  
   170  		if err != nil {
   171  			return false, err
   172  		}
   173  
   174  		bVal, err := bIter.Next(ctx)
   175  
   176  		if err != nil {
   177  			return false, err
   178  		}
   179  
   180  		if aVal == nil || bVal == nil || !aVal.Equals(bVal) {
   181  			return false, nil
   182  		}
   183  	}
   184  	return true, nil
   185  }
   186  
   187  // Since merge() is only called when canMerge() is true, we know s1 and s2 are exactly equal.
   188  func merge(s1, s2 types.Splice) types.Splice {
   189  	return s1
   190  }
   191  
   192  func apply(ctx context.Context, source, target types.List, offset uint64, s types.Splice) (types.List, error) {
   193  	toAdd := make([]types.Valuable, s.SpAdded)
   194  	iter, err := source.IteratorAt(ctx, s.SpFrom)
   195  
   196  	if err != nil {
   197  		return types.EmptyList, err
   198  	}
   199  
   200  	for i := 0; uint64(i) < s.SpAdded; i++ {
   201  		v, err := iter.Next(ctx)
   202  
   203  		if err != nil {
   204  			return types.EmptyList, err
   205  		}
   206  
   207  		if v == nil {
   208  			d.Panic("List diff returned a splice that inserts a nonexistent element.")
   209  		}
   210  		toAdd[i] = v
   211  	}
   212  	return target.Edit().Splice(s.SpAt+offset, s.SpRemoved, toAdd...).List(ctx)
   213  }
   214  
   215  func describeSplice(s types.Splice) string {
   216  	return fmt.Sprintf("%d elements removed at %d; adding %d elements", s.SpRemoved, s.SpAt, s.SpAdded)
   217  }