OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package memory | 5 package memory |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | 8 "bytes" |
9 "fmt" | 9 "fmt" |
10 "sort" | 10 "sort" |
11 | 11 |
12 ds "github.com/luci/gae/service/datastore" | 12 ds "github.com/luci/gae/service/datastore" |
13 "github.com/luci/gae/service/datastore/serialize" | 13 "github.com/luci/gae/service/datastore/serialize" |
14 "github.com/luci/gkvlite" | 14 "github.com/luci/gkvlite" |
15 ) | 15 ) |
16 | 16 |
17 var indexCreationDeterministic = false | |
18 | |
19 type qIndexSlice []*ds.IndexDefinition | 17 type qIndexSlice []*ds.IndexDefinition |
20 | 18 |
21 func (s qIndexSlice) Len() int { return len(s) } | 19 func (s qIndexSlice) Len() int { return len(s) } |
22 func (s qIndexSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | 20 func (s qIndexSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
23 func (s qIndexSlice) Less(i, j int) bool { return s[i].Less(s[j]) } | 21 func (s qIndexSlice) Less(i, j int) bool { return s[i].Less(s[j]) } |
24 | 22 |
25 func defaultIndicies(kind string, pmap ds.PropertyMap) []*ds.IndexDefinition { | 23 func defaultIndexes(kind string, pmap ds.PropertyMap) []*ds.IndexDefinition { |
26 ret := make(qIndexSlice, 0, 2*len(pmap)+1) | 24 ret := make(qIndexSlice, 0, 2*len(pmap)+1) |
27 ret = append(ret, &ds.IndexDefinition{Kind: kind}) | 25 ret = append(ret, &ds.IndexDefinition{Kind: kind}) |
28 for name, pvals := range pmap { | 26 for name, pvals := range pmap { |
29 needsIndex := false | 27 needsIndex := false |
30 for _, v := range pvals { | 28 for _, v := range pvals { |
31 if v.IndexSetting() == ds.ShouldIndex { | 29 if v.IndexSetting() == ds.ShouldIndex { |
32 needsIndex = true | 30 needsIndex = true |
33 break | 31 break |
34 } | 32 } |
35 } | 33 } |
36 if !needsIndex { | 34 if !needsIndex { |
37 continue | 35 continue |
38 } | 36 } |
39 ret = append(ret, &ds.IndexDefinition{Kind: kind, SortBy: []ds.I
ndexColumn{{Property: name}}}) | 37 ret = append(ret, &ds.IndexDefinition{Kind: kind, SortBy: []ds.I
ndexColumn{{Property: name}}}) |
40 ret = append(ret, &ds.IndexDefinition{Kind: kind, SortBy: []ds.I
ndexColumn{{Property: name, Direction: ds.DESCENDING}}}) | 38 ret = append(ret, &ds.IndexDefinition{Kind: kind, SortBy: []ds.I
ndexColumn{{Property: name, Direction: ds.DESCENDING}}}) |
41 } | 39 } |
42 » if indexCreationDeterministic { | 40 » if serializationDeterministic { |
43 sort.Sort(ret) | 41 sort.Sort(ret) |
44 } | 42 } |
45 return ret | 43 return ret |
46 } | 44 } |
47 | 45 |
48 func indexEntriesWithBuiltins(k ds.Key, pm ds.PropertyMap, complexIdxs []*ds.Ind
exDefinition) *memStore { | 46 func indexEntriesWithBuiltins(k ds.Key, pm ds.PropertyMap, complexIdxs []*ds.Ind
exDefinition) *memStore { |
49 » sip := partiallySerialize(pm) | 47 » sip := partiallySerialize(k, pm) |
50 » return sip.indexEntries(k, append(defaultIndicies(k.Kind(), pm), complex
Idxs...)) | 48 » return sip.indexEntries(k.Namespace(), append(defaultIndexes(k.Kind(), p
m), complexIdxs...)) |
51 } | 49 } |
52 | 50 |
53 // serializedPvals is all of the serialized DSProperty values in qASC order. | 51 // serializedPvals is all of the serialized DSProperty values in qASC order. |
54 type serializedPvals [][]byte | 52 type serializedPvals [][]byte |
55 | 53 |
56 func (s serializedPvals) Len() int { return len(s) } | 54 func (s serializedPvals) Len() int { return len(s) } |
57 func (s serializedPvals) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | 55 func (s serializedPvals) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
58 func (s serializedPvals) Less(i, j int) bool { return bytes.Compare(s[i], s[j])
< 0 } | 56 func (s serializedPvals) Less(i, j int) bool { return bytes.Compare(s[i], s[j])
< 0 } |
59 | 57 |
60 // prop name -> [<serialized DSProperty>, ...] | 58 // prop name -> [<serialized DSProperty>, ...] |
| 59 // includes special values '__key__' and '__ancestor__' which contains all of |
| 60 // the ancestor entries for this key. |
61 type serializedIndexablePmap map[string]serializedPvals | 61 type serializedIndexablePmap map[string]serializedPvals |
62 | 62 |
63 func partiallySerialize(pm ds.PropertyMap) (ret serializedIndexablePmap) { | 63 func partiallySerialize(k ds.Key, pm ds.PropertyMap) (ret serializedIndexablePma
p) { |
64 » if len(pm) == 0 { | 64 » ret = make(serializedIndexablePmap, len(pm)+2) |
65 » » return | 65 » ret["__key__"] = [][]byte{serialize.ToBytes(ds.MkProperty(k))} |
| 66 » for k != nil { |
| 67 » » ret["__ancestor__"] = append(ret["__ancestor__"], serialize.ToBy
tes(ds.MkProperty(k))) |
| 68 » » k = k.Parent() |
66 } | 69 } |
67 | |
68 buf := &bytes.Buffer{} | |
69 ret = make(serializedIndexablePmap, len(pm)) | |
70 for k, vals := range pm { | 70 for k, vals := range pm { |
| 71 dups := stringSet{} |
71 newVals := make(serializedPvals, 0, len(vals)) | 72 newVals := make(serializedPvals, 0, len(vals)) |
72 for _, v := range vals { | 73 for _, v := range vals { |
73 if v.IndexSetting() == ds.NoIndex { | 74 if v.IndexSetting() == ds.NoIndex { |
74 continue | 75 continue |
75 } | 76 } |
76 » » » buf.Reset() | 77 » » » data := serialize.ToBytes(v) |
77 » » » serialize.WriteProperty(buf, serialize.WithoutContext, v
) | 78 » » » dataS := string(data) |
78 » » » newVal := make([]byte, buf.Len()) | 79 » » » if !dups.add(dataS) { |
79 » » » copy(newVal, buf.Bytes()) | 80 » » » » continue |
80 » » » newVals = append(newVals, newVal) | 81 » » » } |
| 82 » » » newVals = append(newVals, data) |
81 } | 83 } |
82 if len(newVals) > 0 { | 84 if len(newVals) > 0 { |
83 sort.Sort(newVals) | |
84 ret[k] = newVals | 85 ret[k] = newVals |
85 } | 86 } |
86 } | 87 } |
87 return | 88 return |
88 } | 89 } |
89 | 90 |
90 // indexRowGen contains enough information to generate all of the index rows whi
ch | 91 // indexRowGen contains enough information to generate all of the index rows whi
ch |
91 // correspond with a propertyList and a ds.IndexDefinition. | 92 // correspond with a propertyList and a ds.IndexDefinition. |
92 type indexRowGen struct { | 93 type indexRowGen struct { |
93 propVec []serializedPvals | 94 propVec []serializedPvals |
94 orders []ds.IndexDirection | 95 orders []ds.IndexDirection |
95 } | 96 } |
96 | 97 |
97 // permute calls cb for each index row, in the sorted order of the rows. | 98 // permute calls cb for each index row, in the sorted order of the rows. |
98 func (s indexRowGen) permute(cb func([]byte)) { | 99 func (s indexRowGen) permute(collSetFn func(k, v []byte)) { |
99 iVec := make([]int, len(s.propVec)) | 100 iVec := make([]int, len(s.propVec)) |
100 iVecLim := make([]int, len(s.propVec)) | 101 iVecLim := make([]int, len(s.propVec)) |
101 | 102 |
102 incPos := func() bool { | 103 incPos := func() bool { |
103 for i := len(iVec) - 1; i >= 0; i-- { | 104 for i := len(iVec) - 1; i >= 0; i-- { |
104 var done bool | 105 var done bool |
105 var newVal int | 106 var newVal int |
106 if s.orders[i] == ds.ASCENDING { | 107 if s.orders[i] == ds.ASCENDING { |
107 newVal = (iVec[i] + 1) % iVecLim[i] | 108 newVal = (iVec[i] + 1) % iVecLim[i] |
108 done = newVal != 0 | 109 done = newVal != 0 |
(...skipping 21 matching lines...) Expand all Loading... |
130 if s.orders[i] == ds.DESCENDING { | 131 if s.orders[i] == ds.DESCENDING { |
131 iVec[i] = iVecLim[i] - 1 | 132 iVec[i] = iVecLim[i] - 1 |
132 } | 133 } |
133 } | 134 } |
134 | 135 |
135 for { | 136 for { |
136 bufsiz := 0 | 137 bufsiz := 0 |
137 for pvalSliceIdx, pvalIdx := range iVec { | 138 for pvalSliceIdx, pvalIdx := range iVec { |
138 bufsiz += len(s.propVec[pvalSliceIdx][pvalIdx]) | 139 bufsiz += len(s.propVec[pvalSliceIdx][pvalIdx]) |
139 } | 140 } |
140 » » buf := bytes.NewBuffer(make([]byte, 0, bufsiz)) | 141 » » buf := serialize.Invertible(bytes.NewBuffer(make([]byte, 0, bufs
iz))) |
141 for pvalSliceIdx, pvalIdx := range iVec { | 142 for pvalSliceIdx, pvalIdx := range iVec { |
142 data := s.propVec[pvalSliceIdx][pvalIdx] | 143 data := s.propVec[pvalSliceIdx][pvalIdx] |
143 » » » if s.orders[pvalSliceIdx] == ds.ASCENDING { | 144 » » » buf.SetInvert(s.orders[pvalSliceIdx] == ds.DESCENDING) |
144 » » » » buf.Write(data) | 145 » » » buf.Write(data) |
145 » » » } else { | |
146 » » » » for _, b := range data { | |
147 » » » » » buf.WriteByte(b ^ 0xFF) | |
148 » » » » } | |
149 » » » } | |
150 } | 146 } |
151 » » cb(buf.Bytes()) | 147 » » collSetFn(buf.Bytes(), []byte{}) |
152 if !incPos() { | 148 if !incPos() { |
153 break | 149 break |
154 } | 150 } |
155 } | 151 } |
156 } | 152 } |
157 | 153 |
158 type matcher struct { | 154 type matcher struct { |
159 buf indexRowGen | 155 buf indexRowGen |
160 } | 156 } |
161 | 157 |
162 // matcher.match checks to see if the mapped, serialized property values | 158 // matcher.match checks to see if the mapped, serialized property values |
163 // match the index. If they do, it returns a indexRowGen. Do not write or modify | 159 // match the index. If they do, it returns a indexRowGen. Do not write or modify |
164 // the data in the indexRowGen. | 160 // the data in the indexRowGen. |
165 func (m *matcher) match(idx *ds.IndexDefinition, sip serializedIndexablePmap) (i
ndexRowGen, bool) { | 161 func (m *matcher) match(sortBy []ds.IndexColumn, sip serializedIndexablePmap) (i
ndexRowGen, bool) { |
166 m.buf.propVec = m.buf.propVec[:0] | 162 m.buf.propVec = m.buf.propVec[:0] |
167 m.buf.orders = m.buf.orders[:0] | 163 m.buf.orders = m.buf.orders[:0] |
168 » for _, sb := range idx.SortBy { | 164 » for _, sb := range sortBy { |
169 » » if sb.Property == "__key__" { | |
170 » » » panic("don't know how to build compound index on __key__
") | |
171 » » } | |
172 if pv, ok := sip[sb.Property]; ok { | 165 if pv, ok := sip[sb.Property]; ok { |
173 m.buf.propVec = append(m.buf.propVec, pv) | 166 m.buf.propVec = append(m.buf.propVec, pv) |
174 m.buf.orders = append(m.buf.orders, sb.Direction) | 167 m.buf.orders = append(m.buf.orders, sb.Direction) |
175 } else { | 168 } else { |
176 return indexRowGen{}, false | 169 return indexRowGen{}, false |
177 } | 170 } |
178 } | 171 } |
179 return m.buf, true | 172 return m.buf, true |
180 } | 173 } |
181 | 174 |
182 func (sip serializedIndexablePmap) indexEntries(k ds.Key, idxs []*ds.IndexDefini
tion) *memStore { | 175 func (sip serializedIndexablePmap) indexEntries(ns string, idxs []*ds.IndexDefin
ition) *memStore { |
183 ret := newMemStore() | 176 ret := newMemStore() |
184 idxColl := ret.SetCollection("idx", nil) | 177 idxColl := ret.SetCollection("idx", nil) |
185 // getIdxEnts retrieves an index collection or adds it if it's not there
. | |
186 getIdxEnts := func(qi *ds.IndexDefinition) *memCollection { | |
187 b := serialize.ToBytes(*qi) | |
188 idxColl.Set(b, []byte{}) | |
189 return ret.SetCollection(fmt.Sprintf("idx:%s:%s", k.Namespace(),
b), nil) | |
190 } | |
191 | |
192 keyData := serialize.ToBytes(k) | |
193 | |
194 walkPermutations := func(prefix []byte, irg indexRowGen, ents *memCollec
tion) { | |
195 prev := []byte{} // intentionally make a non-nil slice, gkvlite
hates nil. | |
196 irg.permute(func(data []byte) { | |
197 buf := bytes.NewBuffer(make([]byte, 0, len(prefix)+len(d
ata)+len(keyData))) | |
198 buf.Write(prefix) | |
199 buf.Write(data) | |
200 buf.Write(keyData) | |
201 ents.Set(buf.Bytes(), prev) | |
202 prev = data | |
203 }) | |
204 } | |
205 | 178 |
206 mtch := matcher{} | 179 mtch := matcher{} |
207 for _, idx := range idxs { | 180 for _, idx := range idxs { |
208 » » if irg, ok := mtch.match(idx, sip); ok { | 181 » » idx = idx.Normalize() |
209 » » » idxEnts := getIdxEnts(idx) | 182 » » if irg, ok := mtch.match(idx.GetFullSortOrder(), sip); ok { |
210 » » » if len(irg.propVec) == 0 { | 183 » » » idxBin := serialize.ToBytes(*idx.PrepForIdxTable()) |
211 » » » » idxEnts.Set(keyData, []byte{}) // propless index
, e.g. kind -> key = nil | 184 » » » idxColl.Set(idxBin, []byte{}) |
212 » » » } else if idx.Ancestor { | 185 » » » coll := ret.SetCollection(fmt.Sprintf("idx:%s:%s", ns, i
dxBin), nil) |
213 » » » » for ancKey := k; ancKey != nil; ancKey = ancKey.
Parent() { | 186 » » » irg.permute(coll.Set) |
214 » » » » » walkPermutations(serialize.ToBytes(ancKe
y), irg, idxEnts) | |
215 » » » » } | |
216 » » » } else { | |
217 » » » » walkPermutations(nil, irg, idxEnts) | |
218 » » » } | |
219 } | 187 } |
220 } | 188 } |
221 | 189 |
222 return ret | 190 return ret |
223 } | 191 } |
224 | 192 |
225 func getCompIdxs(idxColl *memCollection) []*ds.IndexDefinition { | 193 // walkCompIdxs walks the table of compound indexes in the store. If `endsWith` |
226 » // load all current complex query index definitions. | 194 // is provided, this will only walk over compound indexes which match |
227 » compIdx := []*ds.IndexDefinition{} | 195 // Kind, Ancestor, and whose SortBy has `endsWith.SortBy` as a suffix. |
228 » complexQueryPrefix := ds.IndexComplexQueryPrefix() | 196 func walkCompIdxs(store *memStore, endsWith *ds.IndexDefinition, cb func(*ds.Ind
exDefinition) bool) { |
229 » idxColl.VisitItemsAscend(complexQueryPrefix, false, func(i *gkvlite.Item
) bool { | |
230 » » if !bytes.HasPrefix(i.Key, complexQueryPrefix) { | |
231 » » » return false | |
232 » » } | |
233 » » qi, err := serialize.ReadIndexDefinition(bytes.NewBuffer(i.Key)) | |
234 » » if err != nil { | |
235 » » » panic(err) // memory corruption | |
236 » » } | |
237 » » compIdx = append(compIdx, &qi) | |
238 » » return true | |
239 » }) | |
240 » return compIdx | |
241 } | |
242 | |
243 func getIdxColl(store *memStore) *memCollection { | |
244 idxColl := store.GetCollection("idx") | 197 idxColl := store.GetCollection("idx") |
245 if idxColl == nil { | 198 if idxColl == nil { |
246 » » idxColl = store.SetCollection("idx", nil) | 199 » » return |
247 } | 200 } |
248 » return idxColl | 201 » itrDef := iterDefinition{c: idxColl} |
| 202 |
| 203 » if endsWith != nil { |
| 204 » » full := serialize.ToBytes(*endsWith.Flip()) |
| 205 » » // chop off the null terminating byte |
| 206 » » itrDef.prefix = full[:len(full)-1] |
| 207 » } |
| 208 |
| 209 » it := itrDef.mkIter() |
| 210 » defer it.stop() |
| 211 » for !it.stopped { |
| 212 » » it.next(nil, func(i *gkvlite.Item) { |
| 213 » » » if i == nil { |
| 214 » » » » return |
| 215 » » » } |
| 216 » » » qi, err := serialize.ReadIndexDefinition(bytes.NewBuffer
(i.Key)) |
| 217 » » » memoryCorruption(err) |
| 218 » » » if !cb(qi.Flip()) { |
| 219 » » » » it.stop() |
| 220 » » » } |
| 221 » » }) |
| 222 » } |
249 } | 223 } |
250 | 224 |
251 func mergeIndexes(ns string, store, oldIdx, newIdx *memStore) { | 225 func mergeIndexes(ns string, store, oldIdx, newIdx *memStore) { |
252 » idxColl := getIdxColl(store) | 226 » prefixBuf := []byte("idx:" + ns + ":") |
253 » prefix := "idx:" + ns + ":" | 227 » origPrefixBufLen := len(prefixBuf) |
254 gkvCollide(oldIdx.GetCollection("idx"), newIdx.GetCollection("idx"), fun
c(k, ov, nv []byte) { | 228 gkvCollide(oldIdx.GetCollection("idx"), newIdx.GetCollection("idx"), fun
c(k, ov, nv []byte) { |
255 » » ks := prefix + string(k) | 229 » » prefixBuf = append(prefixBuf[:origPrefixBufLen], k...) |
256 » » if idxColl.Get(k) == nil { | 230 » » ks := string(prefixBuf) |
257 » » » // avoids unnecessary mutation, otherwise the idx collec
tion thrashes on | |
258 » » » // every update. | |
259 » » » idxColl.Set(k, []byte{}) | |
260 » » } | |
261 | 231 |
262 coll := store.GetCollection(ks) | 232 coll := store.GetCollection(ks) |
263 if coll == nil { | 233 if coll == nil { |
264 coll = store.SetCollection(ks, nil) | 234 coll = store.SetCollection(ks, nil) |
265 } | 235 } |
| 236 |
266 oldColl := oldIdx.GetCollection(ks) | 237 oldColl := oldIdx.GetCollection(ks) |
267 newColl := newIdx.GetCollection(ks) | 238 newColl := newIdx.GetCollection(ks) |
268 | 239 |
269 switch { | 240 switch { |
270 case ov == nil && nv != nil: // all additions | 241 case ov == nil && nv != nil: // all additions |
271 newColl.VisitItemsAscend(nil, false, func(i *gkvlite.Ite
m) bool { | 242 newColl.VisitItemsAscend(nil, false, func(i *gkvlite.Ite
m) bool { |
272 » » » » coll.Set(i.Key, i.Val) | 243 » » » » coll.Set(i.Key, []byte{}) |
273 return true | 244 return true |
274 }) | 245 }) |
275 case ov != nil && nv == nil: // all deletions | 246 case ov != nil && nv == nil: // all deletions |
276 oldColl.VisitItemsAscend(nil, false, func(i *gkvlite.Ite
m) bool { | 247 oldColl.VisitItemsAscend(nil, false, func(i *gkvlite.Ite
m) bool { |
277 coll.Delete(i.Key) | 248 coll.Delete(i.Key) |
278 return true | 249 return true |
279 }) | 250 }) |
280 case ov != nil && nv != nil: // merge | 251 case ov != nil && nv != nil: // merge |
281 gkvCollide(oldColl, newColl, func(k, ov, nv []byte) { | 252 gkvCollide(oldColl, newColl, func(k, ov, nv []byte) { |
282 if nv == nil { | 253 if nv == nil { |
283 coll.Delete(k) | 254 coll.Delete(k) |
284 } else { | 255 } else { |
285 » » » » » coll.Set(k, nv) | 256 » » » » » coll.Set(k, []byte{}) |
286 } | 257 } |
287 }) | 258 }) |
288 default: | 259 default: |
289 » » » panic("impossible") | 260 » » » impossible(fmt.Errorf("both values from gkvCollide were
nil?")) |
290 } | 261 } |
291 // TODO(riannucci): remove entries from idxColl and remove index
collections | 262 // TODO(riannucci): remove entries from idxColl and remove index
collections |
292 // when there are no index entries for that index any more. | 263 // when there are no index entries for that index any more. |
293 }) | 264 }) |
294 } | 265 } |
295 | 266 |
296 func addIndex(store *memStore, ns string, compIdx []*ds.IndexDefinition) { | 267 func addIndex(store *memStore, ns string, compIdx []*ds.IndexDefinition) { |
297 » store.GetCollection("ents:"+ns).VisitItemsAscend(nil, true, func(i *gkvl
ite.Item) bool { | 268 » normalized := make([]*ds.IndexDefinition, len(compIdx)) |
298 » » pm, err := rpmWoCtx(i.Val, ns) | 269 » idxColl := store.SetCollection("idx", nil) |
299 » » if err != nil { | 270 » for i, idx := range compIdx { |
300 » » » panic(err) // memory corruption | 271 » » normalized[i] = idx.Normalize() |
301 » » } | 272 » » idxColl.Set(serialize.ToBytes(*normalized[i].PrepForIdxTable()),
[]byte{}) |
302 » » k, err := serialize.ReadKey(bytes.NewBuffer(i.Key), serialize.Wi
thoutContext, globalAppID, ns) | 273 » } |
303 » » if err != nil { | 274 |
304 » » » panic(err) | 275 » if allEnts := store.GetCollection("ents:" + ns); allEnts != nil { |
305 » » } | 276 » » allEnts.VisitItemsAscend(nil, true, func(i *gkvlite.Item) bool { |
306 » » sip := partiallySerialize(pm) | 277 » » » pm, err := rpmWoCtx(i.Val, ns) |
307 » » mergeIndexes(ns, store, newMemStore(), sip.indexEntries(k, compI
dx)) | 278 » » » memoryCorruption(err) |
| 279 |
| 280 » » » prop, err := serialize.ReadProperty(bytes.NewBuffer(i.Ke
y), serialize.WithoutContext, globalAppID, ns) |
| 281 » » » memoryCorruption(err) |
| 282 |
| 283 » » » k := prop.Value().(ds.Key) |
| 284 |
| 285 » » » sip := partiallySerialize(k, pm) |
| 286 |
| 287 » » » mergeIndexes(ns, store, |
| 288 » » » » newMemStore(), |
| 289 » » » » sip.indexEntries(ns, normalized)) |
| 290 » » » return true |
| 291 » » }) |
| 292 » } |
| 293 } |
| 294 |
| 295 func updateIndexes(store *memStore, key ds.Key, oldEnt, newEnt ds.PropertyMap) { |
| 296 » // load all current complex query index definitions. |
| 297 » compIdx := []*ds.IndexDefinition{} |
| 298 » walkCompIdxs(store, nil, func(i *ds.IndexDefinition) bool { |
| 299 » » compIdx = append(compIdx, i) |
308 return true | 300 return true |
309 }) | 301 }) |
310 } | |
311 | |
312 func updateIndicies(store *memStore, key ds.Key, oldEnt, newEnt ds.PropertyMap)
{ | |
313 // load all current complex query index definitions. | |
314 compIdx := getCompIdxs(getIdxColl(store)) | |
315 | 302 |
316 mergeIndexes(key.Namespace(), store, | 303 mergeIndexes(key.Namespace(), store, |
317 indexEntriesWithBuiltins(key, oldEnt, compIdx), | 304 indexEntriesWithBuiltins(key, oldEnt, compIdx), |
318 indexEntriesWithBuiltins(key, newEnt, compIdx)) | 305 indexEntriesWithBuiltins(key, newEnt, compIdx)) |
319 } | 306 } |
OLD | NEW |