go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/key.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package datastore
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/base64"
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"strings"
    24  
    25  	"google.golang.org/protobuf/proto"
    26  
    27  	pb "go.chromium.org/luci/gae/service/datastore/internal/protos/datastore"
    28  )
    29  
    30  // KeyTok is a single token from a multi-part Key.
    31  type KeyTok struct {
    32  	Kind     string
    33  	IntID    int64
    34  	StringID string
    35  }
    36  
    37  // IsIncomplete returns true iff this token doesn't define either a StringID or
    38  // an IntID.
    39  func (k KeyTok) IsIncomplete() bool {
    40  	return k.StringID == "" && k.IntID == 0
    41  }
    42  
    43  // Special returns true iff this token begins and ends with "__"
    44  func (k KeyTok) Special() bool {
    45  	return len(k.Kind) >= 2 && k.Kind[:2] == "__" && k.Kind[len(k.Kind)-2:] == "__"
    46  }
    47  
    48  // ID returns the 'active' id as a Property (either the StringID or the IntID).
    49  func (k KeyTok) ID() Property {
    50  	if k.StringID != "" {
    51  		return MkProperty(k.StringID)
    52  	}
    53  	return MkProperty(k.IntID)
    54  }
    55  
    56  // Less returns true iff k would sort before other.
    57  func (k KeyTok) Less(other KeyTok) bool {
    58  	if k.Kind < other.Kind {
    59  		return true
    60  	} else if k.Kind > other.Kind {
    61  		return false
    62  	}
    63  	a, b := k.ID(), other.ID()
    64  	return a.Less(&b)
    65  }
    66  
    67  // KeyContext is the context in which a key is generated.
    68  type KeyContext struct {
    69  	AppID     string
    70  	Namespace string
    71  }
    72  
    73  // MkKeyContext is a helper function to create a new KeyContext.
    74  //
    75  // It is preferable to field-based struct initialization because, as a function,
    76  // it has the ability to enforce an exact number of parameters.
    77  func MkKeyContext(appID, namespace string) KeyContext {
    78  	return KeyContext{AppID: appID, Namespace: namespace}
    79  }
    80  
    81  // Matches returns true iff the AppID and Namespace parameters are the same for
    82  // the two KeyContext instances.
    83  func (kc KeyContext) Matches(o KeyContext) bool {
    84  	return (kc.AppID == o.AppID && kc.Namespace == o.Namespace)
    85  }
    86  
    87  // NewKeyToks creates a new Key. It is the Key implementation returned from
    88  // the various PropertyMap serialization routines, as well as the native key
    89  // implementation for the in-memory implementation of gae.
    90  //
    91  // See NewKeyToks for a version of this function which automatically
    92  // provides aid and ns.
    93  func (kc KeyContext) NewKeyToks(toks []KeyTok) *Key {
    94  	if len(toks) == 0 {
    95  		return nil
    96  	}
    97  	newToks := make([]KeyTok, len(toks))
    98  	copy(newToks, toks)
    99  	return &Key{kc, newToks}
   100  }
   101  
   102  // NewKey is a wrapper around NewToks which has an interface similar to NewKey
   103  // in the SDK.
   104  //
   105  // See NewKey for a version of this function which automatically provides aid
   106  // and ns.
   107  func (kc KeyContext) NewKey(kind, stringID string, intID int64, parent *Key) *Key {
   108  	if parent == nil {
   109  		return &Key{kc, []KeyTok{{kind, intID, stringID}}}
   110  	}
   111  
   112  	toks := parent.toks
   113  	newToks := make([]KeyTok, len(toks), len(toks)+1)
   114  	copy(newToks, toks)
   115  	newToks = append(newToks, KeyTok{kind, intID, stringID})
   116  	return &Key{kc, newToks}
   117  }
   118  
   119  // MakeKey is a convenience function for manufacturing a *Key. It should only
   120  // be used when elems... is known statically (e.g. in the code) to be correct.
   121  //
   122  // elems is pairs of (string, string|int|int32|int64) pairs, which correspond to
   123  // Kind/id pairs. Example:
   124  //
   125  //	MkKeyContext("aid", "namespace").MakeKey("Parent", 1, "Child", "id")
   126  //
   127  // Would create the key:
   128  //
   129  //	aid:namespace:/Parent,1/Child,id
   130  //
   131  // If elems is not parsable (e.g. wrong length, wrong types, etc.) this method
   132  // will panic.
   133  //
   134  // See MakeKey for a version of this function which automatically
   135  // provides aid and ns.
   136  func (kc KeyContext) MakeKey(elems ...any) *Key {
   137  	if len(elems) == 0 {
   138  		return nil
   139  	}
   140  
   141  	if len(elems)%2 != 0 {
   142  		panic(fmt.Errorf("datastore.MakeKey: odd number of tokens: %v", elems))
   143  	}
   144  
   145  	toks := make([]KeyTok, len(elems)/2)
   146  	for i := 0; len(elems) > 0; i, elems = i+1, elems[2:] {
   147  		knd, ok := elems[0].(string)
   148  		if !ok {
   149  			panic(fmt.Errorf("datastore.MakeKey: bad kind: %v", elems[i]))
   150  		}
   151  		t := &toks[i]
   152  		t.Kind = knd
   153  		switch x := elems[1].(type) {
   154  		case string:
   155  			t.StringID = x
   156  		case int:
   157  			t.IntID = int64(x)
   158  		case int32:
   159  			t.IntID = int64(x)
   160  		case int64:
   161  			t.IntID = int64(x)
   162  		case uint16:
   163  			t.IntID = int64(x)
   164  		case uint32:
   165  			t.IntID = int64(x)
   166  		default:
   167  			panic(fmt.Errorf("datastore.MakeKey: bad id: %v", x))
   168  		}
   169  	}
   170  
   171  	return kc.NewKeyToks(toks)
   172  }
   173  
   174  // NewKeyFromMeta constructs a key (potentially partial) based on meta fields.
   175  //
   176  // Looks at `$key`, `$kind`, `$id`, `$parent`. Returns an error if necessary
   177  // fields are missing.
   178  func (kc KeyContext) NewKeyFromMeta(mgs MetaGetterSetter) (*Key, error) {
   179  	if key, _ := GetMetaDefault(mgs, "key", nil).(*Key); key != nil {
   180  		return key, nil
   181  	}
   182  
   183  	kind := GetMetaDefault(mgs, "kind", "").(string)
   184  	if kind == "" {
   185  		return nil, errors.New("unable to extract $kind")
   186  	}
   187  
   188  	// get id - allow both to be default for default keys
   189  	sid := GetMetaDefault(mgs, "id", "").(string)
   190  	iid := GetMetaDefault(mgs, "id", 0).(int64)
   191  
   192  	par, _ := GetMetaDefault(mgs, "parent", nil).(*Key)
   193  
   194  	return kc.NewKey(kind, sid, iid, par), nil
   195  }
   196  
   197  // Key is the type used for all datastore operations.
   198  type Key struct {
   199  	kc   KeyContext
   200  	toks []KeyTok
   201  }
   202  
   203  var _ interface {
   204  	json.Marshaler
   205  	json.Unmarshaler
   206  } = (*Key)(nil)
   207  
   208  // NewKeyEncoded decodes and returns a *Key
   209  func NewKeyEncoded(encoded string) (ret *Key, err error) {
   210  	ret = &Key{}
   211  	// Re-add padding
   212  	if m := len(encoded) % 4; m != 0 {
   213  		encoded += strings.Repeat("=", 4-m)
   214  	}
   215  	b, err := base64.URLEncoding.DecodeString(encoded)
   216  	if err != nil {
   217  		return
   218  	}
   219  
   220  	r := &pb.Reference{}
   221  	if err = proto.Unmarshal(b, r); err != nil {
   222  		return
   223  	}
   224  
   225  	ret.kc = MkKeyContext(r.GetApp(), r.GetNameSpace())
   226  	ret.toks = make([]KeyTok, len(r.Path.Element))
   227  	for i, e := range r.Path.Element {
   228  		ret.toks[i] = KeyTok{
   229  			Kind:     e.GetType(),
   230  			IntID:    e.GetId(),
   231  			StringID: e.GetName(),
   232  		}
   233  	}
   234  	return
   235  }
   236  
   237  // LastTok returns the last KeyTok in this Key. Non-nil Keys are always guaranteed
   238  // to have at least one token.
   239  func (k *Key) LastTok() KeyTok {
   240  	return k.toks[len(k.toks)-1]
   241  }
   242  
   243  // AppID returns the application ID that this Key is for.
   244  func (k *Key) AppID() string { return k.kc.AppID }
   245  
   246  // Namespace returns the namespace that this Key is for.
   247  func (k *Key) Namespace() string { return k.kc.Namespace }
   248  
   249  // KeyContext returns the KeyContext that this Key is using.
   250  func (k *Key) KeyContext() *KeyContext {
   251  	kc := k.kc
   252  	return &kc
   253  }
   254  
   255  // Kind returns the Kind of the child KeyTok
   256  func (k *Key) Kind() string { return k.toks[len(k.toks)-1].Kind }
   257  
   258  // StringID returns the StringID of the child KeyTok
   259  func (k *Key) StringID() string { return k.toks[len(k.toks)-1].StringID }
   260  
   261  // IntID returns the IntID of the child KeyTok
   262  func (k *Key) IntID() int64 { return k.toks[len(k.toks)-1].IntID }
   263  
   264  // String returns a human-readable representation of the key in the form of
   265  //
   266  //	AID:NS:/Kind,id/Kind,id/...
   267  func (k *Key) String() string {
   268  	b := bytes.NewBuffer(make([]byte, 0, 512))
   269  	fmt.Fprintf(b, "%s:%s:", k.kc.AppID, k.kc.Namespace)
   270  	for _, t := range k.toks {
   271  		if t.StringID != "" {
   272  			fmt.Fprintf(b, "/%s,%q", t.Kind, t.StringID)
   273  		} else {
   274  			fmt.Fprintf(b, "/%s,%d", t.Kind, t.IntID)
   275  		}
   276  	}
   277  	return b.String()
   278  }
   279  
   280  // IsIncomplete returns true iff the last token of this Key doesn't define
   281  // either a StringID or an IntID.
   282  func (k *Key) IsIncomplete() bool {
   283  	return k.LastTok().IsIncomplete()
   284  }
   285  
   286  // Valid determines if a key is valid, according to a couple of rules:
   287  //   - k is not nil
   288  //   - every token of k:
   289  //   - (if !allowSpecial) token's kind doesn't start with '__'
   290  //   - token's kind and appid are non-blank
   291  //   - token is not incomplete
   292  //   - all tokens have the same namespace and appid
   293  func (k *Key) Valid(allowSpecial bool, kc KeyContext) bool {
   294  	if !kc.Matches(k.kc) {
   295  		return false
   296  	}
   297  	for _, t := range k.toks {
   298  		if t.IsIncomplete() {
   299  			return false
   300  		}
   301  		if !allowSpecial && t.Special() {
   302  			return false
   303  		}
   304  		if t.Kind == "" {
   305  			return false
   306  		}
   307  		if t.StringID != "" && t.IntID != 0 {
   308  			return false
   309  		}
   310  	}
   311  	return true
   312  }
   313  
   314  // PartialValid returns true iff this key is suitable for use in a Put
   315  // operation. This is the same as Valid(k, false, ...), but also allowing k to
   316  // be IsIncomplete().
   317  func (k *Key) PartialValid(kc KeyContext) bool {
   318  	if k.IsIncomplete() {
   319  		if !kc.Matches(k.kc) {
   320  			return false
   321  		}
   322  		k = kc.NewKey(k.Kind(), "", 1, k.Parent())
   323  	}
   324  	return k.Valid(false, kc)
   325  }
   326  
   327  // Parent returns the parent Key of this *Key, or nil. The parent
   328  // will always have the concrete type of *Key.
   329  func (k *Key) Parent() *Key {
   330  	if len(k.toks) <= 1 {
   331  		return nil
   332  	}
   333  	return k.kc.NewKeyToks(k.toks[:len(k.toks)-1])
   334  }
   335  
   336  // MarshalJSON allows this key to be automatically marshaled by encoding/json.
   337  func (k *Key) MarshalJSON() ([]byte, error) {
   338  	return []byte(`"` + k.Encode() + `"`), nil
   339  }
   340  
   341  // Encode encodes the provided key as a base64-encoded protobuf.
   342  //
   343  // This encoding is compatible with the SDK-provided encoding and is agnostic
   344  // to the underlying implementation of the Key.
   345  //
   346  // It's encoded with the urlsafe base64 table without padding.
   347  func (k *Key) Encode() string {
   348  	e := make([]*pb.Path_Element, len(k.toks))
   349  	for i, t := range k.toks {
   350  		t := t
   351  		e[i] = &pb.Path_Element{
   352  			Type: &t.Kind,
   353  		}
   354  		if t.StringID != "" {
   355  			e[i].Name = &t.StringID
   356  		} else {
   357  			e[i].Id = &t.IntID
   358  		}
   359  	}
   360  	var namespace *string
   361  	if ns := k.kc.Namespace; ns != "" {
   362  		namespace = &ns
   363  	}
   364  	r, err := proto.Marshal(&pb.Reference{
   365  		App:       &k.kc.AppID,
   366  		NameSpace: namespace,
   367  		Path: &pb.Path{
   368  			Element: e,
   369  		},
   370  	})
   371  	if err != nil {
   372  		panic(err)
   373  	}
   374  
   375  	// trim padding
   376  	return strings.TrimRight(base64.URLEncoding.EncodeToString(r), "=")
   377  }
   378  
   379  // UnmarshalJSON allows this key to be automatically unmarshaled by encoding/json.
   380  func (k *Key) UnmarshalJSON(buf []byte) error {
   381  	if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
   382  		return errors.New("datastore: bad JSON key")
   383  	}
   384  	nk, err := NewKeyEncoded(string(buf[1 : len(buf)-1]))
   385  	if err != nil {
   386  		return err
   387  	}
   388  	*k = *nk
   389  	return nil
   390  }
   391  
   392  // GobEncode allows the Key to be encoded in a Gob struct.
   393  func (k *Key) GobEncode() ([]byte, error) {
   394  	return []byte(k.Encode()), nil
   395  }
   396  
   397  // GobDecode allows the Key to be decoded in a Gob struct.
   398  func (k *Key) GobDecode(buf []byte) error {
   399  	nk, err := NewKeyEncoded(string(buf))
   400  	if err != nil {
   401  		return err
   402  	}
   403  	*k = *nk
   404  	return nil
   405  }
   406  
   407  // Root returns the entity root for the given key.
   408  func (k *Key) Root() *Key {
   409  	if len(k.toks) > 1 {
   410  		ret := *k
   411  		ret.toks = ret.toks[:1]
   412  		return &ret
   413  	}
   414  	return k
   415  }
   416  
   417  // Less returns true iff k would sort before other.
   418  func (k *Key) Less(other *Key) bool {
   419  	if k.kc.AppID < other.kc.AppID {
   420  		return true
   421  	} else if k.kc.AppID > other.kc.AppID {
   422  		return false
   423  	}
   424  
   425  	if k.kc.Namespace < other.kc.Namespace {
   426  		return true
   427  	} else if k.kc.Namespace > other.kc.Namespace {
   428  		return false
   429  	}
   430  
   431  	lim := len(k.toks)
   432  	if len(other.toks) < lim {
   433  		lim = len(other.toks)
   434  	}
   435  	for i := 0; i < lim; i++ {
   436  		a, b := k.toks[i], other.toks[i]
   437  		if a.Less(b) {
   438  			return true
   439  		} else if b.Less(a) {
   440  			return false
   441  		}
   442  	}
   443  	return len(k.toks) < len(other.toks)
   444  }
   445  
   446  // HasAncestor returns true iff other is an ancestor of k (or if other == k).
   447  func (k *Key) HasAncestor(other *Key) bool {
   448  	if !k.kc.Matches(other.kc) {
   449  		return false
   450  	}
   451  	if len(k.toks) < len(other.toks) {
   452  		return false
   453  	}
   454  	for i, tok := range other.toks {
   455  		if tok != k.toks[i] {
   456  			return false
   457  		}
   458  	}
   459  	return true
   460  }
   461  
   462  // GQL returns a correctly formatted Cloud Datastore GQL key literal.
   463  //
   464  // The flavor of GQL that this emits is defined here:
   465  //
   466  //	https://cloud.google.com/datastore/docs/apis/gql/gql_reference
   467  func (k *Key) GQL() string {
   468  	ret := &bytes.Buffer{}
   469  	fmt.Fprintf(ret, "KEY(DATASET(%s)", gqlQuoteString(k.kc.AppID))
   470  	if ns := k.kc.Namespace; ns != "" {
   471  		fmt.Fprintf(ret, ", NAMESPACE(%s)", gqlQuoteString(ns))
   472  	}
   473  	for _, t := range k.toks {
   474  		if t.IntID != 0 {
   475  			fmt.Fprintf(ret, ", %s, %d", gqlQuoteString(t.Kind), t.IntID)
   476  		} else {
   477  			fmt.Fprintf(ret, ", %s, %s", gqlQuoteString(t.Kind), gqlQuoteString(t.StringID))
   478  		}
   479  	}
   480  	if _, err := ret.WriteString(")"); err != nil {
   481  		panic(err)
   482  	}
   483  	return ret.String()
   484  }
   485  
   486  // Equal returns true iff the two keys represent identical key values.
   487  func (k *Key) Equal(other *Key) bool {
   488  	if k == nil || other == nil {
   489  		return k == nil && other == nil
   490  	}
   491  	return k.IncompleteEqual(other) && (k.LastTok() == other.LastTok())
   492  }
   493  
   494  // IncompleteEqual asserts that, were the two keys incomplete, they would be
   495  // equal.
   496  //
   497  // This asserts equality for the full lineage of the key, except for its last
   498  // token ID.
   499  func (k *Key) IncompleteEqual(other *Key) (ret bool) {
   500  	ret = (k.kc.Matches(other.kc) &&
   501  		len(k.toks) == len(other.toks))
   502  	if ret {
   503  		for i, t := range k.toks {
   504  			if i == len(k.toks)-1 {
   505  				// Last token: check only Kind.
   506  				if ret = (t.Kind == other.toks[i].Kind); !ret {
   507  					return
   508  				}
   509  			} else {
   510  				if ret = t == other.toks[i]; !ret {
   511  					return
   512  				}
   513  			}
   514  		}
   515  	}
   516  	return
   517  }
   518  
   519  // Incomplete returns an incomplete version of the key. The ID fields of the
   520  // last token will be set to zero/empty.
   521  func (k *Key) Incomplete() *Key {
   522  	if k.IsIncomplete() {
   523  		return k
   524  	}
   525  	return k.kc.NewKey(k.Kind(), "", 0, k.Parent())
   526  }
   527  
   528  // WithID returns the key generated by setting the ID of its last token to
   529  // the specified value.
   530  //
   531  // To generate this, k is reduced to its Incomplete form, then populated with a
   532  // new ID. The resulting key will have the same token linage as k (i.e., will
   533  // be IncompleteEqual).
   534  func (k *Key) WithID(stringID string, intID int64) *Key {
   535  	if k.StringID() == stringID && k.IntID() == intID {
   536  		return k
   537  	}
   538  	return k.kc.NewKey(k.Kind(), stringID, intID, k.Parent())
   539  }
   540  
   541  // Split componentizes the key into pieces (AppID, Namespace and tokens)
   542  //
   543  // Each token represents one piece of they key's 'path'.
   544  //
   545  // toks is guaranteed to be empty if and only if k is nil. If k is non-nil then
   546  // it contains at least one token.
   547  func (k *Key) Split() (appID, namespace string, toks []KeyTok) {
   548  	appID = k.kc.AppID
   549  	namespace = k.kc.Namespace
   550  	toks = make([]KeyTok, len(k.toks))
   551  	copy(toks, k.toks)
   552  	return
   553  }
   554  
   555  // EstimateSize estimates the size of a Key.
   556  //
   557  // It uses https://cloud.google.com/appengine/articles/storage_breakdown?csw=1
   558  // as a guide for these values.
   559  func (k *Key) EstimateSize() int64 {
   560  	ret := int64(len(k.kc.AppID))
   561  	ret += int64(len(k.kc.Namespace))
   562  	for _, t := range k.toks {
   563  		ret += int64(len(t.Kind))
   564  		if t.StringID != "" {
   565  			ret += int64(len(t.StringID))
   566  		} else {
   567  			ret += 8
   568  		}
   569  	}
   570  	return ret
   571  }