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 }