Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(102)

Side by Side Diff: go/src/infra/gae/libs/wrapper/memory/plist.go

Issue 1230303003: Revert "Refactor current GAE abstraction library to be free of the SDK*" (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 package memory
6
7 import (
8 "bytes"
9 "fmt"
10 "reflect"
11 "sort"
12 "time"
13
14 "appengine"
15 "appengine/datastore"
16
17 "github.com/luci/gkvlite"
18 "github.com/luci/luci-go/common/cmpbin"
19 )
20
21 type typData struct {
22 noIndex bool
23 typ propValType
24 data interface{}
25 }
26
27 func newTypData(noIndex bool, v interface{}) (ret *typData, err error) {
28 typ := pvUNKNOWN
29
30 switch x := v.(type) {
31 case nil:
32 typ = pvNull
33 case time.Time:
34 typ = pvTime
35 case int64:
36 typ = pvInt
37 case float64:
38 typ = pvFloat
39 case bool:
40 if x {
41 typ = pvBoolTrue
42 } else {
43 typ = pvBoolFalse
44 }
45 case []byte, datastore.ByteString:
46 typ = pvBytes
47 case appengine.BlobKey:
48 typ = pvBlobKey
49 case string:
50 typ = pvStr
51 case appengine.GeoPoint:
52 typ = pvGeoPoint
53 case *datastore.Key:
54 typ = pvKey
55 }
56 if typ == pvUNKNOWN {
57 err = fmt.Errorf("propValTypeOf: unknown type of %T: %#v", v, v)
58 }
59
60 return &typData{noIndex, typ, v}, err
61 }
62
63 func (td *typData) WriteBinary(buf *bytes.Buffer, nso nsOption) error {
64 typb := byte(td.typ)
65 if td.noIndex {
66 typb |= 0x80
67 }
68 buf.WriteByte(typb)
69 switch td.typ {
70 case pvNull, pvBoolFalse, pvBoolTrue:
71 return nil
72 case pvInt:
73 cmpbin.WriteInt(buf, td.data.(int64))
74 case pvFloat:
75 writeFloat64(buf, td.data.(float64))
76 case pvStr:
77 writeString(buf, td.data.(string))
78 case pvBytes:
79 if td.noIndex {
80 writeBytes(buf, td.data.([]byte))
81 } else {
82 writeBytes(buf, td.data.(datastore.ByteString))
83 }
84 case pvTime:
85 writeTime(buf, td.data.(time.Time))
86 case pvGeoPoint:
87 writeGeoPoint(buf, td.data.(appengine.GeoPoint))
88 case pvKey:
89 writeKey(buf, nso, td.data.(*datastore.Key))
90 case pvBlobKey:
91 writeString(buf, string(td.data.(appengine.BlobKey)))
92 default:
93 return fmt.Errorf("write: unknown type! %v", td)
94 }
95 return nil
96 }
97
98 func (td *typData) ReadBinary(buf *bytes.Buffer, nso nsOption, ns string) error {
99 typb, err := buf.ReadByte()
100 if err != nil {
101 return err
102 }
103 td.noIndex = (typb & 0x80) != 0 // highbit means noindex
104 td.typ = propValType(typb & 0x7f)
105 switch td.typ {
106 case pvNull:
107 td.data = nil
108 case pvBoolTrue:
109 td.data = true
110 case pvBoolFalse:
111 td.data = false
112 case pvInt:
113 td.data, _, err = cmpbin.ReadInt(buf)
114 case pvFloat:
115 td.data, err = readFloat64(buf)
116 case pvStr:
117 td.data, err = readString(buf)
118 case pvBytes:
119 b := []byte(nil)
120 if b, err = readBytes(buf); err != nil {
121 return err
122 }
123 if td.noIndex {
124 td.data = b
125 } else {
126 td.data = datastore.ByteString(b)
127 }
128 case pvTime:
129 td.data, err = readTime(buf)
130 case pvGeoPoint:
131 td.data, err = readGeoPoint(buf)
132 case pvKey:
133 td.data, err = readKey(buf, nso, ns)
134 case pvBlobKey:
135 s := ""
136 if s, err = readString(buf); err != nil {
137 return err
138 }
139 td.data = appengine.BlobKey(s)
140 default:
141 return fmt.Errorf("read: unknown type! %v", td)
142 }
143
144 return err
145 }
146
147 type pvals struct {
148 name string
149 vals []*typData
150 }
151
152 type propertyList []datastore.Property
153
154 var _ = datastore.PropertyLoadSaver((*propertyList)(nil))
155
156 func (pl *propertyList) Load(ch <-chan datastore.Property) error {
157 return (*datastore.PropertyList)(pl).Load(ch)
158 }
159
160 func (pl *propertyList) Save(ch chan<- datastore.Property) error {
161 return (*datastore.PropertyList)(pl).Save(ch)
162 }
163
164 // collatedProperties is the reduction of a *propertyList such that each entry
165 // in a collatedProperties has a unique name. For example, collating this:
166 // pl := &propertyList{
167 // datastore.Property{Name: "wat", Val: "hello"},
168 // datastore.Property{Name: "other", Val: 100},
169 // datastore.Property{Name: "wat", Val: "goodbye", noIndex: true},
170 // }
171 //
172 // Would get a collatedProperties which looked like:
173 // c := collatedProperties{
174 // &pvals{"wat", []*typData{&{false, pvStr, "hello"},
175 // &{true, pvStr, "goodbye"}}},
176 // &pvals{"other", []*typData{&{false, pvInt, 100}}}
177 // }
178 type collatedProperties []*pvals
179
180 func (c collatedProperties) defaultIndicies(kind string) []*qIndex {
181 ret := make([]*qIndex, 0, 2*len(c)+1)
182 ret = append(ret, &qIndex{kind, false, nil})
183 for _, pvals := range c {
184 needsIndex := false
185 for _, v := range pvals.vals {
186 if !v.noIndex {
187 needsIndex = true
188 break
189 }
190 }
191 if !needsIndex {
192 continue
193 }
194 ret = append(ret, &qIndex{kind, false, []qSortBy{{pvals.name, qA SC}}})
195 ret = append(ret, &qIndex{kind, false, []qSortBy{{pvals.name, qD EC}}})
196 }
197 return ret
198 }
199
200 // serializedPval is a single pvals.vals entry which has been serialized (in
201 // qASC order).
202 type serializedPval []byte
203
204 // serializedPvals is all of the pvals.vals entries from a single pvals (in qASC
205 // order). It does not include the pvals.name field.
206 type serializedPvals []serializedPval
207
208 func (s serializedPvals) Len() int { return len(s) }
209 func (s serializedPvals) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
210 func (s serializedPvals) Less(i, j int) bool { return bytes.Compare(s[i], s[j]) < 0 }
211
212 type mappedPlist map[string]serializedPvals
213
214 func (c collatedProperties) indexableMap() (mappedPlist, error) {
215 ret := make(mappedPlist, len(c))
216 for _, pv := range c {
217 data := make(serializedPvals, 0, len(pv.vals))
218 for _, v := range pv.vals {
219 if v.noIndex {
220 continue
221 }
222 buf := &bytes.Buffer{}
223 if err := v.WriteBinary(buf, noNS); err != nil {
224 return nil, err
225 }
226 data = append(data, buf.Bytes())
227 }
228 if len(data) == 0 {
229 continue
230 }
231 sort.Sort(data)
232 ret[pv.name] = data
233 }
234 return ret, nil
235 }
236
237 // indexRowGen contains enough information to generate all of the index rows whi ch
238 // correspond with a propertyList and a qIndex.
239 type indexRowGen struct {
240 propVec []serializedPvals
241 orders []qDirection
242 }
243
244 // permute calls cb for each index row, in the sorted order of the rows.
245 func (s indexRowGen) permute(cb func([]byte)) {
246 iVec := make([]int, len(s.propVec))
247 iVecLim := make([]int, len(s.propVec))
248
249 incPos := func() bool {
250 for i := len(iVec) - 1; i >= 0; i-- {
251 var done bool
252 var newVal int
253 if s.orders[i] == qASC {
254 newVal = (iVec[i] + 1) % iVecLim[i]
255 done = newVal != 0
256 } else {
257 newVal = (iVec[i] - 1)
258 if newVal < 0 {
259 newVal = iVecLim[i] - 1
260 } else {
261 done = true
262 }
263 }
264 iVec[i] = newVal
265 if done {
266 return true
267 }
268 }
269 return false
270 }
271
272 for i, sps := range s.propVec {
273 iVecLim[i] = len(sps)
274 }
275
276 for i := range iVec {
277 if s.orders[i] == qDEC {
278 iVec[i] = iVecLim[i] - 1
279 }
280 }
281
282 for {
283 bufsiz := 0
284 for pvalSliceIdx, pvalIdx := range iVec {
285 bufsiz += len(s.propVec[pvalSliceIdx][pvalIdx])
286 }
287 buf := bytes.NewBuffer(make([]byte, 0, bufsiz))
288 for pvalSliceIdx, pvalIdx := range iVec {
289 data := s.propVec[pvalSliceIdx][pvalIdx]
290 if s.orders[pvalSliceIdx] == qASC {
291 buf.Write(data)
292 } else {
293 for _, b := range data {
294 buf.WriteByte(b ^ 0xFF)
295 }
296 }
297 }
298 cb(buf.Bytes())
299 if !incPos() {
300 break
301 }
302 }
303 }
304
305 type matcher struct {
306 buf indexRowGen
307 }
308
309 // matcher.match checks to see if the mapped, serialized property values
310 // match the index. If they do, it returns a indexRowGen. Do not write or modify
311 // the data in the indexRowGen.
312 func (m *matcher) match(idx *qIndex, mpvals mappedPlist) (indexRowGen, bool) {
313 m.buf.propVec = m.buf.propVec[:0]
314 m.buf.orders = m.buf.orders[:0]
315 for _, sb := range idx.sortby {
316 if pv, ok := mpvals[sb.prop]; ok {
317 m.buf.propVec = append(m.buf.propVec, pv)
318 m.buf.orders = append(m.buf.orders, sb.dir)
319 } else {
320 return indexRowGen{}, false
321 }
322 }
323 return m.buf, true
324 }
325
326 func (c collatedProperties) indexEntries(k *datastore.Key, idxs []*qIndex) (*mem Store, error) {
327 m, err := c.indexableMap()
328 if err != nil {
329 return nil, err
330 }
331
332 ret := newMemStore()
333 idxColl := ret.SetCollection("idx", nil)
334 // getIdxEnts retrieves an index collection or adds it if it's not there .
335 getIdxEnts := func(qi *qIndex) *memCollection {
336 buf := &bytes.Buffer{}
337 qi.WriteBinary(buf)
338 b := buf.Bytes()
339 idxColl.Set(b, []byte{})
340 return ret.SetCollection(fmt.Sprintf("idx:%s:%s", k.Namespace(), b), nil)
341 }
342
343 buf := &bytes.Buffer{}
344 writeKey(buf, noNS, k) // ns is in idxEnts collection name.
345 keyData := buf.Bytes()
346
347 walkPermutations := func(prefix []byte, irg indexRowGen, ents *memCollec tion) {
348 prev := []byte{} // intentionally make a non-nil slice, gkvlite hates nil.
349 irg.permute(func(data []byte) {
350 buf := bytes.NewBuffer(make([]byte, 0, len(prefix)+len(d ata)+len(keyData)))
351 buf.Write(prefix)
352 buf.Write(data)
353 buf.Write(keyData)
354 ents.Set(buf.Bytes(), prev)
355 prev = data
356 })
357 }
358
359 mtch := matcher{}
360 for _, idx := range idxs {
361 if irg, ok := mtch.match(idx, m); ok {
362 idxEnts := getIdxEnts(idx)
363 if len(irg.propVec) == 0 {
364 idxEnts.Set(keyData, []byte{}) // propless index , e.g. kind -> key = nil
365 } else if idx.ancestor {
366 for ancKey := k; ancKey != nil; ancKey = ancKey. Parent() {
367 buf := &bytes.Buffer{}
368 writeKey(buf, noNS, ancKey)
369 walkPermutations(buf.Bytes(), irg, idxEn ts)
370 }
371 } else {
372 walkPermutations(nil, irg, idxEnts)
373 }
374 }
375 }
376
377 return ret, nil
378 }
379
380 func (pl *propertyList) indexEntriesWithBuiltins(k *datastore.Key, complexIdxs [ ]*qIndex) (ret *memStore, err error) {
381 c, err := pl.collate()
382 if err == nil {
383 ret, err = c.indexEntries(k, append(c.defaultIndicies(k.Kind()), complexIdxs...))
384 }
385 return
386 }
387
388 func (pl *propertyList) collate() (collatedProperties, error) {
389 if pl == nil || len(*pl) == 0 {
390 return nil, nil
391 }
392
393 cols := []*pvals{}
394 colIdx := map[string]int{}
395
396 for _, p := range *pl {
397 if idx, ok := colIdx[p.Name]; ok {
398 c := cols[idx]
399 td, err := newTypData(p.NoIndex, p.Value)
400 if err != nil {
401 return nil, err
402 }
403 c.vals = append(c.vals, td)
404 } else {
405 colIdx[p.Name] = len(cols)
406 td, err := newTypData(p.NoIndex, p.Value)
407 if err != nil {
408 return nil, err
409 }
410 cols = append(cols, &pvals{p.Name, []*typData{td}})
411 }
412 }
413
414 return cols, nil
415 }
416
417 func (pl *propertyList) addCollated(pv *pvals) {
418 for _, v := range pv.vals {
419 *pl = append(*pl, datastore.Property{
420 Name: pv.name,
421 Multiple: len(pv.vals) > 1,
422 NoIndex: v.noIndex,
423 Value: v.data,
424 })
425 }
426 }
427
428 func updateIndicies(store *memStore, key *datastore.Key, oldEnt, newEnt *propert yList) error {
429 var err error
430
431 idxColl := store.GetCollection("idx")
432 if idxColl == nil {
433 idxColl = store.SetCollection("idx", nil)
434 }
435
436 // load all current complex query index definitions.
437 compIdx := []*qIndex{}
438 idxColl.VisitItemsAscend(complexQueryPrefix, false, func(i *gkvlite.Item ) bool {
439 if !bytes.HasPrefix(i.Key, complexQueryPrefix) {
440 return false
441 }
442 qi := &qIndex{}
443 if err = qi.ReadBinary(bytes.NewBuffer(i.Key)); err != nil {
444 return false
445 }
446 compIdx = append(compIdx, qi)
447 return true
448 })
449 if err != nil {
450 return err
451 }
452
453 oldIdx, err := oldEnt.indexEntriesWithBuiltins(key, compIdx)
454 if err != nil {
455 return err
456 }
457
458 newIdx, err := newEnt.indexEntriesWithBuiltins(key, compIdx)
459 if err != nil {
460 return err
461 }
462
463 prefix := "idx:" + key.Namespace() + ":"
464
465 gkvCollide(oldIdx.GetCollection("idx"), newIdx.GetCollection("idx"), fun c(k, ov, nv []byte) {
466 ks := prefix + string(k)
467 idxColl.Set(k, []byte{})
468
469 coll := store.GetCollection(ks)
470 if coll == nil {
471 coll = store.SetCollection(ks, nil)
472 }
473 oldColl := oldIdx.GetCollection(ks)
474 newColl := newIdx.GetCollection(ks)
475
476 switch {
477 case ov == nil && nv != nil: // all additions
478 newColl.VisitItemsAscend(nil, false, func(i *gkvlite.Ite m) bool {
479 coll.Set(i.Key, i.Val)
480 return true
481 })
482 case ov != nil && nv == nil: // all deletions
483 oldColl.VisitItemsAscend(nil, false, func(i *gkvlite.Ite m) bool {
484 coll.Delete(i.Key)
485 return true
486 })
487 case ov != nil && nv != nil: // merge
488 gkvCollide(oldColl, newColl, func(k, ov, nv []byte) {
489 if nv == nil {
490 coll.Delete(k)
491 } else {
492 coll.Set(k, nv)
493 }
494 })
495 default:
496 panic("impossible")
497 }
498 // TODO(riannucci): remove entries from idxColl and remove index collections
499 // when there are no index entries for that index any more.
500 })
501
502 return nil
503 }
504
505 func (pl *propertyList) MarshalBinary() ([]byte, error) {
506 cols, err := pl.collate()
507 if err != nil || len(cols) == 0 {
508 return nil, err
509 }
510
511 pieces := make([][]byte, 0, len(*pl)*2+1)
512 for _, pv := range cols {
513 // TODO(riannucci): estimate buffer size better.
514 buf := bytes.NewBuffer(make([]byte, 0, cmpbin.MaxIntLen64+len(pv .name)))
515 writeString(buf, pv.name)
516 err := pv.WriteBinary(buf)
517 if err != nil {
518 return nil, err
519 }
520 pieces = append(pieces, buf.Bytes())
521 }
522 return bytes.Join(pieces, nil), nil
523 }
524
525 func (pl *propertyList) UnmarshalBinary(data []byte) error {
526 buf := bytes.NewBuffer(data)
527 for buf.Len() > 0 {
528 name, err := readString(buf)
529 if err != nil {
530 return err
531 }
532
533 pv := &pvals{name: name}
534 err = pv.ReadBinary(buf)
535 if err != nil {
536 return err
537 }
538 pl.addCollated(pv)
539 }
540
541 return nil
542 }
543
544 func toPL(src interface{}) (ret *propertyList, err error) {
545 propchan := make(chan datastore.Property)
546 ret = &propertyList{}
547 go func() { err = datastore.SaveStruct(src, propchan) }()
548 err2 := ret.Load(propchan)
549 if err != nil {
550 return
551 }
552 return ret, err2
553 }
554
555 func fromPL(props *propertyList, dst interface{}) (err error) {
556 propchan := make(chan datastore.Property)
557 go func() { err = props.Save(propchan) }()
558 err2 := datastore.LoadStruct(dst, propchan)
559 if err != nil {
560 return err
561 }
562 return err2
563 }
564
565 type propValType byte
566
567 var byteSliceType = reflect.TypeOf([]byte(nil))
568
569 // These constants are in the order described by
570 // https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type _ordering
571 // with a slight divergence for the Int/Time split.
572 // NOTE: this enum can only occupy 7 bits, because we use the high bit to encode
573 // indexed/non-indexed. See typData.WriteBinary.
574 const (
575 pvNull propValType = iota
576 pvInt
577
578 // NOTE: this is a slight divergence; times and integers actually sort
579 // together (apparently?) in datastore. This is probably insane, and I d on't
580 // want to add the complexity of field 'meaning' as a sparate concept fr om the
581 // field's 'type' (which is what datastore seems to do, judging from the
582 // protobufs). So if you're here because you implemented an app which re lies
583 // on time.Time and int64 sorting together, then this is why your app ac ts
584 // differently in production. My advice is to NOT DO THAT. If you really want
585 // this (and you probably don't), you should take care of the time.Time <->
586 // int64 conversion in your app and just use a property type of int64.
587 pvTime
588
589 // NOTE: this is also a slight divergence, but not a semantic one. IIUC, in
590 // datastore 'bool' is actually the type and the value is either 0 or
591 // 1 (taking another byte to store). Since we have plenty of space in th is
592 // type byte, I just merge the value into the type for booleans. If this
593 // becomes problematic, consider changing this to just pvBool, and then
594 // encoding a 0 or 1 as a byte in the relevant marshalling routines.
595 pvBoolFalse
596 pvBoolTrue
597 pvBytes // []byte or datastore.ByteString
598 pvStr // string or string noindex
599 pvFloat
600 pvGeoPoint
601
602 // These two are problematic, because they force us to bind to the appen gine
603 // SDK code. If we can drop support for these and turn them into hard er rors,
604 // that could let us decouple from the various appengine SDKs. Maybe.
605 pvKey // TODO(riannucci): remove support for this (use a string)
606 pvBlobKey // TODO(riannucci): remove support for this (use a string)
607
608 pvUNKNOWN
609 )
610
611 func (p *pvals) ReadBinary(buf *bytes.Buffer) error {
612 n, _, err := cmpbin.ReadUint(buf)
613 if err != nil {
614 return err
615 }
616
617 p.vals = make([]*typData, n)
618 for i := range p.vals {
619 p.vals[i] = &typData{}
620 err := p.vals[i].ReadBinary(buf, withNS, "")
621 if err != nil {
622 return err
623 }
624 }
625
626 return nil
627 }
628
629 func (p *pvals) WriteBinary(buf *bytes.Buffer) error {
630 cmpbin.WriteUint(buf, uint64(len(p.vals)))
631 for _, v := range p.vals {
632 if err := v.WriteBinary(buf, withNS); err != nil {
633 return err
634 }
635 }
636 return nil
637 }
OLDNEW
« no previous file with comments | « go/src/infra/gae/libs/wrapper/memory/memory.infra_testing ('k') | go/src/infra/gae/libs/wrapper/memory/plist_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698