OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package gae | |
6 | |
7 import ( | |
8 "errors" | |
9 "fmt" | |
10 "math" | |
11 "reflect" | |
12 "time" | |
13 ) | |
14 | |
15 var ( | |
16 // ErrDSMetaFieldUnset is returned from DSPropertyLoadSaver.{Get,Set}Met
a | |
17 // implementations when the specified meta key isn't set on the struct a
t | |
18 // all. | |
19 ErrDSMetaFieldUnset = fmt.Errorf("gae: meta field unset") | |
20 ) | |
21 | |
22 var ( | |
23 typeOfBSKey = reflect.TypeOf(BSKey("")) | |
24 typeOfBool = reflect.TypeOf(false) | |
25 typeOfByteSlice = reflect.TypeOf([]byte(nil)) | |
26 typeOfDSByteString = reflect.TypeOf(DSByteString(nil)) | |
27 typeOfDSGeoPoint = reflect.TypeOf(DSGeoPoint{}) | |
28 typeOfDSKey = reflect.TypeOf((*DSKey)(nil)).Elem() | |
29 typeOfFloat64 = reflect.TypeOf(float64(0)) | |
30 typeOfInt64 = reflect.TypeOf(int64(0)) | |
31 typeOfString = reflect.TypeOf("") | |
32 typeOfTime = reflect.TypeOf(time.Time{}) | |
33 ) | |
34 | |
35 var ( | |
36 minTime = time.Unix(int64(math.MinInt64)/1e6, (int64(math.MinInt64)%1e6)
*1e3) | |
37 maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)
*1e3) | |
38 ) | |
39 | |
40 type IndexSetting bool | |
41 | |
42 const ( | |
43 // ShouldIndex is the default, which is why it must assume the zero valu
e, | |
44 // even though it's werid :(. | |
45 ShouldIndex IndexSetting = false | |
46 NoIndex IndexSetting = true | |
47 ) | |
48 | |
49 func (i IndexSetting) String() string { | |
50 if i { | |
51 return "NoIndex" | |
52 } | |
53 return "ShouldIndex" | |
54 } | |
55 | |
56 // DSProperty is a value plus an indicator of whether the value should be | |
57 // indexed. Name and Multiple are stored in the DSPropertyMap object. | |
58 type DSProperty struct { | |
59 value interface{} | |
60 indexSetting IndexSetting | |
61 propType DSPropertyType | |
62 } | |
63 | |
64 // MkDSProperty makes a new indexed* DSProperty and returns it. If val is an | |
65 // invalid value, this panics (so don't do it). If you want to handle the error | |
66 // normally, use SetValue(..., ShouldIndex) instead. | |
67 // | |
68 // *indexed if val is not an unindexable type like []byte. | |
69 func MkDSProperty(val interface{}) DSProperty { | |
70 ret := DSProperty{} | |
71 if err := ret.SetValue(val, ShouldIndex); err != nil { | |
72 panic(err) | |
73 } | |
74 return ret | |
75 } | |
76 | |
77 // MkDSPropertyNI makes a new DSProperty (with noindex set to true), and returns | |
78 // it. If val is an invalid value, this panics (so don't do it). If you want to | |
79 // handle the error normally, use SetValue(..., NoIndex) instead. | |
80 func MkDSPropertyNI(val interface{}) DSProperty { | |
81 ret := DSProperty{} | |
82 if err := ret.SetValue(val, NoIndex); err != nil { | |
83 panic(err) | |
84 } | |
85 return ret | |
86 } | |
87 | |
88 // DSPropertyConverter may be implemented by the pointer-to a struct field which | |
89 // is serialized by RawDatastore. Its ToDSProperty will be called on save, and | |
90 // it's FromDSProperty will be called on load (from datastore). The method may | |
91 // do arbitrary computation, and if it encounters an error, may return it. This | |
92 // error will be a fatal error (as defined by DSPropertyLoadSaver) for the | |
93 // struct conversion. | |
94 // | |
95 // Example: | |
96 // type Complex complex | |
97 // func (c *Complex) ToDSProperty() (ret DSProperty, err error) { | |
98 // // something like: | |
99 // err = ret.SetValue(fmt.Sprint(*c), true) | |
100 // return | |
101 // } | |
102 // func (c *Complex) FromDSProperty(p DSProperty) (err error) { | |
103 // ... load *c from p ... | |
104 // } | |
105 // | |
106 // type MyStruct struct { | |
107 // Complexity []Complex // acts like []complex, but can be serialized to DS | |
108 // } | |
109 type DSPropertyConverter interface { | |
110 // TODO(riannucci): Allow a convertable to return multiple values. This
is | |
111 // eminently doable (as long as the single-slice restriction is kept).
It | |
112 // could also cut down on the amount of reflection necessary when resolv
ing | |
113 // a path in a struct (in the struct loading routine in helper). | |
114 | |
115 ToDSProperty() (DSProperty, error) | |
116 FromDSProperty(DSProperty) error | |
117 } | |
118 | |
119 // DSPropertyType is a single-byte representation of the type of data contained | |
120 // in a DSProperty. The specific values of this type information are chosen so | |
121 // that the types sort according to the order of types as sorted by the | |
122 // datastore. | |
123 type DSPropertyType byte | |
124 | |
125 // These constants are in the order described by | |
126 // https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type
_ordering | |
127 // with a slight divergence for the Int/Time split. | |
128 // NOTE: this enum can only occupy 7 bits, because we use the high bit to encode | |
129 // indexed/non-indexed. See typData.WriteBinary. | |
130 const ( | |
131 DSPTNull DSPropertyType = iota | |
132 DSPTInt | |
133 | |
134 // DSPTTime is a slight divergence from the way that datastore natively
stores | |
135 // time. In datastore, times and integers actually sort together | |
136 // (apparently?). This is probably insane, and I don't want to add the | |
137 // complexity of field 'meaning' as a sparate concept from the field's '
type' | |
138 // (which is what datastore seems to do, judging from the protobufs). So
if | |
139 // you're here because you implemented an app which relies on time.Time
and | |
140 // int64 sorting together, then this is why your app acts differently in | |
141 // production. My advice is to NOT DO THAT. If you really want this (and
you | |
142 // probably don't), you should take care of the time.Time <-> int64 conv
ersion | |
143 // in your app and just use a property type of int64 (consider using | |
144 // DSPropertyConverter). | |
145 DSPTTime | |
146 | |
147 // DSPTBoolFalse and True are also a slight divergence, but not a semant
ic | |
148 // one. IIUC, in datastore 'bool' is actually the type and the value is
either | |
149 // 0 or 1 (taking another byte to store). Since we have plenty of space
in | |
150 // this type byte, I just merge the value into the type for booleans. If
this | |
151 // becomes problematic, consider changing this to just pvBool, and then | |
152 // encoding a 0 or 1 as a byte in the relevant marshalling routines. | |
153 DSPTBoolFalse | |
154 DSPTBoolTrue | |
155 | |
156 DSPTBytes // []byte or datastore.ByteString | |
157 DSPTString // string or string noindex | |
158 DSPTFloat | |
159 DSPTGeoPoint | |
160 DSPTKey | |
161 DSPTBlobKey | |
162 | |
163 DSPTUnknown | |
164 ) | |
165 | |
166 func (t DSPropertyType) String() string { | |
167 switch t { | |
168 case DSPTNull: | |
169 return "DSPTNull" | |
170 case DSPTInt: | |
171 return "DSPTInt" | |
172 case DSPTTime: | |
173 return "DSPTTime" | |
174 case DSPTBoolFalse: | |
175 return "DSPTBoolFalse" | |
176 case DSPTBoolTrue: | |
177 return "DSPTBoolTrue" | |
178 case DSPTBytes: | |
179 return "DSPTBytes" | |
180 case DSPTString: | |
181 return "DSPTString" | |
182 case DSPTFloat: | |
183 return "DSPTFloat" | |
184 case DSPTGeoPoint: | |
185 return "DSPTGeoPoint" | |
186 case DSPTKey: | |
187 return "DSPTKey" | |
188 case DSPTBlobKey: | |
189 return "DSPTBlobKey" | |
190 default: | |
191 return fmt.Sprintf("DSPTUnknown(%02x)", byte(t)) | |
192 } | |
193 } | |
194 | |
195 // DSPropertyTypeOf returns the DSPT* type of the given DSProperty-compatible | |
196 // value v. If checkValid is true, this method will also ensure that time.Time | |
197 // and DSGeoPoint have valid values. | |
198 func DSPropertyTypeOf(v interface{}, checkValid bool) (DSPropertyType, error) { | |
199 switch x := v.(type) { | |
200 case nil: | |
201 return DSPTNull, nil | |
202 case int64: | |
203 return DSPTInt, nil | |
204 case float64: | |
205 return DSPTFloat, nil | |
206 case bool: | |
207 if x { | |
208 return DSPTBoolTrue, nil | |
209 } | |
210 return DSPTBoolFalse, nil | |
211 case []byte, DSByteString: | |
212 return DSPTBytes, nil | |
213 case BSKey: | |
214 return DSPTBlobKey, nil | |
215 case string: | |
216 return DSPTString, nil | |
217 case DSKey: | |
218 // TODO(riannucci): Check key for validity in its own namespace? | |
219 return DSPTKey, nil | |
220 case time.Time: | |
221 err := error(nil) | |
222 if checkValid && (x.Before(minTime) || x.After(maxTime)) { | |
223 err = errors.New("time value out of range") | |
224 } | |
225 if checkValid && x.Location() != time.UTC { | |
226 err = fmt.Errorf("time value has wrong Location: %s", x.
Location()) | |
227 } | |
228 return DSPTTime, err | |
229 case DSGeoPoint: | |
230 err := error(nil) | |
231 if checkValid && !x.Valid() { | |
232 err = errors.New("invalid GeoPoint value") | |
233 } | |
234 return DSPTGeoPoint, err | |
235 default: | |
236 return DSPTUnknown, fmt.Errorf("gae: DSProperty has bad type %T"
, v) | |
237 } | |
238 } | |
239 | |
240 // DSUpconvertUnderlyingType takes an object o, and attempts to convert it to | |
241 // its native datastore-compatible type. e.g. int16 will convert to int64, and | |
242 // `type Foo string` will convert to `string`. | |
243 func DSUpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, refl
ect.Type) { | |
244 v := reflect.ValueOf(o) | |
245 switch t.Kind() { | |
246 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.In
t64: | |
247 o = v.Int() | |
248 t = typeOfInt64 | |
249 case reflect.Bool: | |
250 o = v.Bool() | |
251 t = typeOfBool | |
252 case reflect.String: | |
253 if t != typeOfBSKey { | |
254 o = v.String() | |
255 t = typeOfString | |
256 } | |
257 case reflect.Float32, reflect.Float64: | |
258 o = v.Float() | |
259 t = typeOfFloat64 | |
260 case reflect.Slice: | |
261 if t != typeOfDSByteString && t.Elem().Kind() == reflect.Uint8 { | |
262 o = v.Bytes() | |
263 t = typeOfByteSlice | |
264 } | |
265 case reflect.Struct: | |
266 if t == typeOfTime { | |
267 // time in a DSProperty can only hold microseconds | |
268 o = v.Interface().(time.Time).Round(time.Microsecond) | |
269 } | |
270 } | |
271 return o, t | |
272 } | |
273 | |
274 // Value returns the current value held by this property. It's guaranteed to | |
275 // be a valid value type (i.e. `p.SetValue(p.Value(), true)` will never return | |
276 // an error). | |
277 func (p DSProperty) Value() interface{} { return p.value } | |
278 | |
279 // IndexSetting says weather or not the datastore should create indicies for | |
280 // this value. | |
281 func (p DSProperty) IndexSetting() IndexSetting { return p.indexSetting } | |
282 | |
283 // Type is the DSPT* type of the data contained in Value(). | |
284 func (p DSProperty) Type() DSPropertyType { return p.propType } | |
285 | |
286 // SetValue sets the Value field of a DSProperty, and ensures that its value | |
287 // conforms to the permissible types. That way, you're guaranteed that if you | |
288 // have a DSProperty, its value is valid. | |
289 // | |
290 // value is the property value. The valid types are: | |
291 // - int64 | |
292 // - bool | |
293 // - string | |
294 // - float64 | |
295 // - DSByteString | |
296 // - DSKey | |
297 // - time.Time | |
298 // - BSKey | |
299 // - DSGeoPoint | |
300 // - []byte (up to 1 megabyte in length) | |
301 // This set is smaller than the set of valid struct field types that the | |
302 // datastore can load and save. A Property Value cannot be a slice (apart | |
303 // from []byte); use multiple Properties instead. Also, a Value's type | |
304 // must be explicitly on the list above; it is not sufficient for the | |
305 // underlying type to be on that list. For example, a Value of "type | |
306 // myInt64 int64" is invalid. Smaller-width integers and floats are also | |
307 // invalid. Again, this is more restrictive than the set of valid struct | |
308 // field types. | |
309 // | |
310 // A value may also be the nil interface value; this is equivalent to | |
311 // Python's None but not directly representable by a Go struct. Loading | |
312 // a nil-valued property into a struct will set that field to the zero | |
313 // value. | |
314 func (p *DSProperty) SetValue(value interface{}, is IndexSetting) (err error) { | |
315 t := reflect.Type(nil) | |
316 pt := DSPTNull | |
317 if value != nil { | |
318 t = reflect.TypeOf(value) | |
319 value, t = DSUpconvertUnderlyingType(value, t) | |
320 if pt, err = DSPropertyTypeOf(value, true); err != nil { | |
321 return | |
322 } | |
323 } | |
324 p.propType = pt | |
325 p.value = value | |
326 p.indexSetting = is | |
327 if t == typeOfByteSlice { | |
328 p.indexSetting = NoIndex | |
329 } | |
330 return | |
331 } | |
332 | |
333 // DSPropertyLoadSaver may be implemented by a user type, and RawDatastore will | |
334 // use this interface to serialize the type instead of trying to automatically | |
335 // create a serialization codec for it with helper.GetPLS. | |
336 type DSPropertyLoadSaver interface { | |
337 // Load takes the values from the given map and attempts to save them in
to | |
338 // the underlying object (usually a struct or a DSPropertyMap). If a fat
al | |
339 // error occurs, it's returned via error. If non-fatal conversion errors | |
340 // occur, error will be a MultiError containing one or more ErrDSFieldMi
smatch | |
341 // objects. | |
342 Load(DSPropertyMap) error | |
343 | |
344 // Save returns the current property as a DSPropertyMap. if withMeta is
true, | |
345 // then the DSPropertyMap contains all the metadata (e.g. '$meta' fields
) | |
346 // which was held by this DSPropertyLoadSaver. | |
347 Save(withMeta bool) (DSPropertyMap, error) | |
348 | |
349 // GetMeta will get information about the field which has the struct tag
in | |
350 // the form of `gae:"$<key>[,<value>]?"`. | |
351 // | |
352 // string and int64 fields will return the <value> in the struct tag, | |
353 // converted to the appropriate type, if the field has the zero value. | |
354 // | |
355 // Example: | |
356 // type MyStruct struct { | |
357 // CoolField int64 `gae:"$id,1"` | |
358 // } | |
359 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") | |
360 // // val == 1 | |
361 // // err == nil | |
362 // | |
363 // val, err := helper.GetPLS(&MyStruct{10}).GetMeta("id") | |
364 // // val == 10 | |
365 // // err == nil | |
366 GetMeta(key string) (interface{}, error) | |
367 | |
368 // SetMeta allows you to set the current value of the meta-keyed field. | |
369 SetMeta(key string, val interface{}) error | |
370 | |
371 // Problem indicates that this PLS has a fatal problem. Usually this is | |
372 // set when the underlying struct has recursion, invalid field types, ne
sted | |
373 // slices, etc. | |
374 Problem() error | |
375 } | |
376 | |
377 // DSPropertyMap represents the contents of a datastore entity in a generic way. | |
378 // It maps from property name to a list of property values which correspond to | |
379 // that property name. It is the spiritual successor to PropertyList from the | |
380 // original SDK. | |
381 // | |
382 // DSPropertyMap may contain "meta" values, which are keyed with a '$' prefix. | |
383 // Technically the datastore allows arbitrary property names, but all of the | |
384 // SDKs go out of their way to try to make all property names valid programming | |
385 // language tokens. Special values must correspond to a single DSProperty... | |
386 // corresponding to 0 is equivalent to unset, and corresponding to >1 is an | |
387 // error. So: | |
388 // | |
389 // { | |
390 // "$id": {MkDSProperty(1)}, // GetProperty("id") -> 1, nil | |
391 // "$foo": {}, // GetProperty("foo") -> nil, ErrDSMetaFieldUnset | |
392 // // GetProperty("bar") -> nil, ErrDSMetaFieldUnset | |
393 // "$meep": { | |
394 // MkDSProperty("hi"), | |
395 // MkDSProperty("there")}, // GetProperty("meep") -> nil, error! | |
396 // } | |
397 // | |
398 // Additionally, Save returns a copy of the map with the meta keys omitted (e.g. | |
399 // these keys are not going to be serialized to the datastore). | |
400 type DSPropertyMap map[string][]DSProperty | |
401 | |
402 var _ DSPropertyLoadSaver = DSPropertyMap(nil) | |
403 | |
404 // Load implements DSPropertyLoadSaver.Load | |
405 func (pm DSPropertyMap) Load(props DSPropertyMap) error { | |
406 for k, v := range props { | |
407 pm[k] = append(pm[k], v...) | |
408 } | |
409 return nil | |
410 } | |
411 | |
412 // Save implements DSPropertyLoadSaver.Save by returning a copy of the | |
413 // current map data. | |
414 func (pm DSPropertyMap) Save(withMeta bool) (DSPropertyMap, error) { | |
415 if len(pm) == 0 { | |
416 return DSPropertyMap{}, nil | |
417 } | |
418 ret := make(DSPropertyMap, len(pm)) | |
419 for k, v := range pm { | |
420 if withMeta || len(k) == 0 || k[0] != '$' { | |
421 ret[k] = append(ret[k], v...) | |
422 } | |
423 } | |
424 return ret, nil | |
425 } | |
426 | |
427 func (pm DSPropertyMap) GetMeta(key string) (interface{}, error) { | |
428 v, ok := pm["$"+key] | |
429 if !ok || len(v) == 0 { | |
430 return nil, ErrDSMetaFieldUnset | |
431 } | |
432 if len(v) > 1 { | |
433 return nil, errors.New("gae: too many values for Meta key") | |
434 } | |
435 return v[0].Value(), nil | |
436 } | |
437 | |
438 func (pm DSPropertyMap) SetMeta(key string, val interface{}) error { | |
439 prop := DSProperty{} | |
440 if err := prop.SetValue(val, NoIndex); err != nil { | |
441 return err | |
442 } | |
443 pm["$"+key] = []DSProperty{prop} | |
444 return nil | |
445 } | |
446 | |
447 func (pm DSPropertyMap) Problem() error { | |
448 return nil | |
449 } | |
OLD | NEW |