]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/google.golang.org/appengine/datastore/save.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / google.golang.org / appengine / datastore / save.go
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 }