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

Side by Side Diff: service/datastore/properties.go

Issue 2342063003: Differentiate between single- and multi- props. (Closed)
Patch Set: Slice is now always a clone. This is marginally worse performance, but a much safer UI. Created 4 years, 3 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
« no previous file with comments | « service/datastore/pls_test.go ('k') | service/datastore/properties_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The LUCI Authors. All rights reserved. 1 // Copyright 2015 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package datastore 5 package datastore
6 6
7 import ( 7 import (
8 "bytes" 8 "bytes"
9 "encoding/base64" 9 "encoding/base64"
10 "errors" 10 "errors"
(...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after
329 329
330 switch t { 330 switch t {
331 case typeOfKey: 331 case typeOfKey:
332 if v.IsNil() { 332 if v.IsNil() {
333 return nil 333 return nil
334 } 334 }
335 } 335 }
336 return o 336 return o
337 } 337 }
338 338
339 func (Property) isAPropertyData() {}
340
341 func (p Property) estimateSize() int64 { return p.EstimateSize() }
342
343 // Slice implements the PropertyData interface.
344 func (p Property) Slice() PropertySlice { return PropertySlice{p} }
345
346 // Clone implements the PropertyData interface.
347 func (p Property) Clone() PropertyData { return p }
348
339 func (p Property) String() string { 349 func (p Property) String() string {
340 switch p.propType { 350 switch p.propType {
341 case PTString, PTBlobKey: 351 case PTString, PTBlobKey:
342 return fmt.Sprintf("%s(%q)", p.propType, p.Value()) 352 return fmt.Sprintf("%s(%q)", p.propType, p.Value())
343 case PTBytes: 353 case PTBytes:
344 return fmt.Sprintf("%s(%#x)", p.propType, p.Value()) 354 return fmt.Sprintf("%s(%#x)", p.propType, p.Value())
345 default: 355 default:
346 return fmt.Sprintf("%s(%v)", p.propType, p.Value()) 356 return fmt.Sprintf("%s(%v)", p.propType, p.Value())
347 } 357 }
348 } 358 }
(...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after
621 if b.Less(a) { 631 if b.Less(a) {
622 return 1 632 return 1
623 } 633 }
624 return -1 634 return -1
625 635
626 default: 636 default:
627 panic(fmt.Errorf("uncomparable type: %s", t)) 637 panic(fmt.Errorf("uncomparable type: %s", t))
628 } 638 }
629 } 639 }
630 640
641 // EstimateSize estimates the amount of space that this Property would consume
642 // if it were committed as part of an entity in the real production datastore.
643 //
644 // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1
645 // as a guide for these values.
646 func (p *Property) EstimateSize() int64 {
647 switch p.Type() {
648 case PTNull:
649 return 1
650 case PTBool:
651 return 1 + 4
652 case PTInt, PTTime, PTFloat:
653 return 1 + 8
654 case PTGeoPoint:
655 return 1 + (8 * 2)
656 case PTString:
657 return 1 + int64(len(p.Value().(string)))
658 case PTBlobKey:
659 return 1 + int64(len(p.Value().(blobstore.Key)))
660 case PTBytes:
661 return 1 + int64(len(p.Value().([]byte)))
662 case PTKey:
663 return 1 + p.Value().(*Key).EstimateSize()
664 }
665 panic(fmt.Errorf("Unknown property type: %s", p.Type().String()))
666 }
667
631 // GQL returns a correctly formatted Cloud Datastore GQL literal which 668 // GQL returns a correctly formatted Cloud Datastore GQL literal which
632 // is valid for a comparison value in the `WHERE` clause. 669 // is valid for a comparison value in the `WHERE` clause.
633 // 670 //
634 // The flavor of GQL that this emits is defined here: 671 // The flavor of GQL that this emits is defined here:
635 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference 672 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference
636 // 673 //
637 // NOTE: GeoPoint values are emitted with speculated future syntax. There is 674 // NOTE: GeoPoint values are emitted with speculated future syntax. There is
638 // currently no syntax for literal GeoPoint values. 675 // currently no syntax for literal GeoPoint values.
639 func (p *Property) GQL() string { 676 func (p *Property) GQL() string {
640 v := p.Value() 677 v := p.Value()
(...skipping 24 matching lines...) Expand all
665 case PTGeoPoint: 702 case PTGeoPoint:
666 // note that cloud SQL doesn't support this yet, but take a good guess at 703 // note that cloud SQL doesn't support this yet, but take a good guess at
667 // it. 704 // it.
668 v := v.(GeoPoint) 705 v := v.(GeoPoint)
669 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng) 706 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng)
670 } 707 }
671 panic(fmt.Errorf("bad type: %s", p.propType)) 708 panic(fmt.Errorf("bad type: %s", p.propType))
672 } 709 }
673 710
674 // PropertySlice is a slice of Properties. It implements sort.Interface. 711 // PropertySlice is a slice of Properties. It implements sort.Interface.
712 //
713 // PropertySlice holds multiple Properties. Writing a PropertySlice to datastore
714 // implicitly marks the property as "multiple", even if it only has one element.
675 type PropertySlice []Property 715 type PropertySlice []Property
676 716
717 func (PropertySlice) isAPropertyData() {}
718
719 // Clone implements the PropertyData interface.
720 func (s PropertySlice) Clone() PropertyData { return s.Slice() }
721
722 // Slice implements the PropertyData interface.
723 func (s PropertySlice) Slice() PropertySlice {
724 if len(s) == 0 {
725 return nil
726 }
727 return append(make(PropertySlice, 0, len(s)), s...)
728 }
729
730 func (s PropertySlice) estimateSize() (v int64) {
731 for _, prop := range s {
732 // Use the public one so we don't have to copy.
733 v += prop.estimateSize()
734 }
735 return
736 }
737
677 func (s PropertySlice) Len() int { return len(s) } 738 func (s PropertySlice) Len() int { return len(s) }
678 func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 739 func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
679 func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) } 740 func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) }
680 741
681 // EstimateSize estimates the amount of space that this Property would consume
682 // if it were committed as part of an entity in the real production datastore.
683 //
684 // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1
685 // as a guide for these values.
686 func (p *Property) EstimateSize() int64 {
687 switch p.Type() {
688 case PTNull:
689 return 1
690 case PTBool:
691 return 1 + 4
692 case PTInt, PTTime, PTFloat:
693 return 1 + 8
694 case PTGeoPoint:
695 return 1 + (8 * 2)
696 case PTString:
697 return 1 + int64(len(p.Value().(string)))
698 case PTBlobKey:
699 return 1 + int64(len(p.Value().(blobstore.Key)))
700 case PTBytes:
701 return 1 + int64(len(p.Value().([]byte)))
702 case PTKey:
703 return 1 + p.Value().(*Key).EstimateSize()
704 }
705 panic(fmt.Errorf("Unknown property type: %s", p.Type().String()))
706 }
707
708 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to 742 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to
709 // abstract the meta argument for RawInterface.GetMulti. 743 // abstract the meta argument for RawInterface.GetMulti.
710 type MetaGetter interface { 744 type MetaGetter interface {
711 // GetMeta will get information about the field which has the struct tag in 745 // GetMeta will get information about the field which has the struct tag in
712 // the form of `gae:"$<key>[,<default>]?"`. 746 // the form of `gae:"$<key>[,<default>]?"`.
713 // 747 //
714 // It returns the value, if any, and true iff the value was retrieved. 748 // It returns the value, if any, and true iff the value was retrieved.
715 // 749 //
716 // Supported metadata types are: 750 // Supported metadata types are:
717 // int64 - may have default (ascii encoded base-10) 751 // int64 - may have default (ascii encoded base-10)
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
778 // the `GetPLS` implementation will add any statically-defined metadata 812 // the `GetPLS` implementation will add any statically-defined metadata
779 // fields. So if GetMeta provides $id, but there's a simple tagged field for 813 // fields. So if GetMeta provides $id, but there's a simple tagged field for
780 // $kind, this method is only expected to return a PropertyMap with "$id ". 814 // $kind, this method is only expected to return a PropertyMap with "$id ".
781 GetAllMeta() PropertyMap 815 GetAllMeta() PropertyMap
782 816
783 // SetMeta allows you to set the current value of the meta-keyed field. 817 // SetMeta allows you to set the current value of the meta-keyed field.
784 // It returns true iff the field was set. 818 // It returns true iff the field was set.
785 SetMeta(key string, val interface{}) bool 819 SetMeta(key string, val interface{}) bool
786 } 820 }
787 821
822 // PropertyData is an interface implemented by Property and PropertySlice to
823 // identify themselves as valid PropertyMap values.
824 type PropertyData interface {
825 // isAPropertyData is a tag that forces PropertyData implementations to only
826 // be supplied by this package.
827 isAPropertyData()
828
829 // Slice returns a PropertySlice representation of this PropertyData.
830 //
831 // The returned PropertySlice is a clone of the original data. Consequen tly,
832 // Consequently, Property-modifying methods such as SetValue should NOT be
833 // called on the results.
834 Slice() PropertySlice
835
836 // estimateSize estimates the aggregate size of the property data.
837 estimateSize() int64
838
839 // Clone creates a duplicate copy of this PropertyData.
840 Clone() PropertyData
841 }
842
788 // PropertyMap represents the contents of a datastore entity in a generic way. 843 // PropertyMap represents the contents of a datastore entity in a generic way.
789 // It maps from property name to a list of property values which correspond to 844 // It maps from property name to a list of property values which correspond to
790 // that property name. It is the spiritual successor to PropertyList from the 845 // that property name. It is the spiritual successor to PropertyList from the
791 // original SDK. 846 // original SDK.
792 // 847 //
793 // PropertyMap may contain "meta" values, which are keyed with a '$' prefix. 848 // PropertyMap may contain "meta" values, which are keyed with a '$' prefix.
794 // Technically the datastore allows arbitrary property names, but all of the 849 // Technically the datastore allows arbitrary property names, but all of the
795 // SDKs go out of their way to try to make all property names valid programming 850 // SDKs go out of their way to try to make all property names valid programming
796 // language tokens. Special values must correspond to a single Property... 851 // language tokens. Special values must correspond to a single Property...
797 // corresponding to 0 is equivalent to unset, and corresponding to >1 is an 852 // corresponding to 0 is equivalent to unset, and corresponding to >1 is an
798 // error. So: 853 // error. So:
799 // 854 //
800 // { 855 // {
801 // "$id": {MkProperty(1)}, // GetProperty("id") -> 1, nil 856 // "$id": {MkProperty(1)}, // GetProperty("id") -> 1, nil
802 // "$foo": {}, // GetProperty("foo") -> nil, ErrMetaFieldUnset 857 // "$foo": {}, // GetProperty("foo") -> nil, ErrMetaFieldUnset
803 // // GetProperty("bar") -> nil, ErrMetaFieldUnset 858 // // GetProperty("bar") -> nil, ErrMetaFieldUnset
804 // "$meep": { 859 // "$meep": {
805 // MkProperty("hi"), 860 // MkProperty("hi"),
806 // MkProperty("there")}, // GetProperty("meep") -> nil, error! 861 // MkProperty("there")}, // GetProperty("meep") -> nil, error!
807 // } 862 // }
808 // 863 //
809 // Additionally, Save returns a copy of the map with the meta keys omitted (e.g. 864 // Additionally, Save returns a copy of the map with the meta keys omitted (e.g.
810 // these keys are not going to be serialized to the datastore). 865 // these keys are not going to be serialized to the datastore).
811 type PropertyMap map[string][]Property 866 type PropertyMap map[string]PropertyData
812 867
813 var _ PropertyLoadSaver = PropertyMap(nil) 868 var _ PropertyLoadSaver = PropertyMap(nil)
814 869
815 // Load implements PropertyLoadSaver.Load 870 // Load implements PropertyLoadSaver.Load
816 func (pm PropertyMap) Load(props PropertyMap) error { 871 func (pm PropertyMap) Load(props PropertyMap) error {
817 for k, v := range props { 872 for k, v := range props {
818 » » pm[k] = append(pm[k], v...) 873 » » pm[k] = v.Clone()
819 } 874 }
820 return nil 875 return nil
821 } 876 }
822 877
823 // Save implements PropertyLoadSaver.Save by returning a copy of the 878 // Save implements PropertyLoadSaver.Save by returning a copy of the
824 // current map data. 879 // current map data.
825 func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) { 880 func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) {
826 if len(pm) == 0 { 881 if len(pm) == 0 {
827 return PropertyMap{}, nil 882 return PropertyMap{}, nil
828 } 883 }
829 ret := make(PropertyMap, len(pm)) 884 ret := make(PropertyMap, len(pm))
830 for k, v := range pm { 885 for k, v := range pm {
831 if withMeta || !isMetaKey(k) { 886 if withMeta || !isMetaKey(k) {
832 » » » ret[k] = append(ret[k], v...) 887 » » » ret[k] = v.Clone()
833 } 888 }
834 } 889 }
835 return ret, nil 890 return ret, nil
836 } 891 }
837 892
838 // GetMeta implements PropertyLoadSaver.GetMeta, and returns the current value 893 // GetMeta implements PropertyLoadSaver.GetMeta, and returns the current value
839 // associated with the metadata key. 894 // associated with the metadata key.
840 func (pm PropertyMap) GetMeta(key string) (interface{}, bool) { 895 func (pm PropertyMap) GetMeta(key string) (interface{}, bool) {
841 » v, ok := pm["$"+key] 896 » pslice := pm.Slice("$" + key)
842 » if !ok || len(v) == 0 { 897 » if len(pslice) > 0 {
843 » » return nil, false 898 » » return pslice[0].Value(), true
844 } 899 }
845 » return v[0].Value(), true 900 » return nil, false
846 } 901 }
847 902
848 // GetAllMeta implements PropertyLoadSaver.GetAllMeta. 903 // GetAllMeta implements PropertyLoadSaver.GetAllMeta.
849 func (pm PropertyMap) GetAllMeta() PropertyMap { 904 func (pm PropertyMap) GetAllMeta() PropertyMap {
850 ret := make(PropertyMap, 8) 905 ret := make(PropertyMap, 8)
851 for k, v := range pm { 906 for k, v := range pm {
852 if isMetaKey(k) { 907 if isMetaKey(k) {
853 » » » newV := make([]Property, len(v)) 908 » » » ret[k] = v.Clone()
854 » » » copy(newV, v)
855 » » » ret[k] = newV
856 } 909 }
857 } 910 }
858 return ret 911 return ret
859 } 912 }
860 913
861 // SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error 914 // SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error
862 // if `val` has an invalid type (e.g. not one supported by Property). 915 // if `val` has an invalid type (e.g. not one supported by Property).
863 func (pm PropertyMap) SetMeta(key string, val interface{}) bool { 916 func (pm PropertyMap) SetMeta(key string, val interface{}) bool {
864 prop := Property{} 917 prop := Property{}
865 if err := prop.SetValue(val, NoIndex); err != nil { 918 if err := prop.SetValue(val, NoIndex); err != nil {
866 return false 919 return false
867 } 920 }
868 » pm["$"+key] = []Property{prop} 921 » pm["$"+key] = prop
869 return true 922 return true
870 } 923 }
871 924
872 // Problem implements PropertyLoadSaver.Problem. It ALWAYS returns nil. 925 // Problem implements PropertyLoadSaver.Problem. It ALWAYS returns nil.
873 func (pm PropertyMap) Problem() error { 926 func (pm PropertyMap) Problem() error {
874 return nil 927 return nil
875 } 928 }
876 929
930 // Slice returns a PropertySlice for the given key
931 //
932 // If the value associated with that key is nil, an empty slice will be
933 // returned. If the value is single Property, a slice of size 1 with that
934 // Property in it will be returned.
935 func (pm PropertyMap) Slice(key string) PropertySlice {
936 if pdata := pm[key]; pdata != nil {
937 return pdata.Slice()
938 }
939 return nil
940 }
941
877 // EstimateSize estimates the size that it would take to encode this PropertyMap 942 // EstimateSize estimates the size that it would take to encode this PropertyMap
878 // in the production Appengine datastore. The calculation excludes metadata 943 // in the production Appengine datastore. The calculation excludes metadata
879 // fields in the map. 944 // fields in the map.
880 // 945 //
881 // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1 946 // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1
882 // as a guide for sizes. 947 // as a guide for sizes.
883 func (pm PropertyMap) EstimateSize() int64 { 948 func (pm PropertyMap) EstimateSize() int64 {
884 ret := int64(0) 949 ret := int64(0)
885 for k, vals := range pm { 950 for k, vals := range pm {
886 if !isMetaKey(k) { 951 if !isMetaKey(k) {
887 ret += int64(len(k)) 952 ret += int64(len(k))
888 » » » for i := range vals { 953 » » » ret += vals.estimateSize()
889 » » » » ret += vals[i].EstimateSize()
890 » » » }
891 } 954 }
892 } 955 }
893 return ret 956 return ret
894 } 957 }
895 958
896 func isMetaKey(k string) bool { 959 func isMetaKey(k string) bool {
897 // empty counts as a metakey since it's not a valid data key, but it's 960 // empty counts as a metakey since it's not a valid data key, but it's
898 // not really a valid metakey either. 961 // not really a valid metakey either.
899 return k == "" || k[0] == '$' 962 return k == "" || k[0] == '$'
900 } 963 }
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
993 if string(s) == string(t) { 1056 if string(s) == string(t) {
994 return 0, true 1057 return 0, true
995 } 1058 }
996 if string(s) < string(t) { 1059 if string(s) < string(t) {
997 return -1, true 1060 return -1, true
998 } 1061 }
999 return 1, true 1062 return 1, true
1000 } 1063 }
1001 return 0, false 1064 return 0, false
1002 } 1065 }
OLDNEW
« no previous file with comments | « service/datastore/pls_test.go ('k') | service/datastore/properties_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698