github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/google.golang.org/appengine/datastore/save.go (about)

     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)
    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  // saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
   115  func saveEntity(defaultAppID string, key *Key, src interface{}) (*pb.EntityProto, error) {
   116  	var err error
   117  	var props []Property
   118  	if e, ok := src.(PropertyLoadSaver); ok {
   119  		props, err = e.Save()
   120  	} else {
   121  		props, err = SaveStruct(src)
   122  	}
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	return propertiesToProto(defaultAppID, key, props)
   127  }
   128  
   129  func saveStructProperty(props *[]Property, name string, noIndex, multiple bool, v reflect.Value) error {
   130  	p := Property{
   131  		Name:     name,
   132  		NoIndex:  noIndex,
   133  		Multiple: multiple,
   134  	}
   135  	switch x := v.Interface().(type) {
   136  	case *Key:
   137  		p.Value = x
   138  	case time.Time:
   139  		p.Value = x
   140  	case appengine.BlobKey:
   141  		p.Value = x
   142  	case appengine.GeoPoint:
   143  		p.Value = x
   144  	case ByteString:
   145  		p.Value = x
   146  	default:
   147  		switch v.Kind() {
   148  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   149  			p.Value = v.Int()
   150  		case reflect.Bool:
   151  			p.Value = v.Bool()
   152  		case reflect.String:
   153  			p.Value = v.String()
   154  		case reflect.Float32, reflect.Float64:
   155  			p.Value = v.Float()
   156  		case reflect.Slice:
   157  			if v.Type().Elem().Kind() == reflect.Uint8 {
   158  				p.NoIndex = true
   159  				p.Value = v.Bytes()
   160  			}
   161  		case reflect.Struct:
   162  			if !v.CanAddr() {
   163  				return fmt.Errorf("datastore: unsupported struct field: value is unaddressable")
   164  			}
   165  			sub, err := newStructPLS(v.Addr().Interface())
   166  			if err != nil {
   167  				return fmt.Errorf("datastore: unsupported struct field: %v", err)
   168  			}
   169  			return sub.(structPLS).save(props, name, noIndex, multiple)
   170  		}
   171  	}
   172  	if p.Value == nil {
   173  		return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type())
   174  	}
   175  	*props = append(*props, p)
   176  	return nil
   177  }
   178  
   179  func (s structPLS) Save() ([]Property, error) {
   180  	var props []Property
   181  	if err := s.save(&props, "", false, false); err != nil {
   182  		return nil, err
   183  	}
   184  	return props, nil
   185  }
   186  
   187  func (s structPLS) save(props *[]Property, prefix string, noIndex, multiple bool) error {
   188  	for i, t := range s.codec.byIndex {
   189  		if t.name == "-" {
   190  			continue
   191  		}
   192  		name := t.name
   193  		if prefix != "" {
   194  			name = prefix + name
   195  		}
   196  		v := s.v.Field(i)
   197  		if !v.IsValid() || !v.CanSet() {
   198  			continue
   199  		}
   200  		noIndex1 := noIndex || t.noIndex
   201  		// For slice fields that aren't []byte, save each element.
   202  		if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
   203  			for j := 0; j < v.Len(); j++ {
   204  				if err := saveStructProperty(props, name, noIndex1, true, v.Index(j)); err != nil {
   205  					return err
   206  				}
   207  			}
   208  			continue
   209  		}
   210  		// Otherwise, save the field itself.
   211  		if err := saveStructProperty(props, name, noIndex1, multiple, v); err != nil {
   212  			return err
   213  		}
   214  	}
   215  	return nil
   216  }
   217  
   218  func propertiesToProto(defaultAppID string, key *Key, props []Property) (*pb.EntityProto, error) {
   219  	e := &pb.EntityProto{
   220  		Key: keyToProto(defaultAppID, key),
   221  	}
   222  	if key.parent == nil {
   223  		e.EntityGroup = &pb.Path{}
   224  	} else {
   225  		e.EntityGroup = keyToProto(defaultAppID, key.root()).Path
   226  	}
   227  	prevMultiple := make(map[string]bool)
   228  
   229  	for _, p := range props {
   230  		if pm, ok := prevMultiple[p.Name]; ok {
   231  			if !pm || !p.Multiple {
   232  				return nil, fmt.Errorf("datastore: multiple Properties with Name %q, but Multiple is false", p.Name)
   233  			}
   234  		} else {
   235  			prevMultiple[p.Name] = p.Multiple
   236  		}
   237  
   238  		x := &pb.Property{
   239  			Name:     proto.String(p.Name),
   240  			Value:    new(pb.PropertyValue),
   241  			Multiple: proto.Bool(p.Multiple),
   242  		}
   243  		switch v := p.Value.(type) {
   244  		case int64:
   245  			x.Value.Int64Value = proto.Int64(v)
   246  		case bool:
   247  			x.Value.BooleanValue = proto.Bool(v)
   248  		case string:
   249  			x.Value.StringValue = proto.String(v)
   250  			if p.NoIndex {
   251  				x.Meaning = pb.Property_TEXT.Enum()
   252  			}
   253  		case float64:
   254  			x.Value.DoubleValue = proto.Float64(v)
   255  		case *Key:
   256  			if v != nil {
   257  				x.Value.Referencevalue = keyToReferenceValue(defaultAppID, v)
   258  			}
   259  		case time.Time:
   260  			if v.Before(minTime) || v.After(maxTime) {
   261  				return nil, fmt.Errorf("datastore: time value out of range")
   262  			}
   263  			x.Value.Int64Value = proto.Int64(toUnixMicro(v))
   264  			x.Meaning = pb.Property_GD_WHEN.Enum()
   265  		case appengine.BlobKey:
   266  			x.Value.StringValue = proto.String(string(v))
   267  			x.Meaning = pb.Property_BLOBKEY.Enum()
   268  		case appengine.GeoPoint:
   269  			if !v.Valid() {
   270  				return nil, fmt.Errorf("datastore: invalid GeoPoint value")
   271  			}
   272  			// NOTE: Strangely, latitude maps to X, longitude to Y.
   273  			x.Value.Pointvalue = &pb.PropertyValue_PointValue{X: &v.Lat, Y: &v.Lng}
   274  			x.Meaning = pb.Property_GEORSS_POINT.Enum()
   275  		case []byte:
   276  			x.Value.StringValue = proto.String(string(v))
   277  			x.Meaning = pb.Property_BLOB.Enum()
   278  			if !p.NoIndex {
   279  				return nil, fmt.Errorf("datastore: cannot index a []byte valued Property with Name %q", p.Name)
   280  			}
   281  		case ByteString:
   282  			x.Value.StringValue = proto.String(string(v))
   283  			x.Meaning = pb.Property_BYTESTRING.Enum()
   284  		default:
   285  			if p.Value != nil {
   286  				return nil, fmt.Errorf("datastore: invalid Value type for a Property with Name %q", p.Name)
   287  			}
   288  		}
   289  
   290  		if p.NoIndex {
   291  			e.RawProperty = append(e.RawProperty, x)
   292  		} else {
   293  			e.Property = append(e.Property, x)
   294  			if len(e.Property) > maxIndexedProperties {
   295  				return nil, errors.New("datastore: too many indexed properties")
   296  			}
   297  		}
   298  	}
   299  	return e, nil
   300  }