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  }