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