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

com.google.appengine.api.datastore.QueryRunnerCloudDatastoreV1 Maven / Gradle / Ivy

/*
 * Copyright 2021 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
 *
 *     https://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.appengine.api.datastore;

import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.ReadPolicy.Consistency;
import com.google.common.collect.Sets;
import com.google.datastore.v1.CompositeFilter;
import com.google.datastore.v1.PartitionId;
import com.google.datastore.v1.PropertyFilter;
import com.google.datastore.v1.PropertyOrder;
import com.google.datastore.v1.PropertyReference;
import com.google.datastore.v1.ReadOptions.ReadConsistency;
import com.google.datastore.v1.RunQueryRequest;
import com.google.datastore.v1.RunQueryResponse;
import com.google.datastore.v1.Value;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.Future;

/**
 * Cloud Datastore v1-service specific code for constructing and sending queries. This class is
 * threadsafe and has no state.
 */
final class QueryRunnerCloudDatastoreV1 implements QueryRunner {

  private final DatastoreServiceConfig datastoreServiceConfig;
  private final CloudDatastoreV1Client cloudDatastoreV1Client;

  QueryRunnerCloudDatastoreV1(
      DatastoreServiceConfig datastoreServiceConfig,
      CloudDatastoreV1Client cloudDatastoreV1Client) {
    this.datastoreServiceConfig = datastoreServiceConfig;
    this.cloudDatastoreV1Client = cloudDatastoreV1Client;
  }

  @Override
  public QueryResultsSource runQuery(FetchOptions fetchOptions, Query query, Transaction txn) {
    RunQueryRequest.Builder queryBldr = toV1Query(query, fetchOptions);
    if (txn != null) {
      TransactionImpl.ensureTxnActive(txn);
      queryBldr
          .getReadOptionsBuilder()
          .setTransaction(InternalTransactionCloudDatastoreV1.get(txn).getTransactionBytes());
    } else if (datastoreServiceConfig.getReadPolicy().getConsistency() == Consistency.EVENTUAL) {
      // Cloud Datastore v1 does not allow both a transaction and a read consistency to be
      // specified.
      queryBldr.getReadOptionsBuilder().setReadConsistency(ReadConsistency.EVENTUAL);
    }

    // NOTE: The v3 version of this class adds index values here. See b/8952900.

    RunQueryRequest request = queryBldr.build();
    Future result = cloudDatastoreV1Client.runQuery(request);

    return new QueryResultsSourceCloudDatastoreV1(
        datastoreServiceConfig.getDatastoreCallbacks(),
        fetchOptions,
        txn,
        query,
        request,
        result,
        cloudDatastoreV1Client);
  }

