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