• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2011 Google Inc. All rights reserved.
2// Use of this source code is governed by the Apache 2.0
3// license that can be found in the LICENSE file.
4
5package datastore
6
7import (
8	"bytes"
9	"encoding/base64"
10	"encoding/gob"
11	"errors"
12	"fmt"
13	"strconv"
14	"strings"
15
16	"github.com/golang/protobuf/proto"
17	"golang.org/x/net/context"
18
19	"google.golang.org/appengine/internal"
20	pb "google.golang.org/appengine/internal/datastore"
21)
22
23// Key represents the datastore key for a stored entity, and is immutable.
24type Key struct {
25	kind      string
26	stringID  string
27	intID     int64
28	parent    *Key
29	appID     string
30	namespace string
31}
32
33// Kind returns the key's kind (also known as entity type).
34func (k *Key) Kind() string {
35	return k.kind
36}
37
38// StringID returns the key's string ID (also known as an entity name or key
39// name), which may be "".
40func (k *Key) StringID() string {
41	return k.stringID
42}
43
44// IntID returns the key's integer ID, which may be 0.
45func (k *Key) IntID() int64 {
46	return k.intID
47}
48
49// Parent returns the key's parent key, which may be nil.
50func (k *Key) Parent() *Key {
51	return k.parent
52}
53
54// AppID returns the key's application ID.
55func (k *Key) AppID() string {
56	return k.appID
57}
58
59// Namespace returns the key's namespace.
60func (k *Key) Namespace() string {
61	return k.namespace
62}
63
64// Incomplete returns whether the key does not refer to a stored entity.
65// In particular, whether the key has a zero StringID and a zero IntID.
66func (k *Key) Incomplete() bool {
67	return k.stringID == "" && k.intID == 0
68}
69
70// valid returns whether the key is valid.
71func (k *Key) valid() bool {
72	if k == nil {
73		return false
74	}
75	for ; k != nil; k = k.parent {
76		if k.kind == "" || k.appID == "" {
77			return false
78		}
79		if k.stringID != "" && k.intID != 0 {
80			return false
81		}
82		if k.parent != nil {
83			if k.parent.Incomplete() {
84				return false
85			}
86			if k.parent.appID != k.appID || k.parent.namespace != k.namespace {
87				return false
88			}
89		}
90	}
91	return true
92}
93
94// Equal returns whether two keys are equal.
95func (k *Key) Equal(o *Key) bool {
96	for k != nil && o != nil {
97		if k.kind != o.kind || k.stringID != o.stringID || k.intID != o.intID || k.appID != o.appID || k.namespace != o.namespace {
98			return false
99		}
100		k, o = k.parent, o.parent
101	}
102	return k == o
103}
104
105// root returns the furthest ancestor of a key, which may be itself.
106func (k *Key) root() *Key {
107	for k.parent != nil {
108		k = k.parent
109	}
110	return k
111}
112
113// marshal marshals the key's string representation to the buffer.
114func (k *Key) marshal(b *bytes.Buffer) {
115	if k.parent != nil {
116		k.parent.marshal(b)
117	}
118	b.WriteByte('/')
119	b.WriteString(k.kind)
120	b.WriteByte(',')
121	if k.stringID != "" {
122		b.WriteString(k.stringID)
123	} else {
124		b.WriteString(strconv.FormatInt(k.intID, 10))
125	}
126}
127
128// String returns a string representation of the key.
129func (k *Key) String() string {
130	if k == nil {
131		return ""
132	}
133	b := bytes.NewBuffer(make([]byte, 0, 512))
134	k.marshal(b)
135	return b.String()
136}
137
138type gobKey struct {
139	Kind      string
140	StringID  string
141	IntID     int64
142	Parent    *gobKey
143	AppID     string
144	Namespace string
145}
146
147func keyToGobKey(k *Key) *gobKey {
148	if k == nil {
149		return nil
150	}
151	return &gobKey{
152		Kind:      k.kind,
153		StringID:  k.stringID,
154		IntID:     k.intID,
155		Parent:    keyToGobKey(k.parent),
156		AppID:     k.appID,
157		Namespace: k.namespace,
158	}
159}
160
161func gobKeyToKey(gk *gobKey) *Key {
162	if gk == nil {
163		return nil
164	}
165	return &Key{
166		kind:      gk.Kind,
167		stringID:  gk.StringID,
168		intID:     gk.IntID,
169		parent:    gobKeyToKey(gk.Parent),
170		appID:     gk.AppID,
171		namespace: gk.Namespace,
172	}
173}
174
175func (k *Key) GobEncode() ([]byte, error) {
176	buf := new(bytes.Buffer)
177	if err := gob.NewEncoder(buf).Encode(keyToGobKey(k)); err != nil {
178		return nil, err
179	}
180	return buf.Bytes(), nil
181}
182
183func (k *Key) GobDecode(buf []byte) error {
184	gk := new(gobKey)
185	if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(gk); err != nil {
186		return err
187	}
188	*k = *gobKeyToKey(gk)
189	return nil
190}
191
192func (k *Key) MarshalJSON() ([]byte, error) {
193	return []byte(`"` + k.Encode() + `"`), nil
194}
195
196func (k *Key) UnmarshalJSON(buf []byte) error {
197	if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
198		return errors.New("datastore: bad JSON key")
199	}
200	k2, err := DecodeKey(string(buf[1 : len(buf)-1]))
201	if err != nil {
202		return err
203	}
204	*k = *k2
205	return nil
206}
207
208// Encode returns an opaque representation of the key
209// suitable for use in HTML and URLs.
210// This is compatible with the Python and Java runtimes.
211func (k *Key) Encode() string {
212	ref := keyToProto("", k)
213
214	b, err := proto.Marshal(ref)
215	if err != nil {
216		panic(err)
217	}
218
219	// Trailing padding is stripped.
220	return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
221}
222
223// DecodeKey decodes a key from the opaque representation returned by Encode.
224func DecodeKey(encoded string) (*Key, error) {
225	// Re-add padding.
226	if m := len(encoded) % 4; m != 0 {
227		encoded += strings.Repeat("=", 4-m)
228	}
229
230	b, err := base64.URLEncoding.DecodeString(encoded)
231	if err != nil {
232		return nil, err
233	}
234
235	ref := new(pb.Reference)
236	if err := proto.Unmarshal(b, ref); err != nil {
237		return nil, err
238	}
239
240	return protoToKey(ref)
241}
242
243// NewIncompleteKey creates a new incomplete key.
244// kind cannot be empty.
245func NewIncompleteKey(c context.Context, kind string, parent *Key) *Key {
246	return NewKey(c, kind, "", 0, parent)
247}
248
249// NewKey creates a new key.
250// kind cannot be empty.
251// Either one or both of stringID and intID must be zero. If both are zero,
252// the key returned is incomplete.
253// parent must either be a complete key or nil.
254func NewKey(c context.Context, kind, stringID string, intID int64, parent *Key) *Key {
255	// If there's a parent key, use its namespace.
256	// Otherwise, use any namespace attached to the context.
257	var namespace string
258	if parent != nil {
259		namespace = parent.namespace
260	} else {
261		namespace = internal.NamespaceFromContext(c)
262	}
263
264	return &Key{
265		kind:      kind,
266		stringID:  stringID,
267		intID:     intID,
268		parent:    parent,
269		appID:     internal.FullyQualifiedAppID(c),
270		namespace: namespace,
271	}
272}
273
274// AllocateIDs returns a range of n integer IDs with the given kind and parent
275// combination. kind cannot be empty; parent may be nil. The IDs in the range
276// returned will not be used by the datastore's automatic ID sequence generator
277// and may be used with NewKey without conflict.
278//
279// The range is inclusive at the low end and exclusive at the high end. In
280// other words, valid intIDs x satisfy low <= x && x < high.
281//
282// If no error is returned, low + n == high.
283func AllocateIDs(c context.Context, kind string, parent *Key, n int) (low, high int64, err error) {
284	if kind == "" {
285		return 0, 0, errors.New("datastore: AllocateIDs given an empty kind")
286	}
287	if n < 0 {
288		return 0, 0, fmt.Errorf("datastore: AllocateIDs given a negative count: %d", n)
289	}
290	if n == 0 {
291		return 0, 0, nil
292	}
293	req := &pb.AllocateIdsRequest{
294		ModelKey: keyToProto("", NewIncompleteKey(c, kind, parent)),
295		Size:     proto.Int64(int64(n)),
296	}
297	res := &pb.AllocateIdsResponse{}
298	if err := internal.Call(c, "datastore_v3", "AllocateIds", req, res); err != nil {
299		return 0, 0, err
300	}
301	// The protobuf is inclusive at both ends. Idiomatic Go (e.g. slices, for loops)
302	// is inclusive at the low end and exclusive at the high end, so we add 1.
303	low = res.GetStart()
304	high = res.GetEnd() + 1
305	if low+int64(n) != high {
306		return 0, 0, fmt.Errorf("datastore: internal error: could not allocate %d IDs", n)
307	}
308	return low, high, nil
309}
310