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  }