OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |