IGNITE-12815: SQL query cancel command. (#7580)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/QueryMXBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/QueryMXBeanImpl.java
new file mode 100644
index 0000000..e8d8cba
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/QueryMXBeanImpl.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *      http://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 org.apache.ignite.internal;
+
+import java.util.UUID;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.cluster.IgniteClusterImpl;
+import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.internal.util.typedef.internal.A;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.apache.ignite.internal.visor.query.VisorQueryCancelOnInitiatorTask;
+import org.apache.ignite.internal.visor.query.VisorQueryCancelOnInitiatorTaskArg;
+import org.apache.ignite.mxbean.QueryMXBean;
+
+import static org.apache.ignite.internal.sql.command.SqlKillQueryCommand.parseGlobalQueryId;
+
+/**
+ * QueryMXBean implementation.
+ */
+public class QueryMXBeanImpl implements QueryMXBean {
+    /** Global query id format. */
+    public static final String EXPECTED_GLOBAL_QRY_ID_FORMAT = "Global query id should have format " +
+        "'{node_id}_{query_id}', e.g. '6fa749ee-7cf8-4635-be10-36a1c75267a7_54321'";
+
+    /** Kernal context. */
+    private final GridKernalContext ctx;
+
+    /** Logger. */
+    private final IgniteLogger log;
+
+    /**
+     * @param ctx Context.
+     */
+    public QueryMXBeanImpl(GridKernalContext ctx) {
+        this.ctx = ctx;
+        this.log = ctx.log(QueryMXBeanImpl.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void cancelSQL(String id) {
+        A.notNull(id, "id");
+
+        if (log.isInfoEnabled())
+            log.info("Killing sql query[id=" + id + ']');
+
+        try {
+            IgniteClusterImpl cluster = ctx.cluster().get();
+
+            T2<UUID, Long> ids = parseGlobalQueryId(id);
+
+            if (ids == null)
+                throw new IllegalArgumentException("Expected global query id. " + EXPECTED_GLOBAL_QRY_ID_FORMAT);
+
+            cluster.compute().execute(new VisorQueryCancelOnInitiatorTask(),
+                new VisorTaskArgument<>(ids.get1(), new VisorQueryCancelOnInitiatorTaskArg(ids.get1(), ids.get2()),
+                    false));
+        }
+        catch (IgniteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
index d600fd3..4613ab6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
@@ -17,14 +17,19 @@
 
 package org.apache.ignite.internal.commandline.query;
 
+import java.util.UUID;
 import java.util.logging.Logger;
 import org.apache.ignite.internal.client.GridClient;
 import org.apache.ignite.internal.client.GridClientConfiguration;
 import org.apache.ignite.internal.commandline.Command;
 import org.apache.ignite.internal.commandline.CommandArgIterator;
 import org.apache.ignite.internal.commandline.CommandLogger;
+import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.internal.visor.query.VisorQueryCancelOnInitiatorTask;
+import org.apache.ignite.internal.visor.query.VisorQueryCancelOnInitiatorTaskArg;
 import org.apache.ignite.internal.visor.service.VisorCancelServiceTask;
 import org.apache.ignite.internal.visor.service.VisorCancelServiceTaskArg;
+import org.apache.ignite.mxbean.QueryMXBean;
 import org.apache.ignite.mxbean.ServiceMXBean;
 import org.apache.ignite.internal.visor.compute.VisorComputeCancelSessionTask;
 import org.apache.ignite.internal.visor.compute.VisorComputeCancelSessionTaskArg;
@@ -36,16 +41,20 @@
 import org.apache.ignite.mxbean.TransactionsMXBean;
 
 import static java.util.Collections.singletonMap;
+import static org.apache.ignite.internal.QueryMXBeanImpl.EXPECTED_GLOBAL_QRY_ID_FORMAT;
 import static org.apache.ignite.internal.commandline.CommandList.KILL;
 import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.SERVICE;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.COMPUTE;
+import static org.apache.ignite.internal.commandline.query.KillSubcommand.SQL;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.TRANSACTION;
+import static org.apache.ignite.internal.sql.command.SqlKillQueryCommand.parseGlobalQueryId;
 
 /**
  * control.sh kill command.
  *
  * @see KillSubcommand
+ * @see QueryMXBean
  * @see ServiceMXBean
  * @see ComputeMXBean
  * @see TransactionsMXBean
@@ -118,6 +127,18 @@
 
                 break;
 
+            case SQL:
+                T2<UUID, Long> ids = parseGlobalQueryId(argIter.nextArg("Expected SQL query id."));
+
+                if (ids == null)
+                    throw new IllegalArgumentException("Expected global query id. " + EXPECTED_GLOBAL_QRY_ID_FORMAT);
+
+                taskArgs = new VisorQueryCancelOnInitiatorTaskArg(ids.get1(), ids.get2());
+
+                taskName = VisorQueryCancelOnInitiatorTask.class.getName();
+
+                break;
+
             default:
                 throw new IllegalArgumentException("Unknown kill subcommand: " + cmd);
         }
@@ -133,6 +154,9 @@
 
         Command.usage(log, "Kill transaction by xid:", KILL, singletonMap("xid", "Transaction identifier."),
             TRANSACTION.toString(), "xid");
+
+        Command.usage(log, "Kill sql query by query id:", KILL, singletonMap("query_id", "Query identifier."),
+            SQL.toString(), "query_id");
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
index d07a27d..1e05435 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.commandline.query;
 
 import org.apache.ignite.mxbean.ComputeMXBean;
+import org.apache.ignite.mxbean.QueryMXBean;
 import org.apache.ignite.mxbean.ServiceMXBean;
 import org.apache.ignite.mxbean.TransactionsMXBean;
 
@@ -25,6 +26,7 @@
  * Subcommands of the kill command.
  *
  * @see KillCommand
+ * @see QueryMXBean
  * @see ComputeMXBean
  * @see TransactionsMXBean
  * @see ServiceMXBean
@@ -37,5 +39,8 @@
     TRANSACTION,
 
     /** Kill service. */
-    SERVICE
+    SERVICE,
+
+    /** Kill sql query. */
+    SQL
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
index 3fae961..d31f3ba 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
@@ -31,6 +31,7 @@
 import org.apache.ignite.internal.GridKernalContextImpl;
 import org.apache.ignite.internal.IgniteKernal;
 import org.apache.ignite.internal.ServiceMXBeanImpl;
+import org.apache.ignite.internal.QueryMXBeanImpl;
 import org.apache.ignite.internal.StripedExecutorMXBeanAdapter;
 import org.apache.ignite.internal.ThreadPoolMXBeanAdapter;
 import org.apache.ignite.internal.TransactionMetricsMxBeanImpl;
@@ -53,6 +54,7 @@
 import org.apache.ignite.mxbean.IgniteMXBean;
 import org.apache.ignite.mxbean.MetricsMxBean;
 import org.apache.ignite.mxbean.ServiceMXBean;
+import org.apache.ignite.mxbean.QueryMXBean;
 import org.apache.ignite.mxbean.StripedExecutorMXBean;
 import org.apache.ignite.mxbean.ThreadPoolMXBean;
 import org.apache.ignite.mxbean.TransactionMetricsMxBean;
@@ -151,6 +153,10 @@
         TransactionsMXBean txMXBean = new TransactionsMXBeanImpl(ctx);
         registerMBean("Transactions", txMXBean.getClass().getSimpleName(), txMXBean, TransactionsMXBean.class);
 
+        // Queries management
+        QueryMXBean qryMXBean = new QueryMXBeanImpl(ctx);
+        registerMBean("Query", qryMXBean.getClass().getSimpleName(), qryMXBean, QueryMXBean.class);
+
         // Compute task management
         ComputeMXBean computeMXBean = new ComputeMXBeanImpl(ctx);
         registerMBean("Compute", computeMXBean.getClass().getSimpleName(), computeMXBean, ComputeMXBean.class);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlKillQueryCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlKillQueryCommand.java
index 604c27f..81638cd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlKillQueryCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlKillQueryCommand.java
@@ -22,18 +22,22 @@
 import org.apache.ignite.internal.sql.SqlLexer;
 import org.apache.ignite.internal.sql.SqlLexerTokenType;
 import org.apache.ignite.internal.sql.SqlParserUtils;
+import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.mxbean.QueryMXBean;
+import org.apache.ignite.spi.systemview.view.SqlQueryView;
+
+import static org.apache.ignite.internal.QueryMXBeanImpl.EXPECTED_GLOBAL_QRY_ID_FORMAT;
 
 /**
  * KILL QUERY command.
+ *
+ * @see QueryMXBean#cancelSQL(String)
+ * @see SqlQueryView#queryId()
  */
 public class SqlKillQueryCommand implements SqlCommand {
     /** */
     private static final String ASYNC = "ASYNC";
 
-    /** */
-    private static final String EXPECTED_GLOBAL_QRY_ID_FORMAT = "Global query id should have format " +
-        "'{node_id}_{query_id}', e.g. '6fa749ee-7cf8-4635-be10-36a1c75267a7_54321'";
-
     /** Node query id. */
     private long nodeQryId;
 
@@ -62,22 +66,16 @@
             if (lex.tokenType() == SqlLexerTokenType.STRING) {
                 String tok = lex.token();
 
-                String[] ids = tok.split("_");
+                T2<UUID, Long> ids = parseGlobalQueryId(tok);
 
-                if (ids.length == 2) {
-                    try {
-                        nodeId = UUID.fromString(ids[0]);
+                if (ids == null)
+                    throw SqlParserUtils.error(lex, EXPECTED_GLOBAL_QRY_ID_FORMAT);
 
-                        nodeQryId = Long.parseLong(ids[1]);
+                nodeId = ids.get1();
 
-                        return;
-                    }
-                    catch (Exception ignore) {
-                        // Fall through.
-                    }
+                nodeQryId = ids.get2();
 
-                   throw SqlParserUtils.error(lex, EXPECTED_GLOBAL_QRY_ID_FORMAT);
-                }
+                return;
             }
         }
 
@@ -99,6 +97,27 @@
     }
 
     /**
+     * Parse global SQL query id.
+     * Format is {origin_node_id}_{query_id}.
+     *
+     * @param globalQryId Global query id.
+     * @return Results of parsing of {@code null} if parse failed.
+     */
+    public static T2<UUID, Long> parseGlobalQueryId(String globalQryId) {
+        String[] ids = globalQryId.split("_");
+
+        if (ids.length != 2)
+            return null;
+
+        try {
+            return new T2<>(UUID.fromString(ids[0]), Long.parseLong(ids[1]));
+        }
+        catch (Exception ignore) {
+            return null;
+        }
+    }
+
+    /**
      * @return Node query id.
      */
     public long nodeQueryId() {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelOnInitiatorTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelOnInitiatorTask.java
new file mode 100644
index 0000000..c1ae710
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelOnInitiatorTask.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *      http://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 org.apache.ignite.internal.visor.query;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.processors.task.GridVisorManagementTask;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorOneNodeTask;
+import org.apache.ignite.lang.IgniteClosure;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Task to cancel queries on initiator node.
+ */
+@GridInternal
+@GridVisorManagementTask
+public class VisorQueryCancelOnInitiatorTask extends VisorOneNodeTask<VisorQueryCancelOnInitiatorTaskArg, Void> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override protected VisorCancelQueryOnInitiatorJob job(VisorQueryCancelOnInitiatorTaskArg arg) {
+        return new VisorCancelQueryOnInitiatorJob(arg, debug);
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override protected Void reduce0(List<ComputeJobResult> results) throws IgniteException {
+        return null;
+    }
+
+    /** Job to cancel query on node. */
+    private static class VisorCancelQueryOnInitiatorJob extends VisorJob<VisorQueryCancelOnInitiatorTaskArg, Void> {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * Create job with specified argument.
+         *
+         * @param arg Job argument.
+         * @param debug Flag indicating whether debug information should be printed into node log.
+         */
+        protected VisorCancelQueryOnInitiatorJob(VisorQueryCancelOnInitiatorTaskArg arg, boolean debug) {
+            super(arg, debug);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected Void run(VisorQueryCancelOnInitiatorTaskArg arg) throws IgniteException {
+            ignite.compute(ignite.cluster().forNodeId(arg.getNodeId())).broadcast(new IgniteClosure<Long, Void>() {
+                /** Auto-injected grid instance. */
+                @IgniteInstanceResource
+                private transient IgniteEx ignite;
+
+                /** {@inheritDoc} */
+                @Override public Void apply(Long qryId) {
+                    ignite.context().query().cancelQueries(Collections.singleton(arg.getQueryId()));
+
+                    return null;
+                }
+            }, arg.getQueryId());
+
+            return null;
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelOnInitiatorTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelOnInitiatorTaskArg.java
new file mode 100644
index 0000000..ba8e30a
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelOnInitiatorTaskArg.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *      http://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 org.apache.ignite.internal.visor.query;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.UUID;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Arguments for task {@link VisorQueryCancelOnInitiatorTask}.
+ */
+public class VisorQueryCancelOnInitiatorTaskArg extends IgniteDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Query initiator. */
+    private UUID nodeId;
+
+    /** Query ID to cancel. */
+    private long qryId;
+
+    /** Default constructor. */
+    public VisorQueryCancelOnInitiatorTaskArg() {
+        // No-op.
+    }
+
+    /**
+     * @param qryId Query id.
+     * @param nodeId Query initiator.
+     */
+    public VisorQueryCancelOnInitiatorTaskArg(UUID nodeId, long qryId) {
+        this.nodeId = nodeId;
+        this.qryId = qryId;
+    }
+
+    /** @return Query initiator. */
+    public UUID getNodeId() {
+        return nodeId;
+    }
+
+    /** @return Query ID to cancel. */
+    public long getQueryId() {
+        return qryId;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeUuid(out, nodeId);
+        out.writeLong(qryId);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        nodeId = U.readUuid(in);
+        qryId = in.readLong();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryCancelOnInitiatorTaskArg.class, this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/mxbean/QueryMXBean.java b/modules/core/src/main/java/org/apache/ignite/mxbean/QueryMXBean.java
new file mode 100644
index 0000000..5721c78
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/mxbean/QueryMXBean.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *      http://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 org.apache.ignite.mxbean;
+
+import org.apache.ignite.spi.systemview.view.SqlQueryView;
+
+/**
+ * Query MXBean interface.
+ */
+public interface QueryMXBean {
+    /**
+     * Kills SQL query by the identifier.
+     *
+     * @param id SQL query id.
+     * @see SqlQueryView#queryId()
+     */
+    @MXBeanDescription("Kills SQL query by the identifier.")
+    void cancelSQL(
+        @MXBeanParameter(name = "id", description = "SQL query id.") String id
+    );
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java b/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
index d884ac5..dd4faa8 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
@@ -47,6 +47,7 @@
 
 import static java.util.Arrays.asList;
 import static org.apache.ignite.IgniteSystemProperties.IGNITE_ENABLE_EXPERIMENTAL_COMMAND;
+import static org.apache.ignite.internal.QueryMXBeanImpl.EXPECTED_GLOBAL_QRY_ID_FORMAT;
 import static org.apache.ignite.internal.commandline.CommandList.CACHE;
 import static org.apache.ignite.internal.commandline.CommandList.SET_STATE;
 import static org.apache.ignite.internal.commandline.CommandList.WAL;
@@ -532,6 +533,15 @@
 
         // Service command format errors.
         assertParseArgsThrows("Expected service name.", "--kill", "service");
+
+        // Transaction command format errors.
+        assertParseArgsThrows("Expected transaction id.", "--kill", "transaction");
+
+        // SQL command format errors.
+        assertParseArgsThrows("Expected SQL query id.", "--kill", "sql");
+
+        assertParseArgsThrows("Expected global query id. " + EXPECTED_GLOBAL_QRY_ID_FORMAT,
+            "--kill", "sql", "not_sql_id");
     }
 
     /**
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
index 0f91993..5c90dac 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
@@ -86,6 +86,12 @@
     Parameters:
       xid  - Transaction identifier.
 
+  Kill sql query by query id:
+    control.(sh|bat) --kill SQL query_id
+
+    Parameters:
+      query_id  - Query identifier.
+
 By default commands affecting the cluster require interactive confirmation.
 Use --yes option to disable it.
 
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
index 0f91993..5c90dac 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
@@ -86,6 +86,12 @@
     Parameters:
       xid  - Transaction identifier.
 
+  Kill sql query by query id:
+    control.(sh|bat) --kill SQL query_id
+
+    Parameters:
+      query_id  - Query identifier.
+
 By default commands affecting the cluster require interactive confirmation.
 Use --yes option to disable it.
 
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
index a0f52f7..9d045e7 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
@@ -19,6 +19,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.ignite.IgniteCache;
 import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.internal.IgniteEx;
@@ -26,7 +27,9 @@
 import org.junit.Test;
 
 import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK;
+import static org.apache.ignite.util.KillCommandsTests.PAGE_SZ;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
+import static org.apache.ignite.util.KillCommandsTests.doTestCancelSQLQuery;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelTx;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelService;
 
@@ -44,10 +47,13 @@
         for (int i = 0; i < SERVER_NODE_CNT; i++)
             srvs.add(grid(i));
 
-        client.getOrCreateCache(
+        IgniteCache<Object, Object> cache = client.getOrCreateCache(
             new CacheConfiguration<>(DEFAULT_CACHE_NAME).setIndexedTypes(Integer.class, Integer.class)
                 .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL));
 
+        for (int i = 0; i < PAGE_SZ * PAGE_SZ; i++)
+            cache.put(i, i);
+
         awaitPartitionMapExchange();
     }
 
@@ -88,6 +94,16 @@
 
     /** */
     @Test
+    public void testCancelSQLQuery() {
+        doTestCancelSQLQuery(client, qryId -> {
+            int res = execute("--kill", "sql", qryId);
+
+            assertEquals(EXIT_CODE_OK, res);
+        });
+    }
+
+    /** */
+    @Test
     public void testCancelUnknownComputeTask() {
         int res = execute("--kill", "compute", IgniteUuid.randomUuid().toString());
 
@@ -109,4 +125,12 @@
 
         assertEquals(EXIT_CODE_OK, res);
     }
+
+    /** */
+    @Test
+    public void testCancelUnknownSQLQuery() {
+        int res = execute("--kill", "sql", srvs.get(0).localNode().id().toString() + "_42");
+
+        assertEquals(EXIT_CODE_OK, res);
+    }
 }
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
index 000e3c2..4c22326 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
@@ -19,21 +19,26 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.ignite.IgniteCache;
 import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.internal.ComputeMXBeanImpl;
 import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.QueryMXBeanImpl;
 import org.apache.ignite.internal.TransactionsMXBeanImpl;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.mxbean.ComputeMXBean;
 import org.apache.ignite.internal.ServiceMXBeanImpl;
+import org.apache.ignite.mxbean.QueryMXBean;
 import org.apache.ignite.mxbean.ServiceMXBean;
 import org.apache.ignite.mxbean.TransactionsMXBean;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
 import static org.apache.ignite.cluster.ClusterState.ACTIVE;
+import static org.apache.ignite.util.KillCommandsTests.PAGE_SZ;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
+import static org.apache.ignite.util.KillCommandsTests.doTestCancelSQLQuery;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelService;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelTx;
 
@@ -52,6 +57,9 @@
     private static IgniteEx killCli;
 
     /** */
+    private static QueryMXBean qryMBean;
+
+    /** */
     private static TransactionsMXBean txMBean;
 
     /** */
@@ -74,10 +82,16 @@
 
         srvs.get(0).cluster().state(ACTIVE);
 
-        startCli.getOrCreateCache(
+        IgniteCache<Object, Object> cache = startCli.getOrCreateCache(
             new CacheConfiguration<>(DEFAULT_CACHE_NAME).setIndexedTypes(Integer.class, Integer.class)
                 .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL));
 
+        for (int i = 0; i < PAGE_SZ * PAGE_SZ; i++)
+            cache.put(i, i);
+
+        qryMBean = getMxBean(killCli.name(), "Query",
+            QueryMXBeanImpl.class.getSimpleName(), QueryMXBean.class);
+
         txMBean = getMxBean(killCli.name(), "Transactions",
             TransactionsMXBeanImpl.class.getSimpleName(), TransactionsMXBean.class);
 
@@ -108,6 +122,12 @@
 
     /** */
     @Test
+    public void testCancelSQLQuery() {
+        doTestCancelSQLQuery(startCli, qryId -> qryMBean.cancelSQL(qryId));
+    }
+
+    /** */
+    @Test
     public void testCancelUnknownComputeTask() {
         computeMBean.cancel(IgniteUuid.randomUuid().toString());
     }
@@ -123,4 +143,10 @@
     public void testCancelUnknownService() {
         svcMxBean.cancel("unknown");
     }
+
+    /** */
+    @Test
+    public void testCancelUnknownSQLQuery() {
+        qryMBean.cancelSQL(srvs.get(0).localNode().id().toString() + "_42");
+    }
 }
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
index f23f522..5dc5c21 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
@@ -20,6 +20,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
 import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.configuration.CacheConfiguration;
@@ -32,9 +33,13 @@
 import static org.apache.ignite.internal.processors.cache.index.AbstractSchemaSelfTest.queryProcessor;
 import static org.apache.ignite.internal.sql.SqlKeyword.COMPUTE;
 import static org.apache.ignite.internal.sql.SqlKeyword.KILL;
+import static org.apache.ignite.internal.sql.SqlKeyword.QUERY;
 import static org.apache.ignite.internal.sql.SqlKeyword.SERVICE;
 import static org.apache.ignite.internal.sql.SqlKeyword.TRANSACTION;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+import static org.apache.ignite.util.KillCommandsTests.PAGE_SZ;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
+import static org.apache.ignite.util.KillCommandsTests.doTestCancelSQLQuery;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelService;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelTx;
 
@@ -44,6 +49,9 @@
     public static final int  NODES_CNT = 3;
 
     /** */
+    public static final String KILL_SQL_QRY = KILL + " " + QUERY;
+
+    /** */
     public static final String KILL_COMPUTE_QRY = KILL + " " + COMPUTE;
 
     /** */
@@ -75,9 +83,12 @@
 
         srvs.get(0).cluster().state(ACTIVE);
 
-        startCli.getOrCreateCache(
+        IgniteCache<Object, Object> cache = startCli.getOrCreateCache(
             new CacheConfiguration<>(DEFAULT_CACHE_NAME).setIndexedTypes(Integer.class, Integer.class)
                 .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL));
+
+        for (int i = 0; i < PAGE_SZ * PAGE_SZ; i++)
+            cache.put(i, i);
     }
 
     /** @throws Exception If failed. */
@@ -101,6 +112,12 @@
 
     /** */
     @Test
+    public void testCancelSQLQuery() {
+        doTestCancelSQLQuery(startCli, qryId -> execute(killCli, KILL_SQL_QRY + " '" + qryId + "'"));
+    }
+
+    /** */
+    @Test
     public void testCancelUnknownComputeTask() {
         execute(killCli, KILL_COMPUTE_QRY + " '" + IgniteUuid.randomUuid() + "'");
     }
@@ -117,6 +134,14 @@
         execute(killCli, KILL_TX_QRY + " 'unknown'");
     }
 
+    /** */
+    @Test
+    public void testCancelUnknownSQLQuery() {
+        assertThrowsWithCause(
+            () -> execute(killCli, KILL_SQL_QRY + " '" + srvs.get(0).localNode().id().toString() + "_42'"),
+            RuntimeException.class);
+    }
+
     /**
      * Execute query on given node.
      *
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
index c580b05..fb944c2 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
@@ -18,11 +18,14 @@
 package org.apache.ignite.util;
 
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Consumer;
+import javax.cache.CacheException;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.IgniteException;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.lang.IgniteFuture;
 import org.apache.ignite.services.Service;
@@ -32,6 +35,7 @@
 import org.apache.ignite.spi.systemview.view.SystemView;
 import org.apache.ignite.transactions.Transaction;
 
+import static org.apache.ignite.internal.processors.cache.index.AbstractSchemaSelfTest.queryProcessor;
 import static org.apache.ignite.internal.processors.service.IgniteServiceProcessor.SVCS_VIEW;
 import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
 import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
@@ -51,6 +55,9 @@
     /** Cache name. */
     public static final String DEFAULT_CACHE_NAME = "default";
 
+    /** Page size. */
+    public static final int PAGE_SZ = 5;
+
     /** Operations timeout. */
     public static final int TIMEOUT = 10_000;
 
@@ -182,6 +189,36 @@
         assertTrue(res);
     }
 
+    /**
+     * Test cancel of the SQL query.
+     *
+     * @param cli Client node.
+     * @param qryCanceler Query cancel closure.
+     */
+    public static void doTestCancelSQLQuery(IgniteEx cli, Consumer<String> qryCanceler) {
+        String qryStr = "SELECT * FROM \"default\".Integer";
+
+        SqlFieldsQuery qry = new SqlFieldsQuery(qryStr).setPageSize(PAGE_SZ);
+        Iterator<List<?>> iter = queryProcessor(cli).querySqlFields(qry, true).iterator();
+
+        assertNotNull(iter.next());
+
+        List<List<?>> sqlQries = execute(cli, "SELECT * FROM SYS.SQL_QUERIES ORDER BY START_TIME");
+
+        assertEquals(2, sqlQries.size());
+
+        String qryId = (String)sqlQries.get(0).get(0);
+
+        assertEquals(qryStr, sqlQries.get(0).get(1));
+
+        qryCanceler.accept(qryId);
+
+        for (int i=0; i < PAGE_SZ - 2; i++)
+            assertNotNull(iter.next());
+
+        assertThrowsWithCause(iter::next, CacheException.class);
+    }
+
     /** */
     public interface TestService extends Service {
         /** */