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 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 } |
OLD | NEW |