| // Copyright 2014 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package datastore | |
| import ( | |
| "fmt" | |
| "reflect" | |
| "strings" | |
| "unicode" | |
| "cloud.google.com/go/internal/fields" | |
| ) | |
| // Entities with more than this many indexed properties will not be saved. | |
| const maxIndexedProperties = 20000 | |
| // Property is a name/value pair plus some metadata. A datastore entity's | |
| // contents are loaded and saved as a sequence of Properties. Each property | |
| // name must be unique within an entity. | |
| type Property struct { | |
| // Name is the property name. | |
| Name string | |
| // Value is the property value. The valid types are: | |
| // - int64 | |
| // - bool | |
| // - string | |
| // - float64 | |
| // - *Key | |
| // - time.Time (retrieved as local time) | |
| // - GeoPoint | |
| // - []byte (up to 1 megabyte in length) | |
| // - *Entity (representing a nested struct) | |
| // Value can also be: | |
| // - []interface{} where each element is one of the above types | |
| // This set is smaller than the set of valid struct field types that the | |
| // datastore can load and save. A Value's type must be explicitly on | |
| // the list above; it is not sufficient for the underlying type to be | |
| // on that list. For example, a Value of "type myInt64 int64" is | |
| // invalid. Smaller-width integers and floats are also invalid. Again, | |
| // this is more restrictive than the set of valid struct field types. | |
| // | |
| // A Value will have an opaque type when loading entities from an index, | |
| // such as via a projection query. Load entities into a struct instead | |
| // of a PropertyLoadSaver when using a projection query. | |
| // | |
| // A Value may also be the nil interface value; this is equivalent to | |
| // Python's None but not directly representable by a Go struct. Loading | |
| // a nil-valued property into a struct will set that field to the zero | |
| // value. | |
| Value interface{} | |
| // NoIndex is whether the datastore cannot index this property. | |
| // If NoIndex is set to false, []byte and string values are limited to | |
| // 1500 bytes. | |
| NoIndex bool | |
| } | |
| // An Entity is the value type for a nested struct. | |
| // This type is only used for a Property's Value. | |
| type Entity struct { | |
| Key *Key | |
| Properties []Property | |
| } | |
| // PropertyLoadSaver can be converted from and to a slice of Properties. | |
| type PropertyLoadSaver interface { | |
| Load([]Property) error | |
| Save() ([]Property, error) | |
| } | |
| // KeyLoader can store a Key. | |
| type KeyLoader interface { | |
| // PropertyLoadSaver is embedded because a KeyLoader | |
| // must also always implement PropertyLoadSaver. | |
| PropertyLoadSaver | |
| LoadKey(k *Key) error | |
| } | |
| // PropertyList converts a []Property to implement PropertyLoadSaver. | |
| type PropertyList []Property | |
| var ( | |
| typeOfPropertyLoadSaver = reflect.TypeOf((*PropertyLoadSaver)(nil)).Elem() | |
| typeOfPropertyList = reflect.TypeOf(PropertyList(nil)) | |
| ) | |
| // Load loads all of the provided properties into l. | |
| // It does not first reset *l to an empty slice. | |
| func (l *PropertyList) Load(p []Property) error { | |
| *l = append(*l, p...) | |
| return nil | |
| } | |
| // Save saves all of l's properties as a slice of Properties. | |
| func (l *PropertyList) Save() ([]Property, error) { | |
| return *l, nil | |
| } | |
| // validPropertyName returns whether name consists of one or more valid Go | |
| // identifiers joined by ".". | |
| func validPropertyName(name string) bool { | |
| if name == "" { | |
| return false | |
| } | |
| for _, s := range strings.Split(name, ".") { | |
| if s == "" { | |
| return false | |
| } | |
| first := true | |
| for _, c := range s { | |
| if first { | |
| first = false | |
| if c != '_' && !unicode.IsLetter(c) { | |
| return false | |
| } | |
| } else { | |
| if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) { | |
| return false | |
| } | |
| } | |
| } | |
| } | |
| return true | |
| } | |
| // parseTag interprets datastore struct field tags | |
| func parseTag(t reflect.StructTag) (name string, keep bool, other interface{}, err error) { | |
| s := t.Get("datastore") | |
| parts := strings.Split(s, ",") | |
| if parts[0] == "-" && len(parts) == 1 { | |
| return "", false, nil, nil | |
| } | |
| if parts[0] != "" && !validPropertyName(parts[0]) { | |
| err = fmt.Errorf("datastore: struct tag has invalid property name: %q", parts[0]) | |
| return "", false, nil, err | |
| } | |
| var opts saveOpts | |
| if len(parts) > 1 { | |
| for _, p := range parts[1:] { | |
| switch p { | |
| case "flatten": | |
| opts.flatten = true | |
| case "omitempty": | |
| opts.omitEmpty = true | |
| case "noindex": | |
| opts.noIndex = true | |
| default: | |
| err = fmt.Errorf("datastore: struct tag has invalid option: %q", p) | |
| return "", false, nil, err | |
| } | |
| } | |
| other = opts | |
| } | |
| return parts[0], true, other, nil | |
| } | |
| func validateType(t reflect.Type) error { | |
| if t.Kind() != reflect.Struct { | |
| return fmt.Errorf("datastore: validate called with non-struct type %s", t) | |
| } | |
| return validateChildType(t, "", false, false, map[reflect.Type]bool{}) | |
| } | |
| // validateChildType is a recursion helper func for validateType | |
| func validateChildType(t reflect.Type, fieldName string, flatten, prevSlice bool, prevTypes map[reflect.Type]bool) error { | |
| if prevTypes[t] { | |
| return nil | |
| } | |
| prevTypes[t] = true | |
| switch t.Kind() { | |
| case reflect.Slice: | |
| if flatten && prevSlice { | |
| return fmt.Errorf("datastore: flattening nested structs leads to a slice of slices: field %q", fieldName) | |
| } | |
| return validateChildType(t.Elem(), fieldName, flatten, true, prevTypes) | |
| case reflect.Struct: | |
| if t == typeOfTime || t == typeOfGeoPoint { | |
| return nil | |
| } | |
| for i := 0; i < t.NumField(); i++ { | |
| f := t.Field(i) | |
| // If a named field is unexported, ignore it. An anonymous | |
| // unexported field is processed, because it may contain | |
| // exported fields, which are visible. | |
| exported := (f.PkgPath == "") | |
| if !exported && !f.Anonymous { | |
| continue | |
| } | |
| _, keep, other, err := parseTag(f.Tag) | |
| // Handle error from parseTag now instead of later (in cache.Fields call). | |
| if err != nil { | |
| return err | |
| } | |
| if !keep { | |
| continue | |
| } | |
| if other != nil { | |
| opts := other.(saveOpts) | |
| flatten = flatten || opts.flatten | |
| } | |
| if err := validateChildType(f.Type, f.Name, flatten, prevSlice, prevTypes); err != nil { | |
| return err | |
| } | |
| } | |
| case reflect.Ptr: | |
| if t == typeOfKeyPtr { | |
| return nil | |
| } | |
| return validateChildType(t.Elem(), fieldName, flatten, prevSlice, prevTypes) | |
| } | |
| return nil | |
| } | |
| // isLeafType determines whether or not a type is a 'leaf type' | |
| // and should not be recursed into, but considered one field. | |
| func isLeafType(t reflect.Type) bool { | |
| return t == typeOfTime || t == typeOfGeoPoint | |
| } | |
| // structCache collects the structs whose fields have already been calculated. | |
| var structCache = fields.NewCache(parseTag, validateType, isLeafType) | |
| // structPLS adapts a struct to be a PropertyLoadSaver. | |
| type structPLS struct { | |
| v reflect.Value | |
| codec fields.List | |
| } | |
| // newStructPLS returns a structPLS, which implements the | |
| // PropertyLoadSaver interface, for the struct pointer p. | |
| func newStructPLS(p interface{}) (*structPLS, error) { | |
| v := reflect.ValueOf(p) | |
| if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { | |
| return nil, ErrInvalidEntityType | |
| } | |
| v = v.Elem() | |
| f, err := structCache.Fields(v.Type()) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &structPLS{v, f}, nil | |
| } | |
| // LoadStruct loads the properties from p to dst. | |
| // dst must be a struct pointer. | |
| // | |
| // The values of dst's unmatched struct fields are not modified, | |
| // and matching slice-typed fields are not reset before appending to | |
| // them. In particular, it is recommended to pass a pointer to a zero | |
| // valued struct on each LoadStruct call. | |
| func LoadStruct(dst interface{}, p []Property) error { | |
| x, err := newStructPLS(dst) | |
| if err != nil { | |
| return err | |
| } | |
| return x.Load(p) | |
| } | |
| // SaveStruct returns the properties from src as a slice of Properties. | |
| // src must be a struct pointer. | |
| func SaveStruct(src interface{}) ([]Property, error) { | |
| x, err := newStructPLS(src) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return x.Save() | |
| } | |
| // plsForLoad tries to convert v to a PropertyLoadSaver. | |
| // If successful, plsForLoad returns a settable v as a PropertyLoadSaver. | |
| // | |
| // plsForLoad is intended to be used with nested struct fields which | |
| // may implement PropertyLoadSaver. | |
| // | |
| // v must be settable. | |
| func plsForLoad(v reflect.Value) (PropertyLoadSaver, error) { | |
| var nilPtr bool | |
| if v.Kind() == reflect.Ptr && v.IsNil() { | |
| nilPtr = true | |
| v.Set(reflect.New(v.Type().Elem())) | |
| } | |
| vpls, err := pls(v) | |
| if nilPtr && (vpls == nil || err != nil) { | |
| // unset v | |
| v.Set(reflect.Zero(v.Type())) | |
| } | |
| return vpls, err | |
| } | |
| // plsForSave tries to convert v to a PropertyLoadSaver. | |
| // If successful, plsForSave returns v as a PropertyLoadSaver. | |
| // | |
| // plsForSave is intended to be used with nested struct fields which | |
| // may implement PropertyLoadSaver. | |
| // | |
| // v must be settable. | |
| func plsForSave(v reflect.Value) (PropertyLoadSaver, error) { | |
| switch v.Kind() { | |
| case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface, reflect.Chan, reflect.Func: | |
| // If v is nil, return early. v contains no data to save. | |
| if v.IsNil() { | |
| return nil, nil | |
| } | |
| } | |
| return pls(v) | |
| } | |
| func pls(v reflect.Value) (PropertyLoadSaver, error) { | |
| if v.Kind() != reflect.Ptr { | |
| if _, ok := v.Interface().(PropertyLoadSaver); ok { | |
| return nil, fmt.Errorf("datastore: PropertyLoadSaver methods must be implemented on a pointer to %T", v.Interface()) | |
| } | |
| v = v.Addr() | |
| } | |
| vpls, _ := v.Interface().(PropertyLoadSaver) | |
| return vpls, nil | |
| } |