Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package datastore | 5 package datastore |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "errors" | 8 "errors" |
| 9 "fmt" | 9 "fmt" |
| 10 "math" | 10 "math" |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 99 // a path in a struct (in the struct loading routine in helper). | 99 // a path in a struct (in the struct loading routine in helper). |
| 100 | 100 |
| 101 ToProperty() (Property, error) | 101 ToProperty() (Property, error) |
| 102 FromProperty(Property) error | 102 FromProperty(Property) error |
| 103 } | 103 } |
| 104 | 104 |
| 105 // PropertyType is a single-byte representation of the type of data contained | 105 // PropertyType is a single-byte representation of the type of data contained |
| 106 // in a Property. The specific values of this type information are chosen so | 106 // in a Property. The specific values of this type information are chosen so |
| 107 // that the types sort according to the order of types as sorted by the | 107 // that the types sort according to the order of types as sorted by the |
| 108 // datastore. | 108 // datastore. |
| 109 // | |
| 110 // Note that indexes may only contain values of the following types: | |
| 111 // PTNull | |
| 112 // PTInt | |
| 113 // PTBool | |
| 114 // PTFloat | |
| 115 // PTString | |
| 116 // PTGeoPoint | |
| 117 // PTKey | |
| 118 // | |
| 119 // The biggest impact of this is that if you do a Projection query, you'll only | |
| 120 // get back Properties with the above types (e.g. if you store a PTTime value, | |
| 121 // then Project on it, you'll get back a PTInt value). For convenience, Property | |
| 122 // has a Project(PropertyType) method which will side-cast to your intended | |
| 123 // type. If you project into a structure with the high-level Interface | |
| 124 // implementation, or use StructPLS, this conversion will be done for you | |
| 125 // automatically, using the type of the destination field to cast. | |
| 109 type PropertyType byte | 126 type PropertyType byte |
| 110 | 127 |
| 111 // These constants are in the order described by | 128 // These constants are in the order described by |
| 112 // https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type _ordering | 129 // https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type _ordering |
| 113 // with a slight divergence for the Int/Time split. | 130 // with a slight divergence for the Int/Time split. |
| 114 // | 131 // |
| 115 // NOTE: this enum can only occupy 7 bits, because we use the high bit to encode | 132 // NOTE: this enum can only occupy 7 bits, because we use the high bit to encode |
| 116 // indexed/non-indexed, and we additionally require that all valid values and | 133 // indexed/non-indexed, and we additionally require that all valid values and |
| 117 // all INVERTED valid values must never equal 0xFF or 0x00. The reason for this | 134 // all INVERTED valid values must never equal 0xFF or 0x00. The reason for this |
| 118 // constraint is that we must always be able to create a byte that sorts before | 135 // constraint is that we must always be able to create a byte that sorts before |
| 119 // and after it. | 136 // and after it. |
| 120 // | 137 // |
| 121 // See "./serialize".WriteProperty and "impl/memory".increment for more info. | 138 // See "./serialize".WriteProperty and "impl/memory".increment for more info. |
| 122 const ( | 139 const ( |
| 140 // PTNull represents the 'nil' value. This is only directly visible when | |
| 141 // reading/writing a PropertyMap. If a PTNull value is loaded into a str uct | |
| 142 // field, the field will be initialized with its zero value. If a struct with | |
| 143 // a zero value is saved from a struct, it will still retain the field's type, | |
| 144 // not the 'nil' type. This is in contrast to other GAE languages such a s | |
| 145 // python where 'None' is a distinct value than the 'zero' value (e.g. a | |
| 146 // StringProperty can have the value "" OR None). | |
| 147 // | |
| 148 // PTNull is a Projection-query type | |
| 123 PTNull PropertyType = iota | 149 PTNull PropertyType = iota |
| 150 | |
| 151 // PTInt is always an int64. | |
| 152 // | |
| 153 // This is a Projection-query type, and may be projected to PTTime. | |
| 124 PTInt | 154 PTInt |
| 125 | |
| 126 // PTTime is a slight divergence from the way that datastore natively st ores | |
| 127 // time. In datastore, times and integers actually sort together | |
| 128 // (apparently?). This is probably insane, and I don't want to add the | |
| 129 // complexity of field 'meaning' as a sparate concept from the field's ' type' | |
| 130 // (which is what datastore seems to do, judging from the protobufs). So if | |
| 131 // you're here because you implemented an app which relies on time.Time and | |
| 132 // int64 sorting together, then this is why your app acts differently in | |
| 133 // production. My advice is to NOT DO THAT. If you really want this (and you | |
| 134 // probably don't), you should take care of the time.Time <-> int64 conv ersion | |
| 135 // in your app and just use a property type of int64 (consider using | |
| 136 // PropertyConverter). | |
| 137 PTTime | 155 PTTime |
| 138 | 156 |
| 139 » // PTBoolFalse and True are also a slight divergence, but not a semantic | 157 » // PTBool represents true or false |
| 140 » // one. IIUC, in datastore 'bool' is actually the type and the value is either | 158 » // |
| 141 » // 0 or 1 (taking another byte to store). Since we have plenty of space in | 159 » // This is a Projection-query type. |
| 142 » // this type byte, I just merge the value into the type for booleans. If this | 160 » PTBool |
| 143 » // becomes problematic, consider changing this to just pvBool, and then | |
| 144 » // encoding a 0 or 1 as a byte in the relevant marshalling routines. | |
| 145 » PTBoolFalse | |
| 146 » PTBoolTrue | |
| 147 | 161 |
| 148 » PTBytes // []byte or datastore.ByteString | 162 » // PTBytes represents []byte |
| 149 » PTString // string or string noindex | 163 » PTBytes |
| 164 | |
| 165 » // PTString is used to represent all strings (text). | |
| 166 » // | |
| 167 » // PTString is a Projection-query type and may be projected to PTBytes o r | |
| 168 » // PTBlobKey. | |
| 169 » PTString | |
| 170 | |
| 171 » // PTFloat is always a float64. | |
| 172 » // | |
| 173 » // This is a Projection-query type. | |
| 150 PTFloat | 174 PTFloat |
| 175 | |
| 176 // PTGeoPoint is a Projection-query type. | |
| 151 PTGeoPoint | 177 PTGeoPoint |
| 178 | |
| 179 // PTKey represents a Key object. | |
| 180 // | |
| 181 // PTKey is a Projection-query type. | |
| 152 PTKey | 182 PTKey |
| 183 | |
| 184 // PTBlobKey represents a blobstore.Key | |
| 153 PTBlobKey | 185 PTBlobKey |
| 154 | 186 |
| 155 // NOTE: THIS MUST BE LAST VALUE FOR THE init() ASSERTION BELOW TO WORK. | 187 // NOTE: THIS MUST BE LAST VALUE FOR THE init() ASSERTION BELOW TO WORK. |
| 156 PTUnknown | 188 PTUnknown |
| 157 ) | 189 ) |
| 158 | 190 |
| 159 func init() { | 191 func init() { |
| 160 if PTUnknown > 0x7e { | 192 if PTUnknown > 0x7e { |
| 161 panic( | 193 panic( |
| 162 "PTUnknown (and therefore PropertyType) exceeds 0x7e. Th is conflicts " + | 194 "PTUnknown (and therefore PropertyType) exceeds 0x7e. Th is conflicts " + |
| 163 "with serialize.WriteProperty's use of the high bit to indicate " + | 195 "with serialize.WriteProperty's use of the high bit to indicate " + |
| 164 "NoIndex and/or \"impl/memory\".increment's abil ity to guarantee " + | 196 "NoIndex and/or \"impl/memory\".increment's abil ity to guarantee " + |
| 165 "incrementability.") | 197 "incrementability.") |
| 166 } | 198 } |
| 167 } | 199 } |
| 168 | 200 |
| 169 func (t PropertyType) String() string { | 201 func (t PropertyType) String() string { |
| 170 switch t { | 202 switch t { |
| 171 case PTNull: | 203 case PTNull: |
| 172 return "PTNull" | 204 return "PTNull" |
| 173 case PTInt: | 205 case PTInt: |
| 174 return "PTInt" | 206 return "PTInt" |
| 175 case PTTime: | 207 case PTTime: |
| 176 return "PTTime" | 208 return "PTTime" |
| 177 » case PTBoolFalse: | 209 » case PTBool: |
| 178 » » return "PTBoolFalse" | 210 » » return "PTBool" |
| 179 » case PTBoolTrue: | |
| 180 » » return "PTBoolTrue" | |
| 181 case PTBytes: | 211 case PTBytes: |
| 182 return "PTBytes" | 212 return "PTBytes" |
| 183 case PTString: | 213 case PTString: |
| 184 return "PTString" | 214 return "PTString" |
| 185 case PTFloat: | 215 case PTFloat: |
| 186 return "PTFloat" | 216 return "PTFloat" |
| 187 case PTGeoPoint: | 217 case PTGeoPoint: |
| 188 return "PTGeoPoint" | 218 return "PTGeoPoint" |
| 189 case PTKey: | 219 case PTKey: |
| 190 return "PTKey" | 220 return "PTKey" |
| 191 case PTBlobKey: | 221 case PTBlobKey: |
| 192 return "PTBlobKey" | 222 return "PTBlobKey" |
| 193 default: | 223 default: |
| 194 return fmt.Sprintf("PTUnknown(%02x)", byte(t)) | 224 return fmt.Sprintf("PTUnknown(%02x)", byte(t)) |
| 195 } | 225 } |
| 196 } | 226 } |
| 197 | 227 |
| 198 // PropertyTypeOf returns the PT* type of the given Property-compatible | 228 // PropertyTypeOf returns the PT* type of the given Property-compatible |
| 199 // value v. If checkValid is true, this method will also ensure that time.Time | 229 // value v. If checkValid is true, this method will also ensure that time.Time |
| 200 // and GeoPoint have valid values. | 230 // and GeoPoint have valid values. |
| 201 func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) { | 231 func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) { |
| 202 switch x := v.(type) { | 232 switch x := v.(type) { |
| 203 case nil: | 233 case nil: |
| 204 return PTNull, nil | 234 return PTNull, nil |
| 205 case int64: | 235 case int64: |
| 206 return PTInt, nil | 236 return PTInt, nil |
| 207 case float64: | 237 case float64: |
| 208 return PTFloat, nil | 238 return PTFloat, nil |
| 209 case bool: | 239 case bool: |
| 210 » » if x { | 240 » » return PTBool, nil |
| 211 » » » return PTBoolTrue, nil | 241 » case []byte: |
| 212 » » } | |
| 213 » » return PTBoolFalse, nil | |
| 214 » case []byte, ByteString: | |
| 215 return PTBytes, nil | 242 return PTBytes, nil |
| 216 case blobstore.Key: | 243 case blobstore.Key: |
| 217 return PTBlobKey, nil | 244 return PTBlobKey, nil |
| 218 case string: | 245 case string: |
| 219 return PTString, nil | 246 return PTString, nil |
| 220 case Key: | 247 case Key: |
| 221 // TODO(riannucci): Check key for validity in its own namespace? | 248 // TODO(riannucci): Check key for validity in its own namespace? |
| 222 return PTKey, nil | 249 return PTKey, nil |
| 223 case time.Time: | 250 case time.Time: |
| 224 err := error(nil) | 251 err := error(nil) |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 265 o = v.Int() | 292 o = v.Int() |
| 266 case reflect.Bool: | 293 case reflect.Bool: |
| 267 o = v.Bool() | 294 o = v.Bool() |
| 268 case reflect.String: | 295 case reflect.String: |
| 269 if t != typeOfBSKey { | 296 if t != typeOfBSKey { |
| 270 o = v.String() | 297 o = v.String() |
| 271 } | 298 } |
| 272 case reflect.Float32, reflect.Float64: | 299 case reflect.Float32, reflect.Float64: |
| 273 o = v.Float() | 300 o = v.Float() |
| 274 case reflect.Slice: | 301 case reflect.Slice: |
| 275 » » if t != typeOfByteString && t.Elem().Kind() == reflect.Uint8 { | 302 » » if t.Elem().Kind() == reflect.Uint8 { |
| 276 o = v.Bytes() | 303 o = v.Bytes() |
| 277 } | 304 } |
| 278 case reflect.Struct: | 305 case reflect.Struct: |
| 279 if t == typeOfTime { | 306 if t == typeOfTime { |
| 280 // time in a Property can only hold microseconds | 307 // time in a Property can only hold microseconds |
| 281 o = v.Interface().(time.Time).Round(time.Microsecond) | 308 o = v.Interface().(time.Time).Round(time.Microsecond) |
| 282 } | 309 } |
| 283 } | 310 } |
| 284 return o | 311 return o |
| 285 } | 312 } |
| 286 | 313 |
| 287 // Value returns the current value held by this property. It's guaranteed to | 314 // Value returns the current value held by this property. It's guaranteed to |
| 288 // be a valid value type (i.e. `p.SetValue(p.Value(), true)` will never return | 315 // be a valid value type (i.e. `p.SetValue(p.Value(), true)` will never return |
| 289 // an error). | 316 // an error). |
| 290 func (p *Property) Value() interface{} { return p.value } | 317 func (p *Property) Value() interface{} { return p.value } |
| 291 | 318 |
| 292 // IndexSetting says weather or not the datastore should create indicies for | 319 // IndexSetting says weather or not the datastore should create indicies for |
| 293 // this value. | 320 // this value. |
| 294 func (p *Property) IndexSetting() IndexSetting { return p.indexSetting } | 321 func (p *Property) IndexSetting() IndexSetting { return p.indexSetting } |
| 295 | 322 |
| 296 // Type is the PT* type of the data contained in Value(). | 323 // Type is the PT* type of the data contained in Value(). |
| 297 func (p *Property) Type() PropertyType { return p.propType } | 324 func (p *Property) Type() PropertyType { return p.propType } |
| 298 | 325 |
| 299 // SetValue sets the Value field of a Property, and ensures that its value | 326 // SetValue sets the Value field of a Property, and ensures that its value |
| 300 // conforms to the permissible types. That way, you're guaranteed that if you | 327 // conforms to the permissible types. That way, you're guaranteed that if you |
| 301 // have a Property, its value is valid. | 328 // have a Property, its value is valid. |
| 302 // | 329 // |
| 303 // value is the property value. The valid types are: | 330 // value is the property value. The valid types are: |
| 304 // - int64 | 331 // - int64 |
| 332 // - time.Time | |
| 305 // - bool | 333 // - bool |
| 306 // - string | 334 // - string |
| 335 // (only the first 1500 bytes is indexable) | |
| 336 // - []byte | |
| 337 // (only the first 1500 bytes is indexable) | |
| 338 // - blobstore.Key | |
| 339 // (only the first 1500 bytes is indexable) | |
| 307 // - float64 | 340 // - float64 |
| 308 // - ByteString | |
| 309 // - Key | 341 // - Key |
| 310 // - time.Time | |
| 311 // - blobstore.Key | |
| 312 // - GeoPoint | 342 // - GeoPoint |
| 313 // - []byte (up to 1 megabyte in length) | |
| 314 // This set is smaller than the set of valid struct field types that the | 343 // This set is smaller than the set of valid struct field types that the |
| 315 // datastore can load and save. A Property Value cannot be a slice (apart | 344 // datastore can load and save. A Property Value cannot be a slice (apart |
| 316 // from []byte); use multiple Properties instead. Also, a Value's type | 345 // from []byte); use multiple Properties instead. Also, a Value's type |
| 317 // must be explicitly on the list above; it is not sufficient for the | 346 // must be explicitly on the list above; it is not sufficient for the |
| 318 // underlying type to be on that list. For example, a Value of "type | 347 // underlying type to be on that list. For example, a Value of "type |
| 319 // myInt64 int64" is invalid. Smaller-width integers and floats are also | 348 // myInt64 int64" is invalid. Smaller-width integers and floats are also |
| 320 // invalid. Again, this is more restrictive than the set of valid struct | 349 // invalid. Again, this is more restrictive than the set of valid struct |
| 321 // field types. | 350 // field types. |
| 322 // | 351 // |
| 323 // A value may also be the nil interface value; this is equivalent to | 352 // A value may also be the nil interface value; this is equivalent to |
| 324 // Python's None but not directly representable by a Go struct. Loading | 353 // Python's None but not directly representable by a Go struct. Loading |
| 325 // a nil-valued property into a struct will set that field to the zero | 354 // a nil-valued property into a struct will set that field to the zero |
| 326 // value. | 355 // value. |
| 327 func (p *Property) SetValue(value interface{}, is IndexSetting) (err error) { | 356 func (p *Property) SetValue(value interface{}, is IndexSetting) (err error) { |
| 328 pt := PTNull | 357 pt := PTNull |
| 329 if value != nil { | 358 if value != nil { |
| 330 value = UpconvertUnderlyingType(value) | 359 value = UpconvertUnderlyingType(value) |
| 331 if pt, err = PropertyTypeOf(value, true); err != nil { | 360 if pt, err = PropertyTypeOf(value, true); err != nil { |
| 332 return | 361 return |
| 333 } | 362 } |
| 334 } | 363 } |
| 335 p.propType = pt | 364 p.propType = pt |
| 336 p.value = value | 365 p.value = value |
| 337 p.indexSetting = is | 366 p.indexSetting = is |
| 338 » if _, ok := value.([]byte); ok { | 367 » return |
| 339 » » p.indexSetting = NoIndex | 368 } |
| 369 | |
| 370 // ForIndex gets a new Property with its value and type converted as if it were | |
| 371 // being stored in a datastore index. See the doc on PropertyType for more info. | |
| 372 func (p Property) ForIndex() Property { | |
| 373 » switch p.propType { | |
| 374 » case PTNull, PTInt, PTBool, PTString, PTFloat, PTGeoPoint, PTKey: | |
| 375 » » return p | |
| 376 | |
| 377 » case PTTime: | |
| 378 » » v, _ := p.Project(PTInt) | |
| 379 » » return Property{v, p.indexSetting, PTInt} | |
| 380 | |
| 381 » case PTBytes, PTBlobKey: | |
| 382 » » v, _ := p.Project(PTString) | |
| 383 » » return Property{v, p.indexSetting, PTString} | |
| 340 } | 384 } |
| 341 » return | 385 » panic(fmt.Errorf("unknown PropertyType: %s", p.propType)) |
| 386 } | |
| 387 | |
| 388 // Project can be used to project a Property retrieved from a Projection query | |
| 389 // into a different datatype. For example, if you have a PTInt property, you | |
| 390 // could Project(PTTime) to convert it to a time.Time. The following conversions | |
| 391 // are supported: | |
| 392 // PTInt <-> PTTime | |
|
dnj (Google)
2015/09/10 16:26:11
Note that you can project a type to itself (identi
iannucci
2015/09/10 17:29:21
Oh, right. Done.
| |
| 393 // PTString <-> PTBlobKey | |
| 394 // PTString <-> PTBytes | |
| 395 // PTNull <-> Anything | |
| 396 func (p *Property) Project(to PropertyType) (interface{}, error) { | |
| 397 » switch { | |
| 398 » case to == p.propType: | |
| 399 » » return p.value, nil | |
| 400 | |
| 401 » case to == PTInt && p.propType == PTTime: | |
| 402 » » t := p.value.(time.Time) | |
| 403 » » v := uint64(t.Unix())*1e6 + uint64(t.Nanosecond()/1e3) | |
| 404 » » return int64(v), nil | |
| 405 | |
| 406 » case to == PTTime && p.propType == PTInt: | |
| 407 » » v := p.value.(int64) | |
| 408 » » return time.Unix(int64(v/1e6), int64((v%1e6)*1e3)).UTC(), nil | |
| 409 | |
| 410 » case to == PTString && p.propType == PTBytes: | |
| 411 » » return string(p.value.([]byte)), nil | |
| 412 | |
| 413 » case to == PTString && p.propType == PTBlobKey: | |
| 414 » » return string(p.value.(blobstore.Key)), nil | |
| 415 | |
| 416 » case to == PTBytes && p.propType == PTString: | |
| 417 » » return []byte(p.value.(string)), nil | |
| 418 | |
| 419 » case to == PTBlobKey && p.propType == PTString: | |
| 420 » » return blobstore.Key(p.value.(string)), nil | |
| 421 | |
| 422 » case to == PTNull: | |
| 423 » » return nil, nil | |
| 424 | |
| 425 » case p.propType == PTNull: | |
| 426 » » switch to { | |
| 427 » » case PTInt: | |
| 428 » » » return int64(0), nil | |
| 429 » » case PTTime: | |
| 430 » » » return time.Time{}, nil | |
| 431 » » case PTBool: | |
| 432 » » » return false, nil | |
| 433 » » case PTBytes: | |
| 434 » » » return []byte(nil), nil | |
| 435 » » case PTString: | |
| 436 » » » return "", nil | |
| 437 » » case PTFloat: | |
| 438 » » » return float64(0), nil | |
| 439 » » case PTGeoPoint: | |
| 440 » » » return GeoPoint{}, nil | |
| 441 » » case PTKey: | |
| 442 » » » return nil, nil | |
| 443 » » case PTBlobKey: | |
| 444 » » » return blobstore.Key(""), nil | |
| 445 » » } | |
| 446 » » fallthrough | |
| 447 » default: | |
| 448 » » return nil, fmt.Errorf("unable to project %s to %s", p.propType, to) | |
| 449 » } | |
| 342 } | 450 } |
| 343 | 451 |
| 344 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to | 452 // MetaGetter is a subinterface of PropertyLoadSaver, but is also used to |
| 345 // abstract the meta argument for RawInterface.GetMulti. | 453 // abstract the meta argument for RawInterface.GetMulti. |
| 346 type MetaGetter interface { | 454 type MetaGetter interface { |
| 347 // GetMeta will get information about the field which has the struct tag in | 455 // GetMeta will get information about the field which has the struct tag in |
| 348 // the form of `gae:"$<key>[,<default>]?"`. | 456 // the form of `gae:"$<key>[,<default>]?"`. |
| 349 // | 457 // |
| 350 // Supported metadata types are: | 458 // Supported metadata types are: |
| 351 // int64 - may have default (ascii encoded base-10) | 459 // int64 - may have default (ascii encoded base-10) |
| (...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 515 dflt = UpconvertUnderlyingType(dflt) | 623 dflt = UpconvertUnderlyingType(dflt) |
| 516 cur, err := gm(key) | 624 cur, err := gm(key) |
| 517 if err != nil { | 625 if err != nil { |
| 518 return dflt | 626 return dflt |
| 519 } | 627 } |
| 520 if dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt) { | 628 if dflt != nil && reflect.TypeOf(cur) != reflect.TypeOf(dflt) { |
| 521 return dflt | 629 return dflt |
| 522 } | 630 } |
| 523 return cur | 631 return cur |
| 524 } | 632 } |
| OLD | NEW |