blob: 66a23c58531f47a59c80be5ab3ef8580976b5340 [file] [log] [blame]
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package datastore
import (
"errors"
"fmt"
"reflect"
"time"
timepb "github.com/golang/protobuf/ptypes/timestamp"
pb "google.golang.org/cloud/datastore/internal/proto"
)
// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
func saveEntity(key *Key, src interface{}) (*pb.Entity, error) {
var err error
var props []Property
if e, ok := src.(PropertyLoadSaver); ok {
props, err = e.Save()
} else {
props, err = SaveStruct(src)
}
if err != nil {
return nil, err
}
return propertiesToProto(key, props)
}
func saveStructProperty(props *[]Property, name string, noIndex, multiple bool, v reflect.Value) error {
p := Property{
Name: name,
NoIndex: noIndex,
Multiple: multiple,
}
switch x := v.Interface().(type) {
case *Key, time.Time:
p.Value = x
default:
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p.Value = v.Int()
case reflect.Bool:
p.Value = v.Bool()
case reflect.String:
p.Value = v.String()
case reflect.Float32, reflect.Float64:
p.Value = v.Float()
case reflect.Slice:
if v.Type().Elem().Kind() == reflect.Uint8 {
p.Value = v.Bytes()
}
case reflect.Struct:
if !v.CanAddr() {
return fmt.Errorf("datastore: unsupported struct field: value is unaddressable")
}
sub, err := newStructPLS(v.Addr().Interface())
if err != nil {
return fmt.Errorf("datastore: unsupported struct field: %v", err)
}
return sub.(structPLS).save(props, name, noIndex, multiple)
}
}
if p.Value == nil {
return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type())
}
*props = append(*props, p)
return nil
}
func (s structPLS) Save() ([]Property, error) {
var props []Property
if err := s.save(&props, "", false, false); err != nil {
return nil, err
}
return props, nil
}
func (s structPLS) save(props *[]Property, prefix string, noIndex, multiple bool) error {
for i, t := range s.codec.byIndex {
if t.name == "-" {
continue
}
name := t.name
if prefix != "" {
name = prefix + name
}
v := s.v.Field(i)
if !v.IsValid() || !v.CanSet() {
continue
}
noIndex1 := noIndex || t.noIndex
// For slice fields that aren't []byte, save each element.
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
for j := 0; j < v.Len(); j++ {
if err := saveStructProperty(props, name, noIndex1, true, v.Index(j)); err != nil {
return err
}
}
continue
}
// Otherwise, save the field itself.
if err := saveStructProperty(props, name, noIndex1, multiple, v); err != nil {
return err
}
}
return nil
}
func propertiesToProto(key *Key, props []Property) (*pb.Entity, error) {
e := &pb.Entity{
Key: keyToProto(key),
Properties: map[string]*pb.Value{},
}
indexedProps := 0
prevMultiple := make(map[string]*pb.Value)
for _, p := range props {
val, err := interfaceToProto(p.Value)
if err != nil {
return nil, fmt.Errorf("datastore: %v for a Property with Name %q", err, p.Name)
}
if !p.NoIndex {
rVal := reflect.ValueOf(p.Value)
if rVal.Kind() == reflect.Slice && rVal.Type().Elem().Kind() != reflect.Uint8 {
indexedProps += rVal.Len()
} else {
indexedProps++
}
}
if indexedProps > maxIndexedProperties {
return nil, errors.New("datastore: too many indexed properties")
}
switch v := p.Value.(type) {
case string:
if len(v) > 1500 && !p.NoIndex {
return nil, fmt.Errorf("datastore: Property with Name %q is too long to index", p.Name)
}
case []byte:
if len(v) > 1500 && !p.NoIndex {
return nil, fmt.Errorf("datastore: Property with Name %q is too long to index", p.Name)
}
}
val.ExcludeFromIndexes = p.NoIndex
if p.Multiple {
if varr, ok := prevMultiple[p.Name]; ok {
arr := varr.ValueType.(*pb.Value_ArrayValue).ArrayValue
arr.Values = append(arr.Values, val)
continue
}
val = &pb.Value{
ValueType: &pb.Value_ArrayValue{&pb.ArrayValue{
Values: []*pb.Value{val},
}},
}
prevMultiple[p.Name] = val
}
if _, ok := e.Properties[p.Name]; ok {
return nil, fmt.Errorf("datastore: duplicate Property with Name %q", p.Name)
}
e.Properties[p.Name] = val
}
return e, nil
}
func interfaceToProto(iv interface{}) (*pb.Value, error) {
val := new(pb.Value)
switch v := iv.(type) {
case int:
val.ValueType = &pb.Value_IntegerValue{int64(v)}
case int32:
val.ValueType = &pb.Value_IntegerValue{int64(v)}
case int64:
val.ValueType = &pb.Value_IntegerValue{v}
case bool:
val.ValueType = &pb.Value_BooleanValue{v}
case string:
val.ValueType = &pb.Value_StringValue{v}
case float32:
val.ValueType = &pb.Value_DoubleValue{float64(v)}
case float64:
val.ValueType = &pb.Value_DoubleValue{v}
case *Key:
if v != nil {
val.ValueType = &pb.Value_KeyValue{keyToProto(v)}
}
case time.Time:
if v.Before(minTime) || v.After(maxTime) {
return nil, errors.New("time value out of range")
}
val.ValueType = &pb.Value_TimestampValue{&timepb.Timestamp{
Seconds: v.Unix(),
Nanos: int32(v.Nanosecond()),
}}
case []byte:
val.ValueType = &pb.Value_BlobValue{v}
default:
if iv != nil {
return nil, fmt.Errorf("invalid Value type %t", iv)
}
}
// TODO(jbd): Support ListValue and EntityValue.
// TODO(jbd): Support types whose underlying type is one of the types above.
return val, nil
}