/** @return Nodes to execute on. */
  private Collection<GridNode> nodes() {
    GridCacheMode cacheMode = cctx.config().getCacheMode();

    switch (cacheMode) {
      case LOCAL:
        if (prj != null)
          U.warn(
              log,
              "Ignoring query projection because it's executed over LOCAL cache "
                  + "(only local node will be queried): "
                  + this);

        return Collections.singletonList(cctx.localNode());

      case REPLICATED:
        if (prj != null) return nodes(cctx, prj);

        GridCacheDistributionMode mode = cctx.config().getDistributionMode();

        return mode == PARTITIONED_ONLY || mode == NEAR_PARTITIONED
            ? Collections.singletonList(cctx.localNode())
            : Collections.singletonList(F.rand(nodes(cctx, null)));

      case PARTITIONED:
        return nodes(cctx, prj);

      default:
        throw new IllegalStateException("Unknown cache distribution mode: " + cacheMode);
    }
  }
  /**
   * @param rmtReducer Optional reducer.
   * @param rmtTransform Optional transformer.
   * @param args Arguments.
   * @return Future.
   */
  @SuppressWarnings("IfMayBeConditional")
  private <R> GridCacheQueryFuture<R> execute(
      @Nullable GridReducer<T, R> rmtReducer,
      @Nullable GridClosure<T, R> rmtTransform,
      @Nullable Object... args) {
    Collection<GridNode> nodes = nodes();

    cctx.checkSecurity(GridSecurityPermission.CACHE_READ);

    if (F.isEmpty(nodes))
      return new GridCacheQueryErrorFuture<>(
          cctx.kernalContext(),
          new GridEmptyProjectionException("There are no data nodes for cache: " + cctx.namexx()));

    if (log.isDebugEnabled())
      log.debug("Executing query [query=" + this + ", nodes=" + nodes + ']');

    if (cctx.deploymentEnabled()) {
      try {
        cctx.deploy().registerClasses(filter, rmtReducer, rmtTransform);
        cctx.deploy().registerClasses(args);
      } catch (GridException e) {
        return new GridCacheQueryErrorFuture<>(cctx.kernalContext(), e);
      }
    }

    if (subjId == null) subjId = cctx.localNodeId();

    taskHash = cctx.kernalContext().job().currentTaskNameHash();

    GridCacheQueryBean bean =
        new GridCacheQueryBean(
            this,
            (GridReducer<Object, Object>) rmtReducer,
            (GridClosure<Object, Object>) rmtTransform,
            args);

    GridCacheQueryManager qryMgr = cctx.queries();

    boolean loc = nodes.size() == 1 && F.first(nodes).id().equals(cctx.localNodeId());

    if (type == SQL_FIELDS)
      return (GridCacheQueryFuture<R>)
          (loc ? qryMgr.queryFieldsLocal(bean) : qryMgr.queryFieldsDistributed(bean, nodes));
    else
      return (GridCacheQueryFuture<R>)
          (loc ? qryMgr.queryLocal(bean) : qryMgr.queryDistributed(bean, nodes));
  }
  /**
   * @param cctx Context.
   * @param type Query type.
   * @param clsName Class name.
   * @param clause Clause.
   * @param filter Scan filter.
   * @param incMeta Include metadata flag.
   * @param keepPortable Keep portable flag.
   * @param prjPred Cache projection filter.
   */
  public GridCacheQueryAdapter(
      GridCacheContext<?, ?> cctx,
      GridCacheQueryType type,
      @Nullable GridPredicate<GridCacheEntry<Object, Object>> prjPred,
      @Nullable String clsName,
      @Nullable String clause,
      @Nullable GridBiPredicate<Object, Object> filter,
      boolean incMeta,
      boolean keepPortable) {
    assert cctx != null;
    assert type != null;

    this.cctx = cctx;
    this.type = type;
    this.clsName = clsName;
    this.clause = clause;
    this.prjPred = prjPred;
    this.filter = filter;
    this.incMeta = incMeta;
    this.keepPortable = keepPortable;

    log = cctx.logger(getClass());

    pageSize = DFLT_PAGE_SIZE;
    timeout = 0;
    keepAll = true;
    incBackups = false;
    dedup = false;
    prj = null;

    metrics = new GridCacheQueryMetricsAdapter();
  }
  /**
   * @param ctx Cache context.
   * @param topic Topic for ordered messages.
   * @param prjPred Projection predicate.
   */
  GridCacheContinuousQueryAdapter(
      GridCacheContext<K, V> ctx,
      Object topic,
      @Nullable GridPredicate<GridCacheEntry<K, V>> prjPred) {
    assert ctx != null;
    assert topic != null;

    this.ctx = ctx;
    this.topic = topic;
    this.prjPred = prjPred;

    log = ctx.logger(getClass());
  }
  /** {@inheritDoc} */
  @Override
  public void close() throws GridException {
    closeLock.lock();

    try {
      if (routineId == null)
        throw new IllegalStateException("Can't cancel query that was not executed.");

      ctx.kernalContext().continuous().stopRoutine(routineId).get();
    } finally {
      closeLock.unlock();
    }
  }
  /**
   * @param res Query result.
   * @param err Error or {@code null} if query executed successfully.
   * @param startTime Start time.
   * @param duration Duration.
   */
  public void onExecuted(Object res, Throwable err, long startTime, long duration) {
    boolean fail = err != null;

    // Update own metrics.
    metrics.onQueryExecute(duration, fail);

    // Update metrics in query manager.
    cctx.queries().onMetricsUpdate(duration, fail);

    if (log.isDebugEnabled())
      log.debug(
          "Query execution finished [qry="
              + this
              + ", startTime="
              + startTime
              + ", duration="
              + duration
              + ", fail="
              + fail
              + ", res="
              + res
              + ']');
  }
  /** {@inheritDoc} */
  @Override
  public void execute(@Nullable GridProjection prj) throws GridException {
    if (cb == null)
      throw new IllegalStateException("Mandatory local callback is not set for the query: " + this);

    if (prj == null) prj = ctx.grid();

    prj = prj.forCache(ctx.name());

    if (prj.nodes().isEmpty())
      throw new GridTopologyException("Failed to execute query (projection is empty): " + this);

    GridCacheMode mode = ctx.config().getCacheMode();

    if (mode == LOCAL || mode == REPLICATED) {
      Collection<GridNode> nodes = prj.nodes();

      GridNode node = nodes.contains(ctx.localNode()) ? ctx.localNode() : F.rand(nodes);

      assert node != null;

      if (nodes.size() > 1 && !ctx.cache().isDrSystemCache()) {
        if (node.id().equals(ctx.localNodeId()))
          U.warn(
              log,
              "Continuous query for "
                  + mode
                  + " cache can be run only on local node. "
                  + "Will execute query locally: "
                  + this);
        else
          U.warn(
              log,
              "Continuous query for "
                  + mode
                  + " cache can be run only on single node. "
                  + "Will execute query on remote node [qry="
                  + this
                  + ", node="
                  + node
                  + ']');
      }

      prj = prj.forNode(node);
    }

    closeLock.lock();

    try {
      if (routineId != null)
        throw new IllegalStateException("Continuous query can't be executed twice.");

      guard.block();

      GridContinuousHandler hnd =
          new GridCacheContinuousQueryHandler<>(ctx.name(), topic, cb, filter, prjPred);

      routineId =
          ctx.kernalContext()
              .continuous()
              .startRoutine(hnd, bufSize, timeInterval, autoUnsubscribe, prj.predicate())
              .get();
    } finally {
      closeLock.unlock();
    }
  }
 /** @throws GridException If query is invalid. */
 public void validate() throws GridException {
   if (type != SCAN && !cctx.config().isQueryIndexEnabled())
     throw new GridException("Indexing is disabled for cache: " + cctx.cache().name());
 }