forked from davars/gae
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pls.go
275 lines (268 loc) · 10.2 KB
/
pls.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// Copyright 2015 The LUCI Authors.
//
// 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"
)
// GetPLS resolves obj into default struct PropertyLoadSaver and
// MetaGetterSetter implementation.
//
// obj must be a non-nil pointer to a struct of some sort.
//
// By default, exported fields will be serialized to/from the datastore. If the
// field is not exported, it will be skipped by the serialization routines.
//
// If a field is of a non-supported type (see Property for the list of supported
// property types), this function will panic. Other problems include duplicate
// field names (due to tagging), recursively defined structs, nested structures
// with multiple slices (e.g. slices of slices, either directly `[][]type` or
// indirectly `[]Embedded` where Embedded contains a slice.)
//
// The following field types are supported:
// * int64, int32, int16, int8, int
// * uint32, uint16, uint8, byte
// * float64, float32
// * string
// * []byte
// * bool
// * time.Time
// * GeoPoint
// * *Key
// * any Type whose underlying type is one of the above types
// * Types which implement PropertyConverter on (*Type)
// * A struct composed of the above types (except for nested slices)
// * A slice of any of the above types
//
// GetPLS supports the following struct tag syntax:
// `gae:"fieldName[,noindex]"` -- an alternate fieldname for an exportable
// field. When the struct is serialized or deserialized, fieldName will be
// associated with the struct field instead of the field's Go name. This is
// useful when writing Go code which interfaces with appengine code written
// in other languages (like python) which use lowercase as their default
// datastore field names.
//
// A fieldName of "-" means that gae will ignore the field for all
// serialization/deserialization.
//
// if noindex is specified, then this field will not be indexed in the
// datastore, even if it was an otherwise indexable type. If fieldName is
// blank, and noindex is specifed, then fieldName will default to the
// field's actual name. Note that by default, all fields (with indexable
// types) are indexed.
//
// `gae:"$metaKey[,<value>]` -- indicates a field is metadata. Metadata
// can be used to control filter behavior, or to store key data when using
// the Interface.KeyForObj* methods. The supported field types are:
// - *Key
// - int64, int32, int16, int8, uint32, uint16, uint8, byte
// - string
// - Toggle (GetMeta and SetMeta treat the field as if it were bool)
// - Any type which implements PropertyConverter
// Additionally, numeric, string and Toggle types allow setting a default
// value in the struct field tag (the "<value>" portion).
//
// Only exported fields allow SetMeta, but all fields of appropriate type
// allow tagged defaults for use with GetMeta. See Examples.
//
// `gae:"[-],extra"` -- indicates that any extra, unrecognized or mismatched
// property types (type in datastore doesn't match your struct's field
// type) should be loaded into and saved from this field. The precise type
// of the field must be PropertyMap. This form allows you to control the
// behavior of reads and writes when your schema changes, or to implement
// something like ndb.Expando with a mix of structured and unstructured
// fields.
//
// If the `-` is present, then datastore write operations will not put
// elements of this map into the datastore.
//
// If the field is non-exported, then read operations from the datastore
// will not populate the members of this map, but extra fields or
// structural differences encountered when reading into this struct will be
// silently ignored. This is useful if you want to just ignore old fields.
//
// If there is a conflict between a field in the struct and a same-named
// Property in the extra field, the field in the struct takes precedence.
//
// Recursive structs are supported, but all extra properties go to the
// topmost structure's Extra field. This is a bit non-intuitive, but the
// implementation complexity was deemed not worth it, since that sort of
// thing is generally only useful on schema changes, which should be
// transient.
//
// Examples:
// // "black hole": ignore mismatches, ignore on write
// _ PropertyMap `gae:"-,extra"
//
// // "expando": full content is read/written
// Expando PropertyMap `gae:",extra"
//
// // "convert": content is read from datastore, but lost on writes. This
// // is useful for doing conversions from an old schema to a new one,
// // since you can retrieve the old data and populate it into new fields,
// // for example. Probably should be used in conjunction with an
// // implementation of the PropertyLoadSaver interface so that you can
// // transparently upconvert to the new schema on load.
// Convert PropertyMap `gae:"-,extra"
//
// Example "special" structure. This is supposed to be some sort of datastore
// singleton object.
// struct secretFoo {
// // _id and _kind are not exported, so setting their values will not be
// // reflected by GetMeta.
// _id int64 `gae:"$id,1"`
// _kind string `gae:"$kind,InternalFooSingleton"`
//
// // Value is exported, so can be read and written by the PropertyLoadSaver,
// // but secretFoo is shared with a python appengine module which has
// // stored this field as 'value' instead of 'Value'.
// Value int64 `gae:"value"`
// }
//
// Example "normal" structure that you might use in a go-only appengine app.
// struct User {
// ID string `gae:"$id"`
// // "kind" is automatically implied by the struct name: "User"
// // "parent" is nil... Users are root entities
//
// // 'Name' will serialized to the datastore in the field 'Name'
// Name string
// }
//
// struct Comment {
// ID int64 `gae:"$id"`
// // "kind" is automatically implied by the struct name: "Comment"
//
// // Parent will be enforced by the application to be a User key.
// Parent *Key `gae:"$parent"`
//
// // 'Lines' will serialized to the datastore in the field 'Lines'
// Lines []string
// }
//
// A pointer-to-struct may also implement MetaGetterSetter to provide more
// sophistocated metadata values. Explicitly defined fields (as shown above)
// always take precedence over fields manipulated by the MetaGetterSetter
// methods. So if your GetMeta handles "kind", but you explicitly have a
// $kind field, the $kind field will take precedence and your GetMeta
// implementation will not be called for "kind".
//
// A struct overloading any of the PropertyLoadSaver or MetaGetterSetter
// interfaces may evoke the default struct behavior by using GetPLS on itself.
// For example:
//
// struct Special {
// Name string
//
// foo string
// }
//
// func (s *Special) Load(props PropertyMap) error {
// if foo, ok := props["foo"]; ok && len(foo) == 1 {
// s.foo = foo
// delete(props, "foo")
// }
// return GetPLS(s).Load(props)
// }
//
// func (s *Special) Save(withMeta bool) (PropertyMap, error) {
// props, err := GetPLS(s).Save(withMeta)
// if err != nil {
// return nil, err
// }
// props["foo"] = []Property{MkProperty(s.foo)}
// return props, nil
// }
//
// func (s *Special) Problem() error {
// return GetPLS(s).Problem()
// }
//
// Additionally, any field ptr-to-type may implement the PropertyConverter
// interface to allow a single field to, for example, implement some alternate
// encoding (json, gzip), or even just serialize to/from a simple string field.
// This applies to normal fields, as well as metadata fields. It can be useful
// for storing struct '$id's which have multi-field meanings. For example, the
// Person struct below could be initialized in go as `&Person{Name{"Jane",
// "Doe"}}`, retaining Jane's name as manipulable Go fields. However, in the
// datastore, it would have a key of `/Person,"Jane|Doe"`, and loading the
// struct from the datastore as part of a Query, for example, would correctly
// populate Person.Name.First and Person.Name.Last.
//
// type Name struct {
// First string
// Last string
// }
//
// func (n *Name) ToProperty() (Property, error) {
// return fmt.Sprintf("%s|%s", n.First, n.Last)
// }
//
// func (n *Name) FromProperty(p Property) error {
// // check p to be a PTString
// // split on "|"
// // assign to n.First, n.Last
// }
//
// type Person struct {
// ID Name `gae:"$id"`
// }
func GetPLS(obj interface{}) interface {
PropertyLoadSaver
MetaGetterSetter
} {
v := reflect.ValueOf(obj)
if !v.IsValid() {
panic(fmt.Errorf("cannot GetPLS(%T): failed to reflect", obj))
}
if v.IsNil() {
panic(fmt.Errorf("cannot GetPLS(%T): pointer is nil", obj))
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
if v.Kind() == reflect.Struct {
s := structPLS{
c: getCodec(v.Type()),
o: v,
}
// If our object implements MetaGetterSetter, use this instead of the built-in
// PLS MetaGetterSetter.
if mgs, ok := obj.(MetaGetterSetter); ok {
s.mgs = mgs
}
return &s
}
}
panic(fmt.Errorf("cannot GetPLS(%T): not a pointer-to-struct", obj))
}
func getMGS(obj interface{}) MetaGetterSetter {
if mgs, ok := obj.(MetaGetterSetter); ok {
return mgs
}
return GetPLS(obj)
}
func getCodec(structType reflect.Type) *structCodec {
structCodecsMutex.RLock()
c, ok := structCodecs[structType]
structCodecsMutex.RUnlock()
if !ok {
structCodecsMutex.Lock()
defer structCodecsMutex.Unlock()
c = getStructCodecLocked(structType)
}
if c.problem != nil {
panic(c.problem)
}
return c
}