331 lines
9.6 KiB
Go
331 lines
9.6 KiB
Go
|
// Copyright 2011 Google Inc. All rights reserved.
|
||
|
// Use of this source code is governed by the Apache 2.0
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package datastore
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"unicode"
|
||
|
)
|
||
|
|
||
|
// Entities with more than this many indexed properties will not be saved.
|
||
|
const maxIndexedProperties = 20000
|
||
|
|
||
|
// []byte fields more than 1 megabyte long will not be loaded or saved.
|
||
|
const maxBlobLen = 1 << 20
|
||
|
|
||
|
// Property is a name/value pair plus some metadata. A datastore entity's
|
||
|
// contents are loaded and saved as a sequence of Properties. An entity can
|
||
|
// have multiple Properties with the same name, provided that p.Multiple is
|
||
|
// true on all of that entity's Properties with that name.
|
||
|
type Property struct {
|
||
|
// Name is the property name.
|
||
|
Name string
|
||
|
// Value is the property value. The valid types are:
|
||
|
// - int64
|
||
|
// - bool
|
||
|
// - string
|
||
|
// - float64
|
||
|
// - ByteString
|
||
|
// - *Key
|
||
|
// - time.Time
|
||
|
// - appengine.BlobKey
|
||
|
// - appengine.GeoPoint
|
||
|
// - []byte (up to 1 megabyte in length)
|
||
|
// - *Entity (representing a nested struct)
|
||
|
// This set is smaller than the set of valid struct field types that the
|
||
|
// datastore can load and save. A Property Value cannot be a slice (apart
|
||
|
// from []byte); use multiple Properties instead. Also, 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.
|
||
|
NoIndex bool
|
||
|
// Multiple is whether the entity can have multiple properties with
|
||
|
// the same name. Even if a particular instance only has one property with
|
||
|
// a certain name, Multiple should be true if a struct would best represent
|
||
|
// it as a field of type []T instead of type T.
|
||
|
Multiple 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
|
||
|
}
|
||
|
|
||
|
// ByteString is a short byte slice (up to 1500 bytes) that can be indexed.
|
||
|
type ByteString []byte
|
||
|
|
||
|
// PropertyLoadSaver can be converted from and to a slice of Properties.
|
||
|
type PropertyLoadSaver interface {
|
||
|
Load([]Property) error
|
||
|
Save() ([]Property, 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 or 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
|
||
|
}
|
||
|
|
||
|
// structCodec describes how to convert a struct to and from a sequence of
|
||
|
// properties.
|
||
|
type structCodec struct {
|
||
|
// fields gives the field codec for the structTag with the given name.
|
||
|
fields map[string]fieldCodec
|
||
|
// hasSlice is whether a struct or any of its nested or embedded structs
|
||
|
// has a slice-typed field (other than []byte).
|
||
|
hasSlice bool
|
||
|
// keyField is the index of a *Key field with structTag __key__.
|
||
|
// This field is not relevant for the top level struct, only for
|
||
|
// nested structs.
|
||
|
keyField int
|
||
|
// complete is whether the structCodec is complete. An incomplete
|
||
|
// structCodec may be encountered when walking a recursive struct.
|
||
|
complete bool
|
||
|
}
|
||
|
|
||
|
// fieldCodec is a struct field's index and, if that struct field's type is
|
||
|
// itself a struct, that substruct's structCodec.
|
||
|
type fieldCodec struct {
|
||
|
// path is the index path to the field
|
||
|
path []int
|
||
|
noIndex bool
|
||
|
// omitEmpty indicates that the field should be omitted on save
|
||
|
// if empty.
|
||
|
omitEmpty bool
|
||
|
// structCodec is the codec fot the struct field at index 'path',
|
||
|
// or nil if the field is not a struct.
|
||
|
structCodec *structCodec
|
||
|
}
|
||
|
|
||
|
// structCodecs collects the structCodecs that have already been calculated.
|
||
|
var (
|
||
|
structCodecsMutex sync.Mutex
|
||
|
structCodecs = make(map[reflect.Type]*structCodec)
|
||
|
)
|
||
|
|
||
|
// getStructCodec returns the structCodec for the given struct type.
|
||
|
func getStructCodec(t reflect.Type) (*structCodec, error) {
|
||
|
structCodecsMutex.Lock()
|
||
|
defer structCodecsMutex.Unlock()
|
||
|
return getStructCodecLocked(t)
|
||
|
}
|
||
|
|
||
|
// getStructCodecLocked implements getStructCodec. The structCodecsMutex must
|
||
|
// be held when calling this function.
|
||
|
func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
|
||
|
c, ok := structCodecs[t]
|
||
|
if ok {
|
||
|
return c, nil
|
||
|
}
|
||
|
c = &structCodec{
|
||
|
fields: make(map[string]fieldCodec),
|
||
|
// We initialize keyField to -1 so that the zero-value is not
|
||
|
// misinterpreted as index 0.
|
||
|
keyField: -1,
|
||
|
}
|
||
|
|
||
|
// Add c to the structCodecs map before we are sure it is good. If t is
|
||
|
// a recursive type, it needs to find the incomplete entry for itself in
|
||
|
// the map.
|
||
|
structCodecs[t] = c
|
||
|
defer func() {
|
||
|
if retErr != nil {
|
||
|
delete(structCodecs, t)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
for i := 0; i < t.NumField(); i++ {
|
||
|
f := t.Field(i)
|
||
|
// Skip unexported fields.
|
||
|
// Note that if f is an anonymous, unexported struct field,
|
||
|
// we will promote its fields.
|
||
|
if f.PkgPath != "" && !f.Anonymous {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
tags := strings.Split(f.Tag.Get("datastore"), ",")
|
||
|
name := tags[0]
|
||
|
opts := make(map[string]bool)
|
||
|
for _, t := range tags[1:] {
|
||
|
opts[t] = true
|
||
|
}
|
||
|
switch {
|
||
|
case name == "":
|
||
|
if !f.Anonymous {
|
||
|
name = f.Name
|
||
|
}
|
||
|
case name == "-":
|
||
|
continue
|
||
|
case name == "__key__":
|
||
|
if f.Type != typeOfKeyPtr {
|
||
|
return nil, fmt.Errorf("datastore: __key__ field on struct %v is not a *datastore.Key", t)
|
||
|
}
|
||
|
c.keyField = i
|
||
|
case !validPropertyName(name):
|
||
|
return nil, fmt.Errorf("datastore: struct tag has invalid property name: %q", name)
|
||
|
}
|
||
|
|
||
|
substructType, fIsSlice := reflect.Type(nil), false
|
||
|
switch f.Type.Kind() {
|
||
|
case reflect.Struct:
|
||
|
substructType = f.Type
|
||
|
case reflect.Slice:
|
||
|
if f.Type.Elem().Kind() == reflect.Struct {
|
||
|
substructType = f.Type.Elem()
|
||
|
}
|
||
|
fIsSlice = f.Type != typeOfByteSlice
|
||
|
c.hasSlice = c.hasSlice || fIsSlice
|
||
|
}
|
||
|
|
||
|
var sub *structCodec
|
||
|
if substructType != nil && substructType != typeOfTime && substructType != typeOfGeoPoint {
|
||
|
var err error
|
||
|
sub, err = getStructCodecLocked(substructType)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if !sub.complete {
|
||
|
return nil, fmt.Errorf("datastore: recursive struct: field %q", f.Name)
|
||
|
}
|
||
|
if fIsSlice && sub.hasSlice {
|
||
|
return nil, fmt.Errorf(
|
||
|
"datastore: flattening nested structs leads to a slice of slices: field %q", f.Name)
|
||
|
}
|
||
|
c.hasSlice = c.hasSlice || sub.hasSlice
|
||
|
// If f is an anonymous struct field, we promote the substruct's fields up to this level
|
||
|
// in the linked list of struct codecs.
|
||
|
if f.Anonymous {
|
||
|
for subname, subfield := range sub.fields {
|
||
|
if name != "" {
|
||
|
subname = name + "." + subname
|
||
|
}
|
||
|
if _, ok := c.fields[subname]; ok {
|
||
|
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", subname)
|
||
|
}
|
||
|
c.fields[subname] = fieldCodec{
|
||
|
path: append([]int{i}, subfield.path...),
|
||
|
noIndex: subfield.noIndex || opts["noindex"],
|
||
|
omitEmpty: subfield.omitEmpty,
|
||
|
structCodec: subfield.structCodec,
|
||
|
}
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if _, ok := c.fields[name]; ok {
|
||
|
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", name)
|
||
|
}
|
||
|
c.fields[name] = fieldCodec{
|
||
|
path: []int{i},
|
||
|
noIndex: opts["noindex"],
|
||
|
omitEmpty: opts["omitempty"],
|
||
|
structCodec: sub,
|
||
|
}
|
||
|
}
|
||
|
c.complete = true
|
||
|
return c, nil
|
||
|
}
|
||
|
|
||
|
// structPLS adapts a struct to be a PropertyLoadSaver.
|
||
|
type structPLS struct {
|
||
|
v reflect.Value
|
||
|
codec *structCodec
|
||
|
}
|
||
|
|
||
|
// 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()
|
||
|
codec, err := getStructCodec(v.Type())
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &structPLS{v, codec}, nil
|
||
|
}
|
||
|
|
||
|
// LoadStruct loads the properties from p to dst.
|
||
|
// dst must be a struct pointer.
|
||
|
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()
|
||
|
}
|