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

Side by Side Diff: service/datastore/key.go

Issue 1355783002: Refactor keys and queries in datastore service and implementation. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: fix comments Created 5 years, 3 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
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 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 {
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
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)
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
119 toks := make([]KeyTok, len(elems)/2)
120 for i := 0; len(elems) > 0; i, elems = i+1, elems[2:] {
121 knd, ok := elems[0].(string)
122 if !ok {
123 panic(fmt.Errorf("datastore.MakeKey: bad kind: %v", elem s[i]))
124 }
125 t := &toks[i]
126 t.Kind = knd
127 switch x := elems[1].(type) {
128 case string:
129 t.StringID = x
130 case int:
131 t.IntID = int64(x)
132 case int32:
133 t.IntID = int64(x)
134 case int64:
135 t.IntID = int64(x)
136 default:
137 panic(fmt.Errorf("datastore.MakeKey: bad id: %v", x))
138 }
139 }
140
141 return NewKeyToks(aid, ns, toks)
142 }
143
144 // NewKeyEncoded decodes and returns a *Key
145 func NewKeyEncoded(encoded string) (ret *Key, err error) {
146 ret = &Key{}
147 // Re-add padding
148 if m := len(encoded) % 4; m != 0 {
149 encoded += strings.Repeat("=", 4-m)
150 }
151 b, err := base64.URLEncoding.DecodeString(encoded)
152 if err != nil {
153 return
154 }
155
156 r := &pb.Reference{}
157 if err = proto.Unmarshal(b, r); err != nil {
158 return
159 }
160
161 ret.appID = r.GetApp()
162 ret.namespace = r.GetNameSpace()
163 ret.toks = make([]KeyTok, len(r.Path.Element))
164 for i, e := range r.Path.Element {
165 ret.toks[i] = KeyTok{
166 Kind: e.GetType(),
167 IntID: e.GetId(),
168 StringID: e.GetName(),
169 }
170 }
171 return
172 }
173
174 func (k *Key) Last() KeyTok {
175 if len(k.toks) > 0 {
176 return k.toks[len(k.toks)-1]
177 }
178 return KeyTok{}
179 }
180
181 // AppID returns the application ID that this Key is for.
182 func (k *Key) AppID() string { return k.appID }
183
184 // Namespace returns the namespace that this Key is for.
185 func (k *Key) Namespace() string { return k.namespace }
186
187 // String returns a human-readable representation of the key in the form of
188 // AID:NS:/Kind,id/Kind,id/...
189 func (k *Key) String() string {
190 b := bytes.NewBuffer(make([]byte, 0, 512))
191 fmt.Fprintf(b, "%s:%s:", k.appID, k.namespace)
192 for _, t := range k.toks {
193 if t.StringID != "" {
194 fmt.Fprintf(b, "/%s,%q", t.Kind, t.StringID)
195 } else {
196 fmt.Fprintf(b, "/%s,%d", t.Kind, t.IntID)
197 }
198 }
199 return b.String()
200 }
201
202 // Incomplete returns true iff k doesn't have an id yet.
203 func (k *Key) Incomplete() bool {
204 return k.Last().Incomplete()
205 }
206
207 // Valid determines if a key is valid, according to a couple rules:
208 // - k is not nil
209 // - every token of k:
210 // - (if !allowSpecial) token's kind doesn't start with '__'
211 // - token's kind and appid are non-blank
212 // - token is not incomplete
213 // - all tokens have the same namespace and appid
214 func (k *Key) Valid(allowSpecial bool, aid, ns string) bool {
215 if aid != k.appID || ns != k.namespace {
216 return false
217 }
218 for _, t := range k.toks {
219 if t.Incomplete() {
220 return false
221 }
222 if !allowSpecial && t.Special() {
223 return false
224 }
225 if t.Kind == "" {
226 return false
227 }
228 if t.StringID != "" && t.IntID != 0 {
229 return false
230 }
231 }
232 return true
233 }
234
235 // PartialValid returns true iff this key is suitable for use in a Put
236 // operation. This is the same as Valid(k, false, ...), but also allowing k to
237 // be Incomplete().
238 func (k *Key) PartialValid(aid, ns string) bool {
239 if k.Incomplete() {
240 k = NewKey(k.AppID(), k.Namespace(), k.Last().Kind, "", 1, k.Par ent())
241 }
242 return k.Valid(false, aid, ns)
243 }
244
245 // Parent returns the parent Key of this *Key, or nil. The parent
246 // will always have the concrete type of *Key.
247 func (k *Key) Parent() *Key {
248 if len(k.toks) <= 1 {
249 return nil
250 }
251 return &Key{k.appID, k.namespace, k.toks[:len(k.toks)-1]}
252 }
253
254 // MarshalJSON allows this key to be automatically marshaled by encoding/json.
255 func (k *Key) MarshalJSON() ([]byte, error) {
256 return []byte(`"` + k.Encode() + `"`), nil
257 }
258
259 // Encode encodes the provided key as a base64-encoded protobuf.
260 //
261 // This encoding is compatible with the SDK-provided encoding and is agnostic
262 // to the underlying implementation of the Key.
263 //
264 // It's encoded with the urlsafe base64 table without padding.
265 func (k *Key) Encode() string {
266 e := make([]*pb.Path_Element, len(k.toks))
267 for i, t := range k.toks {
268 t := t
269 e[i] = &pb.Path_Element{
270 Type: &t.Kind,
271 }
272 if t.StringID != "" {
273 e[i].Name = &t.StringID
274 } else {
275 e[i].Id = &t.IntID
276 }
277 }
278 var namespace *string
279 if k.namespace != "" {
280 namespace = &k.namespace
281 }
282 r, err := proto.Marshal(&pb.Reference{
283 App: &k.appID,
284 NameSpace: namespace,
285 Path: &pb.Path{
286 Element: e,
287 },
288 })
289 if err != nil {
290 panic(err)
291 }
292
293 // trim padding
294 return strings.TrimRight(base64.URLEncoding.EncodeToString(r), "=")
295 }
296
297 // UnmarshalJSON allows this key to be automatically unmarshaled by encoding/jso n.
298 func (k *Key) UnmarshalJSON(buf []byte) error {
299 if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
300 return errors.New("datastore: bad JSON key")
301 }
302 nk, err := NewKeyEncoded(string(buf[1 : len(buf)-1]))
303 if err != nil {
304 return err
305 }
306 *k = *nk
307 return nil
308 }
309
310 // Root returns the entity root for the given key.
311 func (k *Key) Root() *Key {
312 if len(k.toks) > 1 {
313 ret := *k
314 ret.toks = ret.toks[:1]
315 return &ret
316 }
317 return k
318 }
319
320 func (k *Key) Less(other *Key) bool {
321 if k.appID < other.appID {
322 return true
323 } else if k.appID > other.appID {
324 return false
325 }
326
327 if k.namespace < other.namespace {
328 return true
329 } else if k.namespace > other.namespace {
330 return false
331 }
332
333 lim := len(k.toks)
334 if len(other.toks) < lim {
335 lim = len(other.toks)
336 }
337 for i := 0; i < lim; i++ {
338 a, b := k.toks[i], other.toks[i]
339 if a.Less(b) {
340 return true
341 } else if b.Less(a) {
342 return false
343 }
344 }
345 return len(k.toks) < len(other.toks)
346 }
347
348 // Equal returns true iff the two keys represent identical key values.
349 func (k *Key) Equal(other *Key) (ret bool) {
350 ret = (k.appID == other.appID &&
351 k.namespace == other.namespace &&
352 len(k.toks) == len(other.toks))
353 if ret {
354 for i, t := range k.toks {
355 if ret = t == other.toks[i]; !ret {
356 return
357 }
358 }
359 }
360 return
361 }
362
363 // Split componentizes the key into pieces (AppID, Namespace and tokens)
364 //
365 // Each token represents one piece of they key's 'path'.
366 //
367 // toks is guaranteed to be empty if and only if k is nil. If k is non-nil then
368 // it contains at least one token.
369 func (k *Key) Split() (appID, namespace string, toks []KeyTok) {
370 appID = k.appID
371 namespace = k.namespace
372 toks = make([]KeyTok, len(k.toks))
373 copy(toks, k.toks)
374 return
375 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698