Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package memory | |
| 6 | |
| 7 import ( | |
| 8 "appengine" | |
| 9 "bytes" | |
| 10 "fmt" | |
| 11 "reflect" | |
| 12 "time" | |
| 13 | |
| 14 "appengine/datastore" | |
| 15 | |
| 16 "github.com/luci/luci-go/common/funnybase" | |
| 17 ) | |
| 18 | |
| 19 type typData struct { | |
| 20 typ propValType | |
| 21 data interface{} | |
| 22 } | |
| 23 | |
| 24 func newTypData(v interface{}) (ret *typData, err error) { | |
| 25 typ := pvUNKNOWN | |
| 26 | |
| 27 switch x := v.(type) { | |
| 28 case nil: | |
| 29 typ = pvNull | |
| 30 case time.Time: | |
| 31 // toMicro return t.Unix()*1e6 + int64(t.Nanosecond()/1e3) | |
| 32 // fromMicro return time.Unix(t/1e6, (t%1e6)*1e3) | |
| 33 typ = pvTime | |
| 34 case int, int8, int16, int32, int64: | |
| 35 typ = pvInt | |
| 36 case float32, float64: | |
| 37 typ = pvFloat | |
| 38 case bool: | |
| 39 if x { | |
| 40 typ = pvBoolTrue | |
| 41 } else { | |
| 42 typ = pvBoolFalse | |
| 43 } | |
| 44 case []byte, datastore.ByteString: | |
| 45 typ = pvBytes | |
| 46 case appengine.BlobKey: | |
| 47 typ = pvBlobKey | |
| 48 case string: | |
| 49 typ = pvStr | |
| 50 case appengine.GeoPoint: | |
| 51 typ = pvGeoPoint | |
| 52 case *datastore.Key: | |
| 53 typ = pvKey | |
| 54 } | |
| 55 if typ == pvUNKNOWN { | |
| 56 err = fmt.Errorf("propValTypeOf: unknown type of %v", v) | |
|
M-A Ruel
2015/05/25 18:21:10
%#v
iannucci
2015/05/27 19:33:32
done
| |
| 57 } | |
| 58 | |
| 59 return &typData{typ, v}, err | |
| 60 } | |
| 61 | |
| 62 func (td *typData) WriteBinary(buf *bytes.Buffer) error { | |
| 63 buf.WriteByte(byte(td.typ)) | |
| 64 switch td.typ { | |
| 65 case pvNull, pvBoolFalse, pvBoolTrue: | |
| 66 return nil | |
| 67 case pvInt: | |
| 68 funnybase.Write(buf, reflect.ValueOf(td.data).Int()) | |
| 69 case pvFloat: | |
| 70 writeFloat64(buf, reflect.ValueOf(td.data).Float()) | |
| 71 case pvStr: | |
| 72 writeString(buf, td.data.(string)) | |
| 73 case pvBytes: | |
| 74 // []byte or datastore.ByteString | |
| 75 b := reflect.ValueOf(td.data).Convert(byteSliceType).Interface() .([]byte) | |
| 76 writeBytes(buf, b) | |
| 77 case pvTime: | |
| 78 t := td.data.(time.Time) | |
| 79 funnybase.WriteUint(buf, uint64(t.Unix())*1e6+uint64(t.Nanosecon d()/1e3)) | |
| 80 case pvGeoPoint: | |
| 81 t := td.data.(appengine.GeoPoint) | |
| 82 writeFloat64(buf, t.Lat) | |
| 83 writeFloat64(buf, t.Lng) | |
| 84 case pvKey: | |
| 85 writeKey(buf, withNS, td.data.(*datastore.Key)) | |
| 86 case pvBlobKey: | |
| 87 writeString(buf, string(td.data.(appengine.BlobKey))) | |
| 88 default: | |
| 89 return fmt.Errorf("write: unknown type! %v", td) | |
| 90 } | |
| 91 return nil | |
| 92 } | |
| 93 | |
| 94 func (td *typData) ReadBinary(buf *bytes.Buffer, indexed bool) error { | |
| 95 typb, err := buf.ReadByte() | |
| 96 if err != nil { | |
| 97 return err | |
| 98 } | |
| 99 td.typ = propValType(typb) | |
| 100 switch td.typ { | |
| 101 case pvNull: | |
| 102 td.data = nil | |
| 103 case pvBoolTrue: | |
| 104 td.data = true | |
| 105 case pvBoolFalse: | |
| 106 td.data = false | |
| 107 case pvInt: | |
| 108 v, err := funnybase.Read(buf) | |
| 109 if err != nil { | |
| 110 return err | |
| 111 } | |
| 112 td.data = v | |
| 113 case pvFloat: | |
| 114 td.data, err = readFloat64(buf) | |
| 115 if err != nil { | |
| 116 return err | |
| 117 } | |
| 118 case pvStr: | |
| 119 td.data, err = readString(buf) | |
| 120 if err != nil { | |
| 121 return err | |
| 122 } | |
| 123 case pvBytes: | |
| 124 b, err := readBytes(buf) | |
| 125 if err != nil { | |
| 126 return err | |
| 127 } | |
| 128 | |
| 129 if indexed { | |
| 130 td.data = datastore.ByteString(b) | |
| 131 } else { | |
| 132 td.data = b | |
| 133 } | |
| 134 case pvTime: | |
| 135 v, err := funnybase.ReadUint(buf) | |
| 136 if err != nil { | |
| 137 return err | |
| 138 } | |
| 139 td.data = time.Unix(int64(v/1e6), int64((v%1e6)*1e3)) | |
| 140 case pvGeoPoint: | |
| 141 pt := appengine.GeoPoint{} | |
| 142 pt.Lat, err = readFloat64(buf) | |
| 143 if err != nil { | |
| 144 return err | |
| 145 } | |
| 146 pt.Lng, err = readFloat64(buf) | |
| 147 if err != nil { | |
| 148 return err | |
| 149 } | |
| 150 td.data = pt | |
| 151 case pvKey: | |
| 152 td.data, err = readKey(buf, true) | |
| 153 if err != nil { | |
| 154 return err | |
| 155 } | |
| 156 case pvBlobKey: | |
|
M-A Ruel
2015/05/25 18:21:10
Do we need to implement this at all?
iannucci
2015/05/27 19:33:32
maybe not... should I just panic for BlobKey's? I'
M-A Ruel
2015/05/27 20:14:47
I don't mind much.
| |
| 157 s, err := readString(buf) | |
| 158 if err != nil { | |
| 159 return err | |
| 160 } | |
| 161 td.data = appengine.BlobKey(s) | |
| 162 default: | |
| 163 return fmt.Errorf("read: unknown type! %v", td) | |
| 164 } | |
| 165 | |
| 166 return nil | |
| 167 } | |
| 168 | |
| 169 type pval struct { | |
| 170 name string | |
| 171 noIndex bool | |
| 172 multi bool | |
| 173 vals []*typData | |
| 174 } | |
| 175 | |
| 176 type propertyList []datastore.Property | |
| 177 | |
| 178 func (pl *propertyList) Load(ch <-chan datastore.Property) error { | |
| 179 return (*datastore.PropertyList)(pl).Load(ch) | |
| 180 } | |
| 181 | |
| 182 func (pl *propertyList) Save(ch chan<- datastore.Property) error { | |
| 183 return (*datastore.PropertyList)(pl).Save(ch) | |
| 184 } | |
| 185 | |
| 186 func (pl *propertyList) collate() ([]*pval, error) { | |
| 187 if pl == nil || len(*pl) == 0 { | |
| 188 return nil, nil | |
| 189 } | |
| 190 | |
| 191 cols := []*pval{} | |
| 192 colIdx := map[string]int{} | |
| 193 | |
| 194 for _, p := range *pl { | |
| 195 var ok bool | |
| 196 idx := 0 | |
| 197 if idx, ok = colIdx[p.Name]; !ok { | |
|
M-A Ruel
2015/05/25 18:21:10
if idx, ok := colIdx[p.Name]; !ok {
and removing
iannucci
2015/05/27 19:33:32
yay!
| |
| 198 colIdx[p.Name] = len(cols) | |
| 199 td, err := newTypData(p.Value) | |
| 200 if err != nil { | |
| 201 return nil, err | |
| 202 } | |
| 203 cols = append(cols, &pval{p.Name, p.NoIndex, p.Multiple, []*typData{td}}) | |
| 204 } else { | |
| 205 c := cols[idx] | |
| 206 if c.noIndex != p.NoIndex { | |
| 207 return nil, fmt.Errorf( | |
| 208 "propertyList.MarshalBinary: field %q ha s conflicting values of NoIndex", p.Name) | |
| 209 } | |
| 210 if c.multi != p.Multiple { | |
| 211 return nil, fmt.Errorf( | |
| 212 "propertyList.MarshalBinary: field %q ha s conflicting values of Multiple", p.Name) | |
| 213 } | |
| 214 td, err := newTypData(p.Value) | |
| 215 if err != nil { | |
| 216 return nil, err | |
| 217 } | |
| 218 c.vals = append(c.vals, td) | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 return cols, nil | |
| 223 } | |
| 224 | |
| 225 func (pl *propertyList) addCollated(pv *pval) { | |
| 226 for _, v := range pv.vals { | |
| 227 *pl = append(*pl, datastore.Property{ | |
| 228 Name: pv.name, | |
| 229 Multiple: pv.multi, | |
| 230 NoIndex: pv.noIndex, | |
| 231 Value: v.data, | |
| 232 }) | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 func (pl *propertyList) MarshalBinary() ([]byte, error) { | |
| 237 cols, err := pl.collate() | |
| 238 if err != nil || len(cols) == 0 { | |
| 239 return nil, err | |
| 240 } | |
| 241 | |
| 242 pieces := make([][]byte, 0, len(*pl)*2+1) | |
| 243 for _, pv := range cols { | |
| 244 // TODO(riannucci): estimate buffer size better. | |
| 245 buf := bytes.NewBuffer(make([]byte, 0, funnybase.MaxFunnyBaseLen 64+len(pv.name))) | |
| 246 writeString(buf, pv.name) | |
| 247 err := pv.WriteBinary(buf) | |
| 248 if err != nil { | |
| 249 return nil, err | |
| 250 } | |
| 251 pieces = append(pieces, buf.Bytes()) | |
| 252 } | |
| 253 return bytes.Join(pieces, nil), nil | |
| 254 } | |
| 255 | |
| 256 func (pl *propertyList) UnmarshalBinary(data []byte) error { | |
| 257 buf := bytes.NewBuffer(data) | |
| 258 for buf.Len() > 0 { | |
| 259 name, err := readString(buf) | |
| 260 if err != nil { | |
| 261 return err | |
| 262 } | |
| 263 | |
| 264 pv := &pval{name: name} | |
| 265 err = pv.ReadBinary(buf) | |
| 266 if err != nil { | |
| 267 return err | |
| 268 } | |
| 269 pl.addCollated(pv) | |
| 270 } | |
| 271 | |
| 272 return nil | |
| 273 } | |
| 274 | |
| 275 func toPL(src interface{}) (ret *propertyList, err error) { | |
| 276 propchan := make(chan datastore.Property) | |
| 277 ret = &propertyList{} | |
| 278 go func() { err = datastore.SaveStruct(src, propchan) }() | |
| 279 err2 := ret.Load(propchan) | |
| 280 if err != nil { | |
| 281 return | |
| 282 } | |
| 283 return ret, err2 | |
| 284 } | |
| 285 | |
| 286 func fromPL(props *propertyList, dst interface{}) (err error) { | |
| 287 propchan := make(chan datastore.Property) | |
| 288 go func() { err = props.Save(propchan) }() | |
| 289 err2 := datastore.LoadStruct(dst, propchan) | |
| 290 if err != nil { | |
| 291 return err | |
| 292 } | |
| 293 return err2 | |
| 294 } | |
| 295 | |
| 296 type propValType byte | |
| 297 | |
| 298 var byteSliceType = reflect.TypeOf([]byte(nil)) | |
| 299 | |
| 300 // These constants are in the order described by | |
| 301 // https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type _ordering | |
| 302 // with a slight divergence for the Int/Time split. | |
| 303 const ( | |
| 304 pvNull propValType = iota | |
| 305 pvInt | |
| 306 | |
| 307 // NOTE: this is a slight divergence; times and integers actually sort | |
| 308 // together (apparently?) in datastore. This is probably insane, and I d on't | |
| 309 // want to add the complexity of field 'meaning' as a sparate concept fr om the | |
| 310 // field's 'type' (which is what datastore seems to do, judging from the | |
| 311 // protobufs). So if you're here because you implemented an app which re lies | |
| 312 // on time.Time and int64 sorting together, then this is why your app ac ts | |
| 313 // differently in production. My advice is to NOT DO THAT. If you really want | |
| 314 // this (and you probably don't), you should take care of the time.Time <-> | |
| 315 // int64 conversion in your app and just use a property type of int64. | |
| 316 pvTime | |
| 317 | |
| 318 // NOTE: this is also a slight divergence, but not a semantic one. IIUC, in | |
| 319 // datastore 'bool' is actually the type and the value is either 0 or | |
| 320 // 1 (taking another byte to store). Since we have plenty of space in th is | |
| 321 // type byte, I just merge the value into the type for booleans. If this | |
| 322 // becomes problematic, consider changing this to just pvBool, and then | |
| 323 // encoding a 0 or 1 as a byte in the relevant marshalling routines. | |
| 324 pvBoolFalse | |
| 325 pvBoolTrue | |
| 326 pvBytes // []byte or datastore.ByteString | |
| 327 pvStr // string or string noindex | |
| 328 pvFloat | |
| 329 pvGeoPoint | |
| 330 pvKey | |
| 331 pvBlobKey | |
| 332 | |
| 333 pvUNKNOWN | |
| 334 ) | |
| 335 | |
| 336 func (p *pval) ReadBinary(buf *bytes.Buffer) error { | |
| 337 prefix, err := buf.ReadByte() | |
| 338 if err != nil { | |
| 339 return err | |
| 340 } | |
| 341 p.multi = (prefix & 1) == 1 | |
| 342 p.noIndex = ((prefix >> 1) & 1) == 1 | |
| 343 n, err := funnybase.ReadUint(buf) | |
| 344 if err != nil { | |
| 345 return err | |
| 346 } | |
| 347 | |
| 348 p.vals = make([]*typData, n) | |
| 349 for i := range p.vals { | |
| 350 p.vals[i] = &typData{} | |
| 351 err := p.vals[i].ReadBinary(buf, !p.noIndex) | |
| 352 if err != nil { | |
| 353 return err | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 return nil | |
| 358 } | |
| 359 | |
| 360 func (p *pval) WriteBinary(buf *bytes.Buffer) error { | |
| 361 buf.WriteByte(btoi(p.noIndex)<<1 | btoi(p.multi)) | |
| 362 funnybase.WriteUint(buf, uint64(len(p.vals))) | |
| 363 for _, v := range p.vals { | |
| 364 err := v.WriteBinary(buf) | |
| 365 if err != nil { | |
| 366 return err | |
| 367 } | |
| 368 } | |
| 369 return nil | |
| 370 } | |
| OLD | NEW |