-
Notifications
You must be signed in to change notification settings - Fork 16
/
data_model_dependencies.go
183 lines (164 loc) · 5.35 KB
/
data_model_dependencies.go
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
package datasource
import (
"sort"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
"gopkg.in/launchdarkly/go-server-sdk-evaluation.v1/ldmodel"
st "gopkg.in/launchdarkly/go-server-sdk.v5/interfaces/ldstoretypes"
"gopkg.in/launchdarkly/go-server-sdk.v5/internal/datakinds"
"gopkg.in/launchdarkly/go-server-sdk.v5/ldcomponents/ldstoreimpl"
)
type kindAndKey struct {
kind st.DataKind
key string
}
// This set type is implemented as a map, but the values do not matter, just the keys.
type kindAndKeySet map[kindAndKey]bool
func (s kindAndKeySet) add(value kindAndKey) {
s[value] = true
}
func (s kindAndKeySet) contains(value kindAndKey) bool {
_, ok := s[value]
return ok
}
func computeDependenciesFrom(kind st.DataKind, fromItem st.ItemDescriptor) kindAndKeySet {
if kind == ldstoreimpl.Features() {
if flag, ok := fromItem.Item.(*ldmodel.FeatureFlag); ok {
var ret kindAndKeySet
if len(flag.Prerequisites) > 0 {
ret = make(kindAndKeySet, len(flag.Prerequisites))
for _, p := range flag.Prerequisites {
ret.add(kindAndKey{ldstoreimpl.Features(), p.Key})
}
}
for _, r := range flag.Rules {
for _, c := range r.Clauses {
if c.Op == ldmodel.OperatorSegmentMatch {
for _, v := range c.Values {
if v.Type() == ldvalue.StringType {
if ret == nil {
ret = make(kindAndKeySet)
}
ret.add(kindAndKey{datakinds.Segments, v.StringValue()})
}
}
}
}
}
return ret
}
}
return nil
}
func sortCollectionsForDataStoreInit(allData []st.Collection) []st.Collection {
colls := make([]st.Collection, 0, len(allData))
for _, coll := range allData {
if doesDataKindSupportDependencies(coll.Kind) {
itemsOut := make([]st.KeyedItemDescriptor, 0, len(coll.Items))
addItemsInDependencyOrder(coll.Kind, coll.Items, &itemsOut)
colls = append(colls, st.Collection{Kind: coll.Kind, Items: itemsOut})
} else {
colls = append(colls, coll)
}
}
sort.Slice(colls, func(i, j int) bool {
return dataKindPriority(colls[i].Kind) < dataKindPriority(colls[j].Kind)
})
return colls
}
func doesDataKindSupportDependencies(kind st.DataKind) bool {
return kind == datakinds.Features //nolint:megacheck
}
func addItemsInDependencyOrder(
kind st.DataKind,
itemsIn []st.KeyedItemDescriptor,
out *[]st.KeyedItemDescriptor,
) {
remainingItems := make(map[string]st.ItemDescriptor, len(itemsIn))
for _, item := range itemsIn {
remainingItems[item.Key] = item.Item
}
for len(remainingItems) > 0 {
// pick a random item that hasn't been visited yet
for firstKey := range remainingItems {
addWithDependenciesFirst(kind, firstKey, remainingItems, out)
break
}
}
}
func addWithDependenciesFirst(
kind st.DataKind,
startingKey string,
remainingItems map[string]st.ItemDescriptor,
out *[]st.KeyedItemDescriptor,
) {
startItem := remainingItems[startingKey]
delete(remainingItems, startingKey) // we won't need to visit this item again
for dep := range computeDependenciesFrom(kind, startItem) {
if dep.kind == kind {
if _, ok := remainingItems[dep.key]; ok {
addWithDependenciesFirst(kind, dep.key, remainingItems, out)
}
}
}
*out = append(*out, st.KeyedItemDescriptor{Key: startingKey, Item: startItem})
}
// Logic for ensuring that segments are processed before features; if we get any other data types that
// haven't been accounted for here, they'll come after those two in an arbitrary order.
func dataKindPriority(kind st.DataKind) int {
switch kind.GetName() {
case "segments":
return 0
case "features":
return 1
default:
return len(kind.GetName()) + 2
}
}
// Maintains a bidirectional dependency graph that can be updated whenever an item has changed.
type dependencyTracker struct {
dependenciesFrom map[kindAndKey]kindAndKeySet
dependenciesTo map[kindAndKey]kindAndKeySet
}
func newDependencyTracker() *dependencyTracker {
return &dependencyTracker{make(map[kindAndKey]kindAndKeySet), make(map[kindAndKey]kindAndKeySet)}
}
// Updates the dependency graph when an item has changed.
func (d *dependencyTracker) updateDependenciesFrom(
kind st.DataKind,
fromKey string,
fromItem st.ItemDescriptor,
) {
fromWhat := kindAndKey{kind, fromKey}
updatedDependencies := computeDependenciesFrom(kind, fromItem)
oldDependencySet := d.dependenciesFrom[fromWhat]
for oldDep := range oldDependencySet {
depsToThisOldDep := d.dependenciesTo[oldDep]
if depsToThisOldDep != nil {
delete(depsToThisOldDep, fromWhat)
}
}
d.dependenciesFrom[fromWhat] = updatedDependencies
for newDep := range updatedDependencies {
depsToThisNewDep := d.dependenciesTo[newDep]
if depsToThisNewDep == nil {
depsToThisNewDep = make(kindAndKeySet)
d.dependenciesTo[newDep] = depsToThisNewDep
}
depsToThisNewDep.add(fromWhat)
}
}
func (d *dependencyTracker) reset() {
d.dependenciesFrom = make(map[kindAndKey]kindAndKeySet)
d.dependenciesTo = make(map[kindAndKey]kindAndKeySet)
}
// Populates the given set with the union of the initial item and all items that directly or indirectly
// depend on it (based on the current state of the dependency graph).
func (d *dependencyTracker) addAffectedItems(itemsOut kindAndKeySet, initialModifiedItem kindAndKey) {
if !itemsOut.contains(initialModifiedItem) {
itemsOut.add(initialModifiedItem)
affectedItems := d.dependenciesTo[initialModifiedItem]
for affectedItem := range affectedItems {
d.addAffectedItems(itemsOut, affectedItem)
}
}
}