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

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

Issue 1243323002: Refactor a bit. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: fix golint Created 5 years, 4 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
« no previous file with comments | « service/rawdatastore/invertible_test.go ('k') | service/rawdatastore/properties_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 gae 5 package rawdatastore
6 6
7 import ( 7 import (
8 "errors" 8 "errors"
9 "fmt" 9 "fmt"
10 "math" 10 "math"
11 "reflect" 11 "reflect"
12 "time" 12 "time"
13
14 "github.com/luci/gae/service/blobstore"
13 ) 15 )
14 16
15 var ( 17 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) 18 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) 19 maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6) *1e3)
38 ) 20 )
39 21
22 // IndexSetting indicates whether or not a Property should be indexed by the
23 // datastore.
40 type IndexSetting bool 24 type IndexSetting bool
41 25
26 // ShouldIndex is the default, which is why it must assume the zero value,
27 // even though it's werid :(.
42 const ( 28 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 29 ShouldIndex IndexSetting = false
46 NoIndex IndexSetting = true 30 NoIndex IndexSetting = true
47 ) 31 )
48 32
49 func (i IndexSetting) String() string { 33 func (i IndexSetting) String() string {
50 if i { 34 if i {
51 return "NoIndex" 35 return "NoIndex"
52 } 36 }
53 return "ShouldIndex" 37 return "ShouldIndex"
54 } 38 }
55 39
56 // DSProperty is a value plus an indicator of whether the value should be 40 // Property is a value plus an indicator of whether the value should be
57 // indexed. Name and Multiple are stored in the DSPropertyMap object. 41 // indexed. Name and Multiple are stored in the PropertyMap object.
58 type DSProperty struct { 42 type Property struct {
59 value interface{} 43 value interface{}
60 indexSetting IndexSetting 44 indexSetting IndexSetting
61 » propType DSPropertyType 45 » propType PropertyType
62 } 46 }
63 47
64 // MkDSProperty makes a new indexed* DSProperty and returns it. If val is an 48 // MkProperty makes a new indexed* Property and returns it. If val is an
65 // invalid value, this panics (so don't do it). If you want to handle the error 49 // invalid value, this panics (so don't do it). If you want to handle the error
66 // normally, use SetValue(..., ShouldIndex) instead. 50 // normally, use SetValue(..., ShouldIndex) instead.
67 // 51 //
68 // *indexed if val is not an unindexable type like []byte. 52 // *indexed if val is not an unindexable type like []byte.
69 func MkDSProperty(val interface{}) DSProperty { 53 func MkProperty(val interface{}) Property {
70 » ret := DSProperty{} 54 » ret := Property{}
71 if err := ret.SetValue(val, ShouldIndex); err != nil { 55 if err := ret.SetValue(val, ShouldIndex); err != nil {
72 panic(err) 56 panic(err)
73 } 57 }
74 return ret 58 return ret
75 } 59 }
76 60
77 // MkDSPropertyNI makes a new DSProperty (with noindex set to true), and returns 61 // MkPropertyNI makes a new Property (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 62 // 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. 63 // handle the error normally, use SetValue(..., NoIndex) instead.
80 func MkDSPropertyNI(val interface{}) DSProperty { 64 func MkPropertyNI(val interface{}) Property {
81 » ret := DSProperty{} 65 » ret := Property{}
82 if err := ret.SetValue(val, NoIndex); err != nil { 66 if err := ret.SetValue(val, NoIndex); err != nil {
83 panic(err) 67 panic(err)
84 } 68 }
85 return ret 69 return ret
86 } 70 }
87 71
88 // DSPropertyConverter may be implemented by the pointer-to a struct field which 72 // PropertyConverter may be implemented by the pointer-to a struct field which
89 // is serialized by RawDatastore. Its ToDSProperty will be called on save, and 73 // is serialized by RawDatastore. Its ToProperty will be called on save, and
90 // it's FromDSProperty will be called on load (from datastore). The method may 74 // it's FromProperty will be called on load (from datastore). The method may
91 // do arbitrary computation, and if it encounters an error, may return it. This 75 // 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 76 // error will be a fatal error (as defined by PropertyLoadSaver) for the
93 // struct conversion. 77 // struct conversion.
94 // 78 //
95 // Example: 79 // Example:
96 // type Complex complex 80 // type Complex complex
97 // func (c *Complex) ToDSProperty() (ret DSProperty, err error) { 81 // func (c *Complex) ToProperty() (ret Property, err error) {
98 // // something like: 82 // // something like:
99 // err = ret.SetValue(fmt.Sprint(*c), true) 83 // err = ret.SetValue(fmt.Sprint(*c), true)
100 // return 84 // return
101 // } 85 // }
102 // func (c *Complex) FromDSProperty(p DSProperty) (err error) { 86 // func (c *Complex) FromProperty(p Property) (err error) {
103 // ... load *c from p ... 87 // ... load *c from p ...
104 // } 88 // }
105 // 89 //
106 // type MyStruct struct { 90 // type MyStruct struct {
107 // Complexity []Complex // acts like []complex, but can be serialized to DS 91 // Complexity []Complex // acts like []complex, but can be serialized to DS
108 // } 92 // }
109 type DSPropertyConverter interface { 93 type PropertyConverter interface {
110 // TODO(riannucci): Allow a convertable to return multiple values. This is 94 // TODO(riannucci): Allow a convertable to return multiple values. This is
111 // eminently doable (as long as the single-slice restriction is kept). It 95 // 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 96 // 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). 97 // a path in a struct (in the struct loading routine in helper).
114 98
115 » ToDSProperty() (DSProperty, error) 99 » ToProperty() (Property, error)
116 » FromDSProperty(DSProperty) error 100 » FromProperty(Property) error
117 } 101 }
118 102
119 // DSPropertyType is a single-byte representation of the type of data contained 103 // PropertyType 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 104 // in a Property. 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 105 // that the types sort according to the order of types as sorted by the
122 // datastore. 106 // datastore.
123 type DSPropertyType byte 107 type PropertyType byte
124 108
125 // These constants are in the order described by 109 // These constants are in the order described by
126 // https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type _ordering 110 // https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type _ordering
127 // with a slight divergence for the Int/Time split. 111 // 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 112 // NOTE: this enum can only occupy 7 bits, because we use the high bit to encode
129 // indexed/non-indexed. See typData.WriteBinary. 113 // indexed/non-indexed. See typData.WriteBinary.
130 const ( 114 const (
131 » DSPTNull DSPropertyType = iota 115 » PTNull PropertyType = iota
132 » DSPTInt 116 » PTInt
133 117
134 » // DSPTTime is a slight divergence from the way that datastore natively stores 118 » // PTTime is a slight divergence from the way that datastore natively st ores
135 // time. In datastore, times and integers actually sort together 119 // time. In datastore, times and integers actually sort together
136 // (apparently?). This is probably insane, and I don't want to add the 120 // (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' 121 // 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 122 // (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 123 // 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 124 // 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 125 // 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 126 // 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 127 // in your app and just use a property type of int64 (consider using
144 » // DSPropertyConverter). 128 » // PropertyConverter).
145 » DSPTTime 129 » PTTime
146 130
147 » // DSPTBoolFalse and True are also a slight divergence, but not a semant ic 131 » // PTBoolFalse and True are also a slight divergence, but not a semantic
148 // one. IIUC, in datastore 'bool' is actually the type and the value is either 132 // 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 133 // 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 134 // 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 135 // becomes problematic, consider changing this to just pvBool, and then
152 // encoding a 0 or 1 as a byte in the relevant marshalling routines. 136 // encoding a 0 or 1 as a byte in the relevant marshalling routines.
153 » DSPTBoolFalse 137 » PTBoolFalse
154 » DSPTBoolTrue 138 » PTBoolTrue
155 139
156 » DSPTBytes // []byte or datastore.ByteString 140 » PTBytes // []byte or datastore.ByteString
157 » DSPTString // string or string noindex 141 » PTString // string or string noindex
158 » DSPTFloat 142 » PTFloat
159 » DSPTGeoPoint 143 » PTGeoPoint
160 » DSPTKey 144 » PTKey
161 » DSPTBlobKey 145 » PTBlobKey
162 146
163 » DSPTUnknown 147 » PTUnknown
164 ) 148 )
165 149
166 func (t DSPropertyType) String() string { 150 func (t PropertyType) String() string {
167 switch t { 151 switch t {
168 » case DSPTNull: 152 » case PTNull:
169 » » return "DSPTNull" 153 » » return "PTNull"
170 » case DSPTInt: 154 » case PTInt:
171 » » return "DSPTInt" 155 » » return "PTInt"
172 » case DSPTTime: 156 » case PTTime:
173 » » return "DSPTTime" 157 » » return "PTTime"
174 » case DSPTBoolFalse: 158 » case PTBoolFalse:
175 » » return "DSPTBoolFalse" 159 » » return "PTBoolFalse"
176 » case DSPTBoolTrue: 160 » case PTBoolTrue:
177 » » return "DSPTBoolTrue" 161 » » return "PTBoolTrue"
178 » case DSPTBytes: 162 » case PTBytes:
179 » » return "DSPTBytes" 163 » » return "PTBytes"
180 » case DSPTString: 164 » case PTString:
181 » » return "DSPTString" 165 » » return "PTString"
182 » case DSPTFloat: 166 » case PTFloat:
183 » » return "DSPTFloat" 167 » » return "PTFloat"
184 » case DSPTGeoPoint: 168 » case PTGeoPoint:
185 » » return "DSPTGeoPoint" 169 » » return "PTGeoPoint"
186 » case DSPTKey: 170 » case PTKey:
187 » » return "DSPTKey" 171 » » return "PTKey"
188 » case DSPTBlobKey: 172 » case PTBlobKey:
189 » » return "DSPTBlobKey" 173 » » return "PTBlobKey"
190 default: 174 default:
191 » » return fmt.Sprintf("DSPTUnknown(%02x)", byte(t)) 175 » » return fmt.Sprintf("PTUnknown(%02x)", byte(t))
192 } 176 }
193 } 177 }
194 178
195 // DSPropertyTypeOf returns the DSPT* type of the given DSProperty-compatible 179 // PropertyTypeOf returns the PT* type of the given Property-compatible
196 // value v. If checkValid is true, this method will also ensure that time.Time 180 // value v. If checkValid is true, this method will also ensure that time.Time
197 // and DSGeoPoint have valid values. 181 // and GeoPoint have valid values.
198 func DSPropertyTypeOf(v interface{}, checkValid bool) (DSPropertyType, error) { 182 func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) {
199 switch x := v.(type) { 183 switch x := v.(type) {
200 case nil: 184 case nil:
201 » » return DSPTNull, nil 185 » » return PTNull, nil
202 case int64: 186 case int64:
203 » » return DSPTInt, nil 187 » » return PTInt, nil
204 case float64: 188 case float64:
205 » » return DSPTFloat, nil 189 » » return PTFloat, nil
206 case bool: 190 case bool:
207 if x { 191 if x {
208 » » » return DSPTBoolTrue, nil 192 » » » return PTBoolTrue, nil
209 } 193 }
210 » » return DSPTBoolFalse, nil 194 » » return PTBoolFalse, nil
211 » case []byte, DSByteString: 195 » case []byte, ByteString:
212 » » return DSPTBytes, nil 196 » » return PTBytes, nil
213 » case BSKey: 197 » case blobstore.Key:
214 » » return DSPTBlobKey, nil 198 » » return PTBlobKey, nil
215 case string: 199 case string:
216 » » return DSPTString, nil 200 » » return PTString, nil
217 » case DSKey: 201 » case Key:
218 // TODO(riannucci): Check key for validity in its own namespace? 202 // TODO(riannucci): Check key for validity in its own namespace?
219 » » return DSPTKey, nil 203 » » return PTKey, nil
220 case time.Time: 204 case time.Time:
221 err := error(nil) 205 err := error(nil)
222 if checkValid && (x.Before(minTime) || x.After(maxTime)) { 206 if checkValid && (x.Before(minTime) || x.After(maxTime)) {
223 err = errors.New("time value out of range") 207 err = errors.New("time value out of range")
224 } 208 }
225 if checkValid && x.Location() != time.UTC { 209 if checkValid && x.Location() != time.UTC {
226 err = fmt.Errorf("time value has wrong Location: %s", x. Location()) 210 err = fmt.Errorf("time value has wrong Location: %s", x. Location())
227 } 211 }
228 » » return DSPTTime, err 212 » » return PTTime, err
229 » case DSGeoPoint: 213 » case GeoPoint:
230 err := error(nil) 214 err := error(nil)
231 if checkValid && !x.Valid() { 215 if checkValid && !x.Valid() {
232 err = errors.New("invalid GeoPoint value") 216 err = errors.New("invalid GeoPoint value")
233 } 217 }
234 » » return DSPTGeoPoint, err 218 » » return PTGeoPoint, err
235 default: 219 default:
236 » » return DSPTUnknown, fmt.Errorf("gae: DSProperty has bad type %T" , v) 220 » » return PTUnknown, fmt.Errorf("gae: Property has bad type %T", v)
237 } 221 }
238 } 222 }
239 223
240 // DSUpconvertUnderlyingType takes an object o, and attempts to convert it to 224 // UpconvertUnderlyingType takes an object o, and attempts to convert it to
241 // its native datastore-compatible type. e.g. int16 will convert to int64, and 225 // its native datastore-compatible type. e.g. int16 will convert to int64, and
242 // `type Foo string` will convert to `string`. 226 // `type Foo string` will convert to `string`.
243 func DSUpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, refl ect.Type) { 227 func UpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, reflec t.Type) {
244 v := reflect.ValueOf(o) 228 v := reflect.ValueOf(o)
245 switch t.Kind() { 229 switch t.Kind() {
246 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.In t64: 230 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.In t64:
247 o = v.Int() 231 o = v.Int()
248 t = typeOfInt64 232 t = typeOfInt64
249 case reflect.Bool: 233 case reflect.Bool:
250 o = v.Bool() 234 o = v.Bool()
251 t = typeOfBool 235 t = typeOfBool
252 case reflect.String: 236 case reflect.String:
253 if t != typeOfBSKey { 237 if t != typeOfBSKey {
254 o = v.String() 238 o = v.String()
255 t = typeOfString 239 t = typeOfString
256 } 240 }
257 case reflect.Float32, reflect.Float64: 241 case reflect.Float32, reflect.Float64:
258 o = v.Float() 242 o = v.Float()
259 t = typeOfFloat64 243 t = typeOfFloat64
260 case reflect.Slice: 244 case reflect.Slice:
261 » » if t != typeOfDSByteString && t.Elem().Kind() == reflect.Uint8 { 245 » » if t != typeOfByteString && t.Elem().Kind() == reflect.Uint8 {
262 o = v.Bytes() 246 o = v.Bytes()
263 t = typeOfByteSlice 247 t = typeOfByteSlice
264 } 248 }
265 case reflect.Struct: 249 case reflect.Struct:
266 if t == typeOfTime { 250 if t == typeOfTime {
267 » » » // time in a DSProperty can only hold microseconds 251 » » » // time in a Property can only hold microseconds
268 o = v.Interface().(time.Time).Round(time.Microsecond) 252 o = v.Interface().(time.Time).Round(time.Microsecond)
269 } 253 }
270 } 254 }
271 return o, t 255 return o, t
272 } 256 }
273 257
274 // Value returns the current value held by this property. It's guaranteed to 258 // 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 259 // be a valid value type (i.e. `p.SetValue(p.Value(), true)` will never return
276 // an error). 260 // an error).
277 func (p DSProperty) Value() interface{} { return p.value } 261 func (p Property) Value() interface{} { return p.value }
278 262
279 // IndexSetting says weather or not the datastore should create indicies for 263 // IndexSetting says weather or not the datastore should create indicies for
280 // this value. 264 // this value.
281 func (p DSProperty) IndexSetting() IndexSetting { return p.indexSetting } 265 func (p Property) IndexSetting() IndexSetting { return p.indexSetting }
282 266
283 // Type is the DSPT* type of the data contained in Value(). 267 // Type is the PT* type of the data contained in Value().
284 func (p DSProperty) Type() DSPropertyType { return p.propType } 268 func (p Property) Type() PropertyType { return p.propType }
285 269
286 // SetValue sets the Value field of a DSProperty, and ensures that its value 270 // SetValue sets the Value field of a Property, and ensures that its value
287 // conforms to the permissible types. That way, you're guaranteed that if you 271 // conforms to the permissible types. That way, you're guaranteed that if you
288 // have a DSProperty, its value is valid. 272 // have a Property, its value is valid.
289 // 273 //
290 // value is the property value. The valid types are: 274 // value is the property value. The valid types are:
291 // - int64 275 // - int64
292 // - bool 276 // - bool
293 // - string 277 // - string
294 // - float64 278 // - float64
295 //» - DSByteString 279 //» - ByteString
296 //» - DSKey 280 //» - Key
297 // - time.Time 281 // - time.Time
298 //» - BSKey 282 //» - blobstore.Key
299 //» - DSGeoPoint 283 //» - GeoPoint
300 // - []byte (up to 1 megabyte in length) 284 // - []byte (up to 1 megabyte in length)
301 // This set is smaller than the set of valid struct field types that the 285 // 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 286 // 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 287 // 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 288 // 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 289 // 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 290 // myInt64 int64" is invalid. Smaller-width integers and floats are also
307 // invalid. Again, this is more restrictive than the set of valid struct 291 // invalid. Again, this is more restrictive than the set of valid struct
308 // field types. 292 // field types.
309 // 293 //
310 // A value may also be the nil interface value; this is equivalent to 294 // 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 295 // 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 296 // a nil-valued property into a struct will set that field to the zero
313 // value. 297 // value.
314 func (p *DSProperty) SetValue(value interface{}, is IndexSetting) (err error) { 298 func (p *Property) SetValue(value interface{}, is IndexSetting) (err error) {
315 t := reflect.Type(nil) 299 t := reflect.Type(nil)
316 » pt := DSPTNull 300 » pt := PTNull
317 if value != nil { 301 if value != nil {
318 t = reflect.TypeOf(value) 302 t = reflect.TypeOf(value)
319 » » value, t = DSUpconvertUnderlyingType(value, t) 303 » » value, t = UpconvertUnderlyingType(value, t)
320 » » if pt, err = DSPropertyTypeOf(value, true); err != nil { 304 » » if pt, err = PropertyTypeOf(value, true); err != nil {
321 return 305 return
322 } 306 }
323 } 307 }
324 p.propType = pt 308 p.propType = pt
325 p.value = value 309 p.value = value
326 p.indexSetting = is 310 p.indexSetting = is
327 if t == typeOfByteSlice { 311 if t == typeOfByteSlice {
328 p.indexSetting = NoIndex 312 p.indexSetting = NoIndex
329 } 313 }
330 return 314 return
331 } 315 }
332 316
333 // DSPropertyLoadSaver may be implemented by a user type, and RawDatastore will 317 // PropertyLoadSaver may be implemented by a user type, and RawDatastore will
334 // use this interface to serialize the type instead of trying to automatically 318 // use this interface to serialize the type instead of trying to automatically
335 // create a serialization codec for it with helper.GetPLS. 319 // create a serialization codec for it with helper.GetPLS.
336 type DSPropertyLoadSaver interface { 320 type PropertyLoadSaver interface {
337 // Load takes the values from the given map and attempts to save them in to 321 // 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 322 » // the underlying object (usually a struct or a PropertyMap). If a fatal
339 // error occurs, it's returned via error. If non-fatal conversion errors 323 // 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 324 » // occur, error will be a MultiError containing one or more ErrFieldMism atch
341 // objects. 325 // objects.
342 » Load(DSPropertyMap) error 326 » Load(PropertyMap) error
343 327
344 » // Save returns the current property as a DSPropertyMap. if withMeta is true, 328 » // Save returns the current property as a PropertyMap. if withMeta is tr ue,
345 » // then the DSPropertyMap contains all the metadata (e.g. '$meta' fields ) 329 » // then the PropertyMap contains all the metadata (e.g. '$meta' fields)
346 » // which was held by this DSPropertyLoadSaver. 330 » // which was held by this PropertyLoadSaver.
347 » Save(withMeta bool) (DSPropertyMap, error) 331 » Save(withMeta bool) (PropertyMap, error)
348 332
349 // GetMeta will get information about the field which has the struct tag in 333 // GetMeta will get information about the field which has the struct tag in
350 // the form of `gae:"$<key>[,<value>]?"`. 334 // the form of `gae:"$<key>[,<value>]?"`.
351 // 335 //
352 // string and int64 fields will return the <value> in the struct tag, 336 // 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. 337 // converted to the appropriate type, if the field has the zero value.
354 // 338 //
355 // Example: 339 // Example:
356 // type MyStruct struct { 340 // type MyStruct struct {
357 // CoolField int64 `gae:"$id,1"` 341 // CoolField int64 `gae:"$id,1"`
358 // } 342 // }
359 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id") 343 // val, err := helper.GetPLS(&MyStruct{}).GetMeta("id")
360 // // val == 1 344 // // val == 1
361 // // err == nil 345 // // err == nil
362 // 346 //
363 // val, err := helper.GetPLS(&MyStruct{10}).GetMeta("id") 347 // val, err := helper.GetPLS(&MyStruct{10}).GetMeta("id")
364 // // val == 10 348 // // val == 10
365 // // err == nil 349 // // err == nil
366 GetMeta(key string) (interface{}, error) 350 GetMeta(key string) (interface{}, error)
367 351
368 // SetMeta allows you to set the current value of the meta-keyed field. 352 // SetMeta allows you to set the current value of the meta-keyed field.
369 SetMeta(key string, val interface{}) error 353 SetMeta(key string, val interface{}) error
370 354
371 // Problem indicates that this PLS has a fatal problem. Usually this is 355 // 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 356 // set when the underlying struct has recursion, invalid field types, ne sted
373 // slices, etc. 357 // slices, etc.
374 Problem() error 358 Problem() error
375 } 359 }
376 360
377 // DSPropertyMap represents the contents of a datastore entity in a generic way. 361 // PropertyMap 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 362 // 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 363 // that property name. It is the spiritual successor to PropertyList from the
380 // original SDK. 364 // original SDK.
381 // 365 //
382 // DSPropertyMap may contain "meta" values, which are keyed with a '$' prefix. 366 // PropertyMap may contain "meta" values, which are keyed with a '$' prefix.
383 // Technically the datastore allows arbitrary property names, but all of the 367 // 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 368 // 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... 369 // language tokens. Special values must correspond to a single Property...
386 // corresponding to 0 is equivalent to unset, and corresponding to >1 is an 370 // corresponding to 0 is equivalent to unset, and corresponding to >1 is an
387 // error. So: 371 // error. So:
388 // 372 //
389 // { 373 // {
390 // "$id": {MkDSProperty(1)}, // GetProperty("id") -> 1, nil 374 // "$id": {MkProperty(1)}, // GetProperty("id") -> 1, nil
391 // "$foo": {}, // GetProperty("foo") -> nil, ErrDSMetaFieldUnset 375 // "$foo": {}, // GetProperty("foo") -> nil, ErrMetaFieldUnset
392 // // GetProperty("bar") -> nil, ErrDSMetaFieldUnset 376 // // GetProperty("bar") -> nil, ErrMetaFieldUnset
393 // "$meep": { 377 // "$meep": {
394 // MkDSProperty("hi"), 378 // MkProperty("hi"),
395 // MkDSProperty("there")}, // GetProperty("meep") -> nil, error! 379 // MkProperty("there")}, // GetProperty("meep") -> nil, error!
396 // } 380 // }
397 // 381 //
398 // Additionally, Save returns a copy of the map with the meta keys omitted (e.g. 382 // 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). 383 // these keys are not going to be serialized to the datastore).
400 type DSPropertyMap map[string][]DSProperty 384 type PropertyMap map[string][]Property
401 385
402 var _ DSPropertyLoadSaver = DSPropertyMap(nil) 386 var _ PropertyLoadSaver = PropertyMap(nil)
403 387
404 // Load implements DSPropertyLoadSaver.Load 388 // Load implements PropertyLoadSaver.Load
405 func (pm DSPropertyMap) Load(props DSPropertyMap) error { 389 func (pm PropertyMap) Load(props PropertyMap) error {
406 for k, v := range props { 390 for k, v := range props {
407 pm[k] = append(pm[k], v...) 391 pm[k] = append(pm[k], v...)
408 } 392 }
409 return nil 393 return nil
410 } 394 }
411 395
412 // Save implements DSPropertyLoadSaver.Save by returning a copy of the 396 // Save implements PropertyLoadSaver.Save by returning a copy of the
413 // current map data. 397 // current map data.
414 func (pm DSPropertyMap) Save(withMeta bool) (DSPropertyMap, error) { 398 func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) {
415 if len(pm) == 0 { 399 if len(pm) == 0 {
416 » » return DSPropertyMap{}, nil 400 » » return PropertyMap{}, nil
417 } 401 }
418 » ret := make(DSPropertyMap, len(pm)) 402 » ret := make(PropertyMap, len(pm))
419 for k, v := range pm { 403 for k, v := range pm {
420 if withMeta || len(k) == 0 || k[0] != '$' { 404 if withMeta || len(k) == 0 || k[0] != '$' {
421 ret[k] = append(ret[k], v...) 405 ret[k] = append(ret[k], v...)
422 } 406 }
423 } 407 }
424 return ret, nil 408 return ret, nil
425 } 409 }
426 410
427 func (pm DSPropertyMap) GetMeta(key string) (interface{}, error) { 411 // GetMeta implements PropertyLoadSaver.GetMeta, and returns the current value
412 // associated with the metadata key. It may return ErrMetaFieldUnset if the
413 // key doesn't exist.
414 func (pm PropertyMap) GetMeta(key string) (interface{}, error) {
428 v, ok := pm["$"+key] 415 v, ok := pm["$"+key]
429 if !ok || len(v) == 0 { 416 if !ok || len(v) == 0 {
430 » » return nil, ErrDSMetaFieldUnset 417 » » return nil, ErrMetaFieldUnset
431 } 418 }
432 if len(v) > 1 { 419 if len(v) > 1 {
433 return nil, errors.New("gae: too many values for Meta key") 420 return nil, errors.New("gae: too many values for Meta key")
434 } 421 }
435 return v[0].Value(), nil 422 return v[0].Value(), nil
436 } 423 }
437 424
438 func (pm DSPropertyMap) SetMeta(key string, val interface{}) error { 425 // SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error
439 » prop := DSProperty{} 426 // if `val` has an invalid type (e.g. not one supported by Property).
427 func (pm PropertyMap) SetMeta(key string, val interface{}) error {
428 » prop := Property{}
440 if err := prop.SetValue(val, NoIndex); err != nil { 429 if err := prop.SetValue(val, NoIndex); err != nil {
441 return err 430 return err
442 } 431 }
443 » pm["$"+key] = []DSProperty{prop} 432 » pm["$"+key] = []Property{prop}
444 return nil 433 return nil
445 } 434 }
446 435
447 func (pm DSPropertyMap) Problem() error { 436 // Problem implements PropertyLoadSaver.Problem. It ALWAYS returns nil.
437 func (pm PropertyMap) Problem() error {
448 return nil 438 return nil
449 } 439 }
OLDNEW
« no previous file with comments | « service/rawdatastore/invertible_test.go ('k') | service/rawdatastore/properties_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698