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 datastore | |
6 | |
7 import ( | |
8 "bytes" | |
9 "encoding/base64" | |
10 "encoding/json" | |
11 "errors" | |
12 "fmt" | |
13 "strings" | |
14 | |
15 "github.com/golang/protobuf/proto" | |
16 pb "github.com/luci/gae/service/datastore/internal/protos/datastore" | |
17 ) | |
18 | |
19 // KeyTok is a single token from a multi-part Key. | |
20 type KeyTok struct { | |
21 Kind string | |
22 IntID int64 | |
23 StringID string | |
24 } | |
25 | |
26 func (k KeyTok) Incomplete() bool { | |
27 return k.StringID == "" && k.IntID == 0 | |
28 } | |
29 | |
30 func (k KeyTok) Special() bool { | |
31 return len(k.Kind) >= 2 && k.Kind[:2] == "__" | |
32 } | |
33 | |
34 func (k KeyTok) ID() Property { | |
35 if k.StringID != "" { | |
36 return Property{value: k.StringID, propType: PTString} | |
37 } | |
38 return Property{value: k.IntID, propType: PTInt} | |
39 } | |
40 | |
41 func (k KeyTok) Less(other KeyTok) bool { | |
42 if k.Kind < other.Kind { | |
dnj
2015/09/18 16:47:58
(see note about double-compare below)
| |
43 return true | |
44 } else if k.Kind > other.Kind { | |
45 return false | |
46 } | |
47 a, b := k.ID(), other.ID() | |
48 return a.Less(&b) | |
49 } | |
50 | |
51 // Key is the type used for all datastore operations. | |
52 type Key struct { | |
53 appID string | |
54 namespace string | |
55 toks []KeyTok | |
dnj
2015/09/18 16:47:58
Note that by construction, this array is expected
iannucci
2015/09/18 22:25:48
done
| |
56 } | |
57 | |
58 var _ interface { | |
59 json.Marshaler | |
60 json.Unmarshaler | |
61 } = (*Key)(nil) | |
62 | |
63 // NewToks creates a new Key. It is the Key implementation | |
64 // returned from the various PropertyMap serialization routines, as well as | |
65 // the native key implementation for the in-memory implementation of gae. | |
66 // | |
67 // See Interface.NewKeyToks for a version of this function which automatically | |
68 // provides aid and ns. | |
69 func NewKeyToks(aid, ns string, toks []KeyTok) *Key { | |
70 if len(toks) == 0 { | |
71 return nil | |
72 } | |
73 newToks := make([]KeyTok, len(toks)) | |
74 copy(newToks, toks) | |
dnj
2015/09/18 16:47:58
Since a KeyTok's fields are mutable, should this b
iannucci
2015/09/18 22:25:48
It is a deep copy, since there are no pointers inv
| |
75 return &Key{aid, ns, newToks} | |
76 } | |
77 | |
78 // NewKey is a wrapper around NewToks which has an interface similar | |
79 // to NewKey in the SDK. | |
80 // | |
81 // See Interface.NewKey for a version of this function which automatically | |
82 // provides aid and ns. | |
83 func NewKey(aid, ns, kind, stringID string, intID int64, parent *Key) *Key { | |
84 if parent == nil { | |
85 return &Key{aid, ns, []KeyTok{{kind, intID, stringID}}} | |
86 } | |
87 | |
88 toks := parent.toks | |
89 newToks := make([]KeyTok, len(toks), len(toks)+1) | |
90 copy(newToks, toks) | |
91 newToks = append(newToks, KeyTok{kind, intID, stringID}) | |
92 return &Key{aid, ns, newToks} | |
93 } | |
94 | |
95 // MakeKey is a convenience function for manufacturing a *Key. It should only | |
96 // be used when elems... is known statically (e.g. in the code) to be correct. | |
97 // | |
98 // elems is pairs of (string, string|int|int32|int64) pairs, which correspond to | |
99 // Kind/id pairs. Example: | |
100 // MakeKey("aid", "namespace", "Parent", 1, "Child", "id") | |
101 // | |
102 // Would create the key: | |
103 // aid:namespace:/Parent,1/Child,id | |
104 // | |
105 // If elems is not parsable (e.g. wrong length, wrong types, etc.) this method | |
106 // will panic. | |
107 // | |
108 // See Interface.MakeKey for a version of this function which automatically | |
109 // provides aid and ns. | |
110 func MakeKey(aid, ns string, elems ...interface{}) *Key { | |
111 if len(elems) == 0 { | |
112 return nil | |
113 } | |
114 | |
115 if len(elems)%2 != 0 { | |
116 panic(fmt.Errorf("datastore.MakeKey: odd number of tokens: %v", elems)) | |
117 } | |
118 toks := make([]KeyTok, len(elems)/2) | |
119 for i := 0; i < len(elems); i += 2 { | |
dnj
2015/09/18 16:47:58
Consider rewriting using slice-based traversal. I'
iannucci
2015/09/18 22:25:48
Done
| |
120 knd, ok := elems[i].(string) | |
121 if !ok { | |
122 panic(fmt.Errorf("datastore.MakeKey: bad kind: %v", elem s[i])) | |
123 } | |
124 toks[i/2].Kind = knd | |
125 switch x := elems[i+1].(type) { | |
126 case string: | |
127 toks[i/2].StringID = x | |
128 case int: | |
129 toks[i/2].IntID = int64(x) | |
130 case int32: | |
131 toks[i/2].IntID = int64(x) | |
132 case int64: | |
133 toks[i/2].IntID = int64(x) | |
134 default: | |
135 panic(fmt.Errorf("datastore.MakeKey: bad id: %v", x)) | |
136 } | |
137 } | |
138 | |
139 return NewKeyToks(aid, ns, toks) | |
140 } | |
141 | |
142 // NewKeyEncoded decodes and returns a *Key | |
143 func NewKeyEncoded(encoded string) (ret *Key, err error) { | |
144 ret = &Key{} | |
145 // Re-add padding | |
146 if m := len(encoded) % 4; m != 0 { | |
147 encoded += strings.Repeat("=", 4-m) | |
148 } | |
149 b, err := base64.URLEncoding.DecodeString(encoded) | |
150 if err != nil { | |
151 return | |
152 } | |
153 | |
154 r := &pb.Reference{} | |
155 if err = proto.Unmarshal(b, r); err != nil { | |
156 return | |
157 } | |
158 | |
159 ret.appID = r.GetApp() | |
160 ret.namespace = r.GetNameSpace() | |
161 ret.toks = make([]KeyTok, len(r.Path.Element)) | |
162 for i, e := range r.Path.Element { | |
163 ret.toks[i] = KeyTok{ | |
164 Kind: e.GetType(), | |
165 IntID: e.GetId(), | |
166 StringID: e.GetName(), | |
167 } | |
168 } | |
169 return | |
170 } | |
171 | |
172 func (k *Key) Last() KeyTok { | |
173 if len(k.toks) > 0 { | |
174 return k.toks[len(k.toks)-1] | |
175 } | |
176 return KeyTok{} | |
177 } | |
178 | |
179 // AppID returns the application ID that this Key is for. | |
180 func (k *Key) AppID() string { return k.appID } | |
dnj
2015/09/18 16:47:58
(*Key)(nil) is treated inconsistently. For some me
iannucci
2015/09/18 22:25:48
In the SDK it's impossible to have a non-nil key w
| |
181 | |
182 // Namespace returns the namespace that this Key is for. | |
183 func (k *Key) Namespace() string { return k.namespace } | |
184 | |
185 // String returns a human-readable representation of the key in the form of | |
186 // AID:NS:/Kind,id/Kind,id/... | |
187 func (k *Key) String() string { | |
188 if k == nil { | |
189 return "" | |
190 } | |
191 b := bytes.NewBuffer(make([]byte, 0, 512)) | |
dnj
2015/09/18 16:47:57
I prefer:
bytes.Buffer{}
b.Grow(512)
But really w
iannucci
2015/09/18 22:25:48
This is a carry over from https://github.com/golan
| |
192 fmt.Fprintf(b, "%s:%s:", k.appID, k.namespace) | |
193 for _, t := range k.toks { | |
194 if t.StringID != "" { | |
195 fmt.Fprintf(b, "/%s,%q", t.Kind, t.StringID) | |
196 } else { | |
197 fmt.Fprintf(b, "/%s,%d", t.Kind, t.IntID) | |
198 } | |
199 } | |
200 return b.String() | |
201 } | |
202 | |
203 // Incomplete returns true iff k doesn't have an id yet. | |
204 func (k *Key) Incomplete() bool { | |
205 return k != nil && k.Last().Incomplete() | |
206 } | |
207 | |
208 // Valid determines if a key is valid, according to a couple rules: | |
209 // - k is not nil | |
210 // - every token of k: | |
211 // - (if !allowSpecial) token's kind doesn't start with '__' | |
212 // - token's kind and appid are non-blank | |
213 // - token is not incomplete | |
214 // - all tokens have the same namespace and appid | |
215 func (k *Key) Valid(allowSpecial bool, aid, ns string) bool { | |
216 if k == nil { | |
217 return false | |
218 } | |
219 if aid != k.AppID() || ns != k.Namespace() { | |
220 return false | |
221 } | |
222 for _, t := range k.toks { | |
223 if t.Incomplete() { | |
224 return false | |
225 } | |
226 if !allowSpecial && t.Special() { | |
227 return false | |
228 } | |
229 if t.Kind == "" { | |
230 return false | |
231 } | |
232 if t.StringID != "" && t.IntID != 0 { | |
233 return false | |
234 } | |
235 } | |
236 return true | |
237 } | |
238 | |
239 // PartialValid returns true iff this key is suitable for use in a Put | |
240 // operation. This is the same as Valid(k, false, ...), but also allowing k to | |
241 // be Incomplete(). | |
242 func (k *Key) PartialValid(aid, ns string) bool { | |
243 if k.Incomplete() { | |
244 k = NewKey(k.AppID(), k.Namespace(), k.Last().Kind, "", 1, k.Par ent()) | |
245 } | |
246 return k.Valid(false, aid, ns) | |
247 } | |
248 | |
249 // Parent returns the parent Key of this *Key, or nil. The parent | |
250 // will always have the concrete type of *Key. | |
251 func (k *Key) Parent() *Key { | |
252 if len(k.toks) <= 1 { | |
253 return nil | |
254 } | |
255 return &Key{k.appID, k.namespace, k.toks[:len(k.toks)-1]} | |
256 } | |
257 | |
258 // MarshalJSON allows this key to be automatically marshaled by encoding/json. | |
259 func (k *Key) MarshalJSON() ([]byte, error) { | |
260 return []byte(`"` + k.Encode() + `"`), nil | |
261 } | |
262 | |
263 // Encode encodes the provided key as a base64-encoded protobuf. | |
264 // | |
265 // This encoding is compatible with the SDK-provided encoding and is agnostic | |
266 // to the underlying implementation of the Key. | |
267 // | |
268 // It's encoded with the urlsafe base64 table. | |
dnj
2015/09/18 16:47:58
... with padding stripped off.
iannucci
2015/09/18 22:25:48
Done
| |
269 func (k *Key) Encode() string { | |
270 e := make([]*pb.Path_Element, len(k.toks)) | |
271 for i, t := range k.toks { | |
272 t := t | |
273 e[i] = &pb.Path_Element{ | |
274 Type: &t.Kind, | |
275 } | |
276 if t.StringID != "" { | |
277 e[i].Name = &t.StringID | |
278 } else { | |
279 e[i].Id = &t.IntID | |
280 } | |
281 } | |
282 var namespace *string | |
283 if k.namespace != "" { | |
284 namespace = &k.namespace | |
285 } | |
286 r, err := proto.Marshal(&pb.Reference{ | |
287 App: &k.appID, | |
288 NameSpace: namespace, | |
289 Path: &pb.Path{ | |
290 Element: e, | |
291 }, | |
292 }) | |
293 if err != nil { | |
294 panic(err) | |
295 } | |
296 | |
297 // trim padding | |
298 return strings.TrimRight(base64.URLEncoding.EncodeToString(r), "=") | |
299 } | |
300 | |
301 // UnmarshalJSON allows this key to be automatically unmarshaled by encoding/jso n. | |
302 func (k *Key) UnmarshalJSON(buf []byte) error { | |
303 if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' { | |
304 return errors.New("datastore: bad JSON key") | |
305 } | |
306 nk, err := NewKeyEncoded(string(buf[1 : len(buf)-1])) | |
307 if err != nil { | |
308 return err | |
309 } | |
310 *k = *nk | |
311 return nil | |
312 } | |
313 | |
314 // Root returns the entity root for the given key. | |
315 func (k *Key) Root() *Key { | |
316 if k != nil && len(k.toks) > 1 { | |
317 ret := *k | |
318 ret.toks = ret.toks[:1] | |
319 return &ret | |
320 } | |
321 return k | |
322 } | |
323 | |
324 func (k *Key) Less(other *Key) bool { | |
325 if k == nil && other != nil { | |
326 return true | |
327 } else if k != nil && other == nil { | |
328 return false | |
329 } | |
dnj
2015/09/18 16:47:58
They could still both be nil. Do:
if k == nil {
iannucci
2015/09/18 22:25:48
none of this exists anymore. You have nil you get
| |
330 | |
331 if k.appID < other.appID { | |
dnj
2015/09/18 16:47:58
Slightly better to avoid double string compare and
iannucci
2015/09/18 22:25:48
Which doesn't exist in 1.4 and whose 1.5 implement
dnj
2015/09/18 22:45:12
On 2015/09/18 22:25:48, iannucci wrote:
> On 2015/
| |
332 return true | |
333 } else if k.appID > other.appID { | |
334 return false | |
335 } | |
336 | |
337 if k.namespace < other.namespace { | |
338 return true | |
339 } else if k.namespace > other.namespace { | |
340 return false | |
341 } | |
342 | |
343 lim := len(k.toks) | |
344 if len(other.toks) < lim { | |
345 lim = len(other.toks) | |
346 } | |
347 for i := 0; i < lim; i++ { | |
348 a, b := k.toks[i], other.toks[i] | |
349 if a.Less(b) { | |
350 return true | |
351 } else if b.Less(a) { | |
352 return false | |
353 } | |
354 } | |
355 return len(k.toks) < len(other.toks) | |
356 } | |
357 | |
358 // Equal returns true iff the two keys represent identical key values. | |
359 func (k *Key) Equal(other *Key) (ret bool) { | |
360 ret = k == nil && other == nil | |
361 if !ret && k != nil && other != nil { | |
362 ret = (k.appID == other.appID && | |
363 k.namespace == other.namespace && | |
364 len(k.toks) == len(other.toks)) | |
365 if ret { | |
366 for i, t := range k.toks { | |
367 if ret = t == other.toks[i]; !ret { | |
368 return | |
369 } | |
370 } | |
371 } | |
372 } | |
373 return | |
374 } | |
375 | |
376 func (k *Key) Split() (appID, namespace string, toks []KeyTok) { | |
377 if k != nil { | |
378 appID = k.appID | |
379 namespace = k.namespace | |
380 toks = make([]KeyTok, len(k.toks)) | |
381 copy(toks, k.toks) | |
382 } | |
383 return | |
384 } | |
OLD | NEW |