提交 d7b4e57a 编写于 作者: S Serge Rider

MySQL plan json format support

上级 2bbefc22
......@@ -14,7 +14,8 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.ui.editors,
org.jkiss.dbeaver.model,
org.jkiss.dbeaver.core,
org.jkiss.dbeaver.data.gis
org.jkiss.dbeaver.data.gis,
com.google.gson
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-Vendor: JKISS
......
......@@ -16,6 +16,10 @@
*/
package org.jkiss.dbeaver.ext.mysql.model.plan;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.jkiss.dbeaver.ext.mysql.model.MySQLDataSource;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement;
......@@ -23,17 +27,26 @@ 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 org.jkiss.utils.CommonUtils;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* MySQL JSON plan
*/
public class MySQLPlanJSON extends MySQLPlanAbstract {
private final String[] nodePropNames = new String[] {
"ordering_operation", "grouping_operation", "nested_loop", "table",
"attached_subqueries", "optimized_away_subqueries", "materialized_from_subquery", "duplicates_removal"
};
private static final Gson gson = new Gson();
private List<MySQLPlanNodeJSON> rootNodes;
public MySQLPlanJSON(JDBCSession session, String query) throws DBCException {
......@@ -47,10 +60,39 @@ public class MySQLPlanJSON extends MySQLPlanAbstract {
try (JDBCResultSet dbResult = dbStat.executeQuery()) {
List<MySQLPlanNodeJSON> nodes = new ArrayList<>();
while (dbResult.next()) {
// MySQLPlanNodePlain node = new MySQLPlanNodePlain(null, dbResult);
// nodes.add(node);
dbResult.next();
String jsonPlan = dbResult.getString(1);
JsonObject planObject = gson.fromJson(jsonPlan, JsonObject.class);
JsonObject queryBlock = planObject.getAsJsonObject("query_block");
MySQLPlanNodeJSON rootNode = new MySQLPlanNodeJSON(null, "select", queryBlock);
/*
for (Map.Entry<String, JsonElement> prop : queryBlock.entrySet()) {
JsonElement value = prop.getValue();
switch (prop.getKey()) {
case "select_id":
selectId = value.getAsLong();
break;
case "query_cost":
break;
case "message":
errorMessage = value.getAsString();
break;
default:
if (value instanceof JsonObject) {
MySQLPlanNodeJSON nodeJSON = new MySQLPlanNodeJSON(null, prop.getKey(), (JsonObject) value);
nodes.add(nodeJSON);
} else if (value instanceof JsonObject) {
}
}
}
*/
if (CommonUtils.isEmpty(rootNode.getNested()) && rootNode.getProperty("message") != null) {
throw new DBCException("Can't explain plan: " + rootNode.getProperty("message"));
}
nodes.add(rootNode);
rootNodes = nodes;
}
......@@ -61,7 +103,8 @@ public class MySQLPlanJSON extends MySQLPlanAbstract {
@Override
public Object getPlanFeature(String feature) {
if (DBCPlanCostNode.FEATURE_PLAN_ROWS.equals(feature)) {
if (DBCPlanCostNode.FEATURE_PLAN_COST.equals(feature) ||
DBCPlanCostNode.FEATURE_PLAN_ROWS.equals(feature)) {
return true;
}
return super.getPlanFeature(feature);
......@@ -74,7 +117,7 @@ public class MySQLPlanJSON extends MySQLPlanAbstract {
@Override
public String getPlanQueryString() {
return "EXPLAIN EXTENDED " + query;
return "EXPLAIN FORMAT=JSON " + query;
}
@Override
......
......@@ -16,14 +16,18 @@
*/
package org.jkiss.dbeaver.ext.mysql.model.plan;
import org.jkiss.dbeaver.model.exec.plan.DBCPlanNodeKind;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.utils.CommonUtils;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
/**
* MySQL execution plan node based on JSON format
......@@ -41,228 +45,188 @@ import java.util.List;
UNION RESULT – the SELECT is a result of a UNION
*/
public class MySQLPlanNodeJSON extends MySQLPlanNode {
protected Integer id;
protected String selectType;
protected String table;
protected String type;
protected String possibleKeys;
protected String key;
protected String keyLength;
protected String ref;
protected Long rowCount;
protected Long filtered;
protected String extra;
protected MySQLPlanNodeJSON parent;
protected List<MySQLPlanNodeJSON> nested;
public MySQLPlanNodeJSON(List<MySQLPlanNodeJSON> nodes) {
// Root node
type = "<plan>";
if (!nodes.isEmpty()) {
this.rowCount = nodes.get(0).rowCount;
}
this.nested = nodes;
}
public class MySQLPlanNodeJSON extends MySQLPlanNode implements DBPPropertySource {
public MySQLPlanNodeJSON(MySQLPlanNodeJSON parent, ResultSet dbResult) {
this.parent = parent;
this.id = JDBCUtils.safeGetInteger(dbResult, "id");
this.selectType = JDBCUtils.safeGetString(dbResult, "select_type");
this.table = JDBCUtils.safeGetString(dbResult, "table");
this.type = JDBCUtils.safeGetString(dbResult, "type");
this.possibleKeys = JDBCUtils.safeGetString(dbResult, "possible_keys");
this.key = JDBCUtils.safeGetString(dbResult, "key");
this.keyLength = JDBCUtils.safeGetString(dbResult, "key_len");
this.ref = JDBCUtils.safeGetString(dbResult, "ref");
this.rowCount = JDBCUtils.safeGetLongNullable(dbResult, "rows");
this.filtered = JDBCUtils.safeGetLongNullable(dbResult, "filtered");
this.extra = JDBCUtils.safeGetString(dbResult, "extra");
}
private MySQLPlanNodeJSON parent;
private String name;
private JsonObject object;
private Map<String, Object> nodeProps = new LinkedHashMap<>();
private JsonObject costInfo;
private List<MySQLPlanNodeJSON> nested;
public MySQLPlanNodeJSON(MySQLPlanNodeJSON parent, String type) {
public MySQLPlanNodeJSON(MySQLPlanNodeJSON parent, String name, JsonObject object) {
this.parent = parent;
this.selectType = type;
}
protected MySQLPlanNodeJSON(MySQLPlanNodeJSON parent, MySQLPlanNodeJSON source) {
this.id = source.id;
this.selectType = source.selectType;
this.table = source.table;
this.type = source.type;
this.possibleKeys = source.possibleKeys;
this.key = source.key;
this.keyLength = source.keyLength;
this.ref = source.ref;
this.rowCount = source.rowCount;
this.filtered = source.filtered;
this.extra = source.extra;
this.parent = parent;
if (source.nested != null) {
this.nested = new ArrayList<>(source.nested.size());
for (MySQLPlanNodeJSON srcNode : source.nested) {
this.nested.add(srcNode.copyNode(this));
this.name = name;
this.object = object;
for (Map.Entry<String, JsonElement> prop : object.entrySet()) {
String propName = prop.getKey();
JsonElement value = prop.getValue();
if (value instanceof JsonObject) {
if ("cost_info".equals(propName)) {
costInfo = (JsonObject) value;
} else {
addNested(propName, (JsonObject) value);
}
} else if (value instanceof JsonArray) {
boolean isProp = false;
int itemIndex = 0;
for (JsonElement item : (JsonArray) value) {
if (item instanceof JsonObject) {
itemIndex++;
addNested(propName + "#" + itemIndex, (JsonObject) item);
} else {
isProp = true;
break;
}
}
if (isProp) {
nodeProps.put(propName, value.toString());
}
} else {
nodeProps.put(propName, value.getAsString());
}
}
}
@Override
public MySQLPlanNodeJSON getParent() {
return parent;
}
void setParent(MySQLPlanNodeJSON node) {
if (this.parent != null && this.parent.nested != null) {
this.parent.nested.remove(this);
}
this.parent = node;
if (this.parent != null) {
this.parent.addChild(this);
private void addNested(String name, JsonObject value) {
if (nested == null) {
nested = new ArrayList<>();
}
nested.add(
new MySQLPlanNodeJSON(this, name, value)
);
}
private void addChild(MySQLPlanNodeJSON node) {
if (this.nested == null) {
this.nested = new ArrayList<>();
}
this.nested.add(node);
@Override
public String getNodeName() {
Object nodeName = nodeProps.get("table_name");
if (nodeName == null) {
}
return nodeName == null ? null : String.valueOf(nodeName);
}
@Property(order = 0, viewable = true)
@Override
public String getNodeName() {
return table;
public String getNodeType() {
return name;
}
@Override
public DBCPlanNodeKind getNodeKind() {
if ("SIMPLE".equals(selectType)) {
return DBCPlanNodeKind.SELECT;
} else if ("JOIN".equals(selectType)) {
return DBCPlanNodeKind.JOIN;
} else if ("UNION".equals(selectType)) {
return DBCPlanNodeKind.UNION;
public Number getNodeCost() {
if (costInfo == null) {
return null;
}
JsonElement readCost = costInfo.get("read_cost");
if (readCost == null) {
readCost = costInfo.get("query_cost");
}
return super.getNodeKind();
if (readCost == null) {
return null;
}
return readCost.getAsDouble();
}
@Override
public String getNodeDescription() {
return ref;
public Number getNodePercent() {
return null;
}
@Override
@Property(order = 3, viewable = true)
public String getNodeType() {
return selectType;
public Number getNodeDuration() {
return null;
}
@Override
public List<MySQLPlanNodeJSON> getNested() {
return nested;
}
@Property(order = 0, viewable = true)
public Integer getId() {
return id;
}
public Number getNodeRowCount() {
Object rowCount = nodeProps.get("rows_examined_per_scan");
if (rowCount == null) {
@Property(order = 1, viewable = true)
public String getSelectType() {
return selectType;
}
return rowCount == null ? null : CommonUtils.toLong(rowCount);
}
@Property(order = 2, viewable = true)
public String getTable() {
return table;
@Override
public MySQLPlanNodeJSON getParent() {
return parent;
}
@Property(order = 4, viewable = true)
public String getPossibleKeys() {
return possibleKeys;
@Override
public Collection<MySQLPlanNodeJSON> getNested() {
return nested;
}
@Property(order = 5, viewable = true)
public String getKey() {
return key;
public Object getProperty(String name) {
return nodeProps.get(name);
}
@Property(order = 6, viewable = true)
public String getKeyLength() {
return keyLength;
@Override
public String toString() {
return object.toString();
}
@Property(order = 7, viewable = true)
public String getRef() {
return ref;
}
//////////////////////////////////////////////////////////
// Properties
@Property(order = 8, viewable = true)
public Long getRowCount() {
return rowCount;
@Override
public Object getEditableValue() {
return this;
}
@Property(order = 9, viewable = true)
public Long getFiltered() {
return filtered;
@Override
public DBPPropertyDescriptor[] getPropertyDescriptors2() {
DBPPropertyDescriptor[] props = new DBPPropertyDescriptor[nodeProps.size()];
int index = 0;
for (Map.Entry<String, Object> attr : nodeProps.entrySet()) {
props[index++] = new PropertyDescriptor(
"Source",
attr.getKey(),
attr.getKey(),
null,
String.class,
false,
null,
null,
false);
}
return props;
}
@Property(order = 10, viewable = true)
public String getExtra() {
return extra;
@Override
public Object getPropertyValue(@Nullable DBRProgressMonitor monitor, Object id) {
return nodeProps.get(id.toString());
}
@Override
public Number getNodeCost() {
return null;
public boolean isPropertySet(Object id) {
return false;//attributes.containsKey(id.toString());
}
@Override
public Number getNodePercent() {
return null;
public boolean isPropertyResettable(Object id) {
return false;
}
@Override
public Number getNodeDuration() {
return null;
public void resetPropertyValue(@Nullable DBRProgressMonitor monitor, Object id) {
}
@Override
public Number getNodeRowCount() {
return rowCount;
}
public void resetPropertyValueToDefault(Object id) {
public boolean isCompositeNode() {
return "PRIMARY".equals(selectType);
}
@Override
public String toString() {
return id + " " + selectType + " " + table;
}
public void setPropertyValue(@Nullable DBRProgressMonitor monitor, Object id, Object value) {
void computeStats() {
if (nested != null) {
for (MySQLPlanNodeJSON child : nested) {
child.computeStats();
}
}
if (rowCount == null) {
if (nested != null) {
long calcCount = 0;
for (MySQLPlanNodeJSON child : nested) {
child.computeStats();
calcCount += CommonUtils.toLong(child.getRowCount());
}
this.rowCount = calcCount;
}
}
}
MySQLPlanNodeJSON copyNode(MySQLPlanNodeJSON parent) {
return new MySQLPlanNodeJSON(parent, this);
@Override
public boolean isDirty(Object id) {
return false;
}
}
......@@ -169,6 +169,51 @@ public class PostgrePlanNode extends AbstractExecutionPlanNode implements DBCPla
return nested;
}
@Override
public Number getNodeCost() {
String totalCost = attributes.get(ATTR_TOTAL_COST);
return totalCost == null ? null : CommonUtils.toDouble(totalCost);
}
@Override
public Number getNodePercent() {
return null;
// String costPercent = attributes.get(ATTR_TOTAL_COST);
// return costPercent == null ? null : CommonUtils.toDouble(costPercent);
}
@Override
public Number getNodeDuration() {
String time = attributes.get(ATTR_ACTUAL_TOTAL_TIME);
return time == null ? null : CommonUtils.toDouble(time);
}
@Override
public Number getNodeRowCount() {
String rows = attributes.get(ATTR_ACTUAL_ROWS);
return rows == null ? null : CommonUtils.toLong(rows);
}
@Override
public String toString() {
StringBuilder title = new StringBuilder();
title.append("Type: ").append(nodeType);
String joinType = attributes.get("Join-Type");
if (!CommonUtils.isEmpty(joinType)) {
title.append(" (").append(joinType).append(")");
}
title.append("; ");
if (!CommonUtils.isEmpty(entity)) {
title.append("Rel: ").append(entity).append(" ");
}
title.append("; Cost: ").append(cost);
return title.toString();
}
//////////////////////////////////////////////////////////
// Properties
@Override
public Object getEditableValue() {
return this;
......@@ -180,15 +225,15 @@ public class PostgrePlanNode extends AbstractExecutionPlanNode implements DBCPla
int index = 0;
for (Map.Entry<String, String> attr : attributes.entrySet()) {
props[index++] = new PropertyDescriptor(
"Source",
attr.getKey(),
attr.getKey(),
null,
String.class,
false,
null,
null,
false);
"Source",
attr.getKey(),
attr.getKey(),
null,
String.class,
false,
null,
null,
false);
}
return props;
}
......@@ -228,45 +273,4 @@ public class PostgrePlanNode extends AbstractExecutionPlanNode implements DBCPla
return false;
}
@Override
public String toString() {
StringBuilder title = new StringBuilder();
title.append("Type: ").append(nodeType);
String joinType = attributes.get("Join-Type");
if (!CommonUtils.isEmpty(joinType)) {
title.append(" (").append(joinType).append(")");
}
title.append("; ");
if (!CommonUtils.isEmpty(entity)) {
title.append("Rel: ").append(entity).append(" ");
}
title.append("; Cost: ").append(cost);
return title.toString();
}
@Override
public Number getNodeCost() {
String totalCost = attributes.get(ATTR_TOTAL_COST);
return totalCost == null ? null : CommonUtils.toDouble(totalCost);
}
@Override
public Number getNodePercent() {
return null;
// String costPercent = attributes.get(ATTR_TOTAL_COST);
// return costPercent == null ? null : CommonUtils.toDouble(costPercent);
}
@Override
public Number getNodeDuration() {
String time = attributes.get(ATTR_ACTUAL_TOTAL_TIME);
return time == null ? null : CommonUtils.toDouble(time);
}
@Override
public Number getNodeRowCount() {
String rows = attributes.get(ATTR_ACTUAL_ROWS);
return rows == null ? null : CommonUtils.toLong(rows);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册