  static RunQueryRequest.Builder toV1Query(Query query, FetchOptions fetchOptions) {
    // This method is derived from DatastoreProtoConverter.toV1Query() and QueryTranslator.

    // Since query object should be normalized, this field should be null except for
    // geo-spatial queries.
    if (query.getFilter() != null) {
      throw new IllegalArgumentException(
          "Geo-spatial queries are not supported in the v1 protocol.");
    }

    RunQueryRequest.Builder requestBldr = RunQueryRequest.newBuilder();

    // Chunk size and prefetch size are ignored.

    PartitionId.Builder partitionId =
        requestBldr
            .getPartitionIdBuilder()
            .setProjectId(DatastoreApiHelper.toProjectId(query.getAppId()));
    if (!query.getNamespace().isEmpty()) {
      partitionId.setNamespaceId(query.getNamespace());
    }

    com.google.datastore.v1.Query.Builder queryBldr = requestBldr.getQueryBuilder();

    if (query.getKind() != null) {
      queryBldr.addKindBuilder().setName(query.getKind());
    }

    if (fetchOptions.getOffset() != null) {
      queryBldr.setOffset(fetchOptions.getOffset());
    }

    if (fetchOptions.getLimit() != null) {
      queryBldr.getLimitBuilder().setValue(fetchOptions.getLimit());
    }

    if (fetchOptions.getStartCursor() != null) {
      queryBldr.setStartCursor(fetchOptions.getStartCursor().toByteString());
    }

    if (fetchOptions.getEndCursor() != null) {
      queryBldr.setEndCursor(fetchOptions.getEndCursor().toByteString());
    }

    Set groupByProperties = Sets.newHashSet();
    if (query.getDistinct()) {
      if (query.getProjections().isEmpty()) {
        throw new IllegalArgumentException(
            "Projected properties must be set to allow for distinct projections");
      }
      for (Projection projection : query.getProjections()) {
        String name = projection.getPropertyName();
        groupByProperties.add(name);
        queryBldr.addDistinctOnBuilder().setName(name);
      }
    }

    // Keys-only queries should be created by calling Query.isKeysOnly(), not adding "__key__"
    // as a projection. The v3 validators catch this at query execution time. The
    // Cloud Datastore v1 validators allow this, so catch it at conversion time instead.
    if (query.isKeysOnly() && !query.getProjections().isEmpty()) {
      throw new IllegalArgumentException("A query cannot have both projections and keys-only set.");
    }

    for (Projection projection : query.getProjections()) {
      String name = projection.getPropertyName();
      if (Entity.KEY_RESERVED_PROPERTY.equals(name)) {
        throw new IllegalArgumentException(
            "projections are not supported for the property: __key__");
      }
      com.google.datastore.v1.Projection.Builder projBuilder = queryBldr.addProjectionBuilder();
      projBuilder.getPropertyBuilder().setName(name);
    }

    if (query.isKeysOnly()) {
      com.google.datastore.v1.Projection.Builder projBuilder = queryBldr.addProjectionBuilder();
      projBuilder.getPropertyBuilder().setName(Entity.KEY_RESERVED_PROPERTY);
    }

    CompositeFilter.Builder compositeFilter = CompositeFilter.newBuilder();
    if (query.getAncestor() != null) {
      compositeFilter
          .addFiltersBuilder()
          .getPropertyFilterBuilder()
          .setOp(PropertyFilter.Operator.HAS_ANCESTOR)
          .setProperty(PropertyReference.newBuilder().setName(Entity.KEY_RESERVED_PROPERTY))
          .setValue(
              Value.newBuilder().setKeyValue(DataTypeTranslator.toV1Key(query.getAncestor())));
    }
    for (Query.FilterPredicate filterPredicate : query.getFilterPredicates()) {
      compositeFilter.addFiltersBuilder().setPropertyFilter(toV1PropertyFilter(filterPredicate));
    }
    if (compositeFilter.getFiltersCount() == 1) {
      queryBldr.setFilter(compositeFilter.getFilters(0));
    } else if (compositeFilter.getFiltersCount() > 1) {
      queryBldr
          .getFilterBuilder()
          .setCompositeFilter(compositeFilter.setOp(CompositeFilter.Operator.AND));
    }

    for (Query.SortPredicate sortPredicate : query.getSortPredicates()) {
      queryBldr.addOrder(toV1PropertyOrder(sortPredicate));
    }

    return requestBldr;
  }

  private static PropertyFilter.Builder toV1PropertyFilter(Query.FilterPredicate predicate) {
    PropertyFilter.Builder filter = PropertyFilter.newBuilder();
    FilterOperator operator = predicate.getOperator();
    Object value = predicate.getValue();
    if (operator == Query.FilterOperator.IN) {
      // Convert a 1-element IN to EQUAL.
      // TODO(b/2105715): Remove this once native IN support is available.
      if (!(predicate.getValue() instanceof Collection)) {
        throw new IllegalArgumentException("IN filter value is not a Collection.");
      }
      Collection valueCollection = (Collection) value;
      if (valueCollection.size() != 1) {
        throw new IllegalArgumentException("This service only supports 1 object for IN.");
      }
      operator = Query.FilterOperator.EQUAL;
      value = valueCollection.iterator().next();
    }
    filter.setOp(toV1PropertyFilterOperator(operator));
    filter.getPropertyBuilder().setName(predicate.getPropertyName());
    filter.setValue(DataTypeTranslator.toV1ValueForQuery(value));

    return filter;
  }

  private static PropertyFilter.Operator toV1PropertyFilterOperator(FilterOperator operator) {
    switch (operator) {
      case LESS_THAN:
        return PropertyFilter.Operator.LESS_THAN;
      case LESS_THAN_OR_EQUAL:
        return PropertyFilter.Operator.LESS_THAN_OR_EQUAL;
      case GREATER_THAN:
        return PropertyFilter.Operator.GREATER_THAN;
      case GREATER_THAN_OR_EQUAL:
        return PropertyFilter.Operator.GREATER_THAN_OR_EQUAL;
      case EQUAL:
        return PropertyFilter.Operator.EQUAL;
      default:
        throw new IllegalArgumentException("Can't convert: " + operator);
    }
  }

  private static PropertyOrder.Builder toV1PropertyOrder(Query.SortPredicate predicate) {
    return PropertyOrder.newBuilder()
        .setProperty(PropertyReference.newBuilder().setName(predicate.getPropertyName()))
        .setDirection(toV1PropertyOrderDirection(predicate.getDirection()));
  }

  private static PropertyOrder.Direction toV1PropertyOrderDirection(Query.SortDirection direction) {
    switch (direction) {
      case ASCENDING:
        return PropertyOrder.Direction.ASCENDING;
      case DESCENDING:
        return PropertyOrder.Direction.DESCENDING;
      default:
        throw new IllegalArgumentException("direction: " + direction);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy