coverage~bigbes/sr-ht-doltede3b0bbbrowse/diff.go

Coverage
0.0% 0/73 statements
Δ
Blob
3ac0764
1 package browse
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7
8 "github.com/dolthub/dolt/go/libraries/doltcore/diff"
9 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
10 )
11
12 // TableDiff summarizes how one table changed in a commit relative to its first
13 // parent. Row-level diffs are out of scope for v1; only the per-table shape and
14 // exact row counts are reported.
15 type TableDiff struct {
16 Name string
17 Added bool
18 Dropped bool
19 SchemaChanged bool
20 // RowsAdded, RowsRemoved and RowsModified are exact counts derived from the
21 // prolly diff. They are 0 when counts could not be computed (e.g. the
22 // primary key set changed, in which case SchemaChanged is true).
23 RowsAdded int64
24 RowsRemoved int64
25 RowsModified int64
26 }
27
28 // CommitDiff is the per-table summary of a single commit versus its first
29 // parent (or the empty root, for the initial commit).
30 type CommitDiff struct {
31 Hash string
32 Tables []TableDiff
33 }
34
35 // CommitSummary computes the per-table diff summary of the commit identified by
36 // hashStr (a commit hash, or any ref resolveCommit accepts) against its first
37 // parent. For the initial commit the comparison is against an empty root, so
38 // every table shows as added.
39 0 func (db *DB) CommitSummary(ctx context.Context, hashStr string) (*CommitDiff, error) {
40 0 commit, err := db.resolveCommit(ctx, hashStr)
41 0 if err != nil {
42 0 return nil, err
43 0 }
44
45 0 toRoot, err := commit.GetRootValue(ctx)
46 0 if err != nil {
47 0 return nil, fmt.Errorf("browse: root value of %q: %w", hashStr, err)
48 0 }
49
50 0 fromRoot, err := db.firstParentRoot(ctx, commit)
51 0 if err != nil {
52 0 return nil, err
53 0 }
54
55 0 deltas, err := diff.GetTableDeltas(ctx, fromRoot, toRoot)
56 0 if err != nil {
57 0 return nil, fmt.Errorf("browse: table deltas for %q: %w", hashStr, err)
58 0 }
59
60 0 h, err := commit.HashOf()
61 0 if err != nil {
62 0 return nil, fmt.Errorf("browse: hash of %q: %w", hashStr, err)
63 0 }
64
65 0 out := &CommitDiff{Hash: h.String(), Tables: make([]TableDiff, 0, len(deltas))}
66 0 for _, delta := range deltas {
67 0 td, err := tableDiff(ctx, delta)
68 0 if err != nil {
69 0 return nil, err
70 0 }
71 0 out.Tables = append(out.Tables, td)
72 }
73
74 0 return out, nil
75 }
76
77 // firstParentRoot returns the root value of the commit's first parent, or an
78 // empty root when the commit has no parents (the initial commit).
79 0 func (db *DB) firstParentRoot(ctx context.Context, commit *doltdb.Commit) (doltdb.RootValue, error) {
80 0 parents, err := commit.ParentHashes(ctx)
81 0 if err != nil {
82 0 return nil, fmt.Errorf("browse: parent hashes: %w", err)
83 0 }
84
85 0 if len(parents) == 0 {
86 0 root, err := doltdb.EmptyRootValue(ctx, db.ddb.ValueReadWriter(), db.ddb.NodeStore())
87 0 if err != nil {
88 0 return nil, fmt.Errorf("browse: empty root: %w", err)
89 0 }
90 0 return root, nil
91 }
92
93 0 oc, err := db.ddb.ResolveHash(ctx, parents[0])
94 0 if err != nil {
95 0 return nil, fmt.Errorf("browse: resolve parent %s: %w", parents[0].String(), err)
96 0 }
97 0 parent, ok := oc.ToCommit()
98 0 if !ok {
99 0 return nil, fmt.Errorf("browse: parent commit %s is not resolvable (ghost)", parents[0].String())
100 0 }
101 0 root, err := parent.GetRootValue(ctx)
102 0 if err != nil {
103 0 return nil, fmt.Errorf("browse: parent root %s: %w", parents[0].String(), err)
104 0 }
105 0 return root, nil
106 }
107
108 // tableDiff summarizes a single TableDelta.
109 0 func tableDiff(ctx context.Context, delta diff.TableDelta) (TableDiff, error) {
110 0 name := delta.ToName.Name
111 0 if delta.IsDrop() {
112 0 name = delta.FromName.Name
113 0 }
114
115 0 td := TableDiff{
116 0 Name: name,
117 0 Added: delta.IsAdd(),
118 0 Dropped: delta.IsDrop(),
119 0 }
120 0
121 0 schemaChanged, err := delta.HasSchemaChanged(ctx)
122 0 if err != nil {
123 0 return TableDiff{}, fmt.Errorf("browse: schema-changed check for %q: %w", name, err)
124 0 }
125 0 td.SchemaChanged = schemaChanged
126 0
127 0 added, removed, modified, err := tableRowStat(ctx, delta)
128 0 if err != nil {
129 0 if errors.Is(err, diff.ErrPrimaryKeySetChanged) {
130 0 // Row counts are undefined when the PK set changed; the schema
131 0 // change is already reflected. Leave counts at 0.
132 0 return td, nil
133 0 }
134 0 return TableDiff{}, fmt.Errorf("browse: row stat for %q: %w", name, err)
135 }
136 0 td.RowsAdded = added
137 0 td.RowsRemoved = removed
138 0 td.RowsModified = modified
139 0
140 0 return td, nil
141 }
142
143 // tableRowStat drains diff.StatForTableDelta into exact added/removed/modified
144 // row counts. StatForTableDelta streams one progress message per changed row,
145 // so we accumulate the per-message Adds/Removes/Changes.
146 0 func tableRowStat(ctx context.Context, delta diff.TableDelta) (added, removed, modified int64, err error) {
147 0 ch := make(chan diff.DiffStatProgress, 128)
148 0 done := make(chan struct{})
149 0 var statErr error
150 0 go func() {
151 0 defer close(done)
152 0 statErr = diff.StatForTableDelta(ctx, ch, delta)
153 0 close(ch)
154 0 }()
155
156 0 var a, r, m uint64
157 0 for p := range ch {
158 0 a += p.Adds
159 0 r += p.Removes
160 0 m += p.Changes
161 0 }
162 0 <-done
163 0
164 0 if statErr != nil {
165 0 return 0, 0, 0, statErr
166 0 }
167 0 return int64(a), int64(r), int64(m), nil
168 }