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 |