Index: service/datastore/properties.go |
diff --git a/service/datastore/properties.go b/service/datastore/properties.go |
index bfdf6ef20ed05b820830df14cbbaf36f0084d0b8..cf5a82268e28f9fa3b1adc23a35c81915de92629 100644 |
--- a/service/datastore/properties.go |
+++ b/service/datastore/properties.go |
@@ -106,6 +106,23 @@ type PropertyConverter interface { |
// in a Property. The specific values of this type information are chosen so |
// that the types sort according to the order of types as sorted by the |
// datastore. |
+// |
+// Note that indexes may only contain values of the following types: |
+// PTNull |
+// PTInt |
+// PTBool |
+// PTFloat |
+// PTString |
+// PTGeoPoint |
+// PTKey |
+// |
+// The biggest impact of this is that if you do a Projection query, you'll only |
+// get back Properties with the above types (e.g. if you store a PTTime value, |
+// then Project on it, you'll get back a PTInt value). For convenience, Property |
+// has a Project(PropertyType) method which will side-cast to your intended |
+// type. If you project into a structure with the high-level Interface |
+// implementation, or use StructPLS, this conversion will be done for you |
+// automatically, using the type of the destination field to cast. |
type PropertyType byte |
// These constants are in the order described by |
@@ -120,36 +137,51 @@ type PropertyType byte |
// |
// See "./serialize".WriteProperty and "impl/memory".increment for more info. |
const ( |
+ // PTNull represents the 'nil' value. This is only directly visible when |
+ // reading/writing a PropertyMap. If a PTNull value is loaded into a struct |
+ // field, the field will be initialized with its zero value. If a struct with |
+ // a zero value is saved from a struct, it will still retain the field's type, |
+ // not the 'nil' type. This is in contrast to other GAE languages such as |
+ // python where 'None' is a distinct value than the 'zero' value (e.g. a |
+ // StringProperty can have the value "" OR None). |
+ // |
+ // PTNull is a Projection-query type |
PTNull PropertyType = iota |
- PTInt |
- // PTTime is a slight divergence from the way that datastore natively stores |
- // time. In datastore, times and integers actually sort together |
- // (apparently?). This is probably insane, and I don't want to add the |
- // complexity of field 'meaning' as a sparate concept from the field's 'type' |
- // (which is what datastore seems to do, judging from the protobufs). So if |
- // you're here because you implemented an app which relies on time.Time and |
- // int64 sorting together, then this is why your app acts differently in |
- // production. My advice is to NOT DO THAT. If you really want this (and you |
- // probably don't), you should take care of the time.Time <-> int64 conversion |
- // in your app and just use a property type of int64 (consider using |
- // PropertyConverter). |
iannucci
2015/09/10 03:56:58
no more divergence, impl/memory does this correctl
|
+ // PTInt is always an int64. |
+ // |
+ // This is a Projection-query type, and may be projected to PTTime. |
+ PTInt |
PTTime |
- // PTBoolFalse and True are also a slight divergence, but not a semantic |
- // one. IIUC, in datastore 'bool' is actually the type and the value is either |
- // 0 or 1 (taking another byte to store). Since we have plenty of space in |
- // this type byte, I just merge the value into the type for booleans. If this |
- // becomes problematic, consider changing this to just pvBool, and then |
- // encoding a 0 or 1 as a byte in the relevant marshalling routines. |
iannucci
2015/09/10 03:56:58
No more divergence, PTBool is the real type now.
|
- PTBoolFalse |
- PTBoolTrue |
- |
- PTBytes // []byte or datastore.ByteString |
- PTString // string or string noindex |
+ // PTBool represents true or false |
+ // |
+ // This is a Projection-query type. |
+ PTBool |
+ |
+ // PTBytes represents []byte |
+ PTBytes |
+ |
+ // PTString is used to represent all strings (text). |
+ // |
+ // PTString is a Projection-query type and may be projected to PTBytes or |
+ // PTBlobKey. |
+ PTString |
+ |
+ // PTFloat is always a float64. |
+ // |
+ // This is a Projection-query type. |
PTFloat |
+ |
+ // PTGeoPoint is a Projection-query type. |
PTGeoPoint |
+ |
+ // PTKey represents a Key object. |
+ // |
+ // PTKey is a Projection-query type. |
PTKey |
+ |
+ // PTBlobKey represents a blobstore.Key |
PTBlobKey |
// NOTE: THIS MUST BE LAST VALUE FOR THE init() ASSERTION BELOW TO WORK. |
@@ -174,10 +206,8 @@ func (t PropertyType) String() string { |
return "PTInt" |
case PTTime: |
return "PTTime" |
- case PTBoolFalse: |
- return "PTBoolFalse" |
- case PTBoolTrue: |
- return "PTBoolTrue" |
+ case PTBool: |
+ return "PTBool" |
case PTBytes: |
return "PTBytes" |
case PTString: |
@@ -207,11 +237,8 @@ func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) { |
case float64: |
return PTFloat, nil |
case bool: |
- if x { |
- return PTBoolTrue, nil |
- } |
- return PTBoolFalse, nil |
- case []byte, ByteString: |
+ return PTBool, nil |
+ case []byte: |
return PTBytes, nil |
case blobstore.Key: |
return PTBlobKey, nil |
@@ -272,7 +299,7 @@ func UpconvertUnderlyingType(o interface{}) interface{} { |
case reflect.Float32, reflect.Float64: |
o = v.Float() |
case reflect.Slice: |
- if t != typeOfByteString && t.Elem().Kind() == reflect.Uint8 { |
+ if t.Elem().Kind() == reflect.Uint8 { |
o = v.Bytes() |
} |
case reflect.Struct: |
@@ -302,15 +329,17 @@ func (p *Property) Type() PropertyType { return p.propType } |
// |
// value is the property value. The valid types are: |
// - int64 |
+// - time.Time |
// - bool |
// - string |
+// (only the first 1500 bytes is indexable) |
+// - []byte |
+// (only the first 1500 bytes is indexable) |
+// - blobstore.Key |
+// (only the first 1500 bytes is indexable) |
iannucci
2015/09/10 03:56:58
Surprise! These are all the same thing in the inde
|
// - float64 |
-// - ByteString |
// - Key |
-// - time.Time |
-// - blobstore.Key |
// - GeoPoint |
-// - []byte (up to 1 megabyte in length) |
// This set is smaller than the set of valid struct field types that the |
// datastore can load and save. A Property Value cannot be a slice (apart |
// from []byte); use multiple Properties instead. Also, a Value's type |
@@ -335,12 +364,91 @@ func (p *Property) SetValue(value interface{}, is IndexSetting) (err error) { |
p.propType = pt |
p.value = value |
p.indexSetting = is |
- if _, ok := value.([]byte); ok { |
- p.indexSetting = NoIndex |
- } |
return |
} |
+// ForIndex gets the Value of this Property, as if it were being stored in |
+// a datastore index. |
+func (p Property) ForIndex() Property { |
+ switch p.propType { |
+ case PTNull, PTInt, PTBool, PTString, PTFloat, PTGeoPoint, PTKey: |
+ return p |
+ |
+ case PTTime: |
+ v, _ := p.Project(PTInt) |
+ return Property{v, p.indexSetting, PTInt} |
+ |
+ case PTBytes, PTBlobKey: |
+ v, _ := p.Project(PTString) |
+ return Property{v, p.indexSetting, PTString} |
+ } |
+ panic(fmt.Errorf("unknown PropertyType: %s", p.propType)) |
+} |
+ |
+// Project can be used to project a Property retrieved from a Projection query |
+// into a different datatype. For example, if you have a PTInt property, you |
+// could Project(PTTime) to convert it to a time.Time. The following conversions |
+// are supported: |
+// PTInt <-> PTTime |
+// PTString <-> PTBlobKey |
+// PTString <-> PTBytes |
+// PTNull <-> Anything |
+func (p *Property) Project(to PropertyType) (interface{}, error) { |
+ switch { |
+ case to == p.propType: |
+ return p.value, nil |
+ |
+ case to == PTInt && p.propType == PTTime: |
+ t := p.value.(time.Time) |
+ v := uint64(t.Unix())*1e6 + uint64(t.Nanosecond()/1e3) |
+ return int64(v), nil |
+ |
+ case to == PTTime && p.propType == PTInt: |
+ v := p.value.(int64) |
+ return time.Unix(int64(v/1e6), int64((v%1e6)*1e3)).UTC(), nil |
+ |
+ case to == PTString && p.propType == PTBytes: |
+ return string(p.value.([]byte)), nil |
+ |
+ case to == PTString && p.propType == PTBlobKey: |
+ return string(p.value.(blobstore.Key)), nil |
+ |
+ case to == PTBytes && p.propType == PTString: |
+ return []byte(p.value.(string)), nil |
+ |
+ case to == PTBlobKey && p.propType == PTString: |
+ return blobstore.Key(p.value.(string)), nil |
+ |
+ case to == PTNull: |
+ return nil, nil |
+ |
+ case p.propType == PTNull: |
+ switch to { |
+ case PTInt: |
+ return int64(0), nil |
+ case PTTime: |
+ return time.Time{}, nil |
+ case PTBool: |
+ return false, nil |
+ case PTBytes: |
+ return []byte(nil), nil |
+ case PTString: |
+ return "", nil |
+ case PTFloat: |
+ return float64(0), nil |
+ case PTGeoPoint: |
+ return GeoPoint{}, nil |
+ case PTKey: |
+ return nil, nil |
+ case PTBlobKey: |
+ return blobstore.Key(""), nil |
+ } |
+ fallthrough |
+ default: |
+ return nil, fmt.Errorf("unable to project %s to %s", p.propType, to) |
+ } |
+} |
+ |
// MetaGetter is a subinterface of PropertyLoadSaver, but is also used to |
// abstract the meta argument for RawInterface.GetMulti. |
type MetaGetter interface { |