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 }