提交 2696d563 authored 作者: Alexey Kuznetsov's avatar Alexey Kuznetsov 提交者: Sergi Vladykin

ISSUE-404 EXPLAIN should process statements with params placeholders. (#405)

* ISSUE-404 EXPLAIN should process statements with params placeholders.

* ISSUE-404 Fixed CommandRemote to support EXPLAIN with params placeholder.

* ISSUE-404 Fixed CommandRemote to support EXPLAIN with params placeholder.

* ISSUE-404 Support EXPLAIN with params placeholder WIP.

* ISSUE-404 Support EXPLAIN with params placeholder WIP.

* ISSUE-404 Support EXPLAIN with params placeholder WIP.

* ISSUE-404 Support EXPLAIN with params placeholder WIP.

* ISSUE-404 Muted tests.

* ISSUE-404 Fixed review notes.

* ISSUE-404 Un-muted tests.
上级 2c153446
......@@ -55,9 +55,11 @@ public class CommandContainer extends Command {
private static void prepareJoinBatch(Prepared prepared) {
if (prepared.isQuery()) {
if (prepared.getType() == CommandInterface.SELECT) {
int type = prepared.getType();
if (type == CommandInterface.SELECT) {
((Query) prepared).prepareJoinBatch();
} else if (prepared.getType() == CommandInterface.EXPLAIN) {
} else if (type == CommandInterface.EXPLAIN || type == CommandInterface.EXPLAIN_ANALYZE) {
prepareJoinBatch(((Explain) prepared).getCommand());
}
}
......
......@@ -456,6 +456,11 @@ public interface CommandInterface {
*/
int ALTER_TABLE_RENAME_CONSTRAINT = 85;
/**
* The type of a EXPLAIN ANALYZE statement.
*/
int EXPLAIN_ANALYZE = 86;
/**
* Get command type.
*
......
......@@ -7,6 +7,8 @@ package org.h2.command;
import java.io.IOException;
import java.util.ArrayList;
import org.h2.engine.Constants;
import org.h2.engine.SessionRemote;
import org.h2.engine.SysProperties;
import org.h2.expression.ParameterInterface;
......@@ -18,6 +20,7 @@ import org.h2.result.ResultRemote;
import org.h2.util.New;
import org.h2.value.Transfer;
import org.h2.value.Value;
import org.h2.value.ValueNull;
/**
* Represents the client-side part of a SQL statement.
......@@ -33,6 +36,7 @@ public class CommandRemote implements CommandInterface {
private SessionRemote session;
private int id;
private boolean isQuery;
private int cmdType = UNKNOWN;
private boolean readonly;
private final int created;
......@@ -55,10 +59,13 @@ public class CommandRemote implements CommandInterface {
for (int i = 0, count = 0; i < transferList.size(); i++) {
try {
Transfer transfer = transferList.get(i);
boolean v16 = s.getClientVersion() >= Constants.TCP_PROTOCOL_VERSION_16;
if (createParams) {
s.traceOperation("SESSION_PREPARE_READ_PARAMS", id);
s.traceOperation(v16 ? "SESSION_PREPARE_READ_PARAMS2" : "SESSION_PREPARE_READ_PARAMS", id);
transfer.
writeInt(SessionRemote.SESSION_PREPARE_READ_PARAMS).
writeInt(v16 ? SessionRemote.SESSION_PREPARE_READ_PARAMS2 : SessionRemote.SESSION_PREPARE_READ_PARAMS).
writeInt(id).writeString(sql);
} else {
s.traceOperation("SESSION_PREPARE", id);
......@@ -68,6 +75,9 @@ public class CommandRemote implements CommandInterface {
s.done(transfer);
isQuery = transfer.readBoolean();
readonly = transfer.readBoolean();
cmdType = v16 && createParams ? transfer.readInt() : UNKNOWN;
int paramCount = transfer.readInt();
if (createParams) {
parameters.clear();
......@@ -203,8 +213,10 @@ public class CommandRemote implements CommandInterface {
}
private void checkParameters() {
for (ParameterInterface p : parameters) {
p.checkSet();
if (cmdType != EXPLAIN) {
for (ParameterInterface p : parameters) {
p.checkSet();
}
}
}
......@@ -212,7 +224,12 @@ public class CommandRemote implements CommandInterface {
int len = parameters.size();
transfer.writeInt(len);
for (ParameterInterface p : parameters) {
transfer.writeValue(p.getParamValue());
Value pVal = p.getParamValue();
if (pVal == null && cmdType == EXPLAIN)
pVal = ValueNull.INSTANCE;
transfer.writeValue(pVal);
}
}
......@@ -260,7 +277,7 @@ public class CommandRemote implements CommandInterface {
@Override
public int getCommandType() {
return UNKNOWN;
return cmdType;
}
}
......@@ -58,6 +58,13 @@ public class Explain extends Prepared {
return query(-1);
}
@Override
protected void checkParameters() {
// Check params only in case of EXPLAIN ANALYZE
if (executeCommand)
super.checkParameters();
}
@Override
public ResultInterface query(int maxrows) {
Column column = new Column("PLAN", Value.STRING);
......@@ -146,6 +153,6 @@ public class Explain extends Prepared {
@Override
public int getType() {
return CommandInterface.EXPLAIN;
return executeCommand ? CommandInterface.EXPLAIN_ANALYZE : CommandInterface.EXPLAIN;
}
}
......@@ -96,6 +96,11 @@ public class Constants {
*/
public static final int TCP_PROTOCOL_VERSION_15 = 15;
/**
* The TCP protocol version number 16.
*/
public static final int TCP_PROTOCOL_VERSION_16 = 16;
/**
* The major version of this database.
*/
......
......@@ -59,6 +59,7 @@ public class SessionRemote extends SessionWithState implements DataHandler {
public static final int SESSION_SET_AUTOCOMMIT = 15;
public static final int SESSION_HAS_PENDING_TRANSACTION = 16;
public static final int LOB_READ = 17;
public static final int SESSION_PREPARE_READ_PARAMS2 = 18;
public static final int STATUS_ERROR = 0;
public static final int STATUS_OK = 1;
......@@ -118,7 +119,7 @@ public class SessionRemote extends SessionWithState implements DataHandler {
trans.setSSL(ci.isSSL());
trans.init();
trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
trans.writeInt(Constants.TCP_PROTOCOL_VERSION_15);
trans.writeInt(Constants.TCP_PROTOCOL_VERSION_16);
trans.writeString(db);
trans.writeString(ci.getOriginalURL());
trans.writeString(ci.getUserName());
......@@ -217,6 +218,10 @@ public class SessionRemote extends SessionWithState implements DataHandler {
}
}
public int getClientVersion() {
return clientVersion;
}
@Override
public boolean getAutoCommit() {
return autoCommit;
......
......@@ -16,6 +16,7 @@ import java.sql.SQLException;
import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.command.Command;
import org.h2.command.CommandInterface;
import org.h2.engine.ConnectionInfo;
import org.h2.engine.Constants;
import org.h2.engine.Engine;
......@@ -85,13 +86,13 @@ public class TcpServerThread implements Runnable {
if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2,
"" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_6);
} else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_15) {
} else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_16) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2,
"" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_15);
"" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_16);
}
int maxClientVersion = transfer.readInt();
if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_15) {
clientVersion = Constants.TCP_PROTOCOL_VERSION_15;
if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_16) {
clientVersion = Constants.TCP_PROTOCOL_VERSION_16;
} else {
clientVersion = minClientVersion;
}
......@@ -256,6 +257,7 @@ public class TcpServerThread implements Runnable {
int operation = transfer.readInt();
switch (operation) {
case SessionRemote.SESSION_PREPARE_READ_PARAMS:
case SessionRemote.SESSION_PREPARE_READ_PARAMS2:
case SessionRemote.SESSION_PREPARE: {
int id = transfer.readInt();
String sql = transfer.readString();
......@@ -264,10 +266,19 @@ public class TcpServerThread implements Runnable {
boolean readonly = command.isReadOnly();
cache.addObject(id, command);
boolean isQuery = command.isQuery();
ArrayList<? extends ParameterInterface> params = command.getParameters();
transfer.writeInt(getState(old)).writeBoolean(isQuery).
writeBoolean(readonly).writeInt(params.size());
if (operation == SessionRemote.SESSION_PREPARE_READ_PARAMS) {
writeBoolean(readonly);
if (operation == SessionRemote.SESSION_PREPARE_READ_PARAMS2) {
transfer.writeInt(command.getCommandType());
}
ArrayList<? extends ParameterInterface> params = command.getParameters();
transfer.writeInt(params.size());
if (operation != SessionRemote.SESSION_PREPARE) {
for (ParameterInterface p : params) {
ParameterRemote.writeMetaData(transfer, p);
}
......
......@@ -238,7 +238,7 @@ public class FileLock implements Runnable {
transfer.setSocket(socket);
transfer.init();
transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_6);
transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_15);
transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_16);
transfer.writeString(null);
transfer.writeString(null);
transfer.writeString(id);
......
......@@ -75,6 +75,8 @@ public class TestCases extends TestBase {
testDeleteGroup();
testDisconnect();
testExecuteTrace();
testExplain();
testExplainAnalyze();
if (config.memory) {
return;
}
......@@ -897,6 +899,225 @@ public class TestCases extends TestBase {
conn.close();
}
private void checkExplain(Statement stat, String sql, String expected) throws SQLException {
ResultSet rs = stat.executeQuery(sql);
assertTrue(rs.next());
assertEquals(expected, rs.getString(1));
}
private void testExplain() throws SQLException {
deleteDb("cases");
Connection conn = getConnection("cases");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE ORGANIZATION(id int primary key, name varchar(100))");
stat.execute("CREATE TABLE PERSON(id int primary key, orgId int, name varchar(100), salary int)");
checkExplain(stat, "/* bla-bla */ EXPLAIN SELECT ID FROM ORGANIZATION WHERE id = ?",
"SELECT\n" +
" ID\n" +
"FROM PUBLIC.ORGANIZATION\n" +
" /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" +
"WHERE ID = ?1");
checkExplain(stat, "EXPLAIN SELECT ID FROM ORGANIZATION WHERE id = 1",
"SELECT\n" +
" ID\n" +
"FROM PUBLIC.ORGANIZATION\n" +
" /* PUBLIC.PRIMARY_KEY_D: ID = 1 */\n" +
"WHERE ID = 1");
checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE id = ?",
"SELECT\n" +
" PERSON.ID,\n" +
" PERSON.ORGID,\n" +
" PERSON.NAME,\n" +
" PERSON.SALARY\n" +
"FROM PUBLIC.PERSON\n" +
" /* PUBLIC.PRIMARY_KEY_8: ID = ?1 */\n" +
"WHERE ID = ?1");
checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE id = 50",
"SELECT\n" +
" PERSON.ID,\n" +
" PERSON.ORGID,\n" +
" PERSON.NAME,\n" +
" PERSON.SALARY\n" +
"FROM PUBLIC.PERSON\n" +
" /* PUBLIC.PRIMARY_KEY_8: ID = 50 */\n" +
"WHERE ID = 50");
checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE salary > ? and salary < ?",
"SELECT\n" +
" PERSON.ID,\n" +
" PERSON.ORGID,\n" +
" PERSON.NAME,\n" +
" PERSON.SALARY\n" +
"FROM PUBLIC.PERSON\n" +
" /* PUBLIC.PERSON.tableScan */\n" +
"WHERE (SALARY > ?1)\n" +
" AND (SALARY < ?2)");
checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE salary > 1000 and salary < 2000",
"SELECT\n" +
" PERSON.ID,\n" +
" PERSON.ORGID,\n" +
" PERSON.NAME,\n" +
" PERSON.SALARY\n" +
"FROM PUBLIC.PERSON\n" +
" /* PUBLIC.PERSON.tableScan */\n" +
"WHERE (SALARY > 1000)\n" +
" AND (SALARY < 2000)");
checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE name = lower(?)",
"SELECT\n" +
" PERSON.ID,\n" +
" PERSON.ORGID,\n" +
" PERSON.NAME,\n" +
" PERSON.SALARY\n" +
"FROM PUBLIC.PERSON\n" +
" /* PUBLIC.PERSON.tableScan */\n" +
"WHERE NAME = LOWER(?1)");
checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE name = lower('Smith')",
"SELECT\n" +
" PERSON.ID,\n" +
" PERSON.ORGID,\n" +
" PERSON.NAME,\n" +
" PERSON.SALARY\n" +
"FROM PUBLIC.PERSON\n" +
" /* PUBLIC.PERSON.tableScan */\n" +
"WHERE NAME = 'smith'");
checkExplain(stat, "EXPLAIN SELECT * FROM PERSON p INNER JOIN ORGANIZATION o ON p.id = o.id WHERE o.id = ? AND p.salary > ?",
"SELECT\n" +
" P.ID,\n" +
" P.ORGID,\n" +
" P.NAME,\n" +
" P.SALARY,\n" +
" O.ID,\n" +
" O.NAME\n" +
"FROM PUBLIC.ORGANIZATION O\n" +
" /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" +
" /* WHERE O.ID = ?1\n" +
" */\n" +
"INNER JOIN PUBLIC.PERSON P\n" +
" /* PUBLIC.PRIMARY_KEY_8: ID = O.ID */\n" +
" ON 1=1\n" +
"WHERE (P.ID = O.ID)\n" +
" AND ((O.ID = ?1)\n" +
" AND (P.SALARY > ?2))");
checkExplain(stat, "EXPLAIN SELECT * FROM PERSON p INNER JOIN ORGANIZATION o ON p.id = o.id WHERE o.id = 10 AND p.salary > 1000",
"SELECT\n" +
" P.ID,\n" +
" P.ORGID,\n" +
" P.NAME,\n" +
" P.SALARY,\n" +
" O.ID,\n" +
" O.NAME\n" +
"FROM PUBLIC.ORGANIZATION O\n" +
" /* PUBLIC.PRIMARY_KEY_D: ID = 10 */\n" +
" /* WHERE O.ID = 10\n" +
" */\n" +
"INNER JOIN PUBLIC.PERSON P\n" +
" /* PUBLIC.PRIMARY_KEY_8: ID = O.ID */\n" +
" ON 1=1\n" +
"WHERE (P.ID = O.ID)\n" +
" AND ((O.ID = 10)\n" +
" AND (P.SALARY > 1000))");
PreparedStatement pStat = conn.prepareStatement("/* bla-bla */ EXPLAIN SELECT ID FROM ORGANIZATION WHERE id = ?");
ResultSet rs = pStat.executeQuery();
assertTrue(rs.next());
assertEquals("SELECT\n" +
" ID\n" +
"FROM PUBLIC.ORGANIZATION\n" +
" /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" +
"WHERE ID = ?1",
rs.getString(1));
conn.close();
}
private void testExplainAnalyze() throws SQLException {
deleteDb("cases");
Connection conn = getConnection("cases");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE ORGANIZATION(id int primary key, name varchar(100))");
stat.execute("CREATE TABLE PERSON(id int primary key, orgId int, name varchar(100), salary int)");
stat.execute("INSERT INTO ORGANIZATION VALUES(1, 'org1')");
stat.execute("INSERT INTO ORGANIZATION VALUES(2, 'org2')");
stat.execute("INSERT INTO PERSON VALUES(1, 1, 'person1', 1000)");
stat.execute("INSERT INTO PERSON VALUES(2, 1, 'person2', 2000)");
stat.execute("INSERT INTO PERSON VALUES(3, 2, 'person3', 3000)");
stat.execute("INSERT INTO PERSON VALUES(4, 2, 'person4', 4000)");
assertThrows(ErrorCode.PARAMETER_NOT_SET_1, stat, "/* bla-bla */ EXPLAIN ANALYZE SELECT ID FROM ORGANIZATION WHERE id = ?");
PreparedStatement pStat = conn.prepareStatement("/* bla-bla */ EXPLAIN ANALYZE SELECT ID FROM ORGANIZATION WHERE id = ?");
assertThrows(ErrorCode.PARAMETER_NOT_SET_1, pStat).executeQuery();
pStat.setInt(1, 1);
ResultSet rs = pStat.executeQuery();
assertTrue(rs.next());
assertEquals("SELECT\n" +
" ID\n" +
"FROM PUBLIC.ORGANIZATION\n" +
" /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" +
" /* scanCount: 2 */\n" +
"WHERE ID = ?1",
rs.getString(1));
pStat = conn.prepareStatement("EXPLAIN ANALYZE SELECT * FROM PERSON p " +
"INNER JOIN ORGANIZATION o ON o.id = p.id WHERE o.id = ?");
assertThrows(ErrorCode.PARAMETER_NOT_SET_1, pStat).executeQuery();
pStat.setInt(1, 1);
rs = pStat.executeQuery();
assertTrue(rs.next());
assertEquals("SELECT\n" +
" P.ID,\n" +
" P.ORGID,\n" +
" P.NAME,\n" +
" P.SALARY,\n" +
" O.ID,\n" +
" O.NAME\n" +
"FROM PUBLIC.ORGANIZATION O\n" +
" /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" +
" /* WHERE O.ID = ?1\n" +
" */\n" +
" /* scanCount: 2 */\n" +
"INNER JOIN PUBLIC.PERSON P\n" +
" /* PUBLIC.PRIMARY_KEY_8: ID = O.ID\n" +
" AND ID = ?1\n" +
" */\n" +
" ON 1=1\n" +
" /* scanCount: 2 */\n" +
"WHERE ((O.ID = ?1)\n" +
" AND (O.ID = P.ID))\n" +
" AND (P.ID = ?1)",
rs.getString(1));
conn.close();
}
private void testAlterTableReconnect() throws SQLException {
deleteDb("cases");
Connection conn = getConnection("cases");
......@@ -1530,4 +1751,4 @@ public class TestCases extends TestBase {
conn.close();
}
}
\ No newline at end of file
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论