Skip to content

datastore: add EnableKeyConversion for compatibility with Cloud Datastore encoded keys #192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
May 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bce4541
Adding foward compatibility for key encoding
goog-lukemc Jan 30, 2019
8d736f0
splitting logic into a new file
goog-lukemc Feb 6, 2019
71b3369
Added implemetnation check
goog-lukemc Feb 7, 2019
1a7bf35
split code into seperate files and completed code review comments
goog-lukemc Feb 7, 2019
74ebe0f
Add additional return to fix nil pointer exception
goog-lukemc Feb 7, 2019
3b5c8ef
added key parent testing and as well as control tests
goog-lukemc Feb 7, 2019
5721432
Fix pointer preference per review comments
goog-lukemc Feb 7, 2019
ee4f33d
Change variable name per comment
goog-lukemc Feb 12, 2019
8e1c38a
Added documentation for datastore key conversation
goog-lukemc Feb 15, 2019
5d06d01
fixed various comment nits
goog-lukemc Feb 15, 2019
f4a9440
fixed various comment nits and typos
goog-lukemc Feb 15, 2019
7789107
Updated variable name to make it more readable
goog-lukemc Feb 25, 2019
7d7c327
Removed \n and spacing issue
goog-lukemc Feb 25, 2019
ea111b8
Update lib alias name to newnds so that the comments and the alias ma…
goog-lukemc Feb 25, 2019
300b945
Update lib alias name to newnds so that the comments and the alias ma…
goog-lukemc Feb 25, 2019
dc956f7
Fixed various greammer issues and string spacing
goog-lukemc Feb 25, 2019
35a8464
passing unit tests
goog-lukemc Mar 14, 2019
68002c3
removed dependancies on new libs
goog-lukemc Mar 14, 2019
0030609
code duplication file - code copied from cloud.google.com/go/datastore
goog-lukemc Mar 14, 2019
a66c279
Spacing and format changes
goog-lukemc Mar 14, 2019
cf0b497
adjusted formating
goog-lukemc Mar 15, 2019
adf4e0f
added sync.Once
goog-lukemc Mar 21, 2019
0fbebe6
Updated Key conversion documentation in readme.md
goog-lukemc Mar 21, 2019
abdd3c6
corrected spelling
goog-lukemc Mar 21, 2019
ac84822
Updated description
goog-lukemc Mar 21, 2019
5069ace
Fixed grammar issues
goog-lukemc Mar 22, 2019
bb0e4f6
Renamed file
goog-lukemc May 2, 2019
a55d898
renaming file
goog-lukemc May 2, 2019
aca5ca6
restructured files to new internal package
goog-lukemc May 2, 2019
097a587
adding new package files
goog-lukemc May 2, 2019
65b66c1
fixed various code review issues
goog-lukemc May 2, 2019
fb6e777
datastore: cleanup keycompat
broady May 9, 2019
2c45d7e
README
broady May 9, 2019
d3c996c
keycompat -> cloudkey
broady May 9, 2019
98afd7e
review comments
broady May 13, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,30 @@ A few APIs were cleaned up, and there are some differences:
[blobstore package](https://google.golang.org/appengine/blobstore).
* `appengine/socket` is not required on App Engine flexible environment / Managed VMs.
Use the standard `net` package instead.

## Key Encode/Decode compatibiltiy to help with datastore library migrations

Key compatibility updates have been added to help customers transition from google.golang.org/appengine/datastore to cloud.google.com/go/datastore.
The `EnableKeyConversion` enables automatic conversion from a key encoded with cloud.google.com/go/datastore to google.golang.org/appengine/datastore key type.

### Enabling key conversion

Enable key conversion by calling `EnableKeyConversion(ctx)` in the `/_ah/startup` handler for basic and manual scaling or any handler in automatic scaling.

#### 1. Basic or manual scaling

This startup handler will enable key conversion for all handlers in the service.

```
http.HandleFunc("/_ah/start", func(w http.ResponseWriter, r *http.Request) {
datastore.EnableKeyConversion(appengine.NewContext(r))
})
```

#### 2. Automatic scaling

`/_ah/start` is not supported for automatic scaling and `/_ah/warmup` is not guaranteed to run, so you must call `datastore.EnableKeyConversion(appengine.NewContext(r))`
before you use code that needs key conversion.

You may want to add this to each of your handlers, or introduce middleware where it's called.
`EnableKeyConversion` is safe for concurrent use. Any call to it after the first is ignored.
120 changes: 120 additions & 0 deletions datastore/internal/cloudkey/cloudkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2019 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

// Package cloudpb is a subset of types and functions, copied from cloud.google.com/go/datastore.
//
// They are copied here to provide compatibility to decode keys generated by the cloud.google.com/go/datastore package.
package cloudkey

import (
"encoding/base64"
"errors"
"strings"

"github.com/golang/protobuf/proto"
cloudpb "google.golang.org/appengine/datastore/internal/cloudpb"
)

/////////////////////////////////////////////////////////////////////
// Code below is copied from https://github.com/googleapis/google-cloud-go/blob/master/datastore/datastore.go
/////////////////////////////////////////////////////////////////////

var (
// ErrInvalidKey is returned when an invalid key is presented.
ErrInvalidKey = errors.New("datastore: invalid key")
)

/////////////////////////////////////////////////////////////////////
// Code below is copied from https://github.com/googleapis/google-cloud-go/blob/master/datastore/key.go
/////////////////////////////////////////////////////////////////////

// Key represents the datastore key for a stored entity.
type Key struct {
// Kind cannot be empty.
Kind string
// Either ID or Name must be zero for the Key to be valid.
// If both are zero, the Key is incomplete.
ID int64
Name string
// Parent must either be a complete Key or nil.
Parent *Key

// Namespace provides the ability to partition your data for multiple
// tenants. In most cases, it is not necessary to specify a namespace.
// See docs on datastore multitenancy for details:
// https://cloud.google.com/datastore/docs/concepts/multitenancy
Namespace string
}

// DecodeKey decodes a key from the opaque representation returned by Encode.
func DecodeKey(encoded string) (*Key, 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 nil, err
}

pKey := new(cloudpb.Key)
if err := proto.Unmarshal(b, pKey); err != nil {
return nil, err
}
return protoToKey(pKey)
}

