File: difference.go

package info (click to toggle)
golang-github-socketplane-libovsdb 0.8.1-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,468 kB
  • sloc: makefile: 59; sh: 27
file content (209 lines) | stat: -rw-r--r-- 7,631 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package updates

import "reflect"

// difference between value 'a' and value 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The result is calculated in 'a' in-place and returned unless the
// difference is 'b' in which case 'b' is returned unmodified. Also returns a
// boolean indicating if there is an actual difference.
func difference(a, b any) (any, bool) {
	return mergeDifference(nil, a, b)
}

// applyDifference returns the result of applying difference 'd' to value 'v'
// along with a boolean indicating if 'v' was changed.
func applyDifference(v, d any) (any, bool) {
	if d == nil {
		return v, false
	}
	// difference can be applied with the same algorithm used to calculate it
	// f(x,f(x,y))=y
	result, changed := difference(v, d)
	dv := reflect.ValueOf(d)
	switch dv.Kind() {
	case reflect.Slice:
		fallthrough
	case reflect.Map:
		// but we need to tweak the interpretation of change for map and slices:
		// when there is no difference between the value and non-empty delta, it
		// actually means the value needs to be emptied so there is actually a
		// change
		if !changed && dv.Len() > 0 {
			return result, true
		}
		// there are no changes when delta is empty
		return result, changed && dv.Len() > 0
	}
	return result, changed
}

// mergeDifference, given an original value 'o' and two differences 'a' and 'b',
// returns a new equivalent difference that when applied on 'o' it would have
// the same result as applying 'a' and 'b' consecutively.
// If 'o' is nil, returns the difference between 'a' and 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The result is calculated in 'a' in-place and returned unless the result is
// 'b' in which case 'b' is returned unmodified. Also returns a boolean
// indicating if there is an actual difference.
func mergeDifference(o, a, b any) (any, bool) {
	kind := reflect.ValueOf(b).Kind()
	if kind == reflect.Invalid {
		kind = reflect.ValueOf(a).Kind()
	}
	switch kind {
	case reflect.Invalid:
		return nil, false
	case reflect.Slice:
		// set differences are transitive
		return setDifference(a, b)
	case reflect.Map:
		return mergeMapDifference(o, a, b)
	case reflect.Array:
		panic("Not implemented")
	default:
		return mergeAtomicDifference(o, a, b)
	}
}

// setDifference calculates the difference between set 'a' and set 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The result is calculated in 'a' in-place and returned unless the difference
// is 'b' in which case 'b' is returned unmodified. Also returns a boolean
// indicating if there is an actual difference.
func setDifference(a, b any) (any, bool) {
	av := reflect.ValueOf(a)
	bv := reflect.ValueOf(b)

	if !av.IsValid() && !bv.IsValid() {
		return nil, false
	} else if (!av.IsValid() || av.Len() == 0) && bv.IsValid() {
		return b, bv.Len() != 0
	} else if (!bv.IsValid() || bv.Len() == 0) && av.IsValid() {
		return a, av.Len() != 0
	}

	// From https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
	// The difference between two sets are all elements that only belong to one
	// of the sets.
	difference := make(map[any]struct{}, bv.Len())
	for i := 0; i < bv.Len(); i++ {
		// supossedly we are working with comparable atomic types with no
		// pointers so we can use the values as map key
		difference[bv.Index(i).Interface()] = struct{}{}
	}
	j := av.Len()
	for i := 0; i < j; {
		vv := av.Index(i)
		vi := vv.Interface()
		if _, ok := difference[vi]; ok {
			// this value of 'a' is in 'b', so remove it from 'a'; to do that,
			// overwrite it with the last value and re-evaluate
			vv.Set(av.Index(j - 1))
			// decrease where the last 'a' value is at
			j--
			// remove from 'b' values
			delete(difference, vi)
		} else {
			// this value of 'a' is not in 'b', evaluate the next value
			i++
		}
	}
	// trim the slice to the actual values held
	av = av.Slice(0, j)
	for item := range difference {
		// this value of 'b' is not in 'a', so add it
		av = reflect.Append(av, reflect.ValueOf(item))
	}

	if av.Len() == 0 {
		return reflect.Zero(av.Type()).Interface(), false
	}

	return av.Interface(), true
}

// mergeMapDifference, given an original map 'o' and two differences 'a' and
// 'b', returns a new equivalent difference that when applied on 'o' it would
// have the same result as applying 'a' and 'b' consecutively.
// If 'o' is nil, returns the difference between 'a' and 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// The result is calculated in 'a' in-place and returned unless the result is
// 'b' in which case 'b' is returned unmodified.
// Returns a boolean indicating if there is an actual difference.
func mergeMapDifference(o, a, b any) (any, bool) {
	av := reflect.ValueOf(a)
	bv := reflect.ValueOf(b)

	if !av.IsValid() && !bv.IsValid() {
		return nil, false
	} else if (!av.IsValid() || av.Len() == 0) && bv.IsValid() {
		return b, bv.Len() != 0
	} else if (!bv.IsValid() || bv.Len() == 0) && av.IsValid() {
		return a, av.Len() != 0
	}

	ov := reflect.ValueOf(o)
	if !ov.IsValid() {
		ov = reflect.Zero(av.Type())
	}

	// From
	// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
	// The difference between two maps are all key-value pairs whose keys
	// appears in only one of the maps, plus the key-value pairs whose keys
	// appear in both maps but with different values. For the latter elements,
	// <row> includes the value from the new column.

	// We can assume that difference is a transitive operation so we calculate
	// the difference between 'a' and 'b' but we need to handle exceptions when
	// the same key is present in all values.
	for i := bv.MapRange(); i.Next(); {
		kv := i.Key()
		bvv := i.Value()
		avv := av.MapIndex(kv)
		ovv := ov.MapIndex(kv)
		// supossedly we are working with comparable types with no pointers so
		// we can compare directly here
		switch {
		case ovv.IsValid() && avv.IsValid() && ovv.Interface() == bvv.Interface():
			// key is present in the three values
			// final result would restore key to the original value, delete from 'a'
			av.SetMapIndex(kv, reflect.Value{})
		case ovv.IsValid() && avv.IsValid() && avv.Interface() == bvv.Interface():
			// key is present in the three values
			// final result would remove key, set in 'a' with 'o' value
			av.SetMapIndex(kv, ovv)
		case avv.IsValid() && avv.Interface() == bvv.Interface():
			// key/value is in 'a' and 'b', delete from 'a'
			av.SetMapIndex(kv, reflect.Value{})
		default:
			// key/value in 'b' is not in 'a', set in 'a' with 'b' value
			av.SetMapIndex(kv, bvv)
		}
	}

	if av.Len() == 0 {
		return reflect.Zero(av.Type()).Interface(), false
	}

	return av.Interface(), true
}

// mergeAtomicDifference, given an original atomic value 'o' and two differences
// 'a' and 'b', returns a new equivalent difference that when applied on 'o' it
// would have the same result as applying 'a' and 'b' consecutively.
// If 'o' is nil, returns the difference between 'a' and 'b'.
// This difference is calculated as described in
// https://docs.openvswitch.org/en/latest/ref/ovsdb-server.7/#update2-notification
// Returns a boolean indicating if there is an actual difference.
func mergeAtomicDifference(o, a, b any) (any, bool) {
	if o != nil {
		return b, !reflect.DeepEqual(o, b)
	}
	return b, !reflect.DeepEqual(a, b)
}