You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

671 lines
20 KiB

  1. // Copyright 2014 Google LLC
  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. package datastore
  15. import (
  16. "context"
  17. "errors"
  18. "fmt"
  19. "log"
  20. "os"
  21. "reflect"
  22. "cloud.google.com/go/internal/trace"
  23. "google.golang.org/api/option"
  24. "google.golang.org/api/transport"
  25. gtransport "google.golang.org/api/transport/grpc"
  26. pb "google.golang.org/genproto/googleapis/datastore/v1"
  27. "google.golang.org/grpc"
  28. )
  29. const (
  30. prodAddr = "datastore.googleapis.com:443"
  31. userAgent = "gcloud-golang-datastore/20160401"
  32. )
  33. // ScopeDatastore grants permissions to view and/or manage datastore entities
  34. const ScopeDatastore = "https://www.googleapis.com/auth/datastore"
  35. // DetectProjectID is a sentinel value that instructs NewClient to detect the
  36. // project ID. It is given in place of the projectID argument. NewClient will
  37. // use the project ID from the given credentials or the default credentials
  38. // (https://developers.google.com/accounts/docs/application-default-credentials)
  39. // if no credentials were provided. When providing credentials, not all
  40. // options will allow NewClient to extract the project ID. Specifically a JWT
  41. // does not have the project ID encoded.
  42. const DetectProjectID = "*detect-project-id*"
  43. // resourcePrefixHeader is the name of the metadata header used to indicate
  44. // the resource being operated on.
  45. const resourcePrefixHeader = "google-cloud-resource-prefix"
  46. // Client is a client for reading and writing data in a datastore dataset.
  47. type Client struct {
  48. conn *grpc.ClientConn
  49. client pb.DatastoreClient
  50. dataset string // Called dataset by the datastore API, synonym for project ID.
  51. }
  52. // NewClient creates a new Client for a given dataset. If the project ID is
  53. // empty, it is derived from the DATASTORE_PROJECT_ID environment variable.
  54. // If the DATASTORE_EMULATOR_HOST environment variable is set, client will use
  55. // its value to connect to a locally-running datastore emulator.
  56. // DetectProjectID can be passed as the projectID argument to instruct
  57. // NewClient to detect the project ID from the credentials.
  58. func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
  59. var o []option.ClientOption
  60. // Environment variables for gcd emulator:
  61. // https://cloud.google.com/datastore/docs/tools/datastore-emulator
  62. // If the emulator is available, dial it without passing any credentials.
  63. if addr := os.Getenv("DATASTORE_EMULATOR_HOST"); addr != "" {
  64. o = []option.ClientOption{
  65. option.WithEndpoint(addr),
  66. option.WithoutAuthentication(),
  67. option.WithGRPCDialOption(grpc.WithInsecure()),
  68. }
  69. } else {
  70. o = []option.ClientOption{
  71. option.WithEndpoint(prodAddr),
  72. option.WithScopes(ScopeDatastore),
  73. option.WithUserAgent(userAgent),
  74. }
  75. }
  76. // Warn if we see the legacy emulator environment variables.
  77. if os.Getenv("DATASTORE_HOST") != "" && os.Getenv("DATASTORE_EMULATOR_HOST") == "" {
  78. log.Print("WARNING: legacy environment variable DATASTORE_HOST is ignored. Use DATASTORE_EMULATOR_HOST instead.")
  79. }
  80. if os.Getenv("DATASTORE_DATASET") != "" && os.Getenv("DATASTORE_PROJECT_ID") == "" {
  81. log.Print("WARNING: legacy environment variable DATASTORE_DATASET is ignored. Use DATASTORE_PROJECT_ID instead.")
  82. }
  83. if projectID == "" {
  84. projectID = os.Getenv("DATASTORE_PROJECT_ID")
  85. }
  86. o = append(o, opts...)
  87. if projectID == DetectProjectID {
  88. creds, err := transport.Creds(ctx, o...)
  89. if err != nil {
  90. return nil, fmt.Errorf("fetching creds: %v", err)
  91. }
  92. if creds.ProjectID == "" {
  93. return nil, errors.New("datastore: see the docs on DetectProjectID")
  94. }
  95. projectID = creds.ProjectID
  96. }
  97. if projectID == "" {
  98. return nil, errors.New("datastore: missing project/dataset id")
  99. }
  100. conn, err := gtransport.Dial(ctx, o...)
  101. if err != nil {
  102. return nil, fmt.Errorf("dialing: %v", err)
  103. }
  104. return &Client{
  105. conn: conn,
  106. client: newDatastoreClient(conn, projectID),
  107. dataset: projectID,
  108. }, nil
  109. }
  110. var (
  111. // ErrInvalidEntityType is returned when functions like Get or Next are
  112. // passed a dst or src argument of invalid type.
  113. ErrInvalidEntityType = errors.New("datastore: invalid entity type")
  114. // ErrInvalidKey is returned when an invalid key is presented.
  115. ErrInvalidKey = errors.New("datastore: invalid key")
  116. // ErrNoSuchEntity is returned when no entity was found for a given key.
  117. ErrNoSuchEntity = errors.New("datastore: no such entity")
  118. )
  119. type multiArgType int
  120. const (
  121. multiArgTypeInvalid multiArgType = iota
  122. multiArgTypePropertyLoadSaver
  123. multiArgTypeStruct
  124. multiArgTypeStructPtr
  125. multiArgTypeInterface
  126. )
  127. // ErrFieldMismatch is returned when a field is to be loaded into a different
  128. // type than the one it was stored from, or when a field is missing or
  129. // unexported in the destination struct.
  130. // StructType is the type of the struct pointed to by the destination argument
  131. // passed to Get or to Iterator.Next.
  132. type ErrFieldMismatch struct {
  133. StructType reflect.Type
  134. FieldName string
  135. Reason string
  136. }
  137. func (e *ErrFieldMismatch) Error() string {
  138. return fmt.Sprintf("datastore: cannot load field %q into a %q: %s",
  139. e.FieldName, e.StructType, e.Reason)
  140. }
  141. // GeoPoint represents a location as latitude/longitude in degrees.
  142. type GeoPoint struct {
  143. Lat, Lng float64
  144. }
  145. // Valid returns whether a GeoPoint is within [-90, 90] latitude and [-180, 180] longitude.
  146. func (g GeoPoint) Valid() bool {
  147. return -90 <= g.Lat && g.Lat <= 90 && -180 <= g.Lng && g.Lng <= 180
  148. }
  149. func keyToProto(k *Key) *pb.Key {
  150. if k == nil {
  151. return nil
  152. }
  153. var path []*pb.Key_PathElement
  154. for {
  155. el := &pb.Key_PathElement{Kind: k.Kind}
  156. if k.ID != 0 {
  157. el.IdType = &pb.Key_PathElement_Id{Id: k.ID}
  158. } else if k.Name != "" {
  159. el.IdType = &pb.Key_PathElement_Name{Name: k.Name}
  160. }
  161. path = append(path, el)
  162. if k.Parent == nil {
  163. break
  164. }
  165. k = k.Parent
  166. }
  167. // The path should be in order [grandparent, parent, child]
  168. // We did it backward above, so reverse back.
  169. for i := 0; i < len(path)/2; i++ {
  170. path[i], path[len(path)-i-1] = path[len(path)-i-1], path[i]
  171. }
  172. key := &pb.Key{Path: path}
  173. if k.Namespace != "" {
  174. key.PartitionId = &pb.PartitionId{
  175. NamespaceId: k.Namespace,
  176. }
  177. }
  178. return key
  179. }
  180. // protoToKey decodes a protocol buffer representation of a key into an
  181. // equivalent *Key object. If the key is invalid, protoToKey will return the
  182. // invalid key along with ErrInvalidKey.
  183. func protoToKey(p *pb.Key) (*Key, error) {
  184. var key *Key
  185. var namespace string
  186. if partition := p.PartitionId; partition != nil {
  187. namespace = partition.NamespaceId
  188. }
  189. for _, el := range p.Path {
  190. key = &Key{
  191. Namespace: namespace,
  192. Kind: el.Kind,
  193. ID: el.GetId(),
  194. Name: el.GetName(),
  195. Parent: key,
  196. }
  197. }
  198. if !key.valid() { // Also detects key == nil.
  199. return key, ErrInvalidKey
  200. }
  201. return key, nil
  202. }
  203. // multiKeyToProto is a batch version of keyToProto.
  204. func multiKeyToProto(keys []*Key) []*pb.Key {
  205. ret := make([]*pb.Key, len(keys))
  206. for i, k := range keys {
  207. ret[i] = keyToProto(k)
  208. }
  209. return ret
  210. }
  211. // multiKeyToProto is a batch version of keyToProto.
  212. func multiProtoToKey(keys []*pb.Key) ([]*Key, error) {
  213. hasErr := false
  214. ret := make([]*Key, len(keys))
  215. err := make(MultiError, len(keys))
  216. for i, k := range keys {
  217. ret[i], err[i] = protoToKey(k)
  218. if err[i] != nil {
  219. hasErr = true
  220. }
  221. }
  222. if hasErr {
  223. return nil, err
  224. }
  225. return ret, nil
  226. }
  227. // multiValid is a batch version of Key.valid. It returns an error, not a
  228. // []bool.
  229. func multiValid(key []*Key) error {
  230. invalid := false
  231. for _, k := range key {
  232. if !k.valid() {
  233. invalid = true
  234. break
  235. }
  236. }
  237. if !invalid {
  238. return nil
  239. }
  240. err := make(MultiError, len(key))
  241. for i, k := range key {
  242. if !k.valid() {
  243. err[i] = ErrInvalidKey
  244. }
  245. }
  246. return err
  247. }
  248. // checkMultiArg checks that v has type []S, []*S, []I, or []P, for some struct
  249. // type S, for some interface type I, or some non-interface non-pointer type P
  250. // such that P or *P implements PropertyLoadSaver.
  251. //
  252. // It returns what category the slice's elements are, and the reflect.Type
  253. // that represents S, I or P.
  254. //
  255. // As a special case, PropertyList is an invalid type for v.
  256. func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) {
  257. // TODO(djd): multiArg is very confusing. Fold this logic into the
  258. // relevant Put/Get methods to make the logic less opaque.
  259. if v.Kind() != reflect.Slice {
  260. return multiArgTypeInvalid, nil
  261. }
  262. if v.Type() == typeOfPropertyList {
  263. return multiArgTypeInvalid, nil
  264. }
  265. elemType = v.Type().Elem()
  266. if reflect.PtrTo(elemType).Implements(typeOfPropertyLoadSaver) {
  267. return multiArgTypePropertyLoadSaver, elemType
  268. }
  269. switch elemType.Kind() {
  270. case reflect.Struct:
  271. return multiArgTypeStruct, elemType
  272. case reflect.Interface:
  273. return multiArgTypeInterface, elemType
  274. case reflect.Ptr:
  275. elemType = elemType.Elem()
  276. if elemType.Kind() == reflect.Struct {
  277. return multiArgTypeStructPtr, elemType
  278. }
  279. }
  280. return multiArgTypeInvalid, nil
  281. }
  282. // Close closes the Client.
  283. func (c *Client) Close() error {
  284. return c.conn.Close()
  285. }
  286. // Get loads the entity stored for key into dst, which must be a struct pointer
  287. // or implement PropertyLoadSaver. If there is no such entity for the key, Get
  288. // returns ErrNoSuchEntity.
  289. //
  290. // The values of dst's unmatched struct fields are not modified, and matching
  291. // slice-typed fields are not reset before appending to them. In particular, it
  292. // is recommended to pass a pointer to a zero valued struct on each Get call.
  293. //
  294. // ErrFieldMismatch is returned when a field is to be loaded into a different
  295. // type than the one it was stored from, or when a field is missing or
  296. // unexported in the destination struct. ErrFieldMismatch is only returned if
  297. // dst is a struct pointer.
  298. func (c *Client) Get(ctx context.Context, key *Key, dst interface{}) (err error) {
  299. ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Get")
  300. defer func() { trace.EndSpan(ctx, err) }()
  301. if dst == nil { // get catches nil interfaces; we need to catch nil ptr here
  302. return ErrInvalidEntityType
  303. }
  304. err = c.get(ctx, []*Key{key}, []interface{}{dst}, nil)
  305. if me, ok := err.(MultiError); ok {
  306. return me[0]
  307. }
  308. return err
  309. }
  310. // GetMulti is a batch version of Get.
  311. //
  312. // dst must be a []S, []*S, []I or []P, for some struct type S, some interface
  313. // type I, or some non-interface non-pointer type P such that P or *P
  314. // implements PropertyLoadSaver. If an []I, each element must be a valid dst
  315. // for Get: it must be a struct pointer or implement PropertyLoadSaver.
  316. //
  317. // As a special case, PropertyList is an invalid type for dst, even though a
  318. // PropertyList is a slice of structs. It is treated as invalid to avoid being
  319. // mistakenly passed when []PropertyList was intended.
  320. //
  321. // err may be a MultiError. See ExampleMultiError to check it.
  322. func (c *Client) GetMulti(ctx context.Context, keys []*Key, dst interface{}) (err error) {
  323. ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.GetMulti")
  324. defer func() { trace.EndSpan(ctx, err) }()
  325. return c.get(ctx, keys, dst, nil)
  326. }
  327. func (c *Client) get(ctx context.Context, keys []*Key, dst interface{}, opts *pb.ReadOptions) error {
  328. v := reflect.ValueOf(dst)
  329. multiArgType, _ := checkMultiArg(v)
  330. // Sanity checks
  331. if multiArgType == multiArgTypeInvalid {
  332. return errors.New("datastore: dst has invalid type")
  333. }
  334. if len(keys) != v.Len() {
  335. return errors.New("datastore: keys and dst slices have different length")
  336. }
  337. if len(keys) == 0 {
  338. return nil
  339. }
  340. // Go through keys, validate them, serialize then, and create a dict mapping them to their indices.
  341. // Equal keys are deduped.
  342. multiErr, any := make(MultiError, len(keys)), false
  343. keyMap := make(map[string][]int, len(keys))
  344. pbKeys := make([]*pb.Key, 0, len(keys))
  345. for i, k := range keys {
  346. if !k.valid() {
  347. multiErr[i] = ErrInvalidKey
  348. any = true
  349. } else if k.Incomplete() {
  350. multiErr[i] = fmt.Errorf("datastore: can't get the incomplete key: %v", k)
  351. any = true
  352. } else {
  353. ks := k.String()
  354. if _, ok := keyMap[ks]; !ok {
  355. pbKeys = append(pbKeys, keyToProto(k))
  356. }
  357. keyMap[ks] = append(keyMap[ks], i)
  358. }
  359. }
  360. if any {
  361. return multiErr
  362. }
  363. req := &pb.LookupRequest{
  364. ProjectId: c.dataset,
  365. Keys: pbKeys,
  366. ReadOptions: opts,
  367. }
  368. resp, err := c.client.Lookup(ctx, req)
  369. if err != nil {
  370. return err
  371. }
  372. found := resp.Found
  373. missing := resp.Missing
  374. // Upper bound 100 iterations to prevent infinite loop.
  375. // We choose 100 iterations somewhat logically:
  376. // Max number of Entities you can request from Datastore is 1,000.
  377. // Max size for a Datastore Entity is 1 MiB.
  378. // Max request size is 10 MiB, so we assume max response size is also 10 MiB.
  379. // 1,000 / 10 = 100.
  380. // Note that if ctx has a deadline, the deadline will probably
  381. // be hit before we reach 100 iterations.
  382. for i := 0; len(resp.Deferred) > 0 && i < 100; i++ {
  383. req.Keys = resp.Deferred
  384. resp, err = c.client.Lookup(ctx, req)
  385. if err != nil {
  386. return err
  387. }
  388. found = append(found, resp.Found...)
  389. missing = append(missing, resp.Missing...)
  390. }
  391. filled := 0
  392. for _, e := range found {
  393. k, err := protoToKey(e.Entity.Key)
  394. if err != nil {
  395. return errors.New("datastore: internal error: server returned an invalid key")
  396. }
  397. filled += len(keyMap[k.String()])
  398. for _, index := range keyMap[k.String()] {
  399. elem := v.Index(index)
  400. if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct {
  401. elem = elem.Addr()
  402. }
  403. if multiArgType == multiArgTypeStructPtr && elem.IsNil() {
  404. elem.Set(reflect.New(elem.Type().Elem()))
  405. }
  406. if err := loadEntityProto(elem.Interface(), e.Entity); err != nil {
  407. multiErr[index] = err
  408. any = true
  409. }
  410. }
  411. }
  412. for _, e := range missing {
  413. k, err := protoToKey(e.Entity.Key)
  414. if err != nil {
  415. return errors.New("datastore: internal error: server returned an invalid key")
  416. }
  417. filled += len(keyMap[k.String()])
  418. for _, index := range keyMap[k.String()] {
  419. multiErr[index] = ErrNoSuchEntity
  420. }
  421. any = true
  422. }
  423. if filled != len(keys) {
  424. return errors.New("datastore: internal error: server returned the wrong number of entities")
  425. }
  426. if any {
  427. return multiErr
  428. }
  429. return nil
  430. }
  431. // Put saves the entity src into the datastore with the given key. src must be
  432. // a struct pointer or implement PropertyLoadSaver; if the struct pointer has
  433. // any unexported fields they will be skipped. If the key is incomplete, the
  434. // returned key will be a unique key generated by the datastore.
  435. func (c *Client) Put(ctx context.Context, key *Key, src interface{}) (*Key, error) {
  436. k, err := c.PutMulti(ctx, []*Key{key}, []interface{}{src})
  437. if err != nil {
  438. if me, ok := err.(MultiError); ok {
  439. return nil, me[0]
  440. }
  441. return nil, err
  442. }
  443. return k[0], nil
  444. }
  445. // PutMulti is a batch version of Put.
  446. //
  447. // src must satisfy the same conditions as the dst argument to GetMulti.
  448. // err may be a MultiError. See ExampleMultiError to check it.
  449. func (c *Client) PutMulti(ctx context.Context, keys []*Key, src interface{}) (ret []*Key, err error) {
  450. // TODO(jba): rewrite in terms of Mutate.
  451. ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.PutMulti")
  452. defer func() { trace.EndSpan(ctx, err) }()
  453. mutations, err := putMutations(keys, src)
  454. if err != nil {
  455. return nil, err
  456. }
  457. // Make the request.
  458. req := &pb.CommitRequest{
  459. ProjectId: c.dataset,
  460. Mutations: mutations,
  461. Mode: pb.CommitRequest_NON_TRANSACTIONAL,
  462. }
  463. resp, err := c.client.Commit(ctx, req)
  464. if err != nil {
  465. return nil, err
  466. }
  467. // Copy any newly minted keys into the returned keys.
  468. ret = make([]*Key, len(keys))
  469. for i, key := range keys {
  470. if key.Incomplete() {
  471. // This key is in the mutation results.
  472. ret[i], err = protoToKey(resp.MutationResults[i].Key)
  473. if err != nil {
  474. return nil, errors.New("datastore: internal error: server returned an invalid key")
  475. }
  476. } else {
  477. ret[i] = key
  478. }
  479. }
  480. return ret, nil
  481. }
  482. func putMutations(keys []*Key, src interface{}) ([]*pb.Mutation, error) {
  483. v := reflect.ValueOf(src)
  484. multiArgType, _ := checkMultiArg(v)
  485. if multiArgType == multiArgTypeInvalid {
  486. return nil, errors.New("datastore: src has invalid type")
  487. }
  488. if len(keys) != v.Len() {
  489. return nil, errors.New("datastore: key and src slices have different length")
  490. }
  491. if len(keys) == 0 {
  492. return nil, nil
  493. }
  494. if err := multiValid(keys); err != nil {
  495. return nil, err
  496. }
  497. mutations := make([]*pb.Mutation, 0, len(keys))
  498. multiErr := make(MultiError, len(keys))
  499. hasErr := false
  500. for i, k := range keys {
  501. elem := v.Index(i)
  502. // Two cases where we need to take the address:
  503. // 1) multiArgTypePropertyLoadSaver => &elem implements PLS
  504. // 2) multiArgTypeStruct => saveEntity needs *struct
  505. if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct {
  506. elem = elem.Addr()
  507. }
  508. p, err := saveEntity(k, elem.Interface())
  509. if err != nil {
  510. multiErr[i] = err
  511. hasErr = true
  512. }
  513. var mut *pb.Mutation
  514. if k.Incomplete() {
  515. mut = &pb.Mutation{Operation: &pb.Mutation_Insert{Insert: p}}
  516. } else {
  517. mut = &pb.Mutation{Operation: &pb.Mutation_Upsert{Upsert: p}}
  518. }
  519. mutations = append(mutations, mut)
  520. }
  521. if hasErr {
  522. return nil, multiErr
  523. }
  524. return mutations, nil
  525. }
  526. // Delete deletes the entity for the given key.
  527. func (c *Client) Delete(ctx context.Context, key *Key) error {
  528. err := c.DeleteMulti(ctx, []*Key{key})
  529. if me, ok := err.(MultiError); ok {
  530. return me[0]
  531. }
  532. return err
  533. }
  534. // DeleteMulti is a batch version of Delete.
  535. //
  536. // err may be a MultiError. See ExampleMultiError to check it.
  537. func (c *Client) DeleteMulti(ctx context.Context, keys []*Key) (err error) {
  538. // TODO(jba): rewrite in terms of Mutate.
  539. ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.DeleteMulti")
  540. defer func() { trace.EndSpan(ctx, err) }()
  541. mutations, err := deleteMutations(keys)
  542. if err != nil {
  543. return err
  544. }
  545. req := &pb.CommitRequest{
  546. ProjectId: c.dataset,
  547. Mutations: mutations,
  548. Mode: pb.CommitRequest_NON_TRANSACTIONAL,
  549. }
  550. _, err = c.client.Commit(ctx, req)
  551. return err
  552. }
  553. func deleteMutations(keys []*Key) ([]*pb.Mutation, error) {
  554. mutations := make([]*pb.Mutation, 0, len(keys))
  555. set := make(map[string]bool, len(keys))
  556. multiErr := make(MultiError, len(keys))
  557. hasErr := false
  558. for i, k := range keys {
  559. if !k.valid() {
  560. multiErr[i] = ErrInvalidKey
  561. hasErr = true
  562. } else if k.Incomplete() {
  563. multiErr[i] = fmt.Errorf("datastore: can't delete the incomplete key: %v", k)
  564. hasErr = true
  565. } else {
  566. ks := k.String()
  567. if !set[ks] {
  568. mutations = append(mutations, &pb.Mutation{
  569. Operation: &pb.Mutation_Delete{Delete: keyToProto(k)},
  570. })
  571. }
  572. set[ks] = true
  573. }
  574. }
  575. if hasErr {
  576. return nil, multiErr
  577. }
  578. return mutations, nil
  579. }
  580. // Mutate applies one or more mutations atomically.
  581. // It returns the keys of the argument Mutations, in the same order.
  582. //
  583. // If any of the mutations are invalid, Mutate returns a MultiError with the errors.
  584. // Mutate returns a MultiError in this case even if there is only one Mutation.
  585. // See ExampleMultiError to check it.
  586. func (c *Client) Mutate(ctx context.Context, muts ...*Mutation) (ret []*Key, err error) {
  587. ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.Mutate")
  588. defer func() { trace.EndSpan(ctx, err) }()
  589. pmuts, err := mutationProtos(muts)
  590. if err != nil {
  591. return nil, err
  592. }
  593. req := &pb.CommitRequest{
  594. ProjectId: c.dataset,
  595. Mutations: pmuts,
  596. Mode: pb.CommitRequest_NON_TRANSACTIONAL,
  597. }
  598. resp, err := c.client.Commit(ctx, req)
  599. if err != nil {
  600. return nil, err
  601. }
  602. // Copy any newly minted keys into the returned keys.
  603. ret = make([]*Key, len(muts))
  604. for i, mut := range muts {
  605. if mut.key.Incomplete() {
  606. // This key is in the mutation results.
  607. ret[i], err = protoToKey(resp.MutationResults[i].Key)
  608. if err != nil {
  609. return nil, errors.New("datastore: internal error: server returned an invalid key")
  610. }
  611. } else {
  612. ret[i] = mut.key
  613. }
  614. }
  615. return ret, nil
  616. }