github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/diff/print_diff.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 diff 23 24 import ( 25 "context" 26 "io" 27 28 "github.com/dustin/go-humanize" 29 "golang.org/x/sync/errgroup" 30 31 "github.com/dolthub/dolt/go/store/types" 32 "github.com/dolthub/dolt/go/store/util/writers" 33 ) 34 35 type prefixOp string 36 37 const ( 38 ADD = "+ " 39 DEL = "- " 40 ) 41 42 type ( 43 printFunc func(ctx context.Context, w io.Writer, op prefixOp, key, val types.Value) error 44 ) 45 46 // PrintDiff writes a textual reprensentation of the diff from |v1| to |v2| 47 // to |w|. If |leftRight| is true then the left-right diff is used for ordered 48 // sequences - see Diff vs DiffLeftRight in Set and Map. 49 func PrintDiff(ctx context.Context, w io.Writer, v1, v2 types.Value, leftRight bool) (err error) { 50 // In the case where the diff involves two simple values, just print out the 51 // diff and return. This is needed because the code below assumes that the 52 // values being compared have a parent. 53 if !ShouldDescend(v1, v2) { 54 err := line(ctx, w, DEL, nil, v1) 55 56 if err != nil { 57 return err 58 } 59 60 return line(ctx, w, ADD, nil, v2) 61 } 62 63 eg, ctx := errgroup.WithContext(ctx) 64 dChan := make(chan Difference, 16) 65 66 // From here on, we can assume that every Difference will have at least one 67 // element in the Path. 68 eg.Go(func() error { 69 defer close(dChan) 70 return Diff(ctx, v1, v2, dChan, leftRight, nil) 71 }) 72 eg.Go(func() error { 73 var lastParentPath types.Path 74 wroteHdr := false 75 firstTime := true 76 77 for d := range dChan { 78 parentPath := d.Path[:len(d.Path)-1] 79 parentPathChanged := !parentPath.Equals(lastParentPath) 80 lastParentPath = parentPath 81 if parentPathChanged && wroteHdr { 82 err = writeFooter(w, &wroteHdr) 83 if err != nil { 84 return err 85 } 86 } 87 if parentPathChanged || firstTime { 88 firstTime = false 89 err = writeHeader(w, parentPath, &wroteHdr) 90 if err != nil { 91 return err 92 } 93 } 94 95 lastPart := d.Path[len(d.Path)-1] 96 parentEl, err := parentPath.Resolve(ctx, v1, nil) 97 98 var key types.Value 99 var pfunc printFunc = line 100 101 switch parent := parentEl.(type) { 102 case types.Map: 103 if indexPath, ok := lastPart.(types.IndexPath); ok { 104 key = indexPath.Index 105 } else if hip, ok := lastPart.(types.HashIndexPath); ok { 106 // In this case, the map has a non-primitive key so the value 107 // is a ref to the key. We need the actual key, not a ref to it. 108 hip1 := hip 109 hip1.IntoKey = true 110 key, err = hip1.Resolve(ctx, parent, nil) 111 if err != nil { 112 return err 113 } 114 } else { 115 panic("unexpected Path type") 116 } 117 case types.Set: 118 // default values are ok 119 case types.Struct: 120 key = types.String(lastPart.(types.FieldPath).Name) 121 pfunc = field 122 case types.List: 123 // default values are ok 124 } 125 126 if d.OldValue != nil { 127 err = pfunc(ctx, w, DEL, key, d.OldValue) 128 } 129 if d.NewValue != nil { 130 err = pfunc(ctx, w, ADD, key, d.NewValue) 131 } 132 if err != nil { 133 return err 134 } 135 } 136 137 return writeFooter(w, &wroteHdr) 138 }) 139 140 return eg.Wait() 141 } 142 143 func writeHeader(w io.Writer, p types.Path, wroteHdr *bool) error { 144 if *wroteHdr { 145 return nil 146 } 147 *wroteHdr = true 148 hdr := "(root)" 149 if len(p) > 0 { 150 hdr = p.String() 151 } 152 return write(w, []byte(hdr+" {\n")) 153 } 154 155 func writeFooter(w io.Writer, wroteHdr *bool) error { 156 if !*wroteHdr { 157 return nil 158 } 159 *wroteHdr = false 160 return write(w, []byte(" }\n")) 161 } 162 163 func line(ctx context.Context, w io.Writer, op prefixOp, key, val types.Value) error { 164 genPrefix := func(w *writers.PrefixWriter) []byte { 165 return []byte(op) 166 } 167 pw := &writers.PrefixWriter{Dest: w, PrefixFunc: genPrefix, NeedsPrefix: true} 168 if key != nil { 169 err := writeEncodedValue(ctx, pw, key) 170 171 if err != nil { 172 return err 173 } 174 175 err = write(w, []byte(": ")) 176 177 if err != nil { 178 return err 179 } 180 } 181 err := writeEncodedValue(ctx, pw, val) 182 183 if err != nil { 184 return err 185 } 186 187 return write(w, []byte("\n")) 188 } 189 190 func field(ctx context.Context, w io.Writer, op prefixOp, name, val types.Value) error { 191 genPrefix := func(w *writers.PrefixWriter) []byte { 192 return []byte(op) 193 } 194 pw := &writers.PrefixWriter{Dest: w, PrefixFunc: genPrefix, NeedsPrefix: true} 195 err := write(pw, []byte(name.(types.String))) 196 197 if err != nil { 198 return err 199 } 200 201 err = write(w, []byte(": ")) 202 203 if err != nil { 204 return err 205 } 206 207 err = writeEncodedValue(ctx, pw, val) 208 209 if err != nil { 210 return err 211 } 212 213 return write(w, []byte("\n")) 214 } 215 216 func writeEncodedValue(ctx context.Context, w io.Writer, v types.Value) error { 217 if v.Kind() != types.BlobKind { 218 return types.WriteEncodedValue(ctx, w, v) 219 } 220 221 err := write(w, []byte("Blob (")) 222 223 if err != nil { 224 return err 225 } 226 227 err = write(w, []byte(humanize.Bytes(v.(types.Blob).Len()))) 228 229 if err != nil { 230 return err 231 } 232 233 return write(w, []byte(")")) 234 } 235 236 func write(w io.Writer, b []byte) error { 237 _, err := w.Write(b) 238 return err 239 }