/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.sql.pg; import com.foundationdb.sql.optimizer.OperatorCompiler; import com.foundationdb.sql.optimizer.ParameterFinder; import com.foundationdb.sql.optimizer.plan.BasePlannable; import com.foundationdb.sql.optimizer.plan.CostEstimate; import com.foundationdb.sql.optimizer.rule.ExplainPlanContext; import com.foundationdb.sql.parser.CallStatementNode; import com.foundationdb.sql.parser.DMLStatementNode; import com.foundationdb.sql.parser.ExplainStatementNode; import com.foundationdb.sql.parser.ParameterNode; import com.foundationdb.sql.parser.StatementNode; import com.foundationdb.sql.server.ServerValueEncoder; import com.foundationdb.qp.operator.QueryBindings; import com.foundationdb.server.explain.Explainable; import com.foundationdb.server.explain.format.DefaultFormatter; import com.foundationdb.server.explain.format.JsonFormatter; import com.foundationdb.server.service.monitor.SessionMonitor.StatementTypes; import com.foundationdb.server.types.TClass; import java.util.Collections; import java.util.List; import java.io.ByteArrayOutputStream; import java.io.IOException; /** SQL statement to explain another one. */ public class PostgresExplainStatement extends PostgresStatementResults implements PostgresStatement { private OperatorCompiler compiler; // Used only to finish generation private List<String> explanation; private String colName; private PostgresType colType; private long aisGeneration; private TClass colTClass; public PostgresExplainStatement(OperatorCompiler compiler) { this.compiler = compiler; colTClass = compiler.getTypesTranslator().typeClassForString(); } public void init(List<String> explanation) { this.explanation = explanation; int maxlen = 32; for (String row : explanation) { if (maxlen < row.length()) maxlen = row.length(); } colName = "OPERATORS"; colType = new PostgresType(PostgresType.TypeOid.VARCHAR_TYPE_OID, (short)-1, maxlen, colTClass.instance(maxlen, false)); } @Override public PostgresType[] getParameterTypes() { return null; } @Override public void sendDescription(PostgresQueryContext context, boolean always, boolean params) throws IOException { PostgresServerSession server = context.getServer(); PostgresMessenger messenger = server.getMessenger(); if (params) { messenger.beginMessage(PostgresMessages.PARAMETER_DESCRIPTION_TYPE.code()); messenger.writeShort(0); messenger.sendMessage(); } messenger.beginMessage(PostgresMessages.ROW_DESCRIPTION_TYPE.code()); messenger.writeShort(1); messenger.writeString(colName); // attname messenger.writeInt(0); // attrelid messenger.writeShort(0); // attnum messenger.writeInt(colType.getOid()); // atttypid messenger.writeShort(colType.getLength()); // attlen messenger.writeInt(colType.getModifier()); // atttypmod messenger.writeShort(0); messenger.sendMessage(); } @Override public TransactionMode getTransactionMode() { return TransactionMode.READ; } @Override public TransactionAbortedMode getTransactionAbortedMode() { return TransactionAbortedMode.NOT_ALLOWED; } @Override public AISGenerationMode getAISGenerationMode() { return AISGenerationMode.NOT_ALLOWED; } @Override public PostgresStatementResult execute(PostgresQueryContext context, QueryBindings bindings, int maxrows) throws IOException { PostgresServerSession server = context.getServer(); server.getSessionMonitor().countEvent(StatementTypes.OTHER_STMT); PostgresMessenger messenger = server.getMessenger(); ServerValueEncoder encoder = server.getValueEncoder(); int nrows = 0; for (String row : explanation) { messenger.beginMessage(PostgresMessages.DATA_ROW_TYPE.code()); messenger.writeShort(1); ByteArrayOutputStream bytes = encoder.encodePObject(row, colType, false); messenger.writeInt(bytes.size()); messenger.writeByteStream(bytes); messenger.sendMessage(); nrows++; if ((maxrows > 0) && (nrows >= maxrows)) break; } return commandComplete("EXPLAIN " + nrows, nrows); } @Override public boolean hasAISGeneration() { return aisGeneration != 0; } @Override public void setAISGeneration(long aisGeneration) { this.aisGeneration = aisGeneration; } @Override public long getAISGeneration() { return aisGeneration; } @Override public PostgresStatement finishGenerating(PostgresServerSession server, String sql, StatementNode stmt, List<ParameterNode> params, int[] paramTypes) { ExplainPlanContext context = new ExplainPlanContext(compiler, new PostgresQueryContext(server)); ExplainStatementNode explainStmt = (ExplainStatementNode)stmt; StatementNode innerStmt = explainStmt.getStatement(); if (params == null) params = new ParameterFinder().find(innerStmt); Explainable explainable; if (innerStmt instanceof CallStatementNode) { explainable = PostgresCallStatementGenerator.explainable(server, (CallStatementNode)innerStmt, params, paramTypes); } else { BasePlannable result = compiler.compile((DMLStatementNode)innerStmt, params, context); explainable = result.getPlannable(); } List<String> explain; if (compiler instanceof PostgresJsonCompiler) { JsonFormatter f = new JsonFormatter(); explain = Collections.singletonList(f.format(explainable.getExplainer(context.getExplainContext()))); } else { DefaultFormatter.LevelOfDetail detail; switch (explainStmt.getDetail()) { case BRIEF: detail = DefaultFormatter.LevelOfDetail.BRIEF; break; default: case NORMAL: detail = DefaultFormatter.LevelOfDetail.NORMAL; break; case VERBOSE: detail = DefaultFormatter.LevelOfDetail.VERBOSE; break; } DefaultFormatter f = new DefaultFormatter(server.getDefaultSchemaName(), detail); explain = f.format(explainable.getExplainer(context.getExplainContext())); } init(explain); compiler = null; return this; } @Override public boolean putInCache() { return false; } @Override public CostEstimate getCostEstimate() { return null; } }