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 "bytes" |
| 9 "fmt" |
| 10 "reflect" |
| 11 "time" |
| 12 |
| 13 "github.com/luci/luci-go/common/funnybase" |
| 14 |
| 15 "appengine" |
| 16 "appengine/datastore" |
| 17 ) |
| 18 |
| 19 type typData struct { |
| 20 noIndex bool |
| 21 typ propValType |
| 22 data interface{} |
| 23 } |
| 24 |
| 25 func newTypData(noIndex bool, v interface{}) (ret *typData, err error) { |
| 26 typ := pvUNKNOWN |
| 27 |
| 28 switch x := v.(type) { |
| 29 case nil: |
| 30 typ = pvNull |
| 31 case time.Time: |
| 32 typ = pvTime |
| 33 case int64: |
| 34 typ = pvInt |
| 35 case float64: |
| 36 typ = pvFloat |
| 37 case bool: |
| 38 if x { |
| 39 typ = pvBoolTrue |
| 40 } else { |
| 41 typ = pvBoolFalse |
| 42 } |
| 43 case []byte, datastore.ByteString: |
| 44 typ = pvBytes |
| 45 case appengine.BlobKey: |
| 46 typ = pvBlobKey |
| 47 case string: |
| 48 typ = pvStr |
| 49 case appengine.GeoPoint: |
| 50 typ = pvGeoPoint |
| 51 case *datastore.Key: |
| 52 typ = pvKey |
| 53 } |
| 54 if typ == pvUNKNOWN { |
| 55 err = fmt.Errorf("propValTypeOf: unknown type of %#v", v) |
| 56 } |
| 57 |
| 58 return &typData{noIndex, typ, v}, err |
| 59 } |
| 60 |
| 61 func (td *typData) WriteBinary(buf *bytes.Buffer) error { |
| 62 typb := byte(td.typ) |
| 63 if td.noIndex { |
| 64 typb |= 0x80 |
| 65 } |
| 66 buf.WriteByte(typb) |
| 67 switch td.typ { |
| 68 case pvNull, pvBoolFalse, pvBoolTrue: |
| 69 return nil |
| 70 case pvInt: |
| 71 funnybase.Write(buf, td.data.(int64)) |
| 72 case pvFloat: |
| 73 writeFloat64(buf, td.data.(float64)) |
| 74 case pvStr: |
| 75 writeString(buf, td.data.(string)) |
| 76 case pvBytes: |
| 77 if td.noIndex { |
| 78 writeBytes(buf, td.data.([]byte)) |
| 79 } else { |
| 80 writeBytes(buf, td.data.(datastore.ByteString)) |
| 81 } |
| 82 case pvTime: |
| 83 t := td.data.(time.Time) |
| 84 funnybase.WriteUint(buf, uint64(t.Unix())*1e6+uint64(t.Nanosecon
d()/1e3)) |
| 85 case pvGeoPoint: |
| 86 t := td.data.(appengine.GeoPoint) |
| 87 writeFloat64(buf, t.Lat) |
| 88 writeFloat64(buf, t.Lng) |
| 89 case pvKey: |
| 90 writeKey(buf, withNS, td.data.(*datastore.Key)) |
| 91 case pvBlobKey: |
| 92 writeString(buf, string(td.data.(appengine.BlobKey))) |
| 93 default: |
| 94 return fmt.Errorf("write: unknown type! %v", td) |
| 95 } |
| 96 return nil |
| 97 } |
| 98 |
| 99 func (td *typData) ReadBinary(buf *bytes.Buffer) error { |
| 100 typb, err := buf.ReadByte() |
| 101 if err != nil { |
| 102 return err |
| 103 } |
| 104 td.noIndex = (typb & 0x80) != 0 // highbit means noindex |
| 105 td.typ = propValType(typb & 0x7f) |
| 106 switch td.typ { |
| 107 case pvNull: |
| 108 td.data = nil |
| 109 case pvBoolTrue: |
| 110 td.data = true |
| 111 case pvBoolFalse: |
| 112 td.data = false |
| 113 case pvInt: |
| 114 v, err := funnybase.Read(buf) |
| 115 if err != nil { |
| 116 return err |
| 117 } |
| 118 td.data = v |
| 119 case pvFloat: |
| 120 td.data, err = readFloat64(buf) |
| 121 if err != nil { |
| 122 return err |
| 123 } |
| 124 case pvStr: |
| 125 td.data, err = readString(buf) |
| 126 if err != nil { |
| 127 return err |
| 128 } |
| 129 case pvBytes: |
| 130 b, err := readBytes(buf) |
| 131 if err != nil { |
| 132 return err |
| 133 } |
| 134 if td.noIndex { |
| 135 td.data = b |
| 136 } else { |
| 137 td.data = datastore.ByteString(b) |
| 138 } |
| 139 case pvTime: |
| 140 v, err := funnybase.ReadUint(buf) |
| 141 if err != nil { |
| 142 return err |
| 143 } |
| 144 td.data = time.Unix(int64(v/1e6), int64((v%1e6)*1e3)) |
| 145 case pvGeoPoint: |
| 146 pt := appengine.GeoPoint{} |
| 147 pt.Lat, err = readFloat64(buf) |
| 148 if err != nil { |
| 149 return err |
| 150 } |
| 151 pt.Lng, err = readFloat64(buf) |
| 152 if err != nil { |
| 153 return err |
| 154 } |
| 155 td.data = pt |
| 156 case pvKey: |
| 157 td.data, err = readKey(buf, true) |
| 158 if err != nil { |
| 159 return err |
| 160 } |
| 161 case pvBlobKey: |
| 162 s, err := readString(buf) |
| 163 if err != nil { |
| 164 return err |
| 165 } |
| 166 td.data = appengine.BlobKey(s) |
| 167 default: |
| 168 return fmt.Errorf("read: unknown type! %v", td) |
| 169 } |
| 170 |
| 171 return nil |
| 172 } |
| 173 |
| 174 type pval struct { |
| 175 name string |
| 176 multi bool |
| 177 vals []*typData |
| 178 } |
| 179 |
| 180 type propertyList []datastore.Property |
| 181 |
| 182 var _ = datastore.PropertyLoadSaver((*propertyList)(nil)) |
| 183 |
| 184 func (pl *propertyList) Load(ch <-chan datastore.Property) error { |
| 185 return (*datastore.PropertyList)(pl).Load(ch) |
| 186 } |
| 187 |
| 188 func (pl *propertyList) Save(ch chan<- datastore.Property) error { |
| 189 return (*datastore.PropertyList)(pl).Save(ch) |
| 190 } |
| 191 |
| 192 func (pl *propertyList) collate() ([]*pval, error) { |
| 193 if pl == nil || len(*pl) == 0 { |
| 194 return nil, nil |
| 195 } |
| 196 |
| 197 cols := []*pval{} |
| 198 colIdx := map[string]int{} |
| 199 |
| 200 for _, p := range *pl { |
| 201 if idx, ok := colIdx[p.Name]; !ok { |
| 202 colIdx[p.Name] = len(cols) |
| 203 td, err := newTypData(p.NoIndex, p.Value) |
| 204 if err != nil { |
| 205 return nil, err |
| 206 } |
| 207 cols = append(cols, &pval{p.Name, p.Multiple, []*typData
{td}}) |
| 208 } else { |
| 209 c := cols[idx] |
| 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.NoIndex, 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: v.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 |
| 331 // These two are problematic, because they force us to bind to the appen
gine |
| 332 // SDK code. If we can drop support for these and turn them into hard er
rors, |
| 333 // that could let us decouple from the various appengine SDKs. Maybe. |
| 334 pvKey // TODO(riannucci): remove support for this (use a string) |
| 335 pvBlobKey // TODO(riannucci): remove support for this (use a string) |
| 336 |
| 337 pvUNKNOWN |
| 338 ) |
| 339 |
| 340 func (p *pval) ReadBinary(buf *bytes.Buffer) error { |
| 341 n, err := funnybase.ReadUint(buf) |
| 342 if err != nil { |
| 343 return err |
| 344 } |
| 345 p.multi = n > 1 |
| 346 |
| 347 p.vals = make([]*typData, n) |
| 348 for i := range p.vals { |
| 349 p.vals[i] = &typData{} |
| 350 err := p.vals[i].ReadBinary(buf) |
| 351 if err != nil { |
| 352 return err |
| 353 } |
| 354 } |
| 355 |
| 356 return nil |
| 357 } |
| 358 |
| 359 func (p *pval) WriteBinary(buf *bytes.Buffer) error { |
| 360 funnybase.WriteUint(buf, uint64(len(p.vals))) |
| 361 for _, v := range p.vals { |
| 362 if err := v.WriteBinary(buf); err != nil { |
| 363 return err |
| 364 } |
| 365 } |
| 366 return nil |
| 367 } |
OLD | NEW |