All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.cloud.datastore.DatastoreImpl Maven / Gradle / Ivy

There is a newer version: 2.24.2
Show newest version
/*
 * Copyright 2015 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.cloud.datastore;

import com.google.api.core.BetaApi;
import com.google.api.gax.retrying.RetrySettings;
import com.google.cloud.BaseService;
import com.google.cloud.ExceptionHandler;
import com.google.cloud.RetryHelper;
import com.google.cloud.RetryHelper.RetryHelperException;
import com.google.cloud.ServiceOptions;
import com.google.cloud.datastore.execution.AggregationQueryExecutor;
import com.google.cloud.datastore.spi.v1.DatastoreRpc;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.datastore.v1.ExplainOptions;
import com.google.datastore.v1.ReadOptions;
import com.google.datastore.v1.ReserveIdsRequest;
import com.google.datastore.v1.TransactionOptions;
import com.google.protobuf.ByteString;
import io.opencensus.common.Scope;
import io.opencensus.trace.Span;
import io.opencensus.trace.Status;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;

final class DatastoreImpl extends BaseService implements Datastore {

  private final DatastoreRpc datastoreRpc;
  private final RetrySettings retrySettings;
  private static final ExceptionHandler TRANSACTION_EXCEPTION_HANDLER =
      TransactionExceptionHandler.build();
  private static final ExceptionHandler TRANSACTION_OPERATION_EXCEPTION_HANDLER =
      TransactionOperationExceptionHandler.build();
  private final TraceUtil traceUtil = TraceUtil.getInstance();

  private final ReadOptionProtoPreparer readOptionProtoPreparer;
  private final AggregationQueryExecutor aggregationQueryExecutor;

  DatastoreImpl(DatastoreOptions options) {
    super(options);
    this.datastoreRpc = options.getDatastoreRpcV1();
    retrySettings =
        MoreObjects.firstNonNull(options.getRetrySettings(), ServiceOptions.getNoRetrySettings());

    readOptionProtoPreparer = new ReadOptionProtoPreparer();
    aggregationQueryExecutor =
        new AggregationQueryExecutor(
            new RetryAndTraceDatastoreRpcDecorator(datastoreRpc, traceUtil, retrySettings, options),
            options);
  }

  @Override
  public Batch newBatch() {
    return new BatchImpl(this);
  }

  @Override
  public Transaction newTransaction(TransactionOptions transactionOptions) {
    return new TransactionImpl(this, transactionOptions);
  }

  @Override
  public Transaction newTransaction() {
    return new TransactionImpl(this);
  }

  static class ReadWriteTransactionCallable implements Callable {

    private final Datastore datastore;
    private final TransactionCallable callable;
    private volatile TransactionOptions options;
    private volatile Transaction transaction;

    ReadWriteTransactionCallable(
        Datastore datastore, TransactionCallable callable, TransactionOptions options) {
      this.datastore = datastore;
      this.callable = callable;
      this.options = options;
      this.transaction = null;
    }

    Datastore getDatastore() {
      return datastore;
    }

    TransactionOptions getOptions() {
      return options;
    }

    Transaction getTransaction() {
      return transaction;
    }

    void setPrevTransactionId(ByteString transactionId) {
      TransactionOptions.ReadWrite readWrite =
          TransactionOptions.ReadWrite.newBuilder().setPreviousTransaction(transactionId).build();
      options = options.toBuilder().setReadWrite(readWrite).build();
    }

    @Override
    public T call() throws DatastoreException {
      transaction = datastore.newTransaction(options);
      try {
        T value = callable.run(transaction);
        transaction.commit();
        return value;
      } catch (Exception ex) {
        transaction.rollback();
        throw DatastoreException.propagateUserException(ex);
      } finally {
        if (transaction.isActive()) {
          transaction.rollback();
        }
        if (options != null
            && options.getModeCase().equals(TransactionOptions.ModeCase.READ_WRITE)) {
          setPrevTransactionId(transaction.getTransactionId());
        }
      }
    }
  }

  @Override
  public  T runInTransaction(final TransactionCallable callable) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_TRANSACTION);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      return RetryHelper.runWithRetries(
          new ReadWriteTransactionCallable(this, callable, null),
          retrySettings,
          TRANSACTION_EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }

  @Override
  public  T runInTransaction(
      final TransactionCallable callable, TransactionOptions transactionOptions) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_TRANSACTION);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      return RetryHelper.runWithRetries(
          new ReadWriteTransactionCallable(this, callable, transactionOptions),
          retrySettings,
          TRANSACTION_EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }

  @Override
  public  QueryResults run(Query query) {
    return run(Optional.empty(), query, null);
  }

  @Override
  public  QueryResults run(Query query, ReadOption... options) {
    return run(toReadOptionsPb(options), query, null);
  }

  @Override
  @BetaApi
  public  QueryResults run(
      Query query,
      com.google.cloud.datastore.models.ExplainOptions explainOptions,
      ReadOption... options) {
    return run(toReadOptionsPb(options), query, explainOptions.toPb());
  }

  @SuppressWarnings("unchecked")
   QueryResults run(
      Optional readOptionsPb, Query query, ExplainOptions explainOptions) {
    return new QueryResultsImpl(
        this, readOptionsPb, (RecordQuery) query, query.getNamespace(), explainOptions);
  }

  @Override
  public AggregationResults runAggregation(AggregationQuery query) {
    return aggregationQueryExecutor.execute(query, null);
  }

  @Override
  public AggregationResults runAggregation(AggregationQuery query, ReadOption... options) {
    return aggregationQueryExecutor.execute(query, null, options);
  }

  @Override
  @BetaApi
  public AggregationResults runAggregation(
      AggregationQuery query, com.google.cloud.datastore.models.ExplainOptions explainOptions) {
    return aggregationQueryExecutor.execute(query, explainOptions);
  }

  @Override
  @BetaApi
  public AggregationResults runAggregation(
      AggregationQuery query,
      com.google.cloud.datastore.models.ExplainOptions explainOptions,
      ReadOption... options) {
    return aggregationQueryExecutor.execute(query, explainOptions, options);
  }

  com.google.datastore.v1.RunQueryResponse runQuery(
      final com.google.datastore.v1.RunQueryRequest requestPb) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_RUNQUERY);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      return RetryHelper.runWithRetries(
          () -> datastoreRpc.runQuery(requestPb),
          retrySettings,
          requestPb.getReadOptions().getTransaction().isEmpty()
              ? EXCEPTION_HANDLER
              : TRANSACTION_OPERATION_EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }

  @Override
  public Key allocateId(IncompleteKey key) {
    return DatastoreHelper.allocateId(this, key);
  }

  private boolean verifyIncompleteKeyType(IncompleteKey... keys) {
    for (IncompleteKey key : keys) {
      if (key instanceof Key) {
        return false;
      }
    }
    return true;
  }

  @Override
  public List allocateId(IncompleteKey... keys) {
    Preconditions.checkArgument(
        verifyIncompleteKeyType(keys), "keys must be IncompleteKey instances");
    if (keys.length == 0) {
      return Collections.emptyList();
    }
    com.google.datastore.v1.AllocateIdsRequest.Builder requestPb =
        com.google.datastore.v1.AllocateIdsRequest.newBuilder();
    for (IncompleteKey key : keys) {
      requestPb.addKeys(trimNameOrId(key).toPb());
    }
    requestPb.setProjectId(getOptions().getProjectId());
    requestPb.setDatabaseId(getOptions().getDatabaseId());
    com.google.datastore.v1.AllocateIdsResponse responsePb = allocateIds(requestPb.build());
    ImmutableList.Builder keyList = ImmutableList.builder();
    for (com.google.datastore.v1.Key keyPb : responsePb.getKeysList()) {
      keyList.add(Key.fromPb(keyPb));
    }
    return keyList.build();
  }

  private com.google.datastore.v1.AllocateIdsResponse allocateIds(
      final com.google.datastore.v1.AllocateIdsRequest requestPb) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_ALLOCATEIDS);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      return RetryHelper.runWithRetries(
          new Callable() {
            @Override
            public com.google.datastore.v1.AllocateIdsResponse call() throws DatastoreException {
              return datastoreRpc.allocateIds(requestPb);
            }
          },
          retrySettings,
          EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }

  private IncompleteKey trimNameOrId(IncompleteKey key) {
    if (key instanceof Key) {
      return IncompleteKey.newBuilder(key).build();
    }
    return key;
  }

  @Override
  public Entity add(FullEntity entity) {
    return DatastoreHelper.add(this, entity);
  }

  @SuppressWarnings("unchecked")
  @Override
  public List add(FullEntity... entities) {
    if (entities.length == 0) {
      return Collections.emptyList();
    }
    List mutationsPb = new ArrayList<>();
    Map completeEntities = new LinkedHashMap<>();
    for (FullEntity entity : entities) {
      Entity completeEntity = null;
      if (entity.getKey() instanceof Key) {
        completeEntity = Entity.convert((FullEntity) entity);
      }
      if (completeEntity != null) {
        if (completeEntities.put(completeEntity.getKey(), completeEntity) != null) {
          throw DatastoreException.throwInvalidRequest(
              "Duplicate entity with the key %s", entity.getKey());
        }
      } else {
        Preconditions.checkArgument(entity.hasKey(), "Entity %s is missing a key", entity);
      }
      mutationsPb.add(
          com.google.datastore.v1.Mutation.newBuilder().setInsert(entity.toPb()).build());
    }
    com.google.datastore.v1.CommitResponse commitResponse = commitMutation(mutationsPb);
    Iterator mutationResults =
        commitResponse.getMutationResultsList().iterator();
    ImmutableList.Builder responseBuilder = ImmutableList.builder();
    for (FullEntity entity : entities) {
      Entity completeEntity = completeEntities.get(entity.getKey());
      if (completeEntity != null) {
        responseBuilder.add(completeEntity);
        mutationResults.next();
      } else {
        responseBuilder.add(
            Entity.newBuilder(Key.fromPb(mutationResults.next().getKey()), entity).build());
      }
    }
    return responseBuilder.build();
  }

  @Override
  public Entity get(Key key) {
    return DatastoreHelper.get(this, key);
  }

  @Override
  public Entity get(Key key, ReadOption... options) {
    return DatastoreHelper.get(this, key, options);
  }

  @Override
  public Iterator get(Key... keys) {
    return get(Optional.empty(), keys);
  }

  @Override
  public Iterator get(Iterable keys, ReadOption... options) {
    return get(toReadOptionsPb(options), Iterables.toArray(keys, Key.class));
  }

  private Optional toReadOptionsPb(ReadOption... options) {
    if (options == null) {
      return Optional.empty();
    }
    return this.readOptionProtoPreparer.prepare(Arrays.asList(options));
  }

  @Override
  public List fetch(Key... keys) {
    return DatastoreHelper.fetch(this, keys);
  }

  @Override
  public List fetch(Iterable keys, ReadOption... options) {
    return DatastoreHelper.fetch(this, Iterables.toArray(keys, Key.class), options);
  }

  Iterator get(Optional readOptionsPb, final Key... keys) {
    if (keys.length == 0) {
      return Collections.emptyIterator();
    }
    com.google.datastore.v1.LookupRequest.Builder requestPb =
        com.google.datastore.v1.LookupRequest.newBuilder();
    readOptionsPb.ifPresent(requestPb::setReadOptions);
    for (Key k : Sets.newLinkedHashSet(Arrays.asList(keys))) {
      requestPb.addKeys(k.toPb());
    }
    requestPb.setProjectId(getOptions().getProjectId());
    requestPb.setDatabaseId(getOptions().getDatabaseId());
    return new ResultsIterator(requestPb);
  }

  final class ResultsIterator extends AbstractIterator {

    private final com.google.datastore.v1.LookupRequest.Builder requestPb;
    Iterator iter;

    ResultsIterator(com.google.datastore.v1.LookupRequest.Builder requestPb) {
      this.requestPb = requestPb;
      loadResults();
    }

    private void loadResults() {
      com.google.datastore.v1.LookupResponse responsePb = lookup(requestPb.build());
      iter = responsePb.getFoundList().iterator();
      requestPb.clearKeys();
      if (responsePb.getDeferredCount() > 0) {
        requestPb.addAllKeys(responsePb.getDeferredList());
      }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Entity computeNext() {
      while (!iter.hasNext()) {
        if (requestPb.getKeysCount() == 0) {
          return endOfData();
        }
        loadResults();
      }
      return Entity.fromPb(iter.next().getEntity());
    }
  }

  com.google.datastore.v1.LookupResponse lookup(
      final com.google.datastore.v1.LookupRequest requestPb) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_LOOKUP);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      return RetryHelper.runWithRetries(
          new Callable() {
            @Override
            public com.google.datastore.v1.LookupResponse call() throws DatastoreException {
              return datastoreRpc.lookup(requestPb);
            }
          },
          retrySettings,
          requestPb.getReadOptions().getTransaction().isEmpty()
              ? EXCEPTION_HANDLER
              : TRANSACTION_OPERATION_EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }

  @Override
  public List reserveIds(Key... keys) {
    ReserveIdsRequest.Builder requestPb = ReserveIdsRequest.newBuilder();
    for (Key key : keys) {
      requestPb.addKeys(key.toPb());
    }
    requestPb.setProjectId(getOptions().getProjectId());
    requestPb.setDatabaseId(getOptions().getDatabaseId());
    com.google.datastore.v1.ReserveIdsResponse responsePb = reserveIds(requestPb.build());
    ImmutableList.Builder keyList = ImmutableList.builder();
    if (responsePb.isInitialized()) {
      for (Key key : keys) {
        keyList.add(key);
      }
    }
    return keyList.build();
  }

  com.google.datastore.v1.ReserveIdsResponse reserveIds(
      final com.google.datastore.v1.ReserveIdsRequest requestPb) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_RESERVEIDS);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      return RetryHelper.runWithRetries(
          new Callable() {
            @Override
            public com.google.datastore.v1.ReserveIdsResponse call() throws DatastoreException {
              return datastoreRpc.reserveIds(requestPb);
            }
          },
          retrySettings,
          EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }

  @Override
  public void update(Entity... entities) {
    if (entities.length > 0) {
      List mutationsPb = new ArrayList<>();
      Map dedupEntities = new LinkedHashMap<>();
      for (Entity entity : entities) {
        dedupEntities.put(entity.getKey(), entity);
      }
      for (Entity entity : dedupEntities.values()) {
        mutationsPb.add(
            com.google.datastore.v1.Mutation.newBuilder().setUpdate(entity.toPb()).build());
      }
      commitMutation(mutationsPb);
    }
  }

  @Override
  public Entity put(FullEntity entity) {
    return DatastoreHelper.put(this, entity);
  }

  @SuppressWarnings("unchecked")
  @Override
  public List put(FullEntity... entities) {
    if (entities.length == 0) {
      return Collections.emptyList();
    }
    List mutationsPb = new ArrayList<>();
    Map dedupEntities = new LinkedHashMap<>();
    for (FullEntity entity : entities) {
      Preconditions.checkArgument(entity.hasKey(), "Entity %s is missing a key", entity);
      if (entity.getKey() instanceof Key) {
        Entity completeEntity = Entity.convert((FullEntity) entity);
        dedupEntities.put(completeEntity.getKey(), completeEntity);
      } else {
        mutationsPb.add(
            com.google.datastore.v1.Mutation.newBuilder().setUpsert(entity.toPb()).build());
      }
    }
    for (Entity entity : dedupEntities.values()) {
      mutationsPb.add(
          com.google.datastore.v1.Mutation.newBuilder().setUpsert(entity.toPb()).build());
    }
    com.google.datastore.v1.CommitResponse commitResponse = commitMutation(mutationsPb);
    Iterator mutationResults =
        commitResponse.getMutationResultsList().iterator();
    ImmutableList.Builder responseBuilder = ImmutableList.builder();
    for (FullEntity entity : entities) {
      Entity completeEntity = dedupEntities.get(entity.getKey());
      if (completeEntity != null) {
        responseBuilder.add(completeEntity);
      } else {
        responseBuilder.add(
            Entity.newBuilder(Key.fromPb(mutationResults.next().getKey()), entity).build());
      }
    }
    return responseBuilder.build();
  }

  @Override
  public void delete(Key... keys) {
    if (keys.length > 0) {
      List mutationsPb = new ArrayList<>();
      Set dedupKeys = new LinkedHashSet<>(Arrays.asList(keys));
      for (Key key : dedupKeys) {
        mutationsPb.add(
            com.google.datastore.v1.Mutation.newBuilder().setDelete(key.toPb()).build());
      }
      commitMutation(mutationsPb);
    }
  }

  @Override
  public KeyFactory newKeyFactory() {
    return DatastoreHelper.newKeyFactory(getOptions());
  }

  private com.google.datastore.v1.CommitResponse commitMutation(
      List mutationsPb) {
    com.google.datastore.v1.CommitRequest.Builder requestPb =
        com.google.datastore.v1.CommitRequest.newBuilder();
    requestPb.setMode(com.google.datastore.v1.CommitRequest.Mode.NON_TRANSACTIONAL);
    requestPb.setProjectId(getOptions().getProjectId());
    requestPb.setDatabaseId(getOptions().getDatabaseId());
    requestPb.addAllMutations(mutationsPb);
    return commit(requestPb.build());
  }

  com.google.datastore.v1.CommitResponse commit(
      final com.google.datastore.v1.CommitRequest requestPb) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_COMMIT);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      return RetryHelper.runWithRetries(
          () -> datastoreRpc.commit(requestPb),
          retrySettings,
          requestPb.getTransaction().isEmpty()
              ? EXCEPTION_HANDLER
              : TRANSACTION_OPERATION_EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }

  ByteString requestTransactionId(
      com.google.datastore.v1.BeginTransactionRequest.Builder requestPb) {
    return beginTransaction(requestPb.build()).getTransaction();
  }

  com.google.datastore.v1.BeginTransactionResponse beginTransaction(
      final com.google.datastore.v1.BeginTransactionRequest requestPb) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_BEGINTRANSACTION);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      return RetryHelper.runWithRetries(
          new Callable() {
            @Override
            public com.google.datastore.v1.BeginTransactionResponse call()
                throws DatastoreException {
              return datastoreRpc.beginTransaction(requestPb);
            }
          },
          retrySettings,
          EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }

  void rollbackTransaction(ByteString transaction) {
    com.google.datastore.v1.RollbackRequest.Builder requestPb =
        com.google.datastore.v1.RollbackRequest.newBuilder();
    requestPb.setTransaction(transaction);
    requestPb.setProjectId(getOptions().getProjectId());
    requestPb.setDatabaseId(getOptions().getDatabaseId());
    rollback(requestPb.build());
  }

  void rollback(final com.google.datastore.v1.RollbackRequest requestPb) {
    Span span = traceUtil.startSpan(TraceUtil.SPAN_NAME_ROLLBACK);
    try (Scope scope = traceUtil.getTracer().withSpan(span)) {
      RetryHelper.runWithRetries(
          new Callable() {
            @Override
            public Void call() throws DatastoreException {
              datastoreRpc.rollback(requestPb);
              return null;
            }
          },
          retrySettings,
          EXCEPTION_HANDLER,
          getOptions().getClock());
    } catch (RetryHelperException e) {
      span.setStatus(Status.UNKNOWN.withDescription(e.getMessage()));
      throw DatastoreException.translateAndThrow(e);
    } finally {
      span.end(TraceUtil.END_SPAN_OPTIONS);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy