github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/prolly/tree/json_diff.go (about) 1 // Copyright 2023 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 package tree 16 17 import ( 18 "bytes" 19 "fmt" 20 "io" 21 "reflect" 22 "strings" 23 24 "github.com/dolthub/go-mysql-server/sql/types" 25 ) 26 27 type JsonDiff struct { 28 Key string 29 From, To *types.JSONDocument 30 Type DiffType 31 } 32 33 type jsonKeyPair struct { 34 key string 35 value interface{} 36 } 37 38 // JsonDiffer computes the diff between two JSON objects. 39 type JsonDiffer struct { 40 root string 41 currentFromPair, currentToPair *jsonKeyPair 42 from, to types.JSONIter 43 subDiffer *JsonDiffer 44 } 45 46 func NewJsonDiffer(root string, from, to types.JsonObject) JsonDiffer { 47 fromIter := types.NewJSONIter(from) 48 toIter := types.NewJSONIter(to) 49 return JsonDiffer{ 50 root: root, 51 from: fromIter, 52 to: toIter, 53 } 54 } 55 56 func (differ *JsonDiffer) appendKey(key string) string { 57 escapedKey := strings.Replace(key, "\"", "\\\"", -1) 58 return fmt.Sprintf("%s.\"%s\"", differ.root, escapedKey) 59 } 60 61 func (differ *JsonDiffer) Next() (diff JsonDiff, err error) { 62 for { 63 if differ.subDiffer != nil { 64 diff, err := differ.subDiffer.Next() 65 if err == io.EOF { 66 differ.subDiffer = nil 67 differ.currentFromPair = nil 68 differ.currentToPair = nil 69 continue 70 } else if err != nil { 71 return JsonDiff{}, err 72 } 73 return diff, nil 74 } 75 if differ.currentFromPair == nil && differ.from.HasNext() { 76 key, value, err := differ.from.Next() 77 if err != nil { 78 return JsonDiff{}, err 79 } 80 differ.currentFromPair = &jsonKeyPair{key, value} 81 } 82 83 if differ.currentToPair == nil && differ.to.HasNext() { 84 key, value, err := differ.to.Next() 85 if err != nil { 86 return JsonDiff{}, err 87 } 88 differ.currentToPair = &jsonKeyPair{key, value} 89 } 90 91 if differ.currentFromPair == nil && differ.currentToPair == nil { 92 return JsonDiff{}, io.EOF 93 } 94 95 var diffType DiffType 96 97 if differ.currentFromPair == nil && differ.currentToPair != nil { 98 diffType = AddedDiff 99 } else if differ.currentFromPair != nil && differ.currentToPair == nil { 100 diffType = RemovedDiff 101 } else { // differ.currentFromPair != nil && differ.currentToPair != nil 102 keyCmp := bytes.Compare([]byte(differ.currentFromPair.key), []byte(differ.currentToPair.key)) 103 if keyCmp > 0 { 104 // `to` key comes before `from` key. Right key must have been inserted. 105 diffType = AddedDiff 106 } else if keyCmp < 0 { 107 // `to` key comes after `from` key. Right key must have been deleted. 108 diffType = RemovedDiff 109 } else { 110 key := differ.currentFromPair.key 111 fromValue := differ.currentFromPair.value 112 toValue := differ.currentToPair.value 113 if reflect.TypeOf(fromValue) != reflect.TypeOf(toValue) { 114 diffType = ModifiedDiff 115 } else { 116 switch from := fromValue.(type) { 117 case types.JsonObject: 118 // Recursively compare the objects to generate diffs. 119 subDiffer := NewJsonDiffer(differ.appendKey(key), from, toValue.(types.JsonObject)) 120 differ.subDiffer = &subDiffer 121 continue 122 case types.JsonArray: 123 if reflect.DeepEqual(fromValue, toValue) { 124 differ.currentFromPair = nil 125 differ.currentToPair = nil 126 continue 127 } else { 128 diffType = ModifiedDiff 129 } 130 default: 131 if fromValue == toValue { 132 differ.currentFromPair = nil 133 differ.currentToPair = nil 134 continue 135 } 136 diffType = ModifiedDiff 137 } 138 } 139 } 140 } 141 142 switch diffType { 143 case AddedDiff: 144 key, value := differ.currentToPair.key, differ.currentToPair.value 145 result := JsonDiff{ 146 Key: differ.appendKey(key), 147 From: nil, 148 To: &types.JSONDocument{Val: value}, 149 Type: AddedDiff, 150 } 151 differ.currentToPair = nil 152 return result, nil 153 case RemovedDiff: 154 key, value := differ.currentFromPair.key, differ.currentFromPair.value 155 result := JsonDiff{ 156 Key: differ.appendKey(key), 157 From: &types.JSONDocument{Val: value}, 158 To: nil, 159 Type: RemovedDiff, 160 } 161 differ.currentFromPair = nil 162 return result, nil 163 case ModifiedDiff: 164 key := differ.currentFromPair.key 165 from := differ.currentFromPair.value 166 to := differ.currentToPair.value 167 result := JsonDiff{ 168 Key: differ.appendKey(key), 169 From: &types.JSONDocument{Val: from}, 170 To: &types.JSONDocument{Val: to}, 171 Type: ModifiedDiff, 172 } 173 differ.currentFromPair = nil 174 differ.currentToPair = nil 175 return result, nil 176 default: 177 panic("unreachable") 178 } 179 } 180 }