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 memory |
| 6 |
| 7 import ( |
| 8 "bytes" |
| 9 "errors" |
| 10 "fmt" |
| 11 |
| 12 "github.com/golang/protobuf/proto" |
| 13 "github.com/luci/luci-go/common/funnybase" |
| 14 "github.com/mjibson/goon" |
| 15 |
| 16 "appengine" |
| 17 "appengine/datastore" |
| 18 "appengine_internal" |
| 19 basepb "appengine_internal/base" |
| 20 ) |
| 21 |
| 22 const keyNumToksReasonableLimit = 50 |
| 23 |
| 24 ///////////////////////////// fakeGAECtxForNewKey ////////////////////////////// |
| 25 |
| 26 type fakeGAECtxForNewKey string |
| 27 |
| 28 var _ = appengine.Context((fakeGAECtxForNewKey)("")) |
| 29 |
| 30 func (fakeGAECtxForNewKey) Debugf(format string, args ...interface{}) {} |
| 31 func (fakeGAECtxForNewKey) Infof(format string, args ...interface{}) {} |
| 32 func (fakeGAECtxForNewKey) Warningf(format string, args ...interface{}) {} |
| 33 func (fakeGAECtxForNewKey) Errorf(format string, args ...interface{}) {} |
| 34 func (fakeGAECtxForNewKey) Criticalf(format string, args ...interface{}) {} |
| 35 func (fakeGAECtxForNewKey) Request() interface{} { retur
n nil } |
| 36 func (f fakeGAECtxForNewKey) Call(service, method string, in, out appengine_inte
rnal.ProtoMessage, opts *appengine_internal.CallOptions) error { |
| 37 if service != "__go__" || method != "GetNamespace" { |
| 38 panic(fmt.Errorf("fakeGAECtxForNewKey: cannot facilitate Call(%q
, %q, ...)", service, method)) |
| 39 } |
| 40 out.(*basepb.StringProto).Value = proto.String(string(f)) |
| 41 return nil |
| 42 } |
| 43 func (fakeGAECtxForNewKey) FullyQualifiedAppID() string { return "dev~my~app" } |
| 44 |
| 45 /////////////////////////////// Key construction /////////////////////////////// |
| 46 |
| 47 func newKey(ns, kind, stringID string, intID int64, parent *datastore.Key) *data
store.Key { |
| 48 return datastore.NewKey(fakeGAECtxForNewKey(ns), kind, stringID, intID,
parent) |
| 49 } |
| 50 func newKeyObjError(ns string, knr goon.KindNameResolver, src interface{}) (*dat
astore.Key, error) { |
| 51 return (&goon.Goon{ |
| 52 Context: fakeGAECtxForNewKey(ns), |
| 53 KindNameResolver: knr}).KeyError(src) |
| 54 } |
| 55 func newKeyObj(ns string, knr goon.KindNameResolver, obj interface{}) *datastore
.Key { |
| 56 k, err := newKeyObjError(ns, knr, obj) |
| 57 if err != nil { |
| 58 panic(err) |
| 59 } |
| 60 return k |
| 61 } |
| 62 func kind(ns string, knr goon.KindNameResolver, src interface{}) string { |
| 63 return newKeyObj(ns, knr, src).Kind() |
| 64 } |
| 65 |
| 66 /////////////////////////////// Binary Encoding //////////////////////////////// |
| 67 |
| 68 type keyTok struct { |
| 69 kind string |
| 70 intID uint64 |
| 71 stringID string |
| 72 } |
| 73 |
| 74 func keyToToks(key *datastore.Key) (namespace string, ret []*keyTok) { |
| 75 var inner func(*datastore.Key) |
| 76 inner = func(k *datastore.Key) { |
| 77 if k.Parent() != nil { |
| 78 inner(k.Parent()) |
| 79 } |
| 80 ret = append(ret, &keyTok{k.Kind(), uint64(k.IntID()), k.StringI
D()}) |
| 81 } |
| 82 inner(key) |
| 83 namespace = key.Namespace() |
| 84 return |
| 85 } |
| 86 |
| 87 func toksToKey(ns string, toks []*keyTok) (ret *datastore.Key) { |
| 88 for _, t := range toks { |
| 89 ret = newKey(ns, t.kind, t.stringID, int64(t.intID), ret) |
| 90 } |
| 91 return |
| 92 } |
| 93 |
| 94 type nsOption bool |
| 95 |
| 96 const ( |
| 97 withNS nsOption = true |
| 98 noNS = false |
| 99 ) |
| 100 |
| 101 func keyBytes(nso nsOption, k *datastore.Key) []byte { |
| 102 buf := &bytes.Buffer{} |
| 103 writeKey(buf, nso, k) |
| 104 return buf.Bytes() |
| 105 } |
| 106 |
| 107 func keyFromByteString(nso nsOption, d string) (*datastore.Key, error) { |
| 108 return readKey(bytes.NewBufferString(d), nso) |
| 109 } |
| 110 |
| 111 func writeKey(buf *bytes.Buffer, nso nsOption, k *datastore.Key) { |
| 112 // namespace ++ #tokens ++ [tok.kind ++ tok.stringID ++ tok.intID?]* |
| 113 namespace, toks := keyToToks(k) |
| 114 if nso == withNS { |
| 115 writeString(buf, namespace) |
| 116 } |
| 117 funnybase.WriteUint(buf, uint64(len(toks))) |
| 118 for _, tok := range toks { |
| 119 writeString(buf, tok.kind) |
| 120 writeString(buf, tok.stringID) |
| 121 if tok.stringID == "" { |
| 122 funnybase.WriteUint(buf, tok.intID) |
| 123 } |
| 124 } |
| 125 } |
| 126 |
| 127 func readKey(buf *bytes.Buffer, nso nsOption) (*datastore.Key, error) { |
| 128 namespace := "" |
| 129 if nso == withNS { |
| 130 err := error(nil) |
| 131 if namespace, err = readString(buf); err != nil { |
| 132 return nil, err |
| 133 } |
| 134 } |
| 135 |
| 136 numToks, err := funnybase.ReadUint(buf) |
| 137 if err != nil { |
| 138 return nil, err |
| 139 } |
| 140 if numToks > keyNumToksReasonableLimit { |
| 141 return nil, fmt.Errorf("readKey: tried to decode huge key of len
gth %d", numToks) |
| 142 } |
| 143 |
| 144 toks := make([]*keyTok, numToks) |
| 145 for i := uint64(0); i < numToks; i++ { |
| 146 tok := &keyTok{} |
| 147 if tok.kind, err = readString(buf); err != nil { |
| 148 return nil, err |
| 149 } |
| 150 if tok.stringID, err = readString(buf); err != nil { |
| 151 return nil, err |
| 152 } |
| 153 if tok.stringID == "" { |
| 154 if tok.intID, err = funnybase.ReadUint(buf); err != nil
{ |
| 155 return nil, err |
| 156 } |
| 157 if tok.intID == 0 { |
| 158 return nil, errors.New("readKey: decoded key wit
h empty stringID and empty intID") |
| 159 } |
| 160 } |
| 161 toks[i] = tok |
| 162 } |
| 163 |
| 164 return toksToKey(namespace, toks), nil |
| 165 } |
| 166 |
| 167 //////////////////////////////// Key utilities ///////////////////////////////// |
| 168 |
| 169 func rootKey(key *datastore.Key) *datastore.Key { |
| 170 for key.Parent() != nil { |
| 171 key = key.Parent() |
| 172 } |
| 173 return key |
| 174 } |
| 175 |
| 176 type keyValidOption bool |
| 177 |
| 178 const ( |
| 179 // UserKeyOnly is used with KeyValid, and ensures that the key is only o
ne |
| 180 // that's valid for a user program to write to. |
| 181 UserKeyOnly keyValidOption = false |
| 182 |
| 183 // AllowSpecialKeys is used with KeyValid, and allows keys for special |
| 184 // metadata objects (like "__entity_group__"). |
| 185 AllowSpecialKeys = true |
| 186 ) |
| 187 |
| 188 // KeyValid checks to see if a key is valid by applying a bunch of constraint |
| 189 // rules to it (e.g. can't have StringID and IntID set at the same time, can't |
| 190 // have a parent key which is Incomplete(), etc.) |
| 191 // |
| 192 // It verifies that the key is also in the provided namespace. It can also |
| 193 // reject keys which are 'special' e.g. have a Kind starting with "__". This |
| 194 // behavior is controllable with opt. |
| 195 func KeyValid(ns string, k *datastore.Key, opt keyValidOption) bool { |
| 196 // copied from the appengine SDK because why would any user program need
to |
| 197 // see if a key is valid? /s |
| 198 if k == nil { |
| 199 return false |
| 200 } |
| 201 // since we do "client-side" validation of namespaces, check this here. |
| 202 if k.Namespace() != ns { |
| 203 return false |
| 204 } |
| 205 for ; k != nil; k = k.Parent() { |
| 206 if opt == UserKeyOnly && len(k.Kind()) >= 2 && k.Kind()[:2] == "
__" { // reserve all Kinds starting with __ |
| 207 return false |
| 208 } |
| 209 if k.Kind() == "" || k.AppID() == "" { |
| 210 return false |
| 211 } |
| 212 if k.StringID() != "" && k.IntID() != 0 { |
| 213 return false |
| 214 } |
| 215 if k.Parent() != nil { |
| 216 if k.Parent().Incomplete() { |
| 217 return false |
| 218 } |
| 219 if k.Parent().AppID() != k.AppID() || k.Parent().Namespa
ce() != k.Namespace() { |
| 220 return false |
| 221 } |
| 222 } |
| 223 } |
| 224 return true |
| 225 } |
| 226 |
| 227 // KeyCouldBeValid is like KeyValid, but it allows for the possibility that the |
| 228 // last token of the key is Incomplete(). It returns true if the Key will become |
| 229 // valid once it recieves an automatically-assigned ID. |
| 230 func KeyCouldBeValid(ns string, k *datastore.Key, opt keyValidOption) bool { |
| 231 // adds an id to k if it's incomplete. |
| 232 if k.Incomplete() { |
| 233 k = newKey(ns, k.Kind(), "", 1, k.Parent()) |
| 234 } |
| 235 return KeyValid(ns, k, opt) |
| 236 } |
OLD | NEW |