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 "bytes" | |
9 "errors" | |
10 "fmt" | |
11 | |
12 "github.com/golang/protobuf/proto" | |
13 "github.com/luci/luci-go/common/funnybase" | |
14 "github.com/mjibson/goon" | |
15 | |
16 "appengine/datastore" | |
17 "appengine_internal" | |
18 basepb "appengine_internal/base" | |
19 ) | |
20 | |
21 const keyNumToksReasonableLimit = 50 | |
22 | |
23 ///////////////////////////// fakeGAECtxForNewKey ////////////////////////////// | |
24 | |
25 type fakeGAECtxForNewKey string | |
26 | |
27 func (fakeGAECtxForNewKey) Debugf(format string, args ...interface{}) {} | |
28 func (fakeGAECtxForNewKey) Infof(format string, args ...interface{}) {} | |
29 func (fakeGAECtxForNewKey) Warningf(format string, args ...interface{}) {} | |
30 func (fakeGAECtxForNewKey) Errorf(format string, args ...interface{}) {} | |
31 func (fakeGAECtxForNewKey) Criticalf(format string, args ...interface{}) {} | |
32 func (fakeGAECtxForNewKey) Request() interface{} { retur n nil } | |
33 func (f fakeGAECtxForNewKey) Call(service, method string, in, out appengine_inte rnal.ProtoMessage, opts *appengine_internal.CallOptions) error { | |
34 if service != "__go__" || method != "GetNamespace" { | |
35 panic(fmt.Errorf("fakeGAECtxForNewKey: cannot facilitate Call(%q , %q, ...)", service, method)) | |
36 } | |
37 out.(*basepb.StringProto).Value = proto.String(string(f)) | |
38 return nil | |
39 } | |
40 func (fakeGAECtxForNewKey) FullyQualifiedAppID() string { return "dev~my~app" } | |
41 | |
42 /////////////////////////////// Key construction /////////////////////////////// | |
43 | |
44 func newKey(ns, kind, stringID string, intID int64, parent *datastore.Key) *data store.Key { | |
45 return datastore.NewKey(fakeGAECtxForNewKey(ns), kind, stringID, intID, parent) | |
46 } | |
47 func newKeyObjError(ns string, knr goon.KindNameResolver, src interface{}) (*dat astore.Key, error) { | |
48 return (&goon.Goon{ | |
49 Context: fakeGAECtxForNewKey(ns), | |
50 KindNameResolver: knr}).KeyError(src) | |
51 } | |
52 func newKeyObj(ns string, knr goon.KindNameResolver, obj interface{}) *datastore .Key { | |
53 k, err := newKeyObjError(ns, knr, obj) | |
54 if err != nil { | |
55 panic(err) | |
56 } | |
57 return k | |
58 } | |
59 func kind(ns string, knr goon.KindNameResolver, src interface{}) string { | |
60 return newKeyObj(ns, knr, src).Kind() | |
61 } | |
62 | |
63 /////////////////////////////// Binary Encoding //////////////////////////////// | |
64 | |
65 type keyTok struct { | |
66 kind string | |
67 intID uint64 | |
68 stringID string | |
69 } | |
70 | |
71 func keyToToks(key *datastore.Key) (namespace string, ret []*keyTok) { | |
72 var inner func(*datastore.Key) | |
73 inner = func(k *datastore.Key) { | |
74 if k.Parent() != nil { | |
75 inner(k.Parent()) | |
76 } | |
77 ret = append(ret, &keyTok{k.Kind(), uint64(k.IntID()), k.StringI D()}) | |
78 } | |
79 inner(key) | |
80 namespace = key.Namespace() | |
81 return | |
82 } | |
83 | |
84 func toksToKey(ns string, toks []*keyTok) (ret *datastore.Key) { | |
85 for _, t := range toks { | |
86 ret = newKey(ns, t.kind, t.stringID, int64(t.intID), ret) | |
87 } | |
88 return | |
89 } | |
90 | |
91 type nsOption bool | |
92 | |
93 const ( | |
94 withNS nsOption = true | |
95 noNS = false | |
96 ) | |
97 | |
98 func keyBytes(nso nsOption, k *datastore.Key) []byte { | |
99 buf := &bytes.Buffer{} | |
100 writeKey(buf, nso, k) | |
101 return buf.Bytes() | |
102 } | |
103 | |
104 func keyFromByteString(nso nsOption, d string) (*datastore.Key, error) { | |
105 return readKey(bytes.NewBufferString(d), nso) | |
106 } | |
107 | |
108 func writeKey(buf *bytes.Buffer, nso nsOption, k *datastore.Key) { | |
109 // namespace ++ #tokens ++ [tok.kind ++ tok.stringID ++ tok.intID?]* | |
110 namespace, toks := keyToToks(k) | |
111 if nso == withNS { | |
112 writeString(buf, namespace) | |
113 } | |
114 funnybase.WriteUint(buf, uint64(len(toks))) | |
115 for _, tok := range toks { | |
116 writeString(buf, tok.kind) | |
117 writeString(buf, tok.stringID) | |
118 if tok.stringID == "" { | |
119 funnybase.WriteUint(buf, tok.intID) | |
120 } | |
121 } | |
122 } | |
123 | |
124 func readKey(buf *bytes.Buffer, nso nsOption) (*datastore.Key, error) { | |
125 namespace := "" | |
126 if nso == withNS { | |
127 var err error | |
128 namespace, err = readString(buf) | |
129 if err != nil { | |
130 return nil, err | |
131 } | |
132 } | |
133 | |
134 numToks, err := funnybase.ReadUint(buf) | |
135 if err != nil { | |
136 return nil, err | |
137 } | |
138 if numToks > keyNumToksReasonableLimit { | |
139 return nil, fmt.Errorf("readKey: tried to decode huge key of len gth %d", numToks) | |
140 } | |
141 | |
142 toks := make([]*keyTok, numToks) | |
143 for i := uint64(0); i < numToks; i++ { | |
144 tok := &keyTok{} | |
145 tok.kind, err = readString(buf) | |
M-A Ruel
2015/05/29 02:01:23
group lines 145-146
iannucci
2015/05/29 02:42:28
done.
| |
146 if err != nil { | |
147 return nil, err | |
148 } | |
149 tok.stringID, err = readString(buf) | |
M-A Ruel
2015/05/29 02:01:23
same
iannucci
2015/05/29 02:42:28
done..
| |
150 if err != nil { | |
151 return nil, err | |
152 } | |
153 if tok.stringID == "" { | |
154 tok.intID, err = funnybase.ReadUint(buf) | |
M-A Ruel
2015/05/29 02:01:23
same
iannucci
2015/05/29 02:42:28
done...
| |
155 if err != nil { | |
156 return nil, err | |
157 } | |
158 if tok.intID == 0 { | |
159 return nil, errors.New("readKey: decoded key wit h empty stringID and empty intID.") | |
160 } | |
161 } | |
162 toks[i] = tok | |
163 } | |
164 | |
165 return toksToKey(namespace, toks), nil | |
166 } | |
167 | |
168 //////////////////////////////// Key utilities ///////////////////////////////// | |
169 | |
170 func rootKey(key *datastore.Key) *datastore.Key { | |
171 for key.Parent() != nil { | |
172 key = key.Parent() | |
173 } | |
174 return key | |
175 } | |
176 | |
177 type keyValidOption bool | |
178 | |
179 const ( | |
180 // UserKeyOnly is used with KeyValid, and ensures that the key is only o ne | |
181 // that's valid for a user program to write to. | |
182 UserKeyOnly keyValidOption = false | |
183 | |
184 // AllowSpecialKeys is used with KeyValid, and allows keys for special | |
185 // metadata objects (like "__entity_group__"). | |
186 AllowSpecialKeys = true | |
187 ) | |
188 | |
189 // KeyValid checks to see if a key is valid by applying a bunch of constraint | |
190 // rules to it (e.g. can't have StringID and IntID set at the same time, can't | |
191 // have a parent key which is Incomplete(), etc.) | |
192 // | |
193 // It verifies that the key is also in the provided namespace. It can also | |
194 // reject keys which are 'special' e.g. have a Kind starting with "__". This | |
195 // behavior is controllable with opt. | |
196 func KeyValid(ns string, k *datastore.Key, opt keyValidOption) bool { | |
197 // copied from the appengine SDK because why would any user program need to | |
198 // see if a key is valid? | |
M-A Ruel
2015/05/29 02:01:23
Can't parse sarcasm. :)
iannucci
2015/05/29 02:42:28
add /s :)
| |
199 if k == nil { | |
200 return false | |
201 } | |
202 // since we do "client-side" validataion of namespaces, check this here. | |
M-A Ruel
2015/05/29 02:01:23
validation
iannucci
2015/05/29 02:42:28
done
| |
203 if k.Namespace() != ns { | |
204 return false | |
205 } | |
206 for ; k != nil; k = k.Parent() { | |
207 if opt == UserKeyOnly && len(k.Kind()) >= 2 && k.Kind()[:2] == " __" { // reserve all Kinds starting with __ | |
208 return false | |
209 } | |
210 if k.Kind() == "" || k.AppID() == "" { | |
211 return false | |
212 } | |
213 if k.StringID() != "" && k.IntID() != 0 { | |
214 return false | |
215 } | |
216 if k.Parent() != nil { | |
217 if k.Parent().Incomplete() { | |
218 return false | |
219 } | |
220 if k.Parent().AppID() != k.AppID() || k.Parent().Namespa ce() != k.Namespace() { | |
221 return false | |
222 } | |
223 } | |
224 } | |
225 return true | |
226 } | |
227 | |
228 // KeyCouldBeValid is like KeyValid, but it allows for the possibility that the | |
229 // last token of the key is Incomplete(). It returns true if the Key will become | |
230 // valid once it recieves an automatically-assigned ID. | |
231 func KeyCouldBeValid(ns string, k *datastore.Key, opt keyValidOption) bool { | |
232 // adds an id to k if it's incomplete. | |
233 testKey := k | |
M-A Ruel
2015/05/29 02:01:23
Remove
| |
234 if k.Incomplete() { | |
235 testKey = newKey(ns, k.Kind(), "", 1, k.Parent()) | |
M-A Ruel
2015/05/29 02:01:23
k = newKey(ns, k.Kind(), "", 1, k.Parent())
| |
236 } | |
237 return KeyValid(ns, testKey, opt) | |
M-A Ruel
2015/05/29 02:01:23
return KeyValid(ns, k, opt)
iannucci
2015/05/29 02:42:28
done
| |
238 } | |
OLD | NEW |