| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package datastore | 5 package serialize |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bytes" | 8 "bytes" |
| 9 "errors" | 9 "errors" |
| 10 "fmt" | 10 "fmt" |
| 11 "sort" | 11 "sort" |
| 12 "time" | 12 "time" |
| 13 | 13 |
| 14 "github.com/luci/gae/service/blobstore" | 14 "github.com/luci/gae/service/blobstore" |
| 15 ds "github.com/luci/gae/service/datastore" |
| 16 "github.com/luci/gae/service/datastore/dskey" |
| 15 "github.com/luci/luci-go/common/cmpbin" | 17 "github.com/luci/luci-go/common/cmpbin" |
| 16 ) | 18 ) |
| 17 | 19 |
| 18 // MaxIndexColumns is the maximum number of sort orders you may have on a | 20 // MaxIndexColumns is the maximum number of sort columns (e.g. sort orders) that |
| 19 // single composite index. 64 was chosen as a likely-astronomical number. | 21 // ReadIndexDefinition is willing to deserialize. 64 was chosen as |
| 22 // a likely-astronomical number. |
| 20 const MaxIndexColumns = 64 | 23 const MaxIndexColumns = 64 |
| 21 | 24 |
| 22 // WritePropertyMapDeterministic allows tests to make WritePropertyMap | 25 // WritePropertyMapDeterministic allows tests to make WritePropertyMap |
| 23 // deterministic. | 26 // deterministic. |
| 24 var WritePropertyMapDeterministic = false | 27 var WritePropertyMapDeterministic = false |
| 25 | 28 |
| 26 // ReadPropertyMapReasonableLimit sets a limit on the number of rows and | 29 // ReadPropertyMapReasonableLimit sets a limit on the number of rows and |
| 27 // number of properties per row which can be read by ReadPropertyMap. The | 30 // 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 | 31 // total number of Property objects readable by this method is this number |
| 29 // squared (e.g. Limit rows * Limit properties) | 32 // squared (e.g. Limit rows * Limit properties) |
| (...skipping 11 matching lines...) Expand all Loading... |
| 41 | 44 |
| 42 // With- and WithoutContext indicate if the serialization method should include | 45 // With- and WithoutContext indicate if the serialization method should include |
| 43 // context for Keys. See KeyContext for more information. | 46 // context for Keys. See KeyContext for more information. |
| 44 const ( | 47 const ( |
| 45 WithContext KeyContext = true | 48 WithContext KeyContext = true |
| 46 WithoutContext = false | 49 WithoutContext = false |
| 47 ) | 50 ) |
| 48 | 51 |
| 49 // WriteKey encodes a key to the buffer. If context is WithContext, then this | 52 // 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. | 53 // encoded value will include the appid and namespace of the key. |
| 51 func WriteKey(buf Buffer, context KeyContext, k Key) (err error) { | 54 func WriteKey(buf Buffer, context KeyContext, k ds.Key) (err error) { |
| 52 // [appid ++ namespace]? ++ #tokens ++ tokens* | 55 // [appid ++ namespace]? ++ #tokens ++ tokens* |
| 53 defer recoverTo(&err) | 56 defer recoverTo(&err) |
| 54 » appid, namespace, toks := KeySplit(k) | 57 » appid, namespace, toks := dskey.Split(k) |
| 55 if context == WithContext { | 58 if context == WithContext { |
| 56 panicIf(buf.WriteByte(1)) | 59 panicIf(buf.WriteByte(1)) |
| 57 _, e := cmpbin.WriteString(buf, appid) | 60 _, e := cmpbin.WriteString(buf, appid) |
| 58 panicIf(e) | 61 panicIf(e) |
| 59 _, e = cmpbin.WriteString(buf, namespace) | 62 _, e = cmpbin.WriteString(buf, namespace) |
| 60 panicIf(e) | 63 panicIf(e) |
| 61 } else { | 64 } else { |
| 62 panicIf(buf.WriteByte(0)) | 65 panicIf(buf.WriteByte(0)) |
| 63 } | 66 } |
| 64 _, e := cmpbin.WriteUint(buf, uint64(len(toks))) | 67 _, e := cmpbin.WriteUint(buf, uint64(len(toks))) |
| 65 panicIf(e) | 68 panicIf(e) |
| 66 for _, tok := range toks { | 69 for _, tok := range toks { |
| 67 panicIf(WriteKeyTok(buf, tok)) | 70 panicIf(WriteKeyTok(buf, tok)) |
| 68 } | 71 } |
| 69 return nil | 72 return nil |
| 70 } | 73 } |
| 71 | 74 |
| 72 // ReadKey deserializes a key from the buffer. The value of context must match | 75 // 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. | 76 // 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 | 77 // If context == WithoutContext, then the appid and namespace parameters are |
| 75 // used in the decoded Key. Otherwise they're ignored. | 78 // used in the decoded Key. Otherwise they're ignored. |
| 76 func ReadKey(buf Buffer, context KeyContext, appid, namespace string) (ret Key,
err error) { | 79 func ReadKey(buf Buffer, context KeyContext, appid, namespace string) (ret ds.Ke
y, err error) { |
| 77 defer recoverTo(&err) | 80 defer recoverTo(&err) |
| 78 actualCtx, e := buf.ReadByte() | 81 actualCtx, e := buf.ReadByte() |
| 79 panicIf(e) | 82 panicIf(e) |
| 80 | 83 |
| 81 actualAid, actualNS := "", "" | 84 actualAid, actualNS := "", "" |
| 82 if actualCtx == 1 { | 85 if actualCtx == 1 { |
| 83 actualAid, _, e = cmpbin.ReadString(buf) | 86 actualAid, _, e = cmpbin.ReadString(buf) |
| 84 panicIf(e) | 87 panicIf(e) |
| 85 actualNS, _, e = cmpbin.ReadString(buf) | 88 actualNS, _, e = cmpbin.ReadString(buf) |
| 86 panicIf(e) | 89 panicIf(e) |
| 87 } else if actualCtx != 0 { | 90 } else if actualCtx != 0 { |
| 88 err = fmt.Errorf("helper: expected actualCtx to be 0 or 1, got %
d", actualCtx) | 91 err = fmt.Errorf("helper: expected actualCtx to be 0 or 1, got %
d", actualCtx) |
| 89 return | 92 return |
| 90 } | 93 } |
| 91 | 94 |
| 92 if context == WithoutContext { | 95 if context == WithoutContext { |
| 93 // overrwrite with the supplied ones | 96 // overrwrite with the supplied ones |
| 94 actualAid = appid | 97 actualAid = appid |
| 95 actualNS = namespace | 98 actualNS = namespace |
| 96 } | 99 } |
| 97 | 100 |
| 98 numToks, _, e := cmpbin.ReadUint(buf) | 101 numToks, _, e := cmpbin.ReadUint(buf) |
| 99 panicIf(e) | 102 panicIf(e) |
| 100 if numToks > ReadKeyNumToksReasonableLimit { | 103 if numToks > ReadKeyNumToksReasonableLimit { |
| 101 err = fmt.Errorf("helper: tried to decode huge key of length %d"
, numToks) | 104 err = fmt.Errorf("helper: tried to decode huge key of length %d"
, numToks) |
| 102 return | 105 return |
| 103 } | 106 } |
| 104 | 107 |
| 105 » toks := make([]KeyTok, numToks) | 108 » toks := make([]ds.KeyTok, numToks) |
| 106 for i := uint64(0); i < numToks; i++ { | 109 for i := uint64(0); i < numToks; i++ { |
| 107 toks[i], e = ReadKeyTok(buf) | 110 toks[i], e = ReadKeyTok(buf) |
| 108 panicIf(e) | 111 panicIf(e) |
| 109 } | 112 } |
| 110 | 113 |
| 111 » return NewKeyToks(actualAid, actualNS, toks), nil | 114 » return dskey.NewToks(actualAid, actualNS, toks), nil |
| 112 } | 115 } |
| 113 | 116 |
| 114 // WriteKeyTok writes a KeyTok to the buffer. You usually want WriteKey | 117 // WriteKeyTok writes a KeyTok to the buffer. You usually want WriteKey |
| 115 // instead of this. | 118 // instead of this. |
| 116 func WriteKeyTok(buf Buffer, tok KeyTok) (err error) { | 119 func WriteKeyTok(buf Buffer, tok ds.KeyTok) (err error) { |
| 117 // tok.kind ++ typ ++ [tok.stringID || tok.intID] | 120 // tok.kind ++ typ ++ [tok.stringID || tok.intID] |
| 118 defer recoverTo(&err) | 121 defer recoverTo(&err) |
| 119 _, e := cmpbin.WriteString(buf, tok.Kind) | 122 _, e := cmpbin.WriteString(buf, tok.Kind) |
| 120 panicIf(e) | 123 panicIf(e) |
| 121 if tok.StringID != "" { | 124 if tok.StringID != "" { |
| 122 » » panicIf(buf.WriteByte(byte(PTString))) | 125 » » panicIf(buf.WriteByte(byte(ds.PTString))) |
| 123 _, e := cmpbin.WriteString(buf, tok.StringID) | 126 _, e := cmpbin.WriteString(buf, tok.StringID) |
| 124 panicIf(e) | 127 panicIf(e) |
| 125 } else { | 128 } else { |
| 126 » » panicIf(buf.WriteByte(byte(PTInt))) | 129 » » panicIf(buf.WriteByte(byte(ds.PTInt))) |
| 127 _, e := cmpbin.WriteInt(buf, tok.IntID) | 130 _, e := cmpbin.WriteInt(buf, tok.IntID) |
| 128 panicIf(e) | 131 panicIf(e) |
| 129 } | 132 } |
| 130 return nil | 133 return nil |
| 131 } | 134 } |
| 132 | 135 |
| 133 // ReadKeyTok reads a KeyTok from the buffer. You usually want ReadKey | 136 // ReadKeyTok reads a KeyTok from the buffer. You usually want ReadKey |
| 134 // instead of this. | 137 // instead of this. |
| 135 func ReadKeyTok(buf Buffer) (ret KeyTok, err error) { | 138 func ReadKeyTok(buf Buffer) (ret ds.KeyTok, err error) { |
| 136 defer recoverTo(&err) | 139 defer recoverTo(&err) |
| 137 e := error(nil) | 140 e := error(nil) |
| 138 ret.Kind, _, e = cmpbin.ReadString(buf) | 141 ret.Kind, _, e = cmpbin.ReadString(buf) |
| 139 panicIf(e) | 142 panicIf(e) |
| 140 | 143 |
| 141 typ, e := buf.ReadByte() | 144 typ, e := buf.ReadByte() |
| 142 panicIf(e) | 145 panicIf(e) |
| 143 | 146 |
| 144 » switch PropertyType(typ) { | 147 » switch ds.PropertyType(typ) { |
| 145 » case PTString: | 148 » case ds.PTString: |
| 146 ret.StringID, _, err = cmpbin.ReadString(buf) | 149 ret.StringID, _, err = cmpbin.ReadString(buf) |
| 147 » case PTInt: | 150 » case ds.PTInt: |
| 148 ret.IntID, _, err = cmpbin.ReadInt(buf) | 151 ret.IntID, _, err = cmpbin.ReadInt(buf) |
| 149 if err == nil && ret.IntID <= 0 { | 152 if err == nil && ret.IntID <= 0 { |
| 150 err = errors.New("helper: decoded key with empty stringI
D and zero/negative intID") | 153 err = errors.New("helper: decoded key with empty stringI
D and zero/negative intID") |
| 151 } | 154 } |
| 152 default: | 155 default: |
| 153 » » err = fmt.Errorf("helper: invalid type %s", PropertyType(typ)) | 156 » » err = fmt.Errorf("helper: invalid type %s", ds.PropertyType(typ)
) |
| 154 } | 157 } |
| 155 return | 158 return |
| 156 } | 159 } |
| 157 | 160 |
| 158 // Write writes a GeoPoint to the buffer. | 161 // WriteGeoPoint writes a GeoPoint to the buffer. |
| 159 func (gp GeoPoint) Write(buf Buffer) (err error) { | 162 func WriteGeoPoint(buf Buffer, gp ds.GeoPoint) (err error) { |
| 160 defer recoverTo(&err) | 163 defer recoverTo(&err) |
| 161 _, e := cmpbin.WriteFloat64(buf, gp.Lat) | 164 _, e := cmpbin.WriteFloat64(buf, gp.Lat) |
| 162 panicIf(e) | 165 panicIf(e) |
| 163 _, e = cmpbin.WriteFloat64(buf, gp.Lng) | 166 _, e = cmpbin.WriteFloat64(buf, gp.Lng) |
| 164 return e | 167 return e |
| 165 } | 168 } |
| 166 | 169 |
| 167 // Read reads a GeoPoint from the buffer. | 170 // ReadGeoPoint reads a GeoPoint from the buffer. |
| 168 func (gp *GeoPoint) Read(buf Buffer) (err error) { | 171 func ReadGeoPoint(buf Buffer) (gp ds.GeoPoint, err error) { |
| 169 defer recoverTo(&err) | 172 defer recoverTo(&err) |
| 170 e := error(nil) | 173 e := error(nil) |
| 171 gp.Lat, _, e = cmpbin.ReadFloat64(buf) | 174 gp.Lat, _, e = cmpbin.ReadFloat64(buf) |
| 172 panicIf(e) | 175 panicIf(e) |
| 173 | 176 |
| 174 gp.Lng, _, e = cmpbin.ReadFloat64(buf) | 177 gp.Lng, _, e = cmpbin.ReadFloat64(buf) |
| 175 panicIf(e) | 178 panicIf(e) |
| 176 | 179 |
| 177 if !gp.Valid() { | 180 if !gp.Valid() { |
| 178 err = fmt.Errorf("helper: decoded invalid GeoPoint: %v", gp) | 181 err = fmt.Errorf("helper: decoded invalid GeoPoint: %v", gp) |
| (...skipping 16 matching lines...) Expand all Loading... |
| 195 | 198 |
| 196 // ReadTime reads a time.Time from the buffer. | 199 // ReadTime reads a time.Time from the buffer. |
| 197 func ReadTime(buf Buffer) (time.Time, error) { | 200 func ReadTime(buf Buffer) (time.Time, error) { |
| 198 v, _, err := cmpbin.ReadUint(buf) | 201 v, _, err := cmpbin.ReadUint(buf) |
| 199 if err != nil { | 202 if err != nil { |
| 200 return time.Time{}, err | 203 return time.Time{}, err |
| 201 } | 204 } |
| 202 return time.Unix(int64(v/1e6), int64((v%1e6)*1e3)).UTC(), nil | 205 return time.Unix(int64(v/1e6), int64((v%1e6)*1e3)).UTC(), nil |
| 203 } | 206 } |
| 204 | 207 |
| 205 // Write writes a Property to the buffer. `context` behaves the same | 208 // WriteProperty 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 | 209 // way that it does for WriteKey, but only has an effect if `p` contains a |
| 207 // Key as its Value. | 210 // Key as its Value. |
| 208 func (p *Property) Write(buf Buffer, context KeyContext) (err error) { | 211 func WriteProperty(buf Buffer, context KeyContext, p ds.Property) (err error) { |
| 209 defer recoverTo(&err) | 212 defer recoverTo(&err) |
| 210 typb := byte(p.Type()) | 213 typb := byte(p.Type()) |
| 211 » if p.IndexSetting() == NoIndex { | 214 » if p.IndexSetting() == ds.NoIndex { |
| 212 typb |= 0x80 | 215 typb |= 0x80 |
| 213 } | 216 } |
| 214 panicIf(buf.WriteByte(typb)) | 217 panicIf(buf.WriteByte(typb)) |
| 215 switch p.Type() { | 218 switch p.Type() { |
| 216 » case PTNull, PTBoolTrue, PTBoolFalse: | 219 » case ds.PTNull, ds.PTBoolTrue, ds.PTBoolFalse: |
| 217 » case PTInt: | 220 » case ds.PTInt: |
| 218 _, err = cmpbin.WriteInt(buf, p.Value().(int64)) | 221 _, err = cmpbin.WriteInt(buf, p.Value().(int64)) |
| 219 » case PTFloat: | 222 » case ds.PTFloat: |
| 220 _, err = cmpbin.WriteFloat64(buf, p.Value().(float64)) | 223 _, err = cmpbin.WriteFloat64(buf, p.Value().(float64)) |
| 221 » case PTString: | 224 » case ds.PTString: |
| 222 _, err = cmpbin.WriteString(buf, p.Value().(string)) | 225 _, err = cmpbin.WriteString(buf, p.Value().(string)) |
| 223 » case PTBytes: | 226 » case ds.PTBytes: |
| 224 » » if p.IndexSetting() == NoIndex { | 227 » » if p.IndexSetting() == ds.NoIndex { |
| 225 _, err = cmpbin.WriteBytes(buf, p.Value().([]byte)) | 228 _, err = cmpbin.WriteBytes(buf, p.Value().([]byte)) |
| 226 } else { | 229 } else { |
| 227 » » » _, err = cmpbin.WriteBytes(buf, p.Value().(ByteString)) | 230 » » » _, err = cmpbin.WriteBytes(buf, p.Value().(ds.ByteString
)) |
| 228 } | 231 } |
| 229 » case PTTime: | 232 » case ds.PTTime: |
| 230 err = WriteTime(buf, p.Value().(time.Time)) | 233 err = WriteTime(buf, p.Value().(time.Time)) |
| 231 » case PTGeoPoint: | 234 » case ds.PTGeoPoint: |
| 232 » » err = p.Value().(GeoPoint).Write(buf) | 235 » » err = WriteGeoPoint(buf, p.Value().(ds.GeoPoint)) |
| 233 » case PTKey: | 236 » case ds.PTKey: |
| 234 » » err = WriteKey(buf, context, p.Value().(Key)) | 237 » » err = WriteKey(buf, context, p.Value().(ds.Key)) |
| 235 » case PTBlobKey: | 238 » case ds.PTBlobKey: |
| 236 _, err = cmpbin.WriteString(buf, string(p.Value().(blobstore.Key
))) | 239 _, err = cmpbin.WriteString(buf, string(p.Value().(blobstore.Key
))) |
| 237 } | 240 } |
| 238 return | 241 return |
| 239 } | 242 } |
| 240 | 243 |
| 241 // Read reads a Property from the buffer. `context`, `appid`, and | 244 // ReadProperty reads a Property from the buffer. `context`, `appid`, and |
| 242 // `namespace` behave the same way they do for ReadKey, but only have an | 245 // `namespace` behave the same way they do for ReadKey, but only have an |
| 243 // effect if the decoded property has a Key value. | 246 // effect if the decoded property has a Key value. |
| 244 func (p *Property) Read(buf Buffer, context KeyContext, appid, namespace string)
(err error) { | 247 func ReadProperty(buf Buffer, context KeyContext, appid, namespace string) (p ds
.Property, err error) { |
| 245 val := interface{}(nil) | 248 val := interface{}(nil) |
| 246 typb, err := buf.ReadByte() | 249 typb, err := buf.ReadByte() |
| 247 if err != nil { | 250 if err != nil { |
| 248 return | 251 return |
| 249 } | 252 } |
| 250 » is := ShouldIndex | 253 » is := ds.ShouldIndex |
| 251 if (typb & 0x80) != 0 { | 254 if (typb & 0x80) != 0 { |
| 252 » » is = NoIndex | 255 » » is = ds.NoIndex |
| 253 } | 256 } |
| 254 » switch PropertyType(typb & 0x7f) { | 257 » switch ds.PropertyType(typb & 0x7f) { |
| 255 » case PTNull: | 258 » case ds.PTNull: |
| 256 » case PTBoolTrue: | 259 » case ds.PTBoolTrue: |
| 257 val = true | 260 val = true |
| 258 » case PTBoolFalse: | 261 » case ds.PTBoolFalse: |
| 259 val = false | 262 val = false |
| 260 » case PTInt: | 263 » case ds.PTInt: |
| 261 val, _, err = cmpbin.ReadInt(buf) | 264 val, _, err = cmpbin.ReadInt(buf) |
| 262 » case PTFloat: | 265 » case ds.PTFloat: |
| 263 val, _, err = cmpbin.ReadFloat64(buf) | 266 val, _, err = cmpbin.ReadFloat64(buf) |
| 264 » case PTString: | 267 » case ds.PTString: |
| 265 val, _, err = cmpbin.ReadString(buf) | 268 val, _, err = cmpbin.ReadString(buf) |
| 266 » case PTBytes: | 269 » case ds.PTBytes: |
| 267 b := []byte(nil) | 270 b := []byte(nil) |
| 268 if b, _, err = cmpbin.ReadBytes(buf); err != nil { | 271 if b, _, err = cmpbin.ReadBytes(buf); err != nil { |
| 269 break | 272 break |
| 270 } | 273 } |
| 271 » » if is == NoIndex { | 274 » » if is == ds.NoIndex { |
| 272 val = b | 275 val = b |
| 273 } else { | 276 } else { |
| 274 » » » val = ByteString(b) | 277 » » » val = ds.ByteString(b) |
| 275 } | 278 } |
| 276 » case PTTime: | 279 » case ds.PTTime: |
| 277 val, err = ReadTime(buf) | 280 val, err = ReadTime(buf) |
| 278 » case PTGeoPoint: | 281 » case ds.PTGeoPoint: |
| 279 » » gp := GeoPoint{} | 282 » » val, err = ReadGeoPoint(buf) |
| 280 » » err = gp.Read(buf) | 283 » case ds.PTKey: |
| 281 » » val = gp | |
| 282 » case PTKey: | |
| 283 val, err = ReadKey(buf, context, appid, namespace) | 284 val, err = ReadKey(buf, context, appid, namespace) |
| 284 » case PTBlobKey: | 285 » case ds.PTBlobKey: |
| 285 s := "" | 286 s := "" |
| 286 if s, _, err = cmpbin.ReadString(buf); err != nil { | 287 if s, _, err = cmpbin.ReadString(buf); err != nil { |
| 287 break | 288 break |
| 288 } | 289 } |
| 289 val = blobstore.Key(s) | 290 val = blobstore.Key(s) |
| 290 default: | 291 default: |
| 291 err = fmt.Errorf("read: unknown type! %v", typb) | 292 err = fmt.Errorf("read: unknown type! %v", typb) |
| 292 } | 293 } |
| 293 if err == nil { | 294 if err == nil { |
| 294 err = p.SetValue(val, is) | 295 err = p.SetValue(val, is) |
| 295 } | 296 } |
| 296 return | 297 return |
| 297 } | 298 } |
| 298 | 299 |
| 299 // Write writes an entire PropertyMap to the buffer. `context` behaves the same | 300 // WritePropertyMap writes an entire PropertyMap to the buffer. `context` behave
s the same |
| 300 // way that it does for WriteKey. If WritePropertyMapDeterministic is true, then | 301 // 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 // 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 // (mostly useful for testing, but also potentially useful if you need to make |
| 303 // a hash of the property data). | 304 // a hash of the property data). |
| 304 // | 305 // |
| 305 // Write skips metadata keys. | 306 // Write skips metadata keys. |
| 306 func (pm PropertyMap) Write(buf Buffer, context KeyContext) (err error) { | 307 func WritePropertyMap(buf Buffer, context KeyContext, pm ds.PropertyMap) (err er
ror) { |
| 307 defer recoverTo(&err) | 308 defer recoverTo(&err) |
| 308 rows := make(sort.StringSlice, 0, len(pm)) | 309 rows := make(sort.StringSlice, 0, len(pm)) |
| 309 tmpBuf := &bytes.Buffer{} | 310 tmpBuf := &bytes.Buffer{} |
| 311 pm, _ = pm.Save(false) |
| 310 for name, vals := range pm { | 312 for name, vals := range pm { |
| 311 if isMetaKey(name) { | |
| 312 continue | |
| 313 } | |
| 314 tmpBuf.Reset() | 313 tmpBuf.Reset() |
| 315 _, e := cmpbin.WriteString(tmpBuf, name) | 314 _, e := cmpbin.WriteString(tmpBuf, name) |
| 316 panicIf(e) | 315 panicIf(e) |
| 317 _, e = cmpbin.WriteUint(tmpBuf, uint64(len(vals))) | 316 _, e = cmpbin.WriteUint(tmpBuf, uint64(len(vals))) |
| 318 panicIf(e) | 317 panicIf(e) |
| 319 for _, p := range vals { | 318 for _, p := range vals { |
| 320 » » » panicIf(p.Write(tmpBuf, context)) | 319 » » » panicIf(WriteProperty(tmpBuf, context, p)) |
| 321 } | 320 } |
| 322 rows = append(rows, tmpBuf.String()) | 321 rows = append(rows, tmpBuf.String()) |
| 323 } | 322 } |
| 324 | 323 |
| 325 if WritePropertyMapDeterministic { | 324 if WritePropertyMapDeterministic { |
| 326 rows.Sort() | 325 rows.Sort() |
| 327 } | 326 } |
| 328 | 327 |
| 329 _, e := cmpbin.WriteUint(buf, uint64(len(pm))) | 328 _, e := cmpbin.WriteUint(buf, uint64(len(pm))) |
| 330 panicIf(e) | 329 panicIf(e) |
| 331 for _, r := range rows { | 330 for _, r := range rows { |
| 332 _, e := buf.WriteString(r) | 331 _, e := buf.WriteString(r) |
| 333 panicIf(e) | 332 panicIf(e) |
| 334 } | 333 } |
| 335 return | 334 return |
| 336 } | 335 } |
| 337 | 336 |
| 338 // Read reads a PropertyMap from the buffer. `context` and | 337 // ReadPropertyMap reads a PropertyMap from the buffer. `context` and |
| 339 // friends behave the same way that they do for ReadKey. | 338 // 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) { | 339 func ReadPropertyMap(buf Buffer, context KeyContext, appid, namespace string) (p
m ds.PropertyMap, err error) { |
| 341 defer recoverTo(&err) | 340 defer recoverTo(&err) |
| 342 | 341 |
| 343 numRows := uint64(0) | 342 numRows := uint64(0) |
| 344 numRows, _, e := cmpbin.ReadUint(buf) | 343 numRows, _, e := cmpbin.ReadUint(buf) |
| 345 panicIf(e) | 344 panicIf(e) |
| 346 if numRows > ReadPropertyMapReasonableLimit { | 345 if numRows > ReadPropertyMapReasonableLimit { |
| 347 err = fmt.Errorf("helper: tried to decode map with huge number o
f rows %d", numRows) | 346 err = fmt.Errorf("helper: tried to decode map with huge number o
f rows %d", numRows) |
| 348 return | 347 return |
| 349 } | 348 } |
| 350 | 349 |
| 351 » name, prop := "", Property{} | 350 » pm = make(ds.PropertyMap, numRows) |
| 351 |
| 352 » name, prop := "", ds.Property{} |
| 352 for i := uint64(0); i < numRows; i++ { | 353 for i := uint64(0); i < numRows; i++ { |
| 353 name, _, e = cmpbin.ReadString(buf) | 354 name, _, e = cmpbin.ReadString(buf) |
| 354 panicIf(e) | 355 panicIf(e) |
| 355 | 356 |
| 356 numProps, _, e := cmpbin.ReadUint(buf) | 357 numProps, _, e := cmpbin.ReadUint(buf) |
| 357 panicIf(e) | 358 panicIf(e) |
| 358 if numProps > ReadPropertyMapReasonableLimit { | 359 if numProps > ReadPropertyMapReasonableLimit { |
| 359 err = fmt.Errorf("helper: tried to decode map with huge
number of properties %d", numProps) | 360 err = fmt.Errorf("helper: tried to decode map with huge
number of properties %d", numProps) |
| 360 return | 361 return |
| 361 } | 362 } |
| 362 » » props := make([]Property, 0, numProps) | 363 » » props := make([]ds.Property, 0, numProps) |
| 363 for j := uint64(0); j < numProps; j++ { | 364 for j := uint64(0); j < numProps; j++ { |
| 364 » » » panicIf(prop.Read(buf, context, appid, namespace)) | 365 » » » prop, err = ReadProperty(buf, context, appid, namespace) |
| 366 » » » panicIf(err) |
| 365 props = append(props, prop) | 367 props = append(props, prop) |
| 366 } | 368 } |
| 367 pm[name] = props | 369 pm[name] = props |
| 368 } | 370 } |
| 369 return | 371 return |
| 370 } | 372 } |
| 371 | 373 |
| 372 func (c *IndexColumn) Write(buf Buffer) (err error) { | 374 // WriteIndexColumn writes an IndexColumn to the buffer. |
| 375 func WriteIndexColumn(buf Buffer, c ds.IndexColumn) (err error) { |
| 373 defer recoverTo(&err) | 376 defer recoverTo(&err) |
| 374 | 377 |
| 375 » if c.Direction == ASCENDING { | 378 » if c.Direction == ds.ASCENDING { |
| 376 panicIf(buf.WriteByte(0)) | 379 panicIf(buf.WriteByte(0)) |
| 377 } else { | 380 } else { |
| 378 panicIf(buf.WriteByte(1)) | 381 panicIf(buf.WriteByte(1)) |
| 379 } | 382 } |
| 380 _, err = cmpbin.WriteString(buf, c.Property) | 383 _, err = cmpbin.WriteString(buf, c.Property) |
| 381 return | 384 return |
| 382 } | 385 } |
| 383 | 386 |
| 384 func (c *IndexColumn) Read(buf Buffer) (err error) { | 387 // ReadIndexColumn reads an IndexColumn from the buffer. |
| 388 func ReadIndexColumn(buf Buffer) (c ds.IndexColumn, err error) { |
| 385 defer recoverTo(&err) | 389 defer recoverTo(&err) |
| 386 | 390 |
| 387 dir, err := buf.ReadByte() | 391 dir, err := buf.ReadByte() |
| 388 panicIf(err) | 392 panicIf(err) |
| 389 | 393 |
| 390 switch dir { | 394 switch dir { |
| 391 case 0: | 395 case 0: |
| 392 » » c.Direction = ASCENDING | 396 » » c.Direction = ds.ASCENDING |
| 393 default: | 397 default: |
| 394 » » c.Direction = DESCENDING | 398 » » c.Direction = ds.DESCENDING |
| 395 } | 399 } |
| 396 c.Property, _, err = cmpbin.ReadString(buf) | 400 c.Property, _, err = cmpbin.ReadString(buf) |
| 397 » return err | 401 » return |
| 398 } | 402 } |
| 399 | 403 |
| 400 func (i *IndexDefinition) Write(buf Buffer) (err error) { | 404 // WriteIndexDefinition writes an IndexDefinition to the buffer |
| 405 func WriteIndexDefinition(buf Buffer, i ds.IndexDefinition) (err error) { |
| 401 defer recoverTo(&err) | 406 defer recoverTo(&err) |
| 402 | 407 |
| 403 if i.Builtin() { | 408 if i.Builtin() { |
| 404 panicIf(buf.WriteByte(0)) | 409 panicIf(buf.WriteByte(0)) |
| 405 } else { | 410 } else { |
| 406 panicIf(buf.WriteByte(1)) | 411 panicIf(buf.WriteByte(1)) |
| 407 } | 412 } |
| 408 _, err = cmpbin.WriteString(buf, i.Kind) | 413 _, err = cmpbin.WriteString(buf, i.Kind) |
| 409 panicIf(err) | 414 panicIf(err) |
| 410 if !i.Ancestor { | 415 if !i.Ancestor { |
| 411 panicIf(buf.WriteByte(0)) | 416 panicIf(buf.WriteByte(0)) |
| 412 } else { | 417 } else { |
| 413 panicIf(buf.WriteByte(1)) | 418 panicIf(buf.WriteByte(1)) |
| 414 } | 419 } |
| 415 _, err = cmpbin.WriteUint(buf, uint64(len(i.SortBy))) | 420 _, err = cmpbin.WriteUint(buf, uint64(len(i.SortBy))) |
| 416 panicIf(err) | 421 panicIf(err) |
| 417 for _, sb := range i.SortBy { | 422 for _, sb := range i.SortBy { |
| 418 » » panicIf(sb.Write(buf)) | 423 » » panicIf(WriteIndexColumn(buf, sb)) |
| 419 } | 424 } |
| 420 return | 425 return |
| 421 } | 426 } |
| 422 | 427 |
| 423 func (i *IndexDefinition) Read(buf Buffer) (err error) { | 428 // ReadIndexDefinition reads an IndexDefinition from the buffer. |
| 429 func ReadIndexDefinition(buf Buffer) (i ds.IndexDefinition, err error) { |
| 424 defer recoverTo(&err) | 430 defer recoverTo(&err) |
| 425 | 431 |
| 426 // discard builtin/complex byte | 432 // discard builtin/complex byte |
| 427 _, err = buf.ReadByte() | 433 _, err = buf.ReadByte() |
| 428 panicIf(err) | 434 panicIf(err) |
| 429 | 435 |
| 430 i.Kind, _, err = cmpbin.ReadString(buf) | 436 i.Kind, _, err = cmpbin.ReadString(buf) |
| 431 panicIf(err) | 437 panicIf(err) |
| 432 | 438 |
| 433 anc, err := buf.ReadByte() | 439 anc, err := buf.ReadByte() |
| 434 panicIf(err) | 440 panicIf(err) |
| 435 | 441 |
| 436 i.Ancestor = anc == 1 | 442 i.Ancestor = anc == 1 |
| 437 | 443 |
| 438 numSorts, _, err := cmpbin.ReadUint(buf) | 444 numSorts, _, err := cmpbin.ReadUint(buf) |
| 439 panicIf(err) | 445 panicIf(err) |
| 440 | 446 |
| 441 if numSorts > MaxIndexColumns { | 447 if numSorts > MaxIndexColumns { |
| 442 » » return fmt.Errorf("datastore: Got over %d sort orders: %d", | 448 » » err = fmt.Errorf("datastore: Got over %d sort orders: %d", |
| 443 MaxIndexColumns, numSorts) | 449 MaxIndexColumns, numSorts) |
| 450 return |
| 444 } | 451 } |
| 445 | 452 |
| 446 if numSorts > 0 { | 453 if numSorts > 0 { |
| 447 » » i.SortBy = make([]IndexColumn, numSorts) | 454 » » i.SortBy = make([]ds.IndexColumn, numSorts) |
| 448 for idx := range i.SortBy { | 455 for idx := range i.SortBy { |
| 449 » » » panicIf(i.SortBy[idx].Read(buf)) | 456 » » » i.SortBy[idx], err = ReadIndexColumn(buf) |
| 457 » » » panicIf(err) |
| 450 } | 458 } |
| 451 } | 459 } |
| 452 | 460 |
| 453 return | 461 return |
| 454 } | 462 } |
| 455 | 463 |
| 464 func toBytesErr(i interface{}, ctx KeyContext) (ret []byte, err error) { |
| 465 buf := &bytes.Buffer{} |
| 466 switch x := i.(type) { |
| 467 case ds.GeoPoint: |
| 468 err = WriteGeoPoint(buf, x) |
| 469 |
| 470 case ds.IndexColumn: |
| 471 err = WriteIndexColumn(buf, x) |
| 472 |
| 473 case ds.IndexDefinition: |
| 474 err = WriteIndexDefinition(buf, x) |
| 475 |
| 476 case ds.Key: |
| 477 err = WriteKey(buf, ctx, x) |
| 478 |
| 479 case ds.KeyTok: |
| 480 err = WriteKeyTok(buf, x) |
| 481 |
| 482 case ds.Property: |
| 483 err = WriteProperty(buf, ctx, x) |
| 484 |
| 485 case ds.PropertyMap: |
| 486 err = WritePropertyMap(buf, ctx, x) |
| 487 |
| 488 case time.Time: |
| 489 err = WriteTime(buf, x) |
| 490 |
| 491 default: |
| 492 err = fmt.Errorf("unknown type for ToBytes: %T", i) |
| 493 } |
| 494 if err == nil { |
| 495 ret = buf.Bytes() |
| 496 } |
| 497 return |
| 498 } |
| 499 |
| 500 // ToBytesErr serializes i to a byte slice, if it's one of the type supported |
| 501 // by this library, otherwise it returns an error. |
| 502 // |
| 503 // Key types will be serialized using the 'WithoutContext' option (e.g. their |
| 504 // encoded forms will not contain AppID or Namespace). |
| 505 func ToBytesErr(i interface{}) ([]byte, error) { |
| 506 return toBytesErr(i, WithoutContext) |
| 507 } |
| 508 |
| 509 // ToBytesWithContextErr serializes i to a byte slice, if it's one of the type |
| 510 // supported by this library, otherwise it returns an error. |
| 511 // |
| 512 // Key types will be serialized using the 'WithContext' option (e.g. their |
| 513 // encoded forms will contain AppID and Namespace). |
| 514 func ToBytesWithContextErr(i interface{}) ([]byte, error) { |
| 515 return toBytesErr(i, WithContext) |
| 516 } |
| 517 |
| 518 // ToBytes serializes i to a byte slice, if it's one of the type supported |
| 519 // by this library. If an error is encountered (e.g. `i` is not a supported |
| 520 // type), this method panics. |
| 521 // |
| 522 // Key types will be serialized using the 'WithoutContext' option (e.g. their |
| 523 // encoded forms will not contain AppID or Namespace). |
| 524 func ToBytes(i interface{}) []byte { |
| 525 ret, err := ToBytesErr(i) |
| 526 if err != nil { |
| 527 panic(err) |
| 528 } |
| 529 return ret |
| 530 } |
| 531 |
| 532 // ToBytesWithContext serializes i to a byte slice, if it's one of the type |
| 533 // supported by this library. If an error is encountered (e.g. `i` is not |
| 534 // a supported type), this method panics. |
| 535 // |
| 536 // Key types will be serialized using the 'WithContext' option (e.g. their |
| 537 // encoded forms will not contain AppID or Namespace). |
| 538 func ToBytesWithContext(i interface{}) []byte { |
| 539 ret, err := ToBytesWithContextErr(i) |
| 540 if err != nil { |
| 541 panic(err) |
| 542 } |
| 543 return ret |
| 544 } |
| 545 |
| 456 type parseError error | 546 type parseError error |
| 457 | 547 |
| 458 func panicIf(err error) { | 548 func panicIf(err error) { |
| 459 if err != nil { | 549 if err != nil { |
| 460 panic(parseError(err)) | 550 panic(parseError(err)) |
| 461 } | 551 } |
| 462 } | 552 } |
| 463 | 553 |
| 464 func recoverTo(err *error) { | 554 func recoverTo(err *error) { |
| 465 if r := recover(); r != nil { | 555 if r := recover(); r != nil { |
| 466 if rerr := r.(parseError); rerr != nil { | 556 if rerr := r.(parseError); rerr != nil { |
| 467 *err = error(rerr) | 557 *err = error(rerr) |
| 468 } | 558 } |
| 469 } | 559 } |
| 470 } | 560 } |
| OLD | NEW |