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 }