public QueryResult runQuery()

in api_dev/src/main/java/com/google/appengine/api/datastore/dev/LocalDatastoreService.java [1191:1410]


  public QueryResult runQuery(Status status, Query query) {
    // Construct a validated query right away so we can fail fast
    // if something is wrong.
    final LocalCompositeIndexManager.ValidatedQuery validatedQuery =
        new LocalCompositeIndexManager.ValidatedQuery(query);
    query = validatedQuery.getV3Query();

    // Modernize the query's cursors.
    // NOTE: Modernization must follow (not precede) construction of
    // LocalCompositeIndexManager.ValidatedQuery.  I don't know why.
    try {
      CursorModernizer.modernizeQueryCursors(query);
    } catch (InvalidConversionException e) {
      throw newError(ErrorCode.BAD_REQUEST, "Invalid cursor");
    }

    String app = query.getApp();
    Profile profile = getOrCreateProfile(app);

    // The real datastore supports executing ancestor queries in transactions.
    // For now we're just going to make sure the entity group of the ancestor
    // is the same entity group with which the transaction is associated and
    // skip providing a transactionally consistent result set.

    synchronized (profile) {
      // Having a transaction implies we have an ancestor, but having an
      // ancestor does not imply we have a transaction.
      if (query.hasTransaction() || query.hasAncestor()) {
        // Query can only have a txn if it is an ancestor query.  Either way we
        // know we've got an ancestor.
        Path groupPath = getGroup(query.getAncestor());
        Profile.EntityGroup eg = profile.getGroup(groupPath);
        if (query.hasTransaction()) {
          if (!app.equals(query.getTransaction().getApp())) {
            throw newError(
                ErrorCode.INTERNAL_ERROR,
                "Can't query app "
                    + app
                    + "in a transaction on app "
                    + query.getTransaction().getApp());
          }

          LiveTxn liveTxn = profile.getTxn(query.getTransaction().getHandle());
          // this will throw an exception if we attempt to read from
          // the wrong entity group
          eg.addTransaction(liveTxn);
          // Use snapshot profile.
          profile = eg.getSnapshot(liveTxn);
        }

        if (query.hasAncestor()) {
          if (query.hasTransaction() || !query.hasFailoverMs()) {
            // Either we have a transaction or the user has requested strongly
            // consistent results.  Either way, we need to apply jobs.
            eg.rollForwardUnappliedJobs();
          }
        }
      }

      if (query.hasSearchQuery()) {
        throw newError(ErrorCode.BAD_REQUEST, "full-text search unsupported");
      }

      // Run as a PseudoKind query if necessary, otherwise check the actual local datastore
      List<EntityProto> queryEntities = pseudoKinds.runQuery(query);
      Map<Reference, Long> versions = null;

      if (queryEntities == null) {
        Collection<VersionedEntity> versionedEntities = null;
        Map<String, Extent> extents = profile.getExtents();
        Extent extent = extents.get(query.getKind());

        if (extent != null) {
          // Make a copy of the list of all the entities in the extent
          versionedEntities = extent.getAllEntities();
        } else if (!query.hasKind()) {
          // Kind-less query, so we need a list containing all entities of
          // all kinds.
          versionedEntities = profile.getAllEntities();
          if (query.orderSize() == 0) {
            // add a sort by key asc to match the behavior of prod
            query.addOrder(
                new Order()
                    .setDirection(Query.Order.Direction.ASCENDING)
                    .setProperty(Entity.KEY_RESERVED_PROPERTY));
          }
        } else {
          // no extent - we're querying for a kind without any entities
        }

        if (versionedEntities != null) {
          queryEntities = new ArrayList<>();
          versions = new HashMap<>();
          for (VersionedEntity entity : versionedEntities) {
            queryEntities.add(entity.entityProto());
            versions.put(entity.entityProto().getKey(), entity.version());
          }
        }
      }
      // Give all entity groups with unapplied jobs the opportunity to catch
      // up.  Note that this will not impact the result of the query we're
      // currently fulfilling since we already have the (unfiltered) result
      // set.
      profile.groom();

      if (queryEntities == null) {
        // so we don't need to check for null anywhere else down below
        queryEntities = Collections.emptyList();
      }

      // Building filter predicate
      List<Predicate<EntityProto>> predicates = new ArrayList<>();
      // apply ancestor restriction
      if (query.hasAncestor()) {
        final List<Element> ancestorPath = query.getAncestor().getPath().elements();
        predicates.add(
            new Predicate<EntityProto>() {
              @Override
              public boolean apply(EntityProto entity) {
                List<Element> path = entity.getKey().getPath().elements();
                return path.size() >= ancestorPath.size()
                    && path.subList(0, ancestorPath.size()).equals(ancestorPath);
              }
            });
      }

      if (query.isShallow()) {
        final long keyPathLength =
            query.hasAncestor() ? query.getAncestor().getPath().elementSize() + 1 : 1;
        predicates.add(
            new Predicate<EntityProto>() {
              @Override
              public boolean apply(EntityProto entity) {
                return entity.getKey().getPath().elementSize() == keyPathLength;
              }
            });
      }

      // apply namespace restriction
      final boolean hasNamespace = query.hasNameSpace();
      final String namespace = query.getNameSpace();
      predicates.add(
          new Predicate<EntityProto>() {
            @Override
            public boolean apply(EntityProto entity) {
              Reference ref = entity.getKey();
              // Filter all elements not in the query's namespace.
              if (hasNamespace) {
                if (!ref.hasNameSpace() || !namespace.equals(ref.getNameSpace())) {
                  return false;
                }
              } else {
                if (ref.hasNameSpace()) {
                  return false;
                }
              }
              return true;
            }
          });

      // Get entityComparator with filter matching capability
      final EntityProtoComparator entityComparator =
          new EntityProtoComparator(
              validatedQuery.getQuery().orders(), validatedQuery.getQuery().filters());

      // applying filter restrictions
      predicates.add(
          new Predicate<EntityProto>() {
            @Override
            public boolean apply(EntityProto entity) {
              return entityComparator.matches(entity);
            }
          });

      Predicate<EntityProto> queryPredicate =
          Predicates.<EntityProto>not(Predicates.<EntityProto>and(predicates));

      // The ordering of the following operations is important to maintain correct
      // query functionality.

      // Filtering entities
      Iterables.removeIf(queryEntities, queryPredicate);

      // Expanding projections
      if (query.propertyNameSize() > 0) {
        queryEntities = createIndexOnlyQueryResults(queryEntities, entityComparator);
      }
      // Sorting entities
      Collections.sort(queryEntities, entityComparator);

      // Apply group by. This must happen after sorting to select the correct first entity.
      queryEntities = applyGroupByProperties(queryEntities, query);

      // store the query and return the results
      LiveQuery liveQuery = new LiveQuery(queryEntities, versions, query, entityComparator, clock);

      // CompositeIndexManager does some filesystem reads/writes
      LocalCompositeIndexManager.getInstance().processQuery(validatedQuery.getV3Query());

      // Using next function to prefetch results and return them from runQuery
      QueryResult result =
          liveQuery.nextResult(
              query.hasOffset() ? query.getOffset() : null,
              query.hasCount() ? query.getCount() : null,
              query.isCompile());
      if (query.isCompile()) {
        result.setCompiledQuery(liveQuery.compileQuery());
      }
      if (result.isMoreResults()) {
        long cursor = queryId.getAndIncrement();
        profile.addQuery(cursor, liveQuery);
        result.getMutableCursor().setApp(query.getApp()).setCursor(cursor);
      }
      // Copy the index list for the query into the result.
      for (Index index : LocalCompositeIndexManager.getInstance().queryIndexList(query)) {
        result.addIndex(wrapIndexInCompositeIndex(app, index));
      } // for
      return result;
    }
  }