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

Side by Side Diff: service/datastore/dskey/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/dskey/generic_key.go ('k') | service/datastore/dskey/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 // adapted from github.com/golang/appengine/datastore
6
7 package dskey
8
9 import (
10 "bytes"
11 "encoding/base64"
12 "errors"
13 "strconv"
14 "strings"
15
16 "github.com/golang/protobuf/proto"
17 ds "github.com/luci/gae/service/datastore"
18 pb "github.com/luci/gae/service/datastore/internal/protos/datastore"
19 )
20
21 // Encode encodes the provided key as a base64-encoded protobuf.
22 //
23 // This encoding is compatible with the SDK-provided encoding and is agnostic
24 // to the underlying implementation of the Key.
25 //
26 // It's encoded with the urlsafe base64 table.
27 func Encode(k ds.Key) string {
28 n := 0
29 for i := k; i != nil; i = i.Parent() {
30 n++
31 }
32 e := make([]*pb.Path_Element, n)
33 for i := k; i != nil; i = i.Parent() {
34 n--
35 kind := i.Kind()
36 e[n] = &pb.Path_Element{
37 Type: &kind,
38 }
39 // At most one of {Name,Id} should be set.
40 // Neither will be set for incomplete keys.
41 if i.StringID() != "" {
42 sid := i.StringID()
43 e[n].Name = &sid
44 } else if i.IntID() != 0 {
45 iid := i.IntID()
46 e[n].Id = &iid
47 }
48 }
49 var namespace *string
50 if k.Namespace() != "" {
51 namespace = proto.String(k.Namespace())
52 }
53 r, err := proto.Marshal(&pb.Reference{
54 App: proto.String(k.AppID()),
55 NameSpace: namespace,
56 Path: &pb.Path{
57 Element: e,
58 },
59 })
60 if err != nil {
61 panic(err)
62 }
63
64 // trim padding
65 return strings.TrimRight(base64.URLEncoding.EncodeToString(r), "=")
66 }
67
68 // ToksDecode decodes a base64-encoded protobuf representation of a Key
69 // into a tokenized form. This is so that implementations of the gae wrapper
70 // can decode to their own implementation of Key.
71 //
72 // This encoding is compatible with the SDK-provided encoding and is agnostic
73 // to the underlying implementation of the Key.
74 func ToksDecode(encoded string) (appID, namespace string, toks []ds.KeyTok, err error) {
75 // Re-add padding
76 if m := len(encoded) % 4; m != 0 {
77 encoded += strings.Repeat("=", 4-m)
78 }
79 b, err := base64.URLEncoding.DecodeString(encoded)
80 if err != nil {
81 return
82 }
83
84 r := &pb.Reference{}
85 if err = proto.Unmarshal(b, r); err != nil {
86 return
87 }
88
89 appID = r.GetApp()
90 namespace = r.GetNameSpace()
91 toks = make([]ds.KeyTok, len(r.Path.Element))
92 for i, e := range r.Path.Element {
93 toks[i] = ds.KeyTok{
94 Kind: e.GetType(),
95 IntID: e.GetId(),
96 StringID: e.GetName(),
97 }
98 }
99 return
100 }
101
102 // MarshalJSON returns a MarshalJSON-compatible serialization of a Key.
103 func MarshalJSON(k ds.Key) ([]byte, error) {
104 return []byte(`"` + Encode(k) + `"`), nil
105 }
106
107 // UnmarshalJSON returns the tokenized version of a ds.Key as encoded by
108 // MarshalJSON.
109 func UnmarshalJSON(buf []byte) (appID, namespace string, toks []ds.KeyTok, err e rror) {
110 if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
111 err = errors.New("datastore: bad JSON key")
112 } else {
113 appID, namespace, toks, err = ToksDecode(string(buf[1 : len(buf) -1]))
114 }
115 return
116 }
117
118 // Incomplete returns true iff k doesn't have an id yet.
119 func Incomplete(k ds.Key) bool {
120 return k != nil && k.StringID() == "" && k.IntID() == 0
121 }
122
123 // Valid determines if a key is valid, according to a couple rules:
124 // - k is not nil
125 // - every token of k:
126 // - (if !allowSpecial) token's kind doesn't start with '__'
127 // - token's kind and appid are non-blank
128 // - token is not incomplete
129 // - all tokens have the same namespace and appid
130 func Valid(k ds.Key, allowSpecial bool, aid, ns string) bool {
131 if k == nil {
132 return false
133 }
134 if aid != k.AppID() || ns != k.Namespace() {
135 return false
136 }
137 for ; k != nil; k = k.Parent() {
138 if !allowSpecial && len(k.Kind()) >= 2 && k.Kind()[:2] == "__" {
139 return false
140 }
141 if k.Kind() == "" || k.AppID() == "" {
142 return false
143 }
144 if k.StringID() != "" && k.IntID() != 0 {
145 return false
146 }
147 if k.Parent() != nil {
148 if k.Parent().Incomplete() {
149 return false
150 }
151 if k.Parent().AppID() != k.AppID() || k.Parent().Namespa ce() != k.Namespace() {
152 return false
153 }
154 }
155 }
156 return true
157 }
158
159 // PartialValid returns true iff this key is suitable for use in a Put
160 // operation. This is the same as Valid(k, false, ...), but also allowing k to
161 // be Incomplete().
162 func PartialValid(k ds.Key, aid, ns string) bool {
163 if k.Incomplete() {
164 k = New(k.AppID(), k.Namespace(), k.Kind(), "", 1, k.Parent())
165 }
166 return Valid(k, false, aid, ns)
167 }
168
169 // Root returns the entity root for the given key.
170 func Root(k ds.Key) ds.Key {
171 for k != nil && k.Parent() != nil {
172 k = k.Parent()
173 }
174 return k
175 }
176
177 // Equal returns true iff the two keys represent identical key values.
178 func Equal(a, b ds.Key) (ret bool) {
179 ret = (a.Kind() == b.Kind() &&
180 a.StringID() == b.StringID() &&
181 a.IntID() == b.IntID() &&
182 a.AppID() == b.AppID() &&
183 a.Namespace() == b.Namespace())
184 if !ret {
185 return
186 }
187 ap, bp := a.Parent(), b.Parent()
188 return (ap == nil && bp == nil) || Equal(ap, bp)
189 }
190
191 func marshalDSKey(b *bytes.Buffer, k ds.Key) {
192 if k.Parent() != nil {
193 marshalDSKey(b, k.Parent())
194 }
195 b.WriteByte('/')
196 b.WriteString(k.Kind())
197 b.WriteByte(',')
198 if k.StringID() != "" {
199 b.WriteString(k.StringID())
200 } else {
201 b.WriteString(strconv.FormatInt(k.IntID(), 10))
202 }
203 }
204
205 // String returns a human-readable representation of the key, and is the
206 // typical implementation of Key.String() (though it isn't guaranteed to be)
207 func String(k ds.Key) string {
208 if k == nil {
209 return ""
210 }
211 b := bytes.NewBuffer(make([]byte, 0, 512))
212 marshalDSKey(b, k)
213 return b.String()
214 }
215
216 // Split splits the key into its constituent parts. Note that if the key is
217 // not Valid, this method may not provide a round-trip for k.
218 func Split(k ds.Key) (appID, namespace string, toks []ds.KeyTok) {
219 if k == nil {
220 return
221 }
222
223 if sk, ok := k.(*Generic); ok {
224 if sk == nil {
225 return
226 }
227 return sk.appID, sk.namespace, sk.toks
228 }
229
230 n := 0
231 for i := k; i != nil; i = i.Parent() {
232 n++
233 }
234 toks = make([]ds.KeyTok, n)
235 for i := k; i != nil; i = i.Parent() {
236 n--
237 toks[n].IntID = i.IntID()
238 toks[n].StringID = i.StringID()
239 toks[n].Kind = i.Kind()
240 }
241 appID = k.AppID()
242 namespace = k.Namespace()
243 return
244 }
OLDNEW
« no previous file with comments | « service/datastore/dskey/generic_key.go ('k') | service/datastore/dskey/key_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698