/** {@inheritDoc} */
  @SuppressWarnings("unchecked")
  @Override
  public CacheQueryFuture<?> queryDistributed(
      GridCacheQueryBean qry, Collection<ClusterNode> nodes) {
    assert cctx.config().getCacheMode() != LOCAL;

    if (log.isDebugEnabled()) log.debug("Executing distributed query: " + qry);

    long reqId = cctx.io().nextIoId();

    final GridCacheDistributedQueryFuture<K, V, ?> fut =
        new GridCacheDistributedQueryFuture<>(cctx, reqId, qry, nodes);

    try {
      qry.query().validate();

      String clsName = qry.query().queryClassName();

      GridCacheQueryRequest req =
          new GridCacheQueryRequest(
              cctx.cacheId(),
              reqId,
              cctx.name(),
              qry.query().type(),
              false,
              qry.query().clause(),
              clsName,
              qry.query().scanFilter(),
              qry.query().partition(),
              qry.reducer(),
              qry.transform(),
              qry.query().pageSize(),
              qry.query().includeBackups(),
              qry.arguments(),
              false,
              qry.query().keepPortable(),
              qry.query().subjectId(),
              qry.query().taskHash());

      addQueryFuture(req.id(), fut);

      final Object topic = topic(cctx.nodeId(), req.id());

      cctx.io().addOrderedHandler(topic, resHnd);

      fut.listen(
          new CI1<IgniteInternalFuture<?>>() {
            @Override
            public void apply(IgniteInternalFuture<?> fut) {
              cctx.io().removeOrderedHandler(topic);
            }
          });

      sendRequest(fut, req, nodes);
    } catch (IgniteCheckedException e) {
      fut.onDone(e);
    }

    return fut;
  }
  /**
   * @param sndId Sender node id.
   * @param req Query request.
   * @return Query info.
   */
  @Nullable
  private GridCacheQueryInfo distributedQueryInfo(UUID sndId, GridCacheQueryRequest req) {
    IgniteReducer<Object, Object> rdc = req.reducer();
    IgniteClosure<Object, Object> trans = req.transformer();

    ClusterNode sndNode = cctx.node(sndId);

    if (sndNode == null) return null;

    GridCacheQueryAdapter<?> qry =
        new GridCacheQueryAdapter<>(
            cctx,
            req.type(),
            log,
            req.pageSize(),
            0,
            false,
            req.includeBackups(),
            false,
            null,
            req.keyValueFilter(),
            req.partition(),
            req.className(),
            req.clause(),
            req.includeMetaData(),
            req.keepPortable(),
            req.subjectId(),
            req.taskHash());

    return new GridCacheQueryInfo(
        false,
        trans,
        rdc,
        qry,
        null,
        sndId,
        req.id(),
        req.includeMetaData(),
        req.allPages(),
        req.arguments());
  }
  /**
   * Processes cache query request.
   *
   * @param sndId Sender node id.
   * @param req Query request.
   */
  @SuppressWarnings("unchecked")
  @Override
  void processQueryRequest(UUID sndId, GridCacheQueryRequest req) {
    if (req.cancel()) {
      cancelIds.add(new CancelMessageId(req.id(), sndId));

      if (req.fields()) removeFieldsQueryResult(sndId, req.id());
      else removeQueryResult(sndId, req.id());
    } else {
      if (!cancelIds.contains(new CancelMessageId(req.id(), sndId))) {
        if (!F.eq(req.cacheName(), cctx.name())) {
          GridCacheQueryResponse res =
              new GridCacheQueryResponse(
                  cctx.cacheId(),
                  req.id(),
                  new IgniteCheckedException(
                      "Received request for incorrect cache [expected="
                          + cctx.name()
                          + ", actual="
                          + req.cacheName()));

          sendQueryResponse(sndId, res, 0);
        } else {
          threads.put(req.id(), Thread.currentThread());

          try {
            GridCacheQueryInfo info = distributedQueryInfo(sndId, req);

            if (info == null) return;

            if (req.fields()) runFieldsQuery(info);
            else runQuery(info);
          } catch (Throwable e) {
            U.error(log(), "Failed to run query.", e);

            sendQueryResponse(
                sndId, new GridCacheQueryResponse(cctx.cacheId(), req.id(), e.getCause()), 0);

            if (e instanceof Error) throw (Error) e;
          } finally {
            threads.remove(req.id());
          }
        }
      }
    }
  }