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

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

Issue 2342063003: Differentiate between single- and multi- props. (Closed)
Patch Set: 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
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 // Clone implements the PropertyData interface.
344 func (p Property) Clone() PropertyData { return p }
345
339 func (p Property) String() string { 346 func (p Property) String() string {
340 switch p.propType { 347 switch p.propType {
341 case PTString, PTBlobKey: 348 case PTString, PTBlobKey:
342 return fmt.Sprintf("%s(%q)", p.propType, p.Value()) 349 return fmt.Sprintf("%s(%q)", p.propType, p.Value())
343 case PTBytes: 350 case PTBytes:
344 return fmt.Sprintf("%s(%#x)", p.propType, p.Value()) 351 return fmt.Sprintf("%s(%#x)", p.propType, p.Value())
345 default: 352 default:
346 return fmt.Sprintf("%s(%v)", p.propType, p.Value()) 353 return fmt.Sprintf("%s(%v)", p.propType, p.Value())
347 } 354 }
348 } 355 }
(...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after
621 if b.Less(a) { 628 if b.Less(a) {
622 return 1 629 return 1
623 } 630 }
624 return -1 631 return -1
625 632
626 default: 633 default:
627 panic(fmt.Errorf("uncomparable type: %s", t)) 634 panic(fmt.Errorf("uncomparable type: %s", t))
628 } 635 }
629 } 636 }
630 637
638 // EstimateSize estimates the amount of space that this Property would consume
639 // if it were committed as part of an entity in the real production datastore.
640 //
641 // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1
642 // as a guide for these values.
643 func (p *Property) EstimateSize() int64 {
644 switch p.Type() {
645 case PTNull:
646 return 1
647 case PTBool:
648 return 1 + 4
649 case PTInt, PTTime, PTFloat:
650 return 1 + 8
651 case PTGeoPoint:
652 return 1 + (8 * 2)
653 case PTString:
654 return 1 + int64(len(p.Value().(string)))
655 case PTBlobKey:
656 return 1 + int64(len(p.Value().(blobstore.Key)))
657 case PTBytes:
658 return 1 + int64(len(p.Value().([]byte)))
659 case PTKey:
660 return 1 + p.Value().(*Key).EstimateSize()
661 }
662 panic(fmt.Errorf("Unknown property type: %s", p.Type().String()))
663 }
664
631 // GQL returns a correctly formatted Cloud Datastore GQL literal which 665 // GQL returns a correctly formatted Cloud Datastore GQL literal which
632 // is valid for a comparison value in the `WHERE` clause. 666 // is valid for a comparison value in the `WHERE` clause.
633 // 667 //
634 // The flavor of GQL that this emits is defined here: 668 // The flavor of GQL that this emits is defined here:
635 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference 669 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference
636 // 670 //
637 // NOTE: GeoPoint values are emitted with speculated future syntax. There is 671 // NOTE: GeoPoint values are emitted with speculated future syntax. There is
638 // currently no syntax for literal GeoPoint values. 672 // currently no syntax for literal GeoPoint values.
639 func (p *Property) GQL() string { 673 func (p *Property) GQL() string {
640 v := p.Value() 674 v := p.Value()
(...skipping 24 matching lines...) Expand all
665 case PTGeoPoint: 699 case PTGeoPoint:
666 // note that cloud SQL doesn't support this yet, but take a good guess at 700 // note that cloud SQL doesn't support this yet, but take a good guess at
667 // it. 701 // it.
668 v := v.(GeoPoint) 702 v := v.(GeoPoint)
669 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng) 703 return fmt.Sprintf("GEOPOINT(%v, %v)", v.Lat, v.Lng)
670 } 704 }
671 panic(fmt.Errorf("bad type: %s", p.propType)) 705 panic(fmt.Errorf("bad type: %s", p.propType))
672 } 706 }
673 707
674 // PropertySlice is a slice of Properties. It implements sort.Interface. 708 // PropertySlice is a slice of Properties. It implements sort.Interface.
709 //
710 // PropertySlice holds multiple Properties. Writing a PropertySlice to datastore
711 // implicitly marks the property as "multiple", even if it only has one element.
675 type PropertySlice []Property 712 type PropertySlice []Property
676 713
714 func (PropertySlice) isAPropertyData() {}
715
716 // Clone implements the PropertyData interface.
717 func (s PropertySlice) Clone() PropertyData {
718 if len(s) == 0 {
719 return PropertySlice(nil)
720 }
721 return append(make(PropertySlice, 0, len(s)), s...)
722 }
723
724 // EstimateSize estimates the amount of space that this PropertySlice would
725 // consume if it were committed as part of an entity in the real production
726 // datastore.
727 func (s PropertySlice) estimateSize() (v int64) {
728 for _, prop := range s {
729 // Use the public one so we don't have to copy.
730 v += prop.estimateSize()
731 }
732 return
733 }
734
677 func (s PropertySlice) Len() int { return len(s) } 735 func (s PropertySlice) Len() int { return len(s) }
678 func (s PropertySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 736 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]) } 737 func (s PropertySlice) Less(i, j int) bool { return s[i].Less(&s[j]) }
680 738
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 739 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to
709 // abstract the meta argument for RawInterface.GetMulti. 740 // abstract the meta argument for RawInterface.GetMulti.
710 type MetaGetter interface { 741 type MetaGetter interface {
711 // GetMeta will get information about the field which has the struct tag in 742 // GetMeta will get information about the field which has the struct tag in
712 // the form of `gae:"$<key>[,<default>]?"`. 743 // the form of `gae:"$<key>[,<default>]?"`.
713 // 744 //
714 // It returns the value, if any, and true iff the value was retrieved. 745 // It returns the value, if any, and true iff the value was retrieved.
715 // 746 //
716 // Supported metadata types are: 747 // Supported metadata types are:
717 // int64 - may have default (ascii encoded base-10) 748 // 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 809 // the `GetPLS` implementation will add any statically-defined metadata
779 // fields. So if GetMeta provides $id, but there's a simple tagged field for 810 // 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 ". 811 // $kind, this method is only expected to return a PropertyMap with "$id ".
781 GetAllMeta() PropertyMap 812 GetAllMeta() PropertyMap
782 813
783 // SetMeta allows you to set the current value of the meta-keyed field. 814 // SetMeta allows you to set the current value of the meta-keyed field.
784 // It returns true iff the field was set. 815 // It returns true iff the field was set.
785 SetMeta(key string, val interface{}) bool 816 SetMeta(key string, val interface{}) bool
786 } 817 }
787 818
819 // PropertyData is an interface implemented by Property and PropertySlice to
820 // identify themselves as valid PropertyMap values.
821 type PropertyData interface {
822 // isAPropertyData is a tag that forces PropertyData implementations to only
823 // be supplied by this package.
824 isAPropertyData()
825
826 // estimateSize estimates the aggregate size of the property data.
827 estimateSize() int64
828
829 // Clone creates a duplicate copy of this PropertyData.
830 Clone() PropertyData
831 }
832
788 // PropertyMap represents the contents of a datastore entity in a generic way. 833 // 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 834 // 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 835 // that property name. It is the spiritual successor to PropertyList from the
791 // original SDK. 836 // original SDK.
792 // 837 //
793 // PropertyMap may contain "meta" values, which are keyed with a '$' prefix. 838 // PropertyMap may contain "meta" values, which are keyed with a '$' prefix.
794 // Technically the datastore allows arbitrary property names, but all of the 839 // 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 840 // 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... 841 // language tokens. Special values must correspond to a single Property...
797 // corresponding to 0 is equivalent to unset, and corresponding to >1 is an 842 // corresponding to 0 is equivalent to unset, and corresponding to >1 is an
798 // error. So: 843 // error. So:
799 // 844 //
800 // { 845 // {
801 // "$id": {MkProperty(1)}, // GetProperty("id") -> 1, nil 846 // "$id": {MkProperty(1)}, // GetProperty("id") -> 1, nil
802 // "$foo": {}, // GetProperty("foo") -> nil, ErrMetaFieldUnset 847 // "$foo": {}, // GetProperty("foo") -> nil, ErrMetaFieldUnset
803 // // GetProperty("bar") -> nil, ErrMetaFieldUnset 848 // // GetProperty("bar") -> nil, ErrMetaFieldUnset
804 // "$meep": { 849 // "$meep": {
805 // MkProperty("hi"), 850 // MkProperty("hi"),
806 // MkProperty("there")}, // GetProperty("meep") -> nil, error! 851 // MkProperty("there")}, // GetProperty("meep") -> nil, error!
807 // } 852 // }
808 // 853 //
809 // Additionally, Save returns a copy of the map with the meta keys omitted (e.g. 854 // 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). 855 // these keys are not going to be serialized to the datastore).
811 type PropertyMap map[string][]Property 856 type PropertyMap map[string]PropertyData
812 857
813 var _ PropertyLoadSaver = PropertyMap(nil) 858 var _ PropertyLoadSaver = PropertyMap(nil)
814 859
815 // Load implements PropertyLoadSaver.Load 860 // Load implements PropertyLoadSaver.Load
816 func (pm PropertyMap) Load(props PropertyMap) error { 861 func (pm PropertyMap) Load(props PropertyMap) error {
817 for k, v := range props { 862 for k, v := range props {
818 » » pm[k] = append(pm[k], v...) 863 » » pm[k] = v.Clone()
dnj 2016/09/16 19:07:22 I'm going to write some code to clear out "pm" her
dnj 2016/09/16 19:21:33 Actually nm on this.
819 } 864 }
820 return nil 865 return nil
821 } 866 }
822 867
823 // Save implements PropertyLoadSaver.Save by returning a copy of the 868 // Save implements PropertyLoadSaver.Save by returning a copy of the
824 // current map data. 869 // current map data.
825 func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) { 870 func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) {
826 if len(pm) == 0 { 871 if len(pm) == 0 {
827 return PropertyMap{}, nil 872 return PropertyMap{}, nil
828 } 873 }
829 ret := make(PropertyMap, len(pm)) 874 ret := make(PropertyMap, len(pm))
830 for k, v := range pm { 875 for k, v := range pm {
831 if withMeta || !isMetaKey(k) { 876 if withMeta || !isMetaKey(k) {
832 » » » ret[k] = append(ret[k], v...) 877 » » » if err := ret.appendPropertyData(k, v); err != nil {
iannucci 2016/09/16 07:11:03 I think this just needs to be ret[k] = v.Clone(
dnj 2016/09/16 19:07:22 Oh good catch. Yes, correct.
878 » » » » return nil, fmt.Errorf("gae: failed to save prop erty %q: %v", k, err)
879 » » » }
833 } 880 }
834 } 881 }
835 return ret, nil 882 return ret, nil
836 } 883 }
837 884
838 // GetMeta implements PropertyLoadSaver.GetMeta, and returns the current value 885 // GetMeta implements PropertyLoadSaver.GetMeta, and returns the current value
839 // associated with the metadata key. 886 // associated with the metadata key.
840 func (pm PropertyMap) GetMeta(key string) (interface{}, bool) { 887 func (pm PropertyMap) GetMeta(key string) (interface{}, bool) {
841 » v, ok := pm["$"+key] 888 » pslice := pm.Slice("$" + key)
842 » if !ok || len(v) == 0 { 889 » if len(pslice) > 0 {
843 » » return nil, false 890 » » return pslice[0].Value(), true
844 } 891 }
845 » return v[0].Value(), true 892 » return nil, false
846 } 893 }
847 894
848 // GetAllMeta implements PropertyLoadSaver.GetAllMeta. 895 // GetAllMeta implements PropertyLoadSaver.GetAllMeta.
849 func (pm PropertyMap) GetAllMeta() PropertyMap { 896 func (pm PropertyMap) GetAllMeta() PropertyMap {
850 ret := make(PropertyMap, 8) 897 ret := make(PropertyMap, 8)
851 for k, v := range pm { 898 for k, v := range pm {
852 if isMetaKey(k) { 899 if isMetaKey(k) {
853 » » » newV := make([]Property, len(v)) 900 » » » ret[k] = v.Clone()
854 » » » copy(newV, v)
855 » » » ret[k] = newV
856 } 901 }
857 } 902 }
858 return ret 903 return ret
859 } 904 }
860 905
861 // SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error 906 // SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error
862 // if `val` has an invalid type (e.g. not one supported by Property). 907 // if `val` has an invalid type (e.g. not one supported by Property).
863 func (pm PropertyMap) SetMeta(key string, val interface{}) bool { 908 func (pm PropertyMap) SetMeta(key string, val interface{}) bool {
864 prop := Property{} 909 prop := Property{}
865 if err := prop.SetValue(val, NoIndex); err != nil { 910 if err := prop.SetValue(val, NoIndex); err != nil {
866 return false 911 return false
867 } 912 }
868 » pm["$"+key] = []Property{prop} 913 » pm["$"+key] = prop
869 return true 914 return true
870 } 915 }
871 916
872 // Problem implements PropertyLoadSaver.Problem. It ALWAYS returns nil. 917 // Problem implements PropertyLoadSaver.Problem. It ALWAYS returns nil.
873 func (pm PropertyMap) Problem() error { 918 func (pm PropertyMap) Problem() error {
874 return nil 919 return nil
875 } 920 }
876 921
922 // Slice returns a PropertySlice for the given key
923 //
924 // If the value associated with that key is nil, an empty slice will be
925 // returned. If the value is single Property, a slice of size 1 with that
926 // Property in it will be returned.
927 func (pm PropertyMap) Slice(key string) PropertySlice {
iannucci 2016/09/16 07:11:03 should we also have a `First(key string)`? Or Sing
dnj 2016/09/16 19:07:22 I thought about this. TBH I am of the opinion that
928 pdata := pm[key]
929 if pdata == nil {
930 return nil
931 }
932 switch t := pdata.(type) {
933 case Property:
934 return PropertySlice{t}
935 case PropertySlice:
936 return t
937 default:
938 panic(fmt.Errorf("unknown PropertyData type %T", t))
939 }
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
959 func (pm PropertyMap) appendPropertyData(key string, pdata PropertyData) error {
960 cur := pm[key]
961 if cur == nil {
962 pm[key] = pdata.Clone()
963 return nil
964 }
965
966 // If there's already a value here, we can only append a new value if th e
967 // current data is a slice.
968 pslice, ok := cur.(PropertySlice)
969 if !ok {
970 return errors.New("cannot add additional value to single-value p roperty")
971 }
972
973 switch add := pdata.(type) {
974 case Property:
975 // Don't need to clone, since Property is by-value.
976 pm[key] = append(pslice, add)
977
978 case PropertySlice:
979 // Don't need to clone, since we're appending pdata, not retaini ng it.
980 if len(add) > 0 {
981 pm[key] = append(pslice, add...)
982 }
983 }
984 return nil
985 }
986
896 func isMetaKey(k string) bool { 987 func isMetaKey(k string) bool {
897 // empty counts as a metakey since it's not a valid data key, but it's 988 // empty counts as a metakey since it's not a valid data key, but it's
898 // not really a valid metakey either. 989 // not really a valid metakey either.
899 return k == "" || k[0] == '$' 990 return k == "" || k[0] == '$'
900 } 991 }
901 992
902 // GetMetaDefault is a helper for GetMeta, allowing a default value. 993 // GetMetaDefault is a helper for GetMeta, allowing a default value.
903 // 994 //
904 // If the metadata key is not available, or its type doesn't equal the 995 // If the metadata key is not available, or its type doesn't equal the
905 // homogenized type of dflt, then dflt will be returned. 996 // homogenized type of dflt, then dflt will be returned.
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
993 if string(s) == string(t) { 1084 if string(s) == string(t) {
994 return 0, true 1085 return 0, true
995 } 1086 }
996 if string(s) < string(t) { 1087 if string(s) < string(t) {
997 return -1, true 1088 return -1, true
998 } 1089 }
999 return 1, true 1090 return 1, true
1000 } 1091 }
1001 return 0, false 1092 return 0, false
1002 } 1093 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698