github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/prolly/tree/mutator.go (about)

     1  // Copyright 2021 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  package tree
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  
    21  	"github.com/dolthub/dolt/go/store/prolly/message"
    22  )
    23  
    24  type MutationIter interface {
    25  	NextMutation(ctx context.Context) (key, value Item)
    26  	Close() error
    27  }
    28  
    29  // ApplyMutations applies a sorted series of edits to a NodeStore,
    30  // returning the new root Node.
    31  //
    32  // The algorithm is structured as follows:
    33  //
    34  //   - Create a new chunker, the main interface for building a new
    35  //     tree.
    36  //
    37  //   - Create two cursors into the previous tree. Both cursors
    38  //     track key indexes in the old keyspace. The first tracks where
    39  //     a new edit will be applied relative to the old keyspace.
    40  //     The second indicates the most recent edit in the new tree
    41  //     relative to the old keyspace. The second cursor is embedded in
    42  //     the chunker, maintained by the chunker, and necessary precedes
    43  //     the first.
    44  //
    45  //   - For every edit, first identify the key index in the old keyspace
    46  //     where the edit will be applied, and move the tracking cursor to
    47  //     that index.
    48  //
    49  //   - Advance the chunker and the second cursor to the new edit point.
    50  //     Refer to the chunker.AdvanceTo docstring for details.
    51  //
    52  //   - Add the edit to the chunker. This applies the edit to the in-progress
    53  //     NodeStore. The new NodeStore may expand or shrink relative to the
    54  //     old tree, but these details are internal to the chunker.
    55  //
    56  //   - Repeat for every edit.
    57  //
    58  //   - Finalize the chunker and resolve the tree's new root Node.
    59  func ApplyMutations[K ~[]byte, O Ordering[K], S message.Serializer](
    60  	ctx context.Context,
    61  	ns NodeStore,
    62  	root Node,
    63  	order O,
    64  	serializer S,
    65  	edits MutationIter,
    66  ) (Node, error) {
    67  	newKey, newValue := edits.NextMutation(ctx)
    68  	if newKey == nil {
    69  		return root, nil // no mutations
    70  	}
    71  
    72  	cur, err := newCursorAtKey(ctx, ns, root, K(newKey), order)
    73  	if err != nil {
    74  		return Node{}, err
    75  	}
    76  
    77  	chkr, err := newChunker(ctx, cur.clone(), 0, ns, serializer)
    78  	if err != nil {
    79  		return Node{}, err
    80  	}
    81  
    82  	for newKey != nil {
    83  
    84  		// move |cur| to the NextMutation mutation point
    85  		err = Seek(ctx, cur, K(newKey), order)
    86  		if err != nil {
    87  			return Node{}, err
    88  		}
    89  
    90  		var oldValue Item
    91  		if cur.Valid() {
    92  			// Compare mutations |newKey| and |newValue|
    93  			// to the existing pair from the cursor
    94  			if order.Compare(K(newKey), K(cur.CurrentKey())) == 0 {
    95  				oldValue = cur.currentValue()
    96  			}
    97  
    98  			// check for no-op mutations
    99  			// this includes comparing the key bytes because two equal keys may have different bytes,
   100  			// in which case we need to update the index to match the bytes in the table.
   101  			if equalValues(newValue, oldValue) && bytes.Equal(newKey, cur.CurrentKey()) {
   102  				newKey, newValue = edits.NextMutation(ctx)
   103  				continue
   104  			}
   105  		}
   106  
   107  		if oldValue == nil && newValue == nil {
   108  			// Don't try to delete what isn't there.
   109  			newKey, newValue = edits.NextMutation(ctx)
   110  			continue
   111  		}
   112  
   113  		// move |chkr| to the NextMutation mutation point
   114  		err = chkr.advanceTo(ctx, cur)
   115  		if err != nil {
   116  			return Node{}, err
   117  		}
   118  
   119  		if oldValue == nil {
   120  			err = chkr.AddPair(ctx, newKey, newValue)
   121  		} else {
   122  			if newValue != nil {
   123  				err = chkr.UpdatePair(ctx, newKey, newValue)
   124  			} else {
   125  				err = chkr.DeletePair(ctx, newKey, oldValue)
   126  			}
   127  		}
   128  		if err != nil {
   129  			return Node{}, err
   130  		}
   131  
   132  		prev := newKey
   133  		newKey, newValue = edits.NextMutation(ctx)
   134  		if newKey != nil {
   135  			assertTrue(order.Compare(K(newKey), K(prev)) > 0, "expected sorted edits")
   136  		}
   137  	}
   138  
   139  	return chkr.Done(ctx)
   140  }
   141  
   142  func equalValues(left, right Item) bool {
   143  	return bytes.Equal(left, right)
   144  }