Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(463)

Side by Side Diff: helper/serialize.go

Issue 1243323002: Refactor a bit. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: fix golint Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « helper/internal/protos/datastore/datastore_v3.pb.go ('k') | helper/serialize_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 helper
6
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "sort"
12 "time"
13
14 "github.com/luci/gae"
15 "github.com/luci/luci-go/common/cmpbin"
16 )
17
18 // WriteDSPropertyMapDeterministic allows tests to make WriteDSPropertyMap
19 // deterministic.
20 var WriteDSPropertyMapDeterministic = false
21
22 // ReadDSPropertyMapReasonableLimit sets a limit on the number of rows and
23 // number of properties per row which can be read by ReadDSPropertyMap. The
24 // total number of Property objects readable by this method is this number
25 // squared (e.g. Limit rows * Limit properties)
26 const ReadDSPropertyMapReasonableLimit uint64 = 30000
27
28 // ReadKeyNumToksReasonableLimit is the maximum number of DSKey tokens that
29 // ReadDSKey is willing to read for a single key.
30 const ReadKeyNumToksReasonableLimit uint64 = 50
31
32 // DSKeyContext controls whether the various Write and Read serializtion
33 // routines should encode the context of DSKeys (read: the appid and namespace).
34 // Frequently the appid and namespace of keys are known in advance and so there' s
35 // no reason to redundantly encode them.
36 type DSKeyContext bool
37
38 // With- and WithoutContext indicate if the serialization method should include
39 // context for DSKeys. See DSKeyContext for more information.
40 const (
41 WithContext DSKeyContext = true
42 WithoutContext = false
43 )
44
45 // WriteDSKey encodes a key to the buffer. If context is WithContext, then this
46 // encoded value will include the appid and namespace of the key.
47 func WriteDSKey(buf *bytes.Buffer, context DSKeyContext, k gae.DSKey) {
48 // [appid ++ namespace]? ++ #tokens ++ tokens*
49 appid, namespace, toks := DSKeySplit(k)
50 if context == WithContext {
51 buf.WriteByte(1)
52 cmpbin.WriteString(buf, appid)
53 cmpbin.WriteString(buf, namespace)
54 } else {
55 buf.WriteByte(0)
56 }
57 cmpbin.WriteUint(buf, uint64(len(toks)))
58 for _, tok := range toks {
59 WriteDSKeyTok(buf, tok)
60 }
61 }
62
63 // ReadDSKey deserializes a key from the buffer. The value of context must match
64 // the value of context that was passed to WriteDSKey when the key was encoded.
65 // If context == WithoutContext, then the appid and namespace parameters are
66 // used in the decoded DSKey. Otherwise they're ignored.
67 func ReadDSKey(buf *bytes.Buffer, context DSKeyContext, appid, namespace string) (ret gae.DSKey, err error) {
68 actualCtx, err := buf.ReadByte()
69 if err != nil {
70 return
71 }
72
73 actualAid, actualNS := "", ""
74 if actualCtx == 1 {
75 if actualAid, _, err = cmpbin.ReadString(buf); err != nil {
76 return
77 }
78 if actualNS, _, err = cmpbin.ReadString(buf); err != nil {
79 return
80 }
81 } else if actualCtx != 0 {
82 err = fmt.Errorf("helper: expected actualCtx to be 0 or 1, got % d", actualCtx)
83 return
84 }
85
86 if context == WithoutContext {
87 // overrwrite with the supplied ones
88 actualAid = appid
89 actualNS = namespace
90 }
91
92 numToks := uint64(0)
93 if numToks, _, err = cmpbin.ReadUint(buf); err != nil {
94 return
95 }
96 if numToks > ReadKeyNumToksReasonableLimit {
97 err = fmt.Errorf("helper: tried to decode huge key of length %d" , numToks)
98 return
99 }
100
101 toks := make([]gae.DSKeyTok, numToks)
102 for i := uint64(0); i < numToks; i++ {
103 if toks[i], err = ReadDSKeyTok(buf); err != nil {
104 return
105 }
106 }
107
108 return NewDSKeyToks(actualAid, actualNS, toks), nil
109 }
110
111 // WriteDSKeyTok writes a DSKeyTok to the buffer. You usually want WriteDSKey
112 // instead of this.
113 func WriteDSKeyTok(buf *bytes.Buffer, tok gae.DSKeyTok) {
114 // tok.kind ++ typ ++ [tok.stringID || tok.intID]
115 cmpbin.WriteString(buf, tok.Kind)
116 if tok.StringID != "" {
117 buf.WriteByte(byte(gae.DSPTString))
118 cmpbin.WriteString(buf, tok.StringID)
119 } else {
120 buf.WriteByte(byte(gae.DSPTInt))
121 cmpbin.WriteInt(buf, tok.IntID)
122 }
123 }
124
125 // ReadDSKeyTok reads a DSKeyTok from the buffer. You usually want ReadDSKey
126 // instead of this.
127 func ReadDSKeyTok(buf *bytes.Buffer) (ret gae.DSKeyTok, err error) {
128 if ret.Kind, _, err = cmpbin.ReadString(buf); err != nil {
129 return
130 }
131 typ, err := buf.ReadByte()
132 if err != nil {
133 return
134 }
135 switch gae.DSPropertyType(typ) {
136 case gae.DSPTString:
137 ret.StringID, _, err = cmpbin.ReadString(buf)
138 case gae.DSPTInt:
139 ret.IntID, _, err = cmpbin.ReadInt(buf)
140 if err == nil && ret.IntID <= 0 {
141 err = errors.New("helper: decoded key with empty stringI D and zero/negative intID")
142 }
143 default:
144 err = fmt.Errorf("helper: invalid type %s", gae.DSPropertyType(t yp))
145 }
146 return
147 }
148
149 // WriteDSGeoPoint writes a DSGeoPoint to the buffer.
150 func WriteDSGeoPoint(buf *bytes.Buffer, gp gae.DSGeoPoint) {
151 cmpbin.WriteFloat64(buf, gp.Lat)
152 cmpbin.WriteFloat64(buf, gp.Lng)
153 }
154
155 // ReadDSGeoPoint reads a DSGeoPoint from the buffer.
156 func ReadDSGeoPoint(buf *bytes.Buffer) (gp gae.DSGeoPoint, err error) {
157 if gp.Lat, _, err = cmpbin.ReadFloat64(buf); err != nil {
158 return
159 }
160 gp.Lng, _, err = cmpbin.ReadFloat64(buf)
161 if err == nil && !gp.Valid() {
162 err = fmt.Errorf("helper: decoded invalid DSGeoPoint: %v", gp)
163 }
164 return
165 }
166
167 // WriteTime writes a time.Time in a byte-sortable way.
168 //
169 // This method truncates the time to microseconds and drops the timezone,
170 // because that's the (undocumented) way that the appengine SDK does it.
171 func WriteTime(buf *bytes.Buffer, t time.Time) {
172 name, off := t.Zone()
173 if name != "UTC" || off != 0 {
174 panic(fmt.Errorf("helper: UTC OR DEATH: %s", t))
175 }
176 cmpbin.WriteUint(buf, uint64(t.Unix())*1e6+uint64(t.Nanosecond()/1e3))
177 }
178
179 // ReadTime reads a time.Time from the buffer.
180 func ReadTime(buf *bytes.Buffer) (time.Time, error) {
181 v, _, err := cmpbin.ReadUint(buf)
182 if err != nil {
183 return time.Time{}, err
184 }
185 return time.Unix(int64(v/1e6), int64((v%1e6)*1e3)).UTC(), nil
186 }
187
188 // WriteDSProperty writes a DSProperty to the buffer. `context` behaves the same
189 // way that it does for WriteDSKey, but only has an effect if `p` contains a
190 // DSKey as its Value.
191 func WriteDSProperty(buf *bytes.Buffer, p gae.DSProperty, context DSKeyContext) {
192 typb := byte(p.Type())
193 if p.IndexSetting() == gae.NoIndex {
194 typb |= 0x80
195 }
196 buf.WriteByte(typb)
197 switch p.Type() {
198 case gae.DSPTNull, gae.DSPTBoolTrue, gae.DSPTBoolFalse:
199 case gae.DSPTInt:
200 cmpbin.WriteInt(buf, p.Value().(int64))
201 case gae.DSPTFloat:
202 cmpbin.WriteFloat64(buf, p.Value().(float64))
203 case gae.DSPTString:
204 cmpbin.WriteString(buf, p.Value().(string))
205 case gae.DSPTBytes:
206 if p.IndexSetting() == gae.NoIndex {
207 cmpbin.WriteBytes(buf, p.Value().([]byte))
208 } else {
209 cmpbin.WriteBytes(buf, p.Value().(gae.DSByteString))
210 }
211 case gae.DSPTTime:
212 WriteTime(buf, p.Value().(time.Time))
213 case gae.DSPTGeoPoint:
214 WriteDSGeoPoint(buf, p.Value().(gae.DSGeoPoint))
215 case gae.DSPTKey:
216 WriteDSKey(buf, context, p.Value().(gae.DSKey))
217 case gae.DSPTBlobKey:
218 cmpbin.WriteString(buf, string(p.Value().(gae.BSKey)))
219 }
220 }
221
222 // ReadDSProperty reads a DSProperty from the buffer. `context`, `appid`, and
223 // `namespace` behave the same way they do for ReadDSKey, but only have an
224 // effect if the decoded property has a DSKey value.
225 func ReadDSProperty(buf *bytes.Buffer, context DSKeyContext, appid, namespace st ring) (p gae.DSProperty, err error) {
226 val := interface{}(nil)
227 typb, err := buf.ReadByte()
228 if err != nil {
229 return
230 }
231 is := gae.ShouldIndex
232 if (typb & 0x80) != 0 {
233 is = gae.NoIndex
234 }
235 switch gae.DSPropertyType(typb & 0x7f) {
236 case gae.DSPTNull:
237 case gae.DSPTBoolTrue:
238 val = true
239 case gae.DSPTBoolFalse:
240 val = false
241 case gae.DSPTInt:
242 val, _, err = cmpbin.ReadInt(buf)
243 case gae.DSPTFloat:
244 val, _, err = cmpbin.ReadFloat64(buf)
245 case gae.DSPTString:
246 val, _, err = cmpbin.ReadString(buf)
247 case gae.DSPTBytes:
248 b := []byte(nil)
249 if b, _, err = cmpbin.ReadBytes(buf); err != nil {
250 break
251 }
252 if is == gae.NoIndex {
253 val = b
254 } else {
255 val = gae.DSByteString(b)
256 }
257 case gae.DSPTTime:
258 val, err = ReadTime(buf)
259 case gae.DSPTGeoPoint:
260 val, err = ReadDSGeoPoint(buf)
261 case gae.DSPTKey:
262 val, err = ReadDSKey(buf, context, appid, namespace)
263 case gae.DSPTBlobKey:
264 s := ""
265 if s, _, err = cmpbin.ReadString(buf); err != nil {
266 break
267 }
268 val = gae.BSKey(s)
269 default:
270 err = fmt.Errorf("read: unknown type! %v", typb)
271 }
272 if err == nil {
273 err = p.SetValue(val, is)
274 }
275 return
276 }
277
278 // WriteDSPropertyMap writes an entire DSPropertyMap to the buffer. `context`
279 // behaves the same way that it does for WriteDSKey. If
280 // WriteDSPropertyMapDeterministic is true, then the rows will be sorted by
281 // property name before they're serialized to buf (mostly useful for testing,
282 // but also potentially useful if you need to make a hash of the property data).
283 func WriteDSPropertyMap(buf *bytes.Buffer, propMap gae.DSPropertyMap, context DS KeyContext) {
284 rows := make(sort.StringSlice, 0, len(propMap))
285 tmpBuf := &bytes.Buffer{}
286 for name, vals := range propMap {
287 tmpBuf.Reset()
288 cmpbin.WriteString(tmpBuf, name)
289 cmpbin.WriteUint(tmpBuf, uint64(len(vals)))
290 for _, p := range vals {
291 WriteDSProperty(tmpBuf, p, context)
292 }
293 rows = append(rows, tmpBuf.String())
294 }
295
296 if WriteDSPropertyMapDeterministic {
297 rows.Sort()
298 }
299
300 cmpbin.WriteUint(buf, uint64(len(propMap)))
301 for _, r := range rows {
302 buf.WriteString(r)
303 }
304 }
305
306 // ReadDSPropertyMap reads a DSPropertyMap from the buffer. `context` and
307 // friends behave the same way that they do for ReadDSKey.
308 func ReadDSPropertyMap(buf *bytes.Buffer, context DSKeyContext, appid, namespace string) (propMap gae.DSPropertyMap, err error) {
309 numRows := uint64(0)
310 if numRows, _, err = cmpbin.ReadUint(buf); err != nil {
311 return
312 }
313 if numRows > ReadDSPropertyMapReasonableLimit {
314 err = fmt.Errorf("helper: tried to decode map with huge number o f rows %d", numRows)
315 return
316 }
317
318 name, prop := "", gae.DSProperty{}
319 propMap = make(gae.DSPropertyMap, numRows)
320 for i := uint64(0); i < numRows; i++ {
321 if name, _, err = cmpbin.ReadString(buf); err != nil {
322 return
323 }
324
325 numProps := uint64(0)
326 if numProps, _, err = cmpbin.ReadUint(buf); err != nil {
327 return
328 }
329 if numProps > ReadDSPropertyMapReasonableLimit {
330 err = fmt.Errorf("helper: tried to decode map with huge number of properties %d", numProps)
331 return
332 }
333 props := make([]gae.DSProperty, 0, numProps)
334 for j := uint64(0); j < numProps; j++ {
335 if prop, err = ReadDSProperty(buf, context, appid, names pace); err != nil {
336 return
337 }
338 props = append(props, prop)
339 }
340 propMap[name] = props
341 }
342 return
343 }
OLDNEW
« no previous file with comments | « helper/internal/protos/datastore/datastore_v3.pb.go ('k') | helper/serialize_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698