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