| // 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 datastore |
| |
| import ( |
| "sync" |
| |
| "golang.org/x/net/context" |
| |
| "google.golang.org/appengine/datastore/internal/cloudkey" |
| "google.golang.org/appengine/internal" |
| ) |
| |
| var keyConversion struct { |
| mu sync.RWMutex |
| appID string // read using getKeyConversionAppID |
| } |
| |
| // EnableKeyConversion enables encoded key compatibility with the Cloud |
| // Datastore client library (cloud.google.com/go/datastore). Encoded keys |
| // generated by the Cloud Datastore client library will be decoded into App |
| // Engine datastore keys. |
| // |
| // The context provided must be an App Engine context if running in App Engine |
| // first generation runtime. This can be called in the /_ah/start handler. It is |
| // safe to call multiple times, and is cheap to call, so can also be inserted as |
| // middleware. |
| // |
| // Enabling key compatibility does not affect the encoding format used by |
| // Key.Encode, it only expands the type of keys that are able to be decoded with |
| // DecodeKey. |
| func EnableKeyConversion(ctx context.Context) { |
| // Only attempt to set appID if it's unset. |
| // If already set, ignore. |
| if getKeyConversionAppID() != "" { |
| return |
| } |
| |
| keyConversion.mu.Lock() |
| // Check again to avoid race where another goroutine set appID between the call |
| // to getKeyConversionAppID above and taking the write lock. |
| if keyConversion.appID == "" { |
| keyConversion.appID = internal.FullyQualifiedAppID(ctx) |
| } |
| keyConversion.mu.Unlock() |
| } |
| |
| func getKeyConversionAppID() string { |
| keyConversion.mu.RLock() |
| appID := keyConversion.appID |
| keyConversion.mu.RUnlock() |
| return appID |
| } |
| |
| // decodeCloudKey attempts to decode the given encoded key generated by the |
| // Cloud Datastore client library (cloud.google.com/go/datastore), returning nil |
| // if the key couldn't be decoded. |
| func decodeCloudKey(encoded string) *Key { |
| appID := getKeyConversionAppID() |
| if appID == "" { |
| return nil |
| } |
| |
| k, err := cloudkey.DecodeKey(encoded) |
| if err != nil { |
| return nil |
| } |
| return convertCloudKey(k, appID) |
| } |
| |
| // convertCloudKey converts a Cloud Datastore key and converts it to an App |
| // Engine Datastore key. Cloud Datastore keys don't include the project/app ID, |
| // so we must add it back in. |
| func convertCloudKey(key *cloudkey.Key, appID string) *Key { |
| if key == nil { |
| return nil |
| } |
| k := &Key{ |
| intID: key.ID, |
| kind: key.Kind, |
| namespace: key.Namespace, |
| parent: convertCloudKey(key.Parent, appID), |
| stringID: key.Name, |
| appID: appID, |
| } |
| return k |
| } |