// valid returns whether the key is valid.
func (k *Key) valid() bool {
if k == nil {
return false
}
for ; k != nil; k = k.Parent {
if k.Kind == "" {
return false
}
if k.Name != "" && k.ID != 0 {
return false
}
if k.Parent != nil {
if k.Parent.Incomplete() {
return false
}
if k.Parent.Namespace != k.Namespace {
return false
}
}
}
return true
}

// Incomplete reports whether the key does not refer to a stored entity.
func (k *Key) Incomplete() bool {
return k.Name == "" && k.ID == 0
}

// protoToKey decodes a protocol buffer representation of a key into an
// equivalent *Key object. If the key is invalid, protoToKey will return the
// invalid key along with ErrInvalidKey.
func protoToKey(p *cloudpb.Key) (*Key, error) {
var key *Key
var namespace string
if partition := p.PartitionId; partition != nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be simplified to namespace := p.GetPartitionId().GetNamespaceId()? Normally, the Get function for protos handles nil values nicely.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything under internal/cloudkey and internal/cloudpb is copied/pasted from cloud.google.com/go/datastore, we should keep as-is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make that more clear? I wasn't sure where the copy/paste started & stopped.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully better now. Added package-level doc, too.

namespace = partition.NamespaceId
}
for _, el := range p.Path {
key = &Key{
Namespace: namespace,
Kind: el.Kind,
ID: el.GetId(),
Name: el.GetName(),
Parent: key,
}
}
if !key.valid() { // Also detects key == nil.
return key, ErrInvalidKey
}
return key, nil
}
Loading