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

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: appease errcheck 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
« no previous file with comments | « service/datastore/interface.go ('k') | service/datastore/key_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 // Incomplete returns true iff this token doesn't define either a StringID or
27 // an IntID.
28 func (k KeyTok) Incomplete() bool {
29 return k.StringID == "" && k.IntID == 0
30 }
31
32 // Special returns true iff this token begins and ends with "__"
33 func (k KeyTok) Special() bool {
34 return len(k.Kind) >= 2 && k.Kind[:2] == "__" && k.Kind[len(k.Kind)-2:] == "__"
35 }
36
37 // ID returns the 'active' id as a Property (either the StringID or the IntID).
38 func (k KeyTok) ID() Property {
39 if k.StringID != "" {
40 return Property{value: k.StringID, propType: PTString}
41 }
42 return Property{value: k.IntID, propType: PTInt}
43 }
44
45 // Less returns true iff k would sort before other.
46 func (k KeyTok) Less(other KeyTok) bool {
47 if k.Kind < other.Kind {
48 return true
49 } else if k.Kind > other.Kind {
50 return false
51 }
52 a, b := k.ID(), other.ID()
53 return a.Less(&b)
54 }
55
56 // Key is the type used for all datastore operations.
57 type Key struct {
58 appID string
59 namespace string
60 toks []KeyTok
61 }
62
63 var _ interface {
64 json.Marshaler
65 json.Unmarshaler
66 } = (*Key)(nil)
67
68 // NewKeyToks creates a new Key. It is the Key implementation returned from the
69 // various PropertyMap serialization routines, as well as the native key
70 // implementation for the in-memory implementation of gae.
71 //
72 // See Interface.NewKeyToks for a version of this function which automatically
73 // provides aid and ns.
74 func NewKeyToks(aid, ns string, toks []KeyTok) *Key {
75 if len(toks) == 0 {
76 return nil
77 }
78 newToks := make([]KeyTok, len(toks))
79 copy(newToks, toks)
80 return &Key{aid, ns, newToks}
81 }
82
83 // NewKey is a wrapper around NewToks which has an interface similar
84 // to NewKey in the SDK.
85 //
86 // See Interface.NewKey for a version of this function which automatically
87 // provides aid and ns.
88 func NewKey(aid, ns, kind, stringID string, intID int64, parent *Key) *Key {
89 if parent == nil {
90 return &Key{aid, ns, []KeyTok{{kind, intID, stringID}}}
91 }
92
93 toks := parent.toks
94 newToks := make([]KeyTok, len(toks), len(toks)+1)
95 copy(newToks, toks)
96 newToks = append(newToks, KeyTok{kind, intID, stringID})
97 return &Key{aid, ns, newToks}
98 }
99
100 // MakeKey is a convenience function for manufacturing a *Key. It should only
101 // be used when elems... is known statically (e.g. in the code) to be correct.
102 //
103 // elems is pairs of (string, string|int|int32|int64) pairs, which correspond to
104 // Kind/id pairs. Example:
105 // MakeKey("aid", "namespace", "Parent", 1, "Child", "id")
106 //
107 // Would create the key:
108 // aid:namespace:/Parent,1/Child,id
109 //
110 // If elems is not parsable (e.g. wrong length, wrong types, etc.) this method
111 // will panic.
112 //
113 // See Interface.MakeKey for a version of this function which automatically
114 // provides aid and ns.
115 func MakeKey(aid, ns string, elems ...interface{}) *Key {
116 if len(elems) == 0 {
117 return nil
118 }
119
120 if len(elems)%2 != 0 {
121 panic(fmt.Errorf("datastore.MakeKey: odd number of tokens: %v", elems))
122 }
123
124 toks := make([]KeyTok, len(elems)/2)
125 for i := 0; len(elems) > 0; i, elems = i+1, elems[2:] {
126 knd, ok := elems[0].(string)
127 if !ok {
128 panic(fmt.Errorf("datastore.MakeKey: bad kind: %v", elem s[i]))
129 }
130 t := &toks[i]
131 t.Kind = knd
132 switch x := elems[1].(type) {
133 case string:
134 t.StringID = x
135 case int:
136 t.IntID = int64(x)
137 case int32:
138 t.IntID = int64(x)
139 case int64:
140 t.IntID = int64(x)
141 default:
142 panic(fmt.Errorf("datastore.MakeKey: bad id: %v", x))
143 }
144 }
145
146 return NewKeyToks(aid, ns, toks)
147 }
148
149 // NewKeyEncoded decodes and returns a *Key
150 func NewKeyEncoded(encoded string) (ret *Key, err error) {
151 ret = &Key{}
152 // Re-add padding
153 if m := len(encoded) % 4; m != 0 {
154 encoded += strings.Repeat("=", 4-m)
155 }
156 b, err := base64.URLEncoding.DecodeString(encoded)
157 if err != nil {
158 return
159 }
160
161 r := &pb.Reference{}
162 if err = proto.Unmarshal(b, r); err != nil {
163 return
164 }
165
166 ret.appID = r.GetApp()
167 ret.namespace = r.GetNameSpace()
168 ret.toks = make([]KeyTok, len(r.Path.Element))
169 for i, e := range r.Path.Element {
170 ret.toks[i] = KeyTok{
171 Kind: e.GetType(),
172 IntID: e.GetId(),
173 StringID: e.GetName(),
174 }
175 }
176 return
177 }
178
179 // Last returns the last KeyTok in this Key. Non-nil Keys are always guaranteed
180 // to have at least one token.
181 func (k *Key) Last() KeyTok {
182 return k.toks[len(k.toks)-1]
183 }
184
185 // AppID returns the application ID that this Key is for.
186 func (k *Key) AppID() string { return k.appID }
187
188 // Namespace returns the namespace that this Key is for.
189 func (k *Key) Namespace() string { return k.namespace }
190
191 // String returns a human-readable representation of the key in the form of
192 // AID:NS:/Kind,id/Kind,id/...
193 func (k *Key) String() string {
194 b := bytes.NewBuffer(make([]byte, 0, 512))
195 fmt.Fprintf(b, "%s:%s:", k.appID, k.namespace)
196 for _, t := range k.toks {
197 if t.StringID != "" {
198 fmt.Fprintf(b, "/%s,%q", t.Kind, t.StringID)
199 } else {
200 fmt.Fprintf(b, "/%s,%d", t.Kind, t.IntID)
201 }
202 }
203 return b.String()
204 }
205
206 // Incomplete returns true iff k doesn't have an id yet.
207 func (k *Key) Incomplete() bool {
208 return k.Last().Incomplete()
209 }
210
211 // Valid determines if a key is valid, according to a couple rules:
212 // - k is not nil
213 // - every token of k:
214 // - (if !allowSpecial) token's kind doesn't start with '__'
215 // - token's kind and appid are non-blank
216 // - token is not incomplete
217 // - all tokens have the same namespace and appid
218 func (k *Key) Valid(allowSpecial bool, aid, ns string) bool {
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 without padding.
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 len(k.toks) > 1 {
317 ret := *k
318 ret.toks = ret.toks[:1]
319 return &ret
320 }
321 return k
322 }
323
324 // Less returns true iff k would sort before other.
325 func (k *Key) Less(other *Key) bool {
326 if k.appID < other.appID {
327 return true
328 } else if k.appID > other.appID {
329 return false
330 }
331
332 if k.namespace < other.namespace {
333 return true
334 } else if k.namespace > other.namespace {
335 return false
336 }
337
338 lim := len(k.toks)
339 if len(other.toks) < lim {
340 lim = len(other.toks)
341 }
342 for i := 0; i < lim; i++ {
343 a, b := k.toks[i], other.toks[i]
344 if a.Less(b) {
345 return true
346 } else if b.Less(a) {
347 return false
348 }
349 }
350 return len(k.toks) < len(other.toks)
351 }
352
353 // GQL returns a correctly formatted Cloud Datastore GQL key literal.
354 //
355 // The flavor of GQL that this emits is defined here:
356 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference
357 func (k *Key) GQL() string {
358 ret := &bytes.Buffer{}
359 fmt.Fprintf(ret, "KEY(DATASET(%s)", gqlQuoteString(k.appID))
360 if k.namespace != "" {
361 fmt.Fprintf(ret, ", NAMESPACE(%s)", gqlQuoteString(k.namespace))
362 }
363 for _, t := range k.toks {
364 if t.IntID != 0 {
365 fmt.Fprintf(ret, ", %s, %d", gqlQuoteString(t.Kind), t.I ntID)
366 } else {
367 fmt.Fprintf(ret, ", %s, %s", gqlQuoteString(t.Kind), gql QuoteString(t.StringID))
368 }
369 }
370 if _, err := ret.WriteString(")"); err != nil {
371 panic(err)
372 }
373 return ret.String()
374 }
375
376 // Equal returns true iff the two keys represent identical key values.
377 func (k *Key) Equal(other *Key) (ret bool) {
378 ret = (k.appID == other.appID &&
379 k.namespace == other.namespace &&
380 len(k.toks) == len(other.toks))
381 if ret {
382 for i, t := range k.toks {
383 if ret = t == other.toks[i]; !ret {
384 return
385 }
386 }
387 }
388 return
389 }
390
391 // Split componentizes the key into pieces (AppID, Namespace and tokens)
392 //
393 // Each token represents one piece of they key's 'path'.
394 //
395 // toks is guaranteed to be empty if and only if k is nil. If k is non-nil then
396 // it contains at least one token.
397 func (k *Key) Split() (appID, namespace string, toks []KeyTok) {
398 appID = k.appID
399 namespace = k.namespace
400 toks = make([]KeyTok, len(k.toks))
401 copy(toks, k.toks)
402 return
403 }
OLDNEW
« no previous file with comments | « service/datastore/interface.go ('k') | service/datastore/key_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698