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 310// standard library. 311func isEmptyValue(v reflect.Value) bool { 312 switch v.Kind() { 313 case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 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 return v.Uint() == 0 321 case reflect.Float32, reflect.Float64: 322 return v.Float() == 0 323 case reflect.Interface, reflect.Ptr: 324 return v.IsNil() 325 } 326 return false 327} 328