/** {@inheritDoc} */
  @Override
  public ResultSet executeQuery(String sql) throws SQLException {
    ensureNotClosed();

    rs = null;

    if (sql == null || sql.isEmpty()) throw new SQLException("SQL query is empty");

    try {
      byte[] packet =
          conn.client()
              .compute()
              .execute(
                  TASK_NAME,
                  JdbcUtils.marshalArgument(
                      JdbcUtils.taskArgument(
                          conn.nodeId(),
                          conn.cacheName(),
                          sql,
                          timeout,
                          args,
                          fetchSize,
                          maxRows)));

      byte status = packet[0];
      byte[] data = new byte[packet.length - 1];

      U.arrayCopy(packet, 1, data, 0, data.length);

      if (status == 1) throw JdbcUtils.unmarshalError(data);
      else {
        List<?> msg = JdbcUtils.unmarshal(data);

        assert msg.size() == 7;

        UUID nodeId = (UUID) msg.get(0);
        UUID futId = (UUID) msg.get(1);
        List<String> tbls = (List<String>) msg.get(2);
        List<String> cols = (List<String>) msg.get(3);
        List<String> types = (List<String>) msg.get(4);
        Collection<List<Object>> fields = (Collection<List<Object>>) msg.get(5);
        boolean finished = (Boolean) msg.get(6);

        return new JdbcResultSet(
            this, nodeId, futId, tbls, cols, types, fields, finished, fetchSize);
      }
    } catch (GridClientException e) {
      throw new SQLException("Failed to query Ignite.", e);
    }
  }