| 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 |