Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(234)

Side by Side Diff: service/datastore/properties.go

Issue 1336443003: Implement projection queries correctly. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: fix doc Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698