diff options
Diffstat (limited to 'vendor/google.golang.org/appengine/datastore/save.go')
-rw-r--r-- | vendor/google.golang.org/appengine/datastore/save.go | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/vendor/google.golang.org/appengine/datastore/save.go b/vendor/google.golang.org/appengine/datastore/save.go new file mode 100644 index 0000000..7b045a5 --- /dev/null +++ b/vendor/google.golang.org/appengine/datastore/save.go | |||
@@ -0,0 +1,333 @@ | |||
1 | // Copyright 2011 Google Inc. All rights reserved. | ||
2 | // Use of this source code is governed by the Apache 2.0 | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | package datastore | ||
6 | |||
7 | import ( | ||
8 | "errors" | ||
9 | "fmt" | ||
10 | "math" | ||
11 | "reflect" | ||
12 | "time" | ||
13 | |||
14 | "github.com/golang/protobuf/proto" | ||
15 | |||
16 | "google.golang.org/appengine" | ||
17 | pb "google.golang.org/appengine/internal/datastore" | ||
18 | ) | ||
19 | |||
20 | func toUnixMicro(t time.Time) int64 { | ||
21 | // We cannot use t.UnixNano() / 1e3 because we want to handle times more than | ||
22 | // 2^63 nanoseconds (which is about 292 years) away from 1970, and those cannot | ||
23 | // be represented in the numerator of a single int64 divide. | ||
24 | return t.Unix()*1e6 + int64(t.Nanosecond()/1e3) | ||
25 | } | ||
26 | |||
27 | func fromUnixMicro(t int64) time.Time { | ||
28 | return time.Unix(t/1e6, (t%1e6)*1e3).UTC() | ||
29 | } | ||
30 | |||
31 | var ( | ||
32 | minTime = time.Unix(int64(math.MinInt64)/1e6, (int64(math.MinInt64)%1e6)*1e3) | ||
33 | maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)*1e3) | ||
34 | ) | ||
35 | |||
36 | // valueToProto converts a named value to a newly allocated Property. | ||
37 | // The returned error string is empty on success. | ||
38 | func valueToProto(defaultAppID, name string, v reflect.Value, multiple bool) (p *pb.Property, errStr string) { | ||
39 | var ( | ||
40 | pv pb.PropertyValue | ||
41 | unsupported bool | ||
42 | ) | ||
43 | switch v.Kind() { | ||
44 | case reflect.Invalid: | ||
45 | // No-op. | ||
46 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
47 | pv.Int64Value = proto.Int64(v.Int()) | ||
48 | case reflect.Bool: | ||
49 | pv.BooleanValue = proto.Bool(v.Bool()) | ||
50 | case reflect.String: | ||
51 | pv.StringValue = proto.String(v.String()) | ||
52 | case reflect.Float32, reflect.Float64: | ||
53 | pv.DoubleValue = proto.Float64(v.Float()) | ||
54 | case reflect.Ptr: | ||
55 | if k, ok := v.Interface().(*Key); ok { | ||
56 | if k != nil { | ||
57 | pv.Referencevalue = keyToReferenceValue(defaultAppID, k) | ||
58 | } | ||
59 | } else { | ||
60 | unsupported = true | ||
61 | } | ||
62 | case reflect.Struct: | ||
63 | switch t := v.Interface().(type) { | ||
64 | case time.Time: | ||
65 | if t.Before(minTime) || t.After(maxTime) { | ||
66 | return nil, "time value out of range" | ||
67 | } | ||
68 | pv.Int64Value = proto.Int64(toUnixMicro(t)) | ||
69 | case appengine.GeoPoint: | ||
70 | if !t.Valid() { | ||
71 | return nil, "invalid GeoPoint value" | ||
72 | } | ||
73 | // NOTE: Strangely, latitude maps to X, longitude to Y. | ||
74 | pv.Pointvalue = &pb.PropertyValue_PointValue{X: &t.Lat, Y: &t.Lng} | ||
75 | default: | ||
76 | unsupported = true | ||
77 | } | ||
78 | case reflect.Slice: | ||
79 | if b, ok := v.Interface().([]byte); ok { | ||
80 | pv.StringValue = proto.String(string(b)) | ||
81 | } else { | ||
82 | // nvToProto should already catch slice values. | ||
83 | // If we get here, we have a slice of slice values. | ||
84 | unsupported = true | ||
85 | } | ||
86 | default: | ||
87 | unsupported = true | ||
88 | } | ||
89 | if unsupported { | ||
90 | return nil, "unsupported datastore value type: " + v.Type().String() | ||
91 | } | ||
92 | p = &pb.Property{ | ||
93 | Name: proto.String(name), | ||
94 | Value: &pv, | ||
95 | Multiple: proto.Bool(multiple), | ||
96 | } | ||
97 | if v.IsValid() { | ||
98 | switch v.Interface().(type) { | ||
99 | case []byte: | ||
100 | p.Meaning = pb.Property_BLOB.Enum() | ||
101 | case ByteString: | ||
102 | p.Meaning = pb.Property_BYTESTRING.Enum() | ||
103 | case appengine.BlobKey: | ||
104 | p.Meaning = pb.Property_BLOBKEY.Enum() | ||
105 | case time.Time: | ||
106 | p.Meaning = pb.Property_GD_WHEN.Enum() | ||
107 | case appengine.GeoPoint: | ||
108 | p.Meaning = pb.Property_GEORSS_POINT.Enum() | ||
109 | } | ||
110 | } | ||
111 | return p, "" | ||
112 | } | ||
113 | |||
114 | type saveOpts struct { | ||
115 | noIndex bool | ||
116 | multiple bool | ||
117 | omitEmpty bool | ||
118 | } | ||
119 | |||
120 | // saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer. | ||
121 | func saveEntity(defaultAppID string, key *Key, src interface{}) (*pb.EntityProto, error) { | ||
122 | var err error | ||
123 | var props []Property | ||
124 | if e, ok := src.(PropertyLoadSaver); ok { | ||
125 | props, err = e.Save() | ||
126 | } else { | ||
127 | props, err = SaveStruct(src) | ||
128 | } | ||
129 | if err != nil { | ||
130 | return nil, err | ||
131 | } | ||
132 | return propertiesToProto(defaultAppID, key, props) | ||
133 | } | ||
134 | |||
135 | func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect.Value) error { | ||
136 | if opts.omitEmpty && isEmptyValue(v) { | ||
137 | return nil | ||
138 | } | ||
139 | p := Property{ | ||
140 | Name: name, | ||
141 | NoIndex: opts.noIndex, | ||
142 | Multiple: opts.multiple, | ||
143 | } | ||
144 | switch x := v.Interface().(type) { | ||
145 | case *Key: | ||
146 | p.Value = x | ||
147 | case time.Time: | ||
148 | p.Value = x | ||
149 | case appengine.BlobKey: | ||
150 | p.Value = x | ||
151 | case appengine.GeoPoint: | ||
152 | p.Value = x | ||
153 | case ByteString: | ||
154 | p.Value = x | ||
155 | default: | ||
156 | switch v.Kind() { | ||
157 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
158 | p.Value = v.Int() | ||
159 | case reflect.Bool: | ||
160 | p.Value = v.Bool() | ||
161 | case reflect.String: | ||
162 | p.Value = v.String() | ||
163 | case reflect.Float32, reflect.Float64: | ||
164 | p.Value = v.Float() | ||
165 | case reflect.Slice: | ||
166 | if v.Type().Elem().Kind() == reflect.Uint8 { | ||
167 | p.NoIndex = true | ||
168 | p.Value = v.Bytes() | ||
169 | } | ||
170 | case reflect.Struct: | ||
171 | if !v.CanAddr() { | ||
172 | return fmt.Errorf("datastore: unsupported struct field: value is unaddressable") | ||
173 | } | ||
174 | sub, err := newStructPLS(v.Addr().Interface()) | ||
175 | if err != nil { | ||
176 | return fmt.Errorf("datastore: unsupported struct field: %v", err) | ||
177 | } | ||
178 | return sub.save(props, name+".", opts) | ||
179 | } | ||
180 | } | ||
181 | if p.Value == nil { | ||
182 | return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type()) | ||
183 | } | ||
184 | *props = append(*props, p) | ||
185 | return nil | ||
186 | } | ||
187 | |||
188 | func (s structPLS) Save() ([]Property, error) { | ||
189 | var props []Property | ||
190 | if err := s.save(&props, "", saveOpts{}); err != nil { | ||
191 | return nil, err | ||
192 | } | ||
193 | return props, nil | ||
194 | } | ||
195 | |||
196 | func (s structPLS) save(props *[]Property, prefix string, opts saveOpts) error { | ||
197 | for name, f := range s.codec.fields { | ||
198 | name = prefix + name | ||
199 | v := s.v.FieldByIndex(f.path) | ||
200 | if !v.IsValid() || !v.CanSet() { | ||
201 | continue | ||
202 | } | ||
203 | var opts1 saveOpts | ||
204 | opts1.noIndex = opts.noIndex || f.noIndex | ||
205 | opts1.multiple = opts.multiple | ||
206 | opts1.omitEmpty = f.omitEmpty // don't propagate | ||
207 | // For slice fields that aren't []byte, save each element. | ||
208 | if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 { | ||
209 | opts1.multiple = true | ||
210 | for j := 0; j < v.Len(); j++ { | ||
211 | if err := saveStructProperty(props, name, opts1, v.Index(j)); err != nil { | ||
212 | return err | ||
213 | } | ||
214 | } | ||
215 | continue | ||
216 | } | ||
217 | // Otherwise, save the field itself. | ||
218 | if err := saveStructProperty(props, name, opts1, v); err != nil { | ||
219 | return err | ||
220 | } | ||
221 | } | ||
222 | return nil | ||
223 | } | ||
224 | |||
225 | func propertiesToProto(defaultAppID string, key *Key, props []Property) (*pb.EntityProto, error) { | ||
226 | e := &pb.EntityProto{ | ||
227 | Key: keyToProto(defaultAppID, key), | ||
228 | } | ||
229 | if key.parent == nil { | ||
230 | e.EntityGroup = &pb.Path{} | ||
231 | } else { | ||
232 | e.EntityGroup = keyToProto(defaultAppID, key.root()).Path | ||
233 | } | ||
234 | prevMultiple := make(map[string]bool) | ||
235 | |||
236 | for _, p := range props { | ||
237 | if pm, ok := prevMultiple[p.Name]; ok { | ||
238 | if !pm || !p.Multiple { | ||
239 | return nil, fmt.Errorf("datastore: multiple Properties with Name %q, but Multiple is false", p.Name) | ||
240 | } | ||
241 | } else { | ||
242 | prevMultiple[p.Name] = p.Multiple | ||
243 | } | ||
244 | |||
245 | x := &pb.Property{ | ||
246 | Name: proto.String(p.Name), | ||
247 | Value: new(pb.PropertyValue), | ||
248 | Multiple: proto.Bool(p.Multiple), | ||
249 | } | ||
250 | switch v := p.Value.(type) { | ||
251 | case int64: | ||
252 | x.Value.Int64Value = proto.Int64(v) | ||
253 | case bool: | ||
254 | x.Value.BooleanValue = proto.Bool(v) | ||
255 | case string: | ||
256 | x.Value.StringValue = proto.String(v) | ||
257 | if p.NoIndex { | ||
258 | x.Meaning = pb.Property_TEXT.Enum() | ||
259 | } | ||
260 | case float64: | ||
261 | x.Value.DoubleValue = proto.Float64(v) | ||
262 | case *Key: | ||
263 | if v != nil { | ||
264 | x.Value.Referencevalue = keyToReferenceValue(defaultAppID, v) | ||
265 | } | ||
266 | case time.Time: | ||
267 | if v.Before(minTime) || v.After(maxTime) { | ||
268 | return nil, fmt.Errorf("datastore: time value out of range") | ||
269 | } | ||
270 | x.Value.Int64Value = proto.Int64(toUnixMicro(v)) | ||
271 | x.Meaning = pb.Property_GD_WHEN.Enum() | ||
272 | case appengine.BlobKey: | ||
273 | x.Value.StringValue = proto.String(string(v)) | ||
274 | x.Meaning = pb.Property_BLOBKEY.Enum() | ||
275 | case appengine.GeoPoint: | ||
276 | if !v.Valid() { | ||
277 | return nil, fmt.Errorf("datastore: invalid GeoPoint value") | ||
278 | } | ||
279 | // NOTE: Strangely, latitude maps to X, longitude to Y. | ||
280 | x.Value.Pointvalue = &pb.PropertyValue_PointValue{X: &v.Lat, Y: &v.Lng} | ||
281 | x.Meaning = pb.Property_GEORSS_POINT.Enum() | ||
282 | case []byte: | ||
283 | x.Value.StringValue = proto.String(string(v)) | ||
284 | x.Meaning = pb.Property_BLOB.Enum() | ||
285 | if !p.NoIndex { | ||
286 | return nil, fmt.Errorf("datastore: cannot index a []byte valued Property with Name %q", p.Name) | ||
287 | } | ||
288 | case ByteString: | ||
289 | x.Value.StringValue = proto.String(string(v)) | ||
290 | x.Meaning = pb.Property_BYTESTRING.Enum() | ||
291 | default: | ||
292 | if p.Value != nil { | ||
293 | return nil, fmt.Errorf("datastore: invalid Value type for a Property with Name %q", p.Name) | ||
294 | } | ||
295 | } | ||
296 | |||
297 | if p.NoIndex { | ||
298 | e.RawProperty = append(e.RawProperty, x) | ||
299 | } else { | ||
300 | e.Property = append(e.Property, x) | ||
301 | if len(e.Property) > maxIndexedProperties { | ||
302 | return nil, errors.New("datastore: too many indexed properties") | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | return e, nil | ||
307 | } | ||
308 | |||
309 | // isEmptyValue is taken from the encoding/json package in the standard library. | ||
310 | func isEmptyValue(v reflect.Value) bool { | ||
311 | switch v.Kind() { | ||
312 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: | ||
313 | // TODO(perfomance): Only reflect.String needed, other property types are not supported (copy/paste from json package) | ||
314 | return v.Len() == 0 | ||
315 | case reflect.Bool: | ||
316 | return !v.Bool() | ||
317 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
318 | return v.Int() == 0 | ||
319 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | ||
320 | // TODO(perfomance): Uint* are unsupported property types - should be removed (copy/paste from json package) | ||
321 | return v.Uint() == 0 | ||
322 | case reflect.Float32, reflect.Float64: | ||
323 | return v.Float() == 0 | ||
324 | case reflect.Interface, reflect.Ptr: | ||
325 | return v.IsNil() | ||
326 | case reflect.Struct: | ||
327 | switch x := v.Interface().(type) { | ||
328 | case time.Time: | ||
329 | return x.IsZero() | ||
330 | } | ||
331 | } | ||
332 | return false | ||
333 | } | ||