diff --git a/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/MySQLDataSource.java b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/MySQLDataSource.java index 34e944e5f03981c70ed9fdaf932c1ee6ac3070a4..cc689493fa802c1fcea9f310d08a08bb3d74f855 100644 --- a/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/MySQLDataSource.java +++ b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/MySQLDataSource.java @@ -612,9 +612,7 @@ public class MySQLDataSource extends JDBCDataSource implements DBSObjectSelector @Override public DBCPlan planQueryExecution(@NotNull DBCSession session, @NotNull String query) throws DBCException { - MySQLPlanAnalyser plan = new MySQLPlanAnalyser(this, query); - plan.explain(session); - return plan; + return new MySQLPlanAnalyser(this).explain(session, query); } @NotNull diff --git a/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanAbstract.java b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanAbstract.java new file mode 100644 index 0000000000000000000000000000000000000000..50101e07619467cf6180d1db813d2dd399b021b3 --- /dev/null +++ b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanAbstract.java @@ -0,0 +1,49 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2019 Serge Rider (serge@jkiss.org) + * + * Licensed 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.jkiss.dbeaver.ext.mysql.model.plan; + +import org.jkiss.dbeaver.ext.mysql.model.MySQLDataSource; +import org.jkiss.dbeaver.model.exec.DBCException; +import org.jkiss.dbeaver.model.exec.DBCSession; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; +import org.jkiss.dbeaver.model.exec.plan.DBCPlanCostNode; +import org.jkiss.dbeaver.model.exec.plan.DBCPlanNode; +import org.jkiss.dbeaver.model.impl.plan.AbstractExecutionPlan; +import org.jkiss.dbeaver.model.sql.SQLUtils; +import org.jkiss.utils.CommonUtils; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * MySQL execution plan analyser + */ +public abstract class MySQLPlanAbstract extends AbstractExecutionPlan { + + protected MySQLDataSource dataSource; + protected String query; + + public MySQLPlanAbstract(MySQLDataSource dataSource, String query) { + this.dataSource = dataSource; + this.query = query; + } + +} diff --git a/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanAnalyser.java b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanAnalyser.java index 8cf2410d86a59a15d2e57629e36359d0f83bf976..9d25c210c0d46c4caf4c57646e5a31318658d9c6 100644 --- a/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanAnalyser.java +++ b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanAnalyser.java @@ -19,163 +19,32 @@ package org.jkiss.dbeaver.ext.mysql.model.plan; import org.jkiss.dbeaver.ext.mysql.model.MySQLDataSource; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.exec.DBCSession; -import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement; -import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet; -import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; -import org.jkiss.dbeaver.model.exec.plan.DBCPlanCostNode; -import org.jkiss.dbeaver.model.exec.plan.DBCPlanNode; -import org.jkiss.dbeaver.model.impl.plan.AbstractExecutionPlan; -import org.jkiss.dbeaver.model.sql.SQLUtils; -import org.jkiss.utils.CommonUtils; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; /** * MySQL execution plan analyser */ -public class MySQLPlanAnalyser extends AbstractExecutionPlan { +public class MySQLPlanAnalyser { private MySQLDataSource dataSource; - private String query; - private List rootNodes; - public MySQLPlanAnalyser(MySQLDataSource dataSource, String query) { + public MySQLPlanAnalyser(MySQLDataSource dataSource) { this.dataSource = dataSource; - this.query = query; - } - - @Override - public Object getPlanFeature(String feature) { - if (DBCPlanCostNode.FEATURE_PLAN_ROWS.equals(feature)) { - return true; - } - return super.getPlanFeature(feature); - } - - @Override - public String getQueryString() { - return query; } - @Override - public String getPlanQueryString() { - return "EXPLAIN EXTENDED " + query; - } - - @Override - public List getPlanNodes(Map options) { - if (CommonUtils.getOption(options, OPTION_KEEP_ORIGINAL)) { - return rootNodes; + public MySQLPlanAbstract explain(DBCSession session, String query) throws DBCException { + if (supportsExplainJSON()) { + return new MySQLPlanJSON(dataSource, query); } else { - List rootCopy = new ArrayList<>(rootNodes.size()); - for (MySQLPlanNodePlain r : rootNodes) { - rootCopy.add(r.copyNode(null)); - } - return convertToPlanTree(rootCopy); + return new MySQLPlanClassic(dataSource, query); } } - public void explain(DBCSession session) - throws DBCException { - String plainQuery = SQLUtils.stripComments(SQLUtils.getDialectFromObject(session.getDataSource()), query).toUpperCase(); - if (!plainQuery.startsWith("SELECT")) { - throw new DBCException("Only SELECT statements could produce execution plan"); - } - JDBCSession connection = (JDBCSession) session; - try { - try (JDBCPreparedStatement dbStat = connection.prepareStatement(getPlanQueryString())) { - try (JDBCResultSet dbResult = dbStat.executeQuery()) { - List nodes = new ArrayList<>(); - while (dbResult.next()) { - MySQLPlanNodePlain node = new MySQLPlanNodePlain(null, dbResult); - nodes.add(node); - } - - rootNodes = nodes; - } - } - } catch (SQLException e) { - throw new DBCException(e, session.getDataSource()); - } - } - - private List convertToPlanTree(List srcNodes) { - List roots = new ArrayList<>(); - - if (srcNodes.size() == 1) { - // Just one node - roots.add(srcNodes.get(0)); + private boolean supportsExplainJSON() { + if (dataSource.isMariaDB()) { + return dataSource.isServerVersionAtLeast(10, 1); } else { - List parsed = new ArrayList<>(); - MySQLPlanNodePlain lastCompositeNode = null; - for (int id = 1; ; id++) { - List nodes = getQueriesById(srcNodes, id); - if (nodes.isEmpty()) { - break; - } - if (nodes.size() == 1) { - MySQLPlanNodePlain firstNode = nodes.get(0); - if (lastCompositeNode != null) { - firstNode.setParent(lastCompositeNode); - } else { - roots.add(firstNode); - } - if (firstNode.isCompositeNode()) { - lastCompositeNode = firstNode; - } - } else { - MySQLPlanNodePlain leftNode = lastCompositeNode; - if (leftNode == null) { - leftNode = nodes.get(0).getParent(); - } - MySQLPlanNodePlain joinNode = joinNodes(srcNodes, leftNode, nodes); - if (leftNode == null) { - roots.add(joinNode); - } - } - parsed.addAll(nodes); - } - // Add the rest - for (MySQLPlanNodePlain node : srcNodes) { - if (node.getId() != null && !parsed.contains(node)) { - roots.add(node); - } - } - } - - for (MySQLPlanNodePlain node : roots) { - node.computeStats(); - } - - return roots; - } - - private List getQueriesById(List srcNodes, int id) { - List subList = new ArrayList<>(); - for (MySQLPlanNodePlain node : srcNodes) { - if (node.getId() != null && node.getId() == id) { - subList.add(node); - } - } - return subList; - } - - private MySQLPlanNodePlain joinNodes(List srcNodes, MySQLPlanNodePlain parent, List nodes) { - MySQLPlanNodePlain leftNode = nodes.get(0); - for (int i = 1; i < nodes.size(); i++) { - MySQLPlanNodePlain rightNode = nodes.get(i); - MySQLPlanNodeJoin nodeJoin = new MySQLPlanNodeJoin(parent, leftNode, rightNode); - leftNode.setParent(nodeJoin); - rightNode.setParent(nodeJoin); - if (parent != null) { - nodeJoin.setParent(parent); - } - leftNode = nodeJoin; + return dataSource.isServerVersionAtLeast(5, 6); } - return leftNode; } } diff --git a/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanClassic.java b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanClassic.java new file mode 100644 index 0000000000000000000000000000000000000000..7e6446eca490a5dd857229e22dd6d918078b1478 --- /dev/null +++ b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanClassic.java @@ -0,0 +1,164 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2019 Serge Rider (serge@jkiss.org) + * + * Licensed 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.jkiss.dbeaver.ext.mysql.model.plan; + +import org.jkiss.dbeaver.ext.mysql.model.MySQLDataSource; +import org.jkiss.dbeaver.model.exec.DBCException; +import org.jkiss.dbeaver.model.exec.DBCSession; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; +import org.jkiss.dbeaver.model.exec.plan.DBCPlanCostNode; +import org.jkiss.dbeaver.model.exec.plan.DBCPlanNode; +import org.jkiss.dbeaver.model.impl.plan.AbstractExecutionPlan; +import org.jkiss.dbeaver.model.sql.SQLUtils; +import org.jkiss.utils.CommonUtils; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * MySQL execution plan analyser + */ +public class MySQLPlanClassic extends MySQLPlanAbstract { + + private List rootNodes; + + public MySQLPlanClassic(JDBCSession session, String query) throws DBCException { + super((MySQLDataSource) session.getDataSource(), query); + + String plainQuery = SQLUtils.stripComments(SQLUtils.getDialectFromObject(dataSource), query).toUpperCase(); + if (!plainQuery.startsWith("SELECT")) { + throw new DBCException("Only SELECT statements could produce execution plan"); + } + try (JDBCPreparedStatement dbStat = session.prepareStatement(getPlanQueryString())) { + try (JDBCResultSet dbResult = dbStat.executeQuery()) { + List nodes = new ArrayList<>(); + while (dbResult.next()) { + MySQLPlanNodePlain node = new MySQLPlanNodePlain(null, dbResult); + nodes.add(node); + } + + rootNodes = nodes; + } + } catch (SQLException e) { + throw new DBCException(e, session.getDataSource()); + } + } + + @Override + public Object getPlanFeature(String feature) { + if (DBCPlanCostNode.FEATURE_PLAN_ROWS.equals(feature)) { + return true; + } + return super.getPlanFeature(feature); + } + + @Override + public String getQueryString() { + return query; + } + + @Override + public String getPlanQueryString() { + return "EXPLAIN EXTENDED " + query; + } + + @Override + public List getPlanNodes(Map options) { + return rootNodes; + } + + private List convertToPlanTree(List srcNodes) { + List roots = new ArrayList<>(); + + if (srcNodes.size() == 1) { + // Just one node + roots.add(srcNodes.get(0)); + } else { + List parsed = new ArrayList<>(); + MySQLPlanNodePlain lastCompositeNode = null; + for (int id = 1; ; id++) { + List nodes = getQueriesById(srcNodes, id); + if (nodes.isEmpty()) { + break; + } + if (nodes.size() == 1) { + MySQLPlanNodePlain firstNode = nodes.get(0); + if (lastCompositeNode != null) { + firstNode.setParent(lastCompositeNode); + } else { + roots.add(firstNode); + } + if (firstNode.isCompositeNode()) { + lastCompositeNode = firstNode; + } + } else { + MySQLPlanNodePlain leftNode = lastCompositeNode; + if (leftNode == null) { + leftNode = nodes.get(0).getParent(); + } + MySQLPlanNodePlain joinNode = joinNodes(srcNodes, leftNode, nodes); + if (leftNode == null) { + roots.add(joinNode); + } + } + parsed.addAll(nodes); + } + // Add the rest + for (MySQLPlanNodePlain node : srcNodes) { + if (node.getId() != null && !parsed.contains(node)) { + roots.add(node); + } + } + } + + for (MySQLPlanNodePlain node : roots) { + node.computeStats(); + } + + return roots; + } + + private List getQueriesById(List srcNodes, int id) { + List subList = new ArrayList<>(); + for (MySQLPlanNodePlain node : srcNodes) { + if (node.getId() != null && node.getId() == id) { + subList.add(node); + } + } + return subList; + } + + private MySQLPlanNodePlain joinNodes(List srcNodes, MySQLPlanNodePlain parent, List nodes) { + MySQLPlanNodePlain leftNode = nodes.get(0); + for (int i = 1; i < nodes.size(); i++) { + MySQLPlanNodePlain rightNode = nodes.get(i); + MySQLPlanNodeJoin nodeJoin = new MySQLPlanNodeJoin(parent, leftNode, rightNode); + leftNode.setParent(nodeJoin); + rightNode.setParent(nodeJoin); + if (parent != null) { + nodeJoin.setParent(parent); + } + leftNode = nodeJoin; + } + return leftNode; + } + +} diff --git a/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanJSON.java b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanJSON.java new file mode 100644 index 0000000000000000000000000000000000000000..9cd116b8f1e3c963b127be4ddf60b37def4537ce --- /dev/null +++ b/plugins/org.jkiss.dbeaver.ext.mysql/src/org/jkiss/dbeaver/ext/mysql/model/plan/MySQLPlanJSON.java @@ -0,0 +1,85 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2019 Serge Rider (serge@jkiss.org) + * + * Licensed 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.jkiss.dbeaver.ext.mysql.model.plan; + +import org.jkiss.dbeaver.ext.mysql.model.MySQLDataSource; +import org.jkiss.dbeaver.model.exec.DBCException; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet; +import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession; +import org.jkiss.dbeaver.model.exec.plan.DBCPlanCostNode; +import org.jkiss.dbeaver.model.sql.SQLUtils; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * MySQL JSON plan + */ +public class MySQLPlanJSON extends MySQLPlanAbstract { + + private List rootNodes; + + public MySQLPlanJSON(JDBCSession session, String query) throws DBCException { + super((MySQLDataSource) session.getDataSource(), query); + + String plainQuery = SQLUtils.stripComments(SQLUtils.getDialectFromObject(dataSource), query).toUpperCase(); + if (!plainQuery.startsWith("SELECT")) { + throw new DBCException("Only SELECT statements could produce execution plan"); + } + try (JDBCPreparedStatement dbStat = session.prepareStatement(getPlanQueryString())) { + try (JDBCResultSet dbResult = dbStat.executeQuery()) { + List nodes = new ArrayList<>(); + + while (dbResult.next()) { +// MySQLPlanNodePlain node = new MySQLPlanNodePlain(null, dbResult); +// nodes.add(node); + } + + rootNodes = nodes; + } + } catch (SQLException e) { + throw new DBCException(e, session.getDataSource()); + } + } + + @Override + public Object getPlanFeature(String feature) { + if (DBCPlanCostNode.FEATURE_PLAN_ROWS.equals(feature)) { + return true; + } + return super.getPlanFeature(feature); + } + + @Override + public String getQueryString() { + return query; + } + + @Override + public String getPlanQueryString() { + return "EXPLAIN EXTENDED " + query; + } + + @Override + public List getPlanNodes(Map options) { + return rootNodes; + } + +}