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