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