github.com/ravendb/ravendb-go-client@v0.0.0-20240229102137-4474ee7aa0fa/json_operation.go (about) 1 package ravendb 2 3 import ( 4 "fmt" 5 "sort" 6 ) 7 8 func jsonOperationEntityChanged(newObj map[string]interface{}, documentInfo *documentInfo, changes map[string][]*DocumentsChanges) bool { 9 var docChanges []*DocumentsChanges 10 11 doc := documentInfo.document 12 if !documentInfo.newDocument && doc != nil { 13 id := documentInfo.id 14 return jsonOperationCompareJson(id, doc, newObj, changes, &docChanges) 15 } 16 17 if changes == nil { 18 return true 19 } 20 21 jsonOperationNewChange("", nil, nil, &docChanges, DocumentChangeDocumentAdded) 22 id := documentInfo.id 23 a := changes[id] 24 a = append(a, docChanges...) 25 changes[id] = a 26 return true 27 } 28 29 func isJSONFloatEqual(oldPropVal float64, newProp interface{}) bool { 30 switch newPropVal := newProp.(type) { 31 case nil: 32 return false 33 case float64: 34 return oldPropVal == newPropVal 35 default: 36 // TODO: can those happen in real life? 37 panicIf(true, "unhandled type of newProp, expected 'float64' and is '%T'", newProp) 38 } 39 return false 40 } 41 42 func isJSONBoolEqual(oldPropVal bool, newProp interface{}) bool { 43 switch newPropVal := newProp.(type) { 44 case nil: 45 return false 46 case bool: 47 return oldPropVal == newPropVal 48 default: 49 // TODO: can those happen in real life? 50 panicIf(true, "unhandled type of newProp, expected 'bool' and is '%T'", newProp) 51 } 52 return false 53 } 54 55 func isJSONStringEqual(oldPropVal string, newProp interface{}) bool { 56 switch newPropVal := newProp.(type) { 57 case nil: 58 return false 59 case string: 60 return oldPropVal == newPropVal 61 default: 62 // TODO: can those happen in real life? 63 panicIf(true, "unhandled type of newProp, expected 'string' and is '%T'", newProp) 64 } 65 return false 66 } 67 68 func jsonOperationCompareJson(id string, originalJson map[string]interface{}, newJson map[string]interface{}, changes map[string][]*DocumentsChanges, docChanges *[]*DocumentsChanges) bool { 69 newJsonProps := getObjectNodeFieldNames(newJson) 70 oldJsonProps := getObjectNodeFieldNames(originalJson) 71 newFields := stringArraySubtract(newJsonProps, oldJsonProps) 72 removedFields := stringArraySubtract(oldJsonProps, newJsonProps) 73 74 for _, field := range removedFields { 75 if changes == nil { 76 return true 77 } 78 jsonOperationNewChange(field, nil, nil, docChanges, DocumentChangeRemovedField) 79 } 80 81 for _, prop := range newJsonProps { 82 switch prop { 83 case MetadataLastModified, 84 MetadataCollection, 85 MetadataChangeVector, 86 MetadataID: 87 continue 88 } 89 if stringArrayContains(newFields, prop) { 90 if changes == nil { 91 return true 92 } 93 v := newJson[prop] 94 jsonOperationNewChange(prop, v, nil, docChanges, DocumentChangeNewField) 95 continue 96 } 97 newProp := newJson[prop] 98 oldProp := originalJson[prop] 99 switch newPropVal := newProp.(type) { 100 case float64: 101 if isJSONFloatEqual(newPropVal, oldProp) { 102 break 103 } 104 if changes == nil { 105 return true 106 } 107 jsonOperationNewChange(prop, newProp, oldProp, docChanges, DocumentChangeFieldChanged) 108 case string: 109 if isJSONStringEqual(newPropVal, oldProp) { 110 break 111 } 112 if changes == nil { 113 return true 114 } 115 jsonOperationNewChange(prop, newProp, oldProp, docChanges, DocumentChangeFieldChanged) 116 case bool: 117 if isJSONBoolEqual(newPropVal, oldProp) { 118 break 119 } 120 121 if changes == nil { 122 return true 123 } 124 jsonOperationNewChange(prop, newProp, oldProp, docChanges, DocumentChangeFieldChanged) 125 case []interface{}: 126 if oldProp == nil || !isInstanceOfArrayOfInterface(oldProp) { 127 if changes == nil { 128 return true 129 } 130 131 jsonOperationNewChange(prop, newProp, oldProp, docChanges, DocumentChangeFieldChanged) 132 break 133 } 134 135 changed := jsonOperationCompareJsonArray(id, oldProp.([]interface{}), newProp.([]interface{}), changes, docChanges, prop) 136 if changes == nil && changed { 137 return true 138 } 139 140 case map[string]interface{}: 141 oldPropVal, ok := oldProp.(map[string]interface{}) 142 // TODO: a better check for nil? 143 if !ok || oldProp == nil { 144 if changes == nil { 145 return true 146 } 147 jsonOperationNewChange(prop, newProp, nil, docChanges, DocumentChangeFieldChanged) 148 break 149 } 150 changed := jsonOperationCompareJson(id, oldPropVal, newPropVal, changes, docChanges) 151 if changes == nil && changed { 152 return true 153 } 154 default: 155 if newProp == nil { 156 if oldProp == nil { 157 break 158 } 159 if changes == nil { 160 return true 161 } 162 jsonOperationNewChange(prop, nil, oldProp, docChanges, DocumentChangeFieldChanged) 163 break 164 } 165 // TODO: array, nil 166 // Write tests for all types 167 panicIf(true, "unhandled type %T, newProp: '%v', oldProp: '%v'", newProp, newProp, oldProp) 168 } 169 } 170 171 if changes == nil || len(*docChanges) == 0 { 172 return false 173 } 174 changes[id] = *docChanges 175 return true 176 } 177 178 func isInstanceOfArrayOfInterface(v interface{}) bool { 179 _, ok := v.([]interface{}) 180 return ok 181 } 182 183 func jsonOperationCompareJsonArray(id string, oldArray []interface{}, newArray []interface{}, changes map[string][]*DocumentsChanges, docChanges *[]*DocumentsChanges, propName string) bool { 184 // if we don't care about the changes 185 if len(oldArray) != len(newArray) && changes == nil { 186 return true 187 } 188 189 changed := false 190 191 position := 0 192 maxPos := len(oldArray) 193 if maxPos > len(newArray) { 194 maxPos = len(newArray) 195 } 196 for ; position < maxPos; position++ { 197 oldVal := oldArray[position] 198 newVal := newArray[position] 199 switch oldVal.(type) { 200 case map[string]interface{}: 201 if _, ok := newVal.(map[string]interface{}); ok { 202 newChanged := jsonOperationCompareJson(id, oldVal.(map[string]interface{}), newVal.(map[string]interface{}), changes, docChanges) 203 if newChanged { 204 changed = newChanged 205 } 206 } else { 207 changed = true 208 if changes != nil { 209 jsonOperationNewChange(propName, newVal, oldVal, docChanges, DocumentChangeArrayValueAdded) 210 } 211 } 212 case []interface{}: 213 if _, ok := newVal.([]interface{}); ok { 214 newChanged := jsonOperationCompareJsonArray(id, oldVal.([]interface{}), newVal.([]interface{}), changes, docChanges, propName) 215 if newChanged { 216 changed = newChanged 217 } 218 } else { 219 changed = true 220 if changes != nil { 221 jsonOperationNewChange(propName, newVal, oldVal, docChanges, DocumentChangeArrayValueChanged) 222 } 223 } 224 default: 225 // NULL case 226 if oldVal == nil { 227 if newVal != nil { 228 changed = true 229 if changes != nil { 230 jsonOperationNewChange(propName, newVal, oldVal, docChanges, DocumentChangeArrayValueChanged) 231 } 232 } 233 break 234 } 235 // Note: this matches Java but also means that 1 == "1" 236 oldValStr := fmt.Sprintf("%v", oldVal) 237 newValStr := fmt.Sprintf("%v", newVal) 238 if oldValStr != newValStr { 239 if changes != nil { 240 jsonOperationNewChange(propName, newVal, oldVal, docChanges, DocumentChangeArrayValueChanged) 241 } 242 changed = true 243 } 244 245 } 246 } 247 248 if changes == nil { 249 return changed 250 } 251 252 // if one of the arrays is larger than the other 253 for ; position < len(oldArray); position++ { 254 jsonOperationNewChange(propName, nil, oldArray[position], docChanges, DocumentChangeArrayValueRemoved) 255 } 256 257 for ; position < len(newArray); position++ { 258 jsonOperationNewChange(propName, newArray[position], nil, docChanges, DocumentChangeArrayValueAdded) 259 } 260 261 return changed 262 } 263 264 func jsonOperationNewChange(name string, newValue interface{}, oldValue interface{}, docChanges *[]*DocumentsChanges, change ChangeType) { 265 documentsChanges := &DocumentsChanges{ 266 FieldNewValue: newValue, 267 FieldOldValue: oldValue, 268 FieldName: name, 269 Change: change, 270 } 271 *docChanges = append(*docChanges, documentsChanges) 272 } 273 274 func getObjectNodeFieldNames(o map[string]interface{}) []string { 275 n := len(o) 276 if n == 0 { 277 return nil 278 } 279 res := make([]string, n) 280 i := 0 281 for k := range o { 282 res[i] = k 283 i++ 284 } 285 // Go randomizes order of map traversal but it's useful to have it 286 // fixed e.g. for tests 287 sort.Strings(res) 288 return res 289 }