github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/diff/summary.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  	"fmt"
    27  	"sync/atomic"
    28  
    29  	humanize "github.com/dustin/go-humanize"
    30  	"golang.org/x/sync/errgroup"
    31  
    32  	"github.com/dolthub/dolt/go/store/d"
    33  	"github.com/dolthub/dolt/go/store/datas"
    34  	"github.com/dolthub/dolt/go/store/types"
    35  	"github.com/dolthub/dolt/go/store/util/status"
    36  )
    37  
    38  // Summary prints a summary of the diff between two values to stdout.
    39  func Summary(ctx context.Context, value1, value2 types.Value) {
    40  	if is1, err := datas.IsCommit(value1); err != nil {
    41  		panic(err)
    42  	} else if is1 {
    43  		if is2, err := datas.IsCommit(value2); err != nil {
    44  			panic(err)
    45  		} else if is2 {
    46  			fmt.Println("Comparing commit values")
    47  
    48  			var err error
    49  			value1, _, err = value1.(types.Struct).MaybeGet(datas.ValueField)
    50  			d.PanicIfError(err)
    51  
    52  			value2, _, err = value2.(types.Struct).MaybeGet(datas.ValueField)
    53  			d.PanicIfError(err)
    54  		}
    55  	}
    56  
    57  	var singular, plural string
    58  	if value1.Kind() == value2.Kind() {
    59  		switch value1.Kind() {
    60  		case types.StructKind:
    61  			singular = "field"
    62  			plural = "fields"
    63  		case types.MapKind:
    64  			singular = "entry"
    65  			plural = "entries"
    66  		default:
    67  			singular = "value"
    68  			plural = "values"
    69  		}
    70  	}
    71  
    72  	eg, ctx := errgroup.WithContext(ctx)
    73  	var rp atomic.Value
    74  	ch := make(chan diffSummaryProgress)
    75  
    76  	eg.Go(func() (err error) {
    77  		defer close(ch)
    78  		defer func() {
    79  			if r := recover(); r != nil {
    80  				rp.Store(r)
    81  				err = fmt.Errorf("panic")
    82  			}
    83  		}()
    84  		err = diffSummary(ctx, ch, value1, value2)
    85  		return
    86  	})
    87  	eg.Go(func() error {
    88  		acc := diffSummaryProgress{}
    89  	LOOP:
    90  		for {
    91  			select {
    92  			case p, ok := <-ch:
    93  				if !ok {
    94  					break LOOP
    95  				}
    96  				acc.Adds += p.Adds
    97  				acc.Removes += p.Removes
    98  				acc.Changes += p.Changes
    99  				acc.NewSize += p.NewSize
   100  				acc.OldSize += p.OldSize
   101  				if status.WillPrint() {
   102  					formatStatus(acc, singular, plural)
   103  				}
   104  			case <-ctx.Done():
   105  				return ctx.Err()
   106  			}
   107  		}
   108  		formatStatus(acc, singular, plural)
   109  		status.Done()
   110  		return nil
   111  	})
   112  
   113  	if err := eg.Wait(); err != nil {
   114  		if r := rp.Load(); r != nil {
   115  			panic(r)
   116  		}
   117  		panic(err)
   118  	}
   119  
   120  }
   121  
   122  type diffSummaryProgress struct {
   123  	Adds, Removes, Changes, NewSize, OldSize uint64
   124  }
   125  
   126  func diffSummary(ctx context.Context, ch chan diffSummaryProgress, v1, v2 types.Value) error {
   127  	if !v1.Equals(v2) {
   128  		if ShouldDescend(v1, v2) {
   129  			var err error
   130  			switch v1.Kind() {
   131  			case types.ListKind:
   132  				err = diffSummaryList(ctx, ch, v1.(types.List), v2.(types.List))
   133  			case types.MapKind:
   134  				err = diffSummaryMap(ctx, ch, v1.(types.Map), v2.(types.Map))
   135  			case types.SetKind:
   136  				err = diffSummarySet(ctx, ch, v1.(types.Set), v2.(types.Set))
   137  			case types.StructKind:
   138  				err = diffSummaryStructs(ctx, ch, v1.(types.Struct), v2.(types.Struct))
   139  			default:
   140  				panic("Unrecognized type in diff function")
   141  			}
   142  			if err != nil {
   143  				return err
   144  			}
   145  		} else {
   146  			ch <- diffSummaryProgress{Adds: 1, Removes: 1, NewSize: 1, OldSize: 1}
   147  		}
   148  	}
   149  	return nil
   150  }
   151  
   152  func diffSummaryList(ctx context.Context, ch chan<- diffSummaryProgress, v1, v2 types.List) error {
   153  	select {
   154  	case ch <- diffSummaryProgress{OldSize: v1.Len(), NewSize: v2.Len()}:
   155  	case <-ctx.Done():
   156  		return ctx.Err()
   157  	}
   158  
   159  	spliceChan := make(chan types.Splice)
   160  	eg, ctx := errgroup.WithContext(ctx)
   161  
   162  	var rp atomic.Value
   163  	eg.Go(func() (err error) {
   164  		defer close(spliceChan)
   165  		defer func() {
   166  			if r := recover(); r != nil {
   167  				rp.Store(r)
   168  				err = fmt.Errorf("panic")
   169  			}
   170  		}()
   171  		return v2.Diff(ctx, v1, spliceChan)
   172  	})
   173  
   174  	eg.Go(func() (err error) {
   175  		defer func() {
   176  			if r := recover(); r != nil {
   177  				rp.Store(r)
   178  				err = fmt.Errorf("panic")
   179  			}
   180  		}()
   181  	LOOP:
   182  		for {
   183  			select {
   184  			case splice, ok := <-spliceChan:
   185  				if !ok {
   186  					break LOOP
   187  				}
   188  				var summary diffSummaryProgress
   189  				if splice.SpRemoved == splice.SpAdded {
   190  					summary = diffSummaryProgress{Changes: splice.SpRemoved}
   191  				} else {
   192  					summary = diffSummaryProgress{Adds: splice.SpAdded, Removes: splice.SpRemoved}
   193  				}
   194  				select {
   195  				case ch <- summary:
   196  				case <-ctx.Done():
   197  					return ctx.Err()
   198  				}
   199  			case <-ctx.Done():
   200  				return ctx.Err()
   201  			}
   202  		}
   203  		return nil
   204  	})
   205  
   206  	if err := eg.Wait(); err != nil {
   207  		if r := rp.Load(); r != nil {
   208  			panic(r)
   209  		}
   210  		return err
   211  	}
   212  	return nil
   213  }
   214  
   215  func diffSummaryMap(ctx context.Context, ch chan<- diffSummaryProgress, v1, v2 types.Map) error {
   216  	return diffSummaryValueChanged(ctx, ch, v1.Len(), v2.Len(), func(ctx context.Context, changeChan chan<- types.ValueChanged) error {
   217  		return v2.Diff(ctx, v1, changeChan)
   218  	})
   219  }
   220  
   221  func diffSummarySet(ctx context.Context, ch chan<- diffSummaryProgress, v1, v2 types.Set) error {
   222  	return diffSummaryValueChanged(ctx, ch, v1.Len(), v2.Len(), func(ctx context.Context, changeChan chan<- types.ValueChanged) error {
   223  		return v2.Diff(ctx, v1, changeChan)
   224  	})
   225  }
   226  
   227  func diffSummaryStructs(ctx context.Context, ch chan<- diffSummaryProgress, v1, v2 types.Struct) error {
   228  	// TODO: Operate on values directly
   229  	t1, err := types.TypeOf(v1)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	t2, err := types.TypeOf(v2)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	size1 := uint64(t1.Desc.(types.StructDesc).Len())
   240  	size2 := uint64(t2.Desc.(types.StructDesc).Len())
   241  	return diffSummaryValueChanged(ctx, ch, size1, size2, func(ctx context.Context, changeChan chan<- types.ValueChanged) error {
   242  		return v2.Diff(ctx, v1, changeChan)
   243  	})
   244  }
   245  
   246  func diffSummaryValueChanged(ctx context.Context, ch chan<- diffSummaryProgress, oldSize, newSize uint64, f diffFunc) error {
   247  	select {
   248  	case ch <- diffSummaryProgress{OldSize: oldSize, NewSize: newSize}:
   249  	case <-ctx.Done():
   250  		return ctx.Err()
   251  	}
   252  
   253  	changeChan := make(chan types.ValueChanged)
   254  
   255  	eg, ctx := errgroup.WithContext(ctx)
   256  
   257  	var rp atomic.Value
   258  	eg.Go(func() (err error) {
   259  		defer close(changeChan)
   260  		defer func() {
   261  			if r := recover(); r != nil {
   262  				rp.Store(r)
   263  				err = fmt.Errorf("panic")
   264  			}
   265  		}()
   266  		return f(ctx, changeChan)
   267  	})
   268  	eg.Go(func() error {
   269  		return reportChanges(ctx, ch, changeChan)
   270  	})
   271  	if err := eg.Wait(); err != nil {
   272  		if r := rp.Load(); r != nil {
   273  			panic(r)
   274  		}
   275  		return err
   276  	}
   277  	return nil
   278  }
   279  
   280  func reportChanges(ctx context.Context, ch chan<- diffSummaryProgress, changeChan chan types.ValueChanged) error {
   281  LOOP:
   282  	for {
   283  		select {
   284  		case change, ok := <-changeChan:
   285  			if !ok {
   286  				break LOOP
   287  			}
   288  			var summary diffSummaryProgress
   289  			switch change.ChangeType {
   290  			case types.DiffChangeAdded:
   291  				summary = diffSummaryProgress{Adds: 1}
   292  			case types.DiffChangeRemoved:
   293  				summary = diffSummaryProgress{Removes: 1}
   294  			case types.DiffChangeModified:
   295  				summary = diffSummaryProgress{Changes: 1}
   296  			default:
   297  				panic("unknown change type")
   298  			}
   299  			select {
   300  			case ch <- summary:
   301  				return nil
   302  			case <-ctx.Done():
   303  				return ctx.Err()
   304  			}
   305  		case <-ctx.Done():
   306  			return ctx.Err()
   307  		}
   308  	}
   309  	return nil
   310  }
   311  
   312  func formatStatus(acc diffSummaryProgress, singular, plural string) {
   313  	pluralize := func(singular, plural string, n uint64) string {
   314  		var noun string
   315  		if n != 1 {
   316  			noun = plural
   317  		} else {
   318  			noun = singular
   319  		}
   320  		return fmt.Sprintf("%s %s", humanize.Comma(int64(n)), noun)
   321  	}
   322  
   323  	insertions := pluralize("insertion", "insertions", acc.Adds)
   324  	deletions := pluralize("deletion", "deletions", acc.Removes)
   325  	changes := pluralize("change", "changes", acc.Changes)
   326  
   327  	oldValues := pluralize(singular, plural, acc.OldSize)
   328  	newValues := pluralize(singular, plural, acc.NewSize)
   329  
   330  	status.Printf("%s (%.2f%%), %s (%.2f%%), %s (%.2f%%), (%s vs %s)", insertions, (float64(100*acc.Adds) / float64(acc.OldSize)), deletions, (float64(100*acc.Removes) / float64(acc.OldSize)), changes, (float64(100*acc.Changes) / float64(acc.OldSize)), oldValues, newValues)
   331  }