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
5package datastore
6
7import (
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
20func 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
27func fromUnixMicro(t int64) time.Time {
28	return time.Unix(t/1e6, (t%1e6)*1e3).UTC()
29}
30
31var (
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.
38func 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
114type saveOpts struct {
115	noIndex   bool
116	multiple  bool
117	omitEmpty bool
118}
119
120// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
121func 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
135func 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
188func (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
196func (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
225func 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.
310func 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}
334