Index: go/src/infra/gae/libs/gae/helper/datastore_key.go |
diff --git a/go/src/infra/gae/libs/gae/helper/datastore_key.go b/go/src/infra/gae/libs/gae/helper/datastore_key.go |
new file mode 100644 |
index 0000000000000000000000000000000000000000..21734e1e7a0c891bf6930dd7e6d3f74adca6e7a9 |
--- /dev/null |
+++ b/go/src/infra/gae/libs/gae/helper/datastore_key.go |
@@ -0,0 +1,236 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// adapted from github.com/golang/appengine/datastore |
+ |
+package helper |
+ |
+import ( |
+ "bytes" |
+ "encoding/base64" |
+ "errors" |
+ "strconv" |
+ "strings" |
+ |
+ "infra/gae/libs/gae" |
+ pb "infra/gae/libs/gae/helper/internal/protos/datastore" |
+ |
+ "github.com/golang/protobuf/proto" |
+) |
+ |
+// DSKeyEncode encodes the provided key as a base64-encoded protobuf. |
+// |
+// This encoding is compatible with the SDK-provided encoding and is agnostic |
+// to the underlying implementation of the DSKey. |
+func DSKeyEncode(k gae.DSKey) string { |
+ n := 0 |
+ for i := k; i != nil; i = i.Parent() { |
+ n++ |
+ } |
+ e := make([]*pb.Path_Element, n) |
+ for i := k; i != nil; i = i.Parent() { |
+ n-- |
+ kind := i.Kind() |
+ e[n] = &pb.Path_Element{ |
+ Type: &kind, |
+ } |
+ // At most one of {Name,Id} should be set. |
+ // Neither will be set for incomplete keys. |
+ if i.StringID() != "" { |
+ sid := i.StringID() |
+ e[n].Name = &sid |
+ } else if i.IntID() != 0 { |
+ iid := i.IntID() |
+ e[n].Id = &iid |
+ } |
+ } |
+ var namespace *string |
+ if k.Namespace() != "" { |
+ namespace = proto.String(k.Namespace()) |
+ } |
+ r, err := proto.Marshal(&pb.Reference{ |
+ App: proto.String(k.AppID()), |
+ NameSpace: namespace, |
+ Path: &pb.Path{ |
+ Element: e, |
+ }, |
+ }) |
+ if err != nil { |
+ panic(err) |
+ } |
+ |
+ // trim padding |
+ return strings.TrimRight(base64.URLEncoding.EncodeToString(r), "=") |
+} |
+ |
+// DSKeyToksDecode decodes a base64-encoded protobuf representation of a DSKey |
+// into a tokenized form. This is so that implementations of the gae wrapper |
+// can decode to their own implementation of DSKey. |
+// |
+// This encoding is compatible with the SDK-provided encoding and is agnostic |
+// to the underlying implementation of the DSKey. |
+func DSKeyToksDecode(encoded string) (appID, namespace string, toks []gae.DSKeyTok, err error) { |
+ // Re-add padding |
+ if m := len(encoded) % 4; m != 0 { |
+ encoded += strings.Repeat("=", 4-m) |
+ } |
+ b, err := base64.URLEncoding.DecodeString(encoded) |
+ if err != nil { |
+ return |
+ } |
+ |
+ r := &pb.Reference{} |
+ if err = proto.Unmarshal(b, r); err != nil { |
+ return |
+ } |
+ |
+ appID = r.GetApp() |
+ namespace = r.GetNameSpace() |
+ toks = make([]gae.DSKeyTok, len(r.Path.Element)) |
+ for i, e := range r.Path.Element { |
+ toks[i] = gae.DSKeyTok{ |
+ Kind: e.GetType(), |
+ IntID: e.GetId(), |
+ StringID: e.GetName(), |
+ } |
+ } |
+ return |
+} |
+ |
+// DSKeyMarshalJSON returns a MarshalJSON-compatible serialization of a DSKey. |
+func DSKeyMarshalJSON(k gae.DSKey) ([]byte, error) { |
+ return []byte(`"` + DSKeyEncode(k) + `"`), nil |
+} |
+ |
+// DSKeyUnmarshalJSON returns the tokenized version of a DSKey as encoded by |
+// DSKeyMarshalJSON. |
+func DSKeyUnmarshalJSON(buf []byte) (appID, namespace string, toks []gae.DSKeyTok, err error) { |
+ if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' { |
+ err = errors.New("datastore: bad JSON key") |
+ } else { |
+ appID, namespace, toks, err = DSKeyToksDecode(string(buf[1 : len(buf)-1])) |
+ } |
+ return |
+} |
+ |
+// DSKeyIncomplete returns true iff k doesn't have an id yet. |
+func DSKeyIncomplete(k gae.DSKey) bool { |
+ return k.StringID() == "" && k.IntID() == 0 |
+} |
+ |
+// DSKeyValid determines if a key is valid, according to a couple rules: |
+// - k is not nil |
+// - k's namespace matches ns |
+// - every token of k: |
+// - (if !allowSpecial) token's kind doesn't start with '__' |
+// - token's kind and appid are non-blank |
+// - token is not incomplete |
+// - all tokens have the same namespace and appid |
+func DSKeyValid(k gae.DSKey, ns string, allowSpecial bool) bool { |
+ if k == nil { |
+ return false |
+ } |
+ // since we do "client-side" validation of namespaces in local |
+ // implementations, it's convenient to check this here. |
+ if k.Namespace() != ns { |
+ return false |
+ } |
+ for ; k != nil; k = k.Parent() { |
+ if !allowSpecial && len(k.Kind()) >= 2 && k.Kind()[:2] == "__" { |
+ return false |
+ } |
+ if k.Kind() == "" || k.AppID() == "" { |
+ return false |
+ } |
+ if k.StringID() != "" && k.IntID() != 0 { |
+ return false |
+ } |
+ if k.Parent() != nil { |
+ if DSKeyIncomplete(k.Parent()) { |
+ return false |
+ } |
+ if k.Parent().AppID() != k.AppID() || k.Parent().Namespace() != k.Namespace() { |
+ return false |
+ } |
+ } |
+ } |
+ return true |
+} |
+ |
+// DSKeyRoot returns the entity root for the given key. |
+func DSKeyRoot(k gae.DSKey) gae.DSKey { |
+ for k != nil && k.Parent() != nil { |
+ k = k.Parent() |
+ } |
+ return k |
+} |
+ |
+// DSKeysEqual returns true iff the two keys represent identical key values. |
+func DSKeysEqual(a, b gae.DSKey) (ret bool) { |
+ ret = (a.Kind() == b.Kind() && |
+ a.StringID() == b.StringID() && |
+ a.IntID() == b.IntID() && |
+ a.AppID() == b.AppID() && |
+ a.Namespace() == b.Namespace()) |
+ if !ret { |
+ return |
+ } |
+ ap, bp := a.Parent(), b.Parent() |
+ return (ap == nil && bp == nil) || DSKeysEqual(ap, bp) |
+} |
+ |
+func marshalDSKey(b *bytes.Buffer, k gae.DSKey) { |
+ if k.Parent() != nil { |
+ marshalDSKey(b, k.Parent()) |
+ } |
+ b.WriteByte('/') |
+ b.WriteString(k.Kind()) |
+ b.WriteByte(',') |
+ if k.StringID() != "" { |
+ b.WriteString(k.StringID()) |
+ } else { |
+ b.WriteString(strconv.FormatInt(k.IntID(), 10)) |
+ } |
+} |
+ |
+// DSKeyString returns a human-readable representation of the key, and is the |
+// typical implementation of DSKey.String() (though it isn't guaranteed to be) |
+func DSKeyString(k gae.DSKey) string { |
+ if k == nil { |
+ return "" |
+ } |
+ b := bytes.NewBuffer(make([]byte, 0, 512)) |
+ marshalDSKey(b, k) |
+ return b.String() |
+} |
+ |
+// DSKeySplit splits the key into its constituent parts. Note that if the key is |
+// not DSKeyValid, this method may not provide a round-trip for k. |
+func DSKeySplit(k gae.DSKey) (appID, namespace string, toks []gae.DSKeyTok) { |
+ if k == nil { |
+ return |
+ } |
+ |
+ if sk, ok := k.(*GenericDSKey); ok { |
+ if sk == nil { |
+ return |
+ } |
+ return sk.appID, sk.namespace, sk.toks |
+ } |
+ |
+ n := 0 |
+ for i := k; i != nil; i = i.Parent() { |
+ n++ |
+ } |
+ toks = make([]gae.DSKeyTok, n) |
+ for i := k; i != nil; i = i.Parent() { |
+ n-- |
+ toks[n].IntID = i.IntID() |
+ toks[n].StringID = i.StringID() |
+ toks[n].Kind = i.Kind() |
+ } |
+ appID = k.AppID() |
+ namespace = k.Namespace() |
+ return |
+} |