// 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 }