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

Side by Side Diff: service/rawdatastore/datastore_key.go

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

Powered by Google App Engine
This is Rietveld 408576698