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