| 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 datastore | |
| 6 | |
| 7 import ( | |
| 8 "bytes" | |
| 9 "errors" | |
| 10 "fmt" | |
| 11 "sort" | |
| 12 "time" | |
| 13 | |
| 14 "github.com/luci/gae/service/blobstore" | |
| 15 "github.com/luci/luci-go/common/cmpbin" | |
| 16 ) | |
| 17 | |
| 18 // MaxIndexColumns is the maximum number of sort orders you may have on a | |
| 19 // single composite index. 64 was chosen as a likely-astronomical number. | |
| 20 const MaxIndexColumns = 64 | |
| 21 | |
| 22 // WritePropertyMapDeterministic allows tests to make WritePropertyMap | |
| 23 // deterministic. | |
| 24 var WritePropertyMapDeterministic = false | |
| 25 | |
| 26 // ReadPropertyMapReasonableLimit sets a limit on the number of rows and | |
| 27 // number of properties per row which can be read by ReadPropertyMap. The | |
| 28 // total number of Property objects readable by this method is this number | |
| 29 // squared (e.g. Limit rows * Limit properties) | |
| 30 const ReadPropertyMapReasonableLimit uint64 = 30000 | |
| 31 | |
| 32 // ReadKeyNumToksReasonableLimit is the maximum number of Key tokens that | |
| 33 // ReadKey is willing to read for a single key. | |
| 34 const ReadKeyNumToksReasonableLimit uint64 = 50 | |
| 35 | |
| 36 // KeyContext controls whether the various Write and Read serializtion | |
| 37 // routines should encode the context of Keys (read: the appid and namespace). | |
| 38 // Frequently the appid and namespace of keys are known in advance and so there'
s | |
| 39 // no reason to redundantly encode them. | |
| 40 type KeyContext bool | |
| 41 | |
| 42 // With- and WithoutContext indicate if the serialization method should include | |
| 43 // context for Keys. See KeyContext for more information. | |
| 44 const ( | |
| 45 WithContext KeyContext = true | |
| 46 WithoutContext = false | |
| 47 ) | |
| 48 | |
| 49 // WriteKey encodes a key to the buffer. If context is WithContext, then this | |
| 50 // encoded value will include the appid and namespace of the key. | |
| 51 func WriteKey(buf Buffer, context KeyContext, k Key) (err error) { | |
| 52 // [appid ++ namespace]? ++ #tokens ++ tokens* | |
| 53 defer recoverTo(&err) | |
| 54 appid, namespace, toks := KeySplit(k) | |
| 55 if context == WithContext { | |
| 56 panicIf(buf.WriteByte(1)) | |
| 57 _, e := cmpbin.WriteString(buf, appid) | |
| 58 panicIf(e) | |
| 59 _, e = cmpbin.WriteString(buf, namespace) | |
| 60 panicIf(e) | |
| 61 } else { | |
| 62 panicIf(buf.WriteByte(0)) | |
| 63 } | |
| 64 _, e := cmpbin.WriteUint(buf, uint64(len(toks))) | |
| 65 panicIf(e) | |
| 66 for _, tok := range toks { | |
| 67 panicIf(WriteKeyTok(buf, tok)) | |
| 68 } | |
| 69 return nil | |
| 70 } | |
| 71 | |
| 72 // ReadKey deserializes a key from the buffer. The value of context must match | |
| 73 // the value of context that was passed to WriteKey when the key was encoded. | |
| 74 // If context == WithoutContext, then the appid and namespace parameters are | |
| 75 // used in the decoded Key. Otherwise they're ignored. | |
| 76 func ReadKey(buf Buffer, context KeyContext, appid, namespace string) (ret Key,
err error) { | |
| 77 defer recoverTo(&err) | |
| 78 actualCtx, e := buf.ReadByte() | |
| 79 panicIf(e) | |
| 80 | |
| 81 actualAid, actualNS := "", "" | |
| 82 if actualCtx == 1 { | |
| 83 actualAid, _, e = cmpbin.ReadString(buf) | |
| 84 panicIf(e) | |
| 85 actualNS, _, e = cmpbin.ReadString(buf) | |
| 86 panicIf(e) | |
| 87 } else if actualCtx != 0 { | |
| 88 err = fmt.Errorf("helper: expected actualCtx to be 0 or 1, got %
d", actualCtx) | |
| 89 return | |
| 90 } | |
| 91 | |
| 92 if context == WithoutContext { | |
| 93 // overrwrite with the supplied ones | |
| 94 actualAid = appid | |
| 95 actualNS = namespace | |
| 96 } | |
| 97 | |
| 98 numToks, _, e := cmpbin.ReadUint(buf) | |
| 99 panicIf(e) | |
| 100 if numToks > ReadKeyNumToksReasonableLimit { | |
| 101 err = fmt.Errorf("helper: tried to decode huge key of length %d"
, numToks) | |
| 102 return | |
| 103 } | |
| 104 | |
| 105 toks := make([]KeyTok, numToks) | |
| 106 for i := uint64(0); i < numToks; i++ { | |
| 107 toks[i], e = ReadKeyTok(buf) | |
| 108 panicIf(e) | |
| 109 } | |
| 110 | |
| 111 return NewKeyToks(actualAid, actualNS, toks), nil | |
| 112 } | |
| 113 | |
| 114 // WriteKeyTok writes a KeyTok to the buffer. You usually want WriteKey | |
| 115 // instead of this. | |
| 116 func WriteKeyTok(buf Buffer, tok KeyTok) (err error) { | |
| 117 // tok.kind ++ typ ++ [tok.stringID || tok.intID] | |
| 118 defer recoverTo(&err) | |
| 119 _, e := cmpbin.WriteString(buf, tok.Kind) | |
| 120 panicIf(e) | |
| 121 if tok.StringID != "" { | |
| 122 panicIf(buf.WriteByte(byte(PTString))) | |
| 123 _, e := cmpbin.WriteString(buf, tok.StringID) | |
| 124 panicIf(e) | |
| 125 } else { | |
| 126 panicIf(buf.WriteByte(byte(PTInt))) | |
| 127 _, e := cmpbin.WriteInt(buf, tok.IntID) | |
| 128 panicIf(e) | |
| 129 } | |
| 130 return nil | |
| 131 } | |
| 132 | |
| 133 // ReadKeyTok reads a KeyTok from the buffer. You usually want ReadKey | |
| 134 // instead of this. | |
| 135 func ReadKeyTok(buf Buffer) (ret KeyTok, err error) { | |
| 136 defer recoverTo(&err) | |
| 137 e := error(nil) | |
| 138 ret.Kind, _, e = cmpbin.ReadString(buf) | |
| 139 panicIf(e) | |
| 140 | |
| 141 typ, e := buf.ReadByte() | |
| 142 panicIf(e) | |
| 143 | |
| 144 switch PropertyType(typ) { | |
| 145 case PTString: | |
| 146 ret.StringID, _, err = cmpbin.ReadString(buf) | |
| 147 case PTInt: | |
| 148 ret.IntID, _, err = cmpbin.ReadInt(buf) | |
| 149 if err == nil && ret.IntID <= 0 { | |
| 150 err = errors.New("helper: decoded key with empty stringI
D and zero/negative intID") | |
| 151 } | |
| 152 default: | |
| 153 err = fmt.Errorf("helper: invalid type %s", PropertyType(typ)) | |
| 154 } | |
| 155 return | |
| 156 } | |
| 157 | |
| 158 // Write writes a GeoPoint to the buffer. | |
| 159 func (gp GeoPoint) Write(buf Buffer) (err error) { | |
| 160 defer recoverTo(&err) | |
| 161 _, e := cmpbin.WriteFloat64(buf, gp.Lat) | |
| 162 panicIf(e) | |
| 163 _, e = cmpbin.WriteFloat64(buf, gp.Lng) | |
| 164 return e | |
| 165 } | |
| 166 | |
| 167 // Read reads a GeoPoint from the buffer. | |
| 168 func (gp *GeoPoint) Read(buf Buffer) (err error) { | |
| 169 defer recoverTo(&err) | |
| 170 e := error(nil) | |
| 171 gp.Lat, _, e = cmpbin.ReadFloat64(buf) | |
| 172 panicIf(e) | |
| 173 | |
| 174 gp.Lng, _, e = cmpbin.ReadFloat64(buf) | |
| 175 panicIf(e) | |
| 176 | |
| 177 if !gp.Valid() { | |
| 178 err = fmt.Errorf("helper: decoded invalid GeoPoint: %v", gp) | |
| 179 } | |
| 180 return | |
| 181 } | |
| 182 | |
| 183 // WriteTime writes a time.Time in a byte-sortable way. | |
| 184 // | |
| 185 // This method truncates the time to microseconds and drops the timezone, | |
| 186 // because that's the (undocumented) way that the appengine SDK does it. | |
| 187 func WriteTime(buf Buffer, t time.Time) error { | |
| 188 name, off := t.Zone() | |
| 189 if name != "UTC" || off != 0 { | |
| 190 panic(fmt.Errorf("helper: UTC OR DEATH: %s", t)) | |
| 191 } | |
| 192 _, err := cmpbin.WriteUint(buf, uint64(t.Unix())*1e6+uint64(t.Nanosecond
()/1e3)) | |
| 193 return err | |
| 194 } | |
| 195 | |
| 196 // ReadTime reads a time.Time from the buffer. | |
| 197 func ReadTime(buf Buffer) (time.Time, error) { | |
| 198 v, _, err := cmpbin.ReadUint(buf) | |
| 199 if err != nil { | |
| 200 return time.Time{}, err | |
| 201 } | |
| 202 return time.Unix(int64(v/1e6), int64((v%1e6)*1e3)).UTC(), nil | |
| 203 } | |
| 204 | |
| 205 // Write writes a Property to the buffer. `context` behaves the same | |
| 206 // way that it does for WriteKey, but only has an effect if `p` contains a | |
| 207 // Key as its Value. | |
| 208 func (p *Property) Write(buf Buffer, context KeyContext) (err error) { | |
| 209 defer recoverTo(&err) | |
| 210 typb := byte(p.Type()) | |
| 211 if p.IndexSetting() == NoIndex { | |
| 212 typb |= 0x80 | |
| 213 } | |
| 214 panicIf(buf.WriteByte(typb)) | |
| 215 switch p.Type() { | |
| 216 case PTNull, PTBoolTrue, PTBoolFalse: | |
| 217 case PTInt: | |
| 218 _, err = cmpbin.WriteInt(buf, p.Value().(int64)) | |
| 219 case PTFloat: | |
| 220 _, err = cmpbin.WriteFloat64(buf, p.Value().(float64)) | |
| 221 case PTString: | |
| 222 _, err = cmpbin.WriteString(buf, p.Value().(string)) | |
| 223 case PTBytes: | |
| 224 if p.IndexSetting() == NoIndex { | |
| 225 _, err = cmpbin.WriteBytes(buf, p.Value().([]byte)) | |
| 226 } else { | |
| 227 _, err = cmpbin.WriteBytes(buf, p.Value().(ByteString)) | |
| 228 } | |
| 229 case PTTime: | |
| 230 err = WriteTime(buf, p.Value().(time.Time)) | |
| 231 case PTGeoPoint: | |
| 232 err = p.Value().(GeoPoint).Write(buf) | |
| 233 case PTKey: | |
| 234 err = WriteKey(buf, context, p.Value().(Key)) | |
| 235 case PTBlobKey: | |
| 236 _, err = cmpbin.WriteString(buf, string(p.Value().(blobstore.Key
))) | |
| 237 } | |
| 238 return | |
| 239 } | |
| 240 | |
| 241 // Read reads a Property from the buffer. `context`, `appid`, and | |
| 242 // `namespace` behave the same way they do for ReadKey, but only have an | |
| 243 // effect if the decoded property has a Key value. | |
| 244 func (p *Property) Read(buf Buffer, context KeyContext, appid, namespace string)
(err error) { | |
| 245 val := interface{}(nil) | |
| 246 typb, err := buf.ReadByte() | |
| 247 if err != nil { | |
| 248 return | |
| 249 } | |
| 250 is := ShouldIndex | |
| 251 if (typb & 0x80) != 0 { | |
| 252 is = NoIndex | |
| 253 } | |
| 254 switch PropertyType(typb & 0x7f) { | |
| 255 case PTNull: | |
| 256 case PTBoolTrue: | |
| 257 val = true | |
| 258 case PTBoolFalse: | |
| 259 val = false | |
| 260 case PTInt: | |
| 261 val, _, err = cmpbin.ReadInt(buf) | |
| 262 case PTFloat: | |
| 263 val, _, err = cmpbin.ReadFloat64(buf) | |
| 264 case PTString: | |
| 265 val, _, err = cmpbin.ReadString(buf) | |
| 266 case PTBytes: | |
| 267 b := []byte(nil) | |
| 268 if b, _, err = cmpbin.ReadBytes(buf); err != nil { | |
| 269 break | |
| 270 } | |
| 271 if is == NoIndex { | |
| 272 val = b | |
| 273 } else { | |
| 274 val = ByteString(b) | |
| 275 } | |
| 276 case PTTime: | |
| 277 val, err = ReadTime(buf) | |
| 278 case PTGeoPoint: | |
| 279 gp := GeoPoint{} | |
| 280 err = gp.Read(buf) | |
| 281 val = gp | |
| 282 case PTKey: | |
| 283 val, err = ReadKey(buf, context, appid, namespace) | |
| 284 case PTBlobKey: | |
| 285 s := "" | |
| 286 if s, _, err = cmpbin.ReadString(buf); err != nil { | |
| 287 break | |
| 288 } | |
| 289 val = blobstore.Key(s) | |
| 290 default: | |
| 291 err = fmt.Errorf("read: unknown type! %v", typb) | |
| 292 } | |
| 293 if err == nil { | |
| 294 err = p.SetValue(val, is) | |
| 295 } | |
| 296 return | |
| 297 } | |
| 298 | |
| 299 // Write writes an entire PropertyMap to the buffer. `context` behaves the same | |
| 300 // way that it does for WriteKey. If WritePropertyMapDeterministic is true, then | |
| 301 // the rows will be sorted by property name before they're serialized to buf | |
| 302 // (mostly useful for testing, but also potentially useful if you need to make | |
| 303 // a hash of the property data). | |
| 304 // | |
| 305 // Write skips metadata keys. | |
| 306 func (pm PropertyMap) Write(buf Buffer, context KeyContext) (err error) { | |
| 307 defer recoverTo(&err) | |
| 308 rows := make(sort.StringSlice, 0, len(pm)) | |
| 309 tmpBuf := &bytes.Buffer{} | |
| 310 for name, vals := range pm { | |
| 311 if isMetaKey(name) { | |
| 312 continue | |
| 313 } | |
| 314 tmpBuf.Reset() | |
| 315 _, e := cmpbin.WriteString(tmpBuf, name) | |
| 316 panicIf(e) | |
| 317 _, e = cmpbin.WriteUint(tmpBuf, uint64(len(vals))) | |
| 318 panicIf(e) | |
| 319 for _, p := range vals { | |
| 320 panicIf(p.Write(tmpBuf, context)) | |
| 321 } | |
| 322 rows = append(rows, tmpBuf.String()) | |
| 323 } | |
| 324 | |
| 325 if WritePropertyMapDeterministic { | |
| 326 rows.Sort() | |
| 327 } | |
| 328 | |
| 329 _, e := cmpbin.WriteUint(buf, uint64(len(pm))) | |
| 330 panicIf(e) | |
| 331 for _, r := range rows { | |
| 332 _, e := buf.WriteString(r) | |
| 333 panicIf(e) | |
| 334 } | |
| 335 return | |
| 336 } | |
| 337 | |
| 338 // Read reads a PropertyMap from the buffer. `context` and | |
| 339 // friends behave the same way that they do for ReadKey. | |
| 340 func (pm PropertyMap) Read(buf Buffer, context KeyContext, appid, namespace stri
ng) (err error) { | |
| 341 defer recoverTo(&err) | |
| 342 | |
| 343 numRows := uint64(0) | |
| 344 numRows, _, e := cmpbin.ReadUint(buf) | |
| 345 panicIf(e) | |
| 346 if numRows > ReadPropertyMapReasonableLimit { | |
| 347 err = fmt.Errorf("helper: tried to decode map with huge number o
f rows %d", numRows) | |
| 348 return | |
| 349 } | |
| 350 | |
| 351 name, prop := "", Property{} | |
| 352 for i := uint64(0); i < numRows; i++ { | |
| 353 name, _, e = cmpbin.ReadString(buf) | |
| 354 panicIf(e) | |
| 355 | |
| 356 numProps, _, e := cmpbin.ReadUint(buf) | |
| 357 panicIf(e) | |
| 358 if numProps > ReadPropertyMapReasonableLimit { | |
| 359 err = fmt.Errorf("helper: tried to decode map with huge
number of properties %d", numProps) | |
| 360 return | |
| 361 } | |
| 362 props := make([]Property, 0, numProps) | |
| 363 for j := uint64(0); j < numProps; j++ { | |
| 364 panicIf(prop.Read(buf, context, appid, namespace)) | |
| 365 props = append(props, prop) | |
| 366 } | |
| 367 pm[name] = props | |
| 368 } | |
| 369 return | |
| 370 } | |
| 371 | |
| 372 func (c *IndexColumn) Write(buf Buffer) (err error) { | |
| 373 defer recoverTo(&err) | |
| 374 | |
| 375 if c.Direction == ASCENDING { | |
| 376 panicIf(buf.WriteByte(0)) | |
| 377 } else { | |
| 378 panicIf(buf.WriteByte(1)) | |
| 379 } | |
| 380 _, err = cmpbin.WriteString(buf, c.Property) | |
| 381 return | |
| 382 } | |
| 383 | |
| 384 func (c *IndexColumn) Read(buf Buffer) (err error) { | |
| 385 defer recoverTo(&err) | |
| 386 | |
| 387 dir, err := buf.ReadByte() | |
| 388 panicIf(err) | |
| 389 | |
| 390 switch dir { | |
| 391 case 0: | |
| 392 c.Direction = ASCENDING | |
| 393 default: | |
| 394 c.Direction = DESCENDING | |
| 395 } | |
| 396 c.Property, _, err = cmpbin.ReadString(buf) | |
| 397 return err | |
| 398 } | |
| 399 | |
| 400 func (i *IndexDefinition) Write(buf Buffer) (err error) { | |
| 401 defer recoverTo(&err) | |
| 402 | |
| 403 if i.Builtin() { | |
| 404 panicIf(buf.WriteByte(0)) | |
| 405 } else { | |
| 406 panicIf(buf.WriteByte(1)) | |
| 407 } | |
| 408 _, err = cmpbin.WriteString(buf, i.Kind) | |
| 409 panicIf(err) | |
| 410 if !i.Ancestor { | |
| 411 panicIf(buf.WriteByte(0)) | |
| 412 } else { | |
| 413 panicIf(buf.WriteByte(1)) | |
| 414 } | |
| 415 _, err = cmpbin.WriteUint(buf, uint64(len(i.SortBy))) | |
| 416 panicIf(err) | |
| 417 for _, sb := range i.SortBy { | |
| 418 panicIf(sb.Write(buf)) | |
| 419 } | |
| 420 return | |
| 421 } | |
| 422 | |
| 423 func (i *IndexDefinition) Read(buf Buffer) (err error) { | |
| 424 defer recoverTo(&err) | |
| 425 | |
| 426 // discard builtin/complex byte | |
| 427 _, err = buf.ReadByte() | |
| 428 panicIf(err) | |
| 429 | |
| 430 i.Kind, _, err = cmpbin.ReadString(buf) | |
| 431 panicIf(err) | |
| 432 | |
| 433 anc, err := buf.ReadByte() | |
| 434 panicIf(err) | |
| 435 | |
| 436 i.Ancestor = anc == 1 | |
| 437 | |
| 438 numSorts, _, err := cmpbin.ReadUint(buf) | |
| 439 panicIf(err) | |
| 440 | |
| 441 if numSorts > MaxIndexColumns { | |
| 442 return fmt.Errorf("datastore: Got over %d sort orders: %d", | |
| 443 MaxIndexColumns, numSorts) | |
| 444 } | |
| 445 | |
| 446 if numSorts > 0 { | |
| 447 i.SortBy = make([]IndexColumn, numSorts) | |
| 448 for idx := range i.SortBy { | |
| 449 panicIf(i.SortBy[idx].Read(buf)) | |
| 450 } | |
| 451 } | |
| 452 | |
| 453 return | |
| 454 } | |
| 455 | |
| 456 type parseError error | |
| 457 | |
| 458 func panicIf(err error) { | |
| 459 if err != nil { | |
| 460 panic(parseError(err)) | |
| 461 } | |
| 462 } | |
| 463 | |
| 464 func recoverTo(err *error) { | |
| 465 if r := recover(); r != nil { | |
| 466 if rerr := r.(parseError); rerr != nil { | |
| 467 *err = error(rerr) | |
| 468 } | |
| 469 } | |
| 470 } | |
| OLD | NEW |