提交 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 { ...@@ -55,9 +55,11 @@ public class CommandContainer extends Command {
private static void prepareJoinBatch(Prepared prepared) { private static void prepareJoinBatch(Prepared prepared) {
if (prepared.isQuery()) { if (prepared.isQuery()) {
if (prepared.getType() == CommandInterface.SELECT) { int type = prepared.getType();
if (type == CommandInterface.SELECT) {
((Query) prepared).prepareJoinBatch(); ((Query) prepared).prepareJoinBatch();
} else if (prepared.getType() == CommandInterface.EXPLAIN) { } else if (type == CommandInterface.EXPLAIN || type == CommandInterface.EXPLAIN_ANALYZE) {
prepareJoinBatch(((Explain) prepared).getCommand()); prepareJoinBatch(((Explain) prepared).getCommand());
} }
} }
......
...@@ -456,6 +456,11 @@ public interface CommandInterface { ...@@ -456,6 +456,11 @@ public interface CommandInterface {
*/ */
int ALTER_TABLE_RENAME_CONSTRAINT = 85; int ALTER_TABLE_RENAME_CONSTRAINT = 85;
/**
* The type of a EXPLAIN ANALYZE statement.
*/
int EXPLAIN_ANALYZE = 86;
/** /**
* Get command type. * Get command type.
* *
......
...@@ -7,6 +7,8 @@ package org.h2.command; ...@@ -7,6 +7,8 @@ package org.h2.command;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import org.h2.engine.Constants;
import org.h2.engine.SessionRemote; import org.h2.engine.SessionRemote;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.expression.ParameterInterface; import org.h2.expression.ParameterInterface;
...@@ -18,6 +20,7 @@ import org.h2.result.ResultRemote; ...@@ -18,6 +20,7 @@ import org.h2.result.ResultRemote;
import org.h2.util.New; import org.h2.util.New;
import org.h2.value.Transfer; import org.h2.value.Transfer;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueNull;
/** /**
* Represents the client-side part of a SQL statement. * Represents the client-side part of a SQL statement.
...@@ -33,6 +36,7 @@ public class CommandRemote implements CommandInterface { ...@@ -33,6 +36,7 @@ public class CommandRemote implements CommandInterface {
private SessionRemote session; private SessionRemote session;
private int id; private int id;
private boolean isQuery; private boolean isQuery;
private int cmdType = UNKNOWN;
private boolean readonly; private boolean readonly;
private final int created; private final int created;
...@@ -55,10 +59,13 @@ public class CommandRemote implements CommandInterface { ...@@ -55,10 +59,13 @@ public class CommandRemote implements CommandInterface {
for (int i = 0, count = 0; i < transferList.size(); i++) { for (int i = 0, count = 0; i < transferList.size(); i++) {
try { try {
Transfer transfer = transferList.get(i); Transfer transfer = transferList.get(i);
boolean v16 = s.getClientVersion() >= Constants.TCP_PROTOCOL_VERSION_16;
if (createParams) { if (createParams) {
s.traceOperation("SESSION_PREPARE_READ_PARAMS", id); s.traceOperation(v16 ? "SESSION_PREPARE_READ_PARAMS2" : "SESSION_PREPARE_READ_PARAMS", id);
transfer. transfer.
writeInt(SessionRemote.SESSION_PREPARE_READ_PARAMS). writeInt(v16 ? SessionRemote.SESSION_PREPARE_READ_PARAMS2 : SessionRemote.SESSION_PREPARE_READ_PARAMS).
writeInt(id).writeString(sql); writeInt(id).writeString(sql);
} else { } else {
s.traceOperation("SESSION_PREPARE", id); s.traceOperation("SESSION_PREPARE", id);
...@@ -68,6 +75,9 @@ public class CommandRemote implements CommandInterface { ...@@ -68,6 +75,9 @@ public class CommandRemote implements CommandInterface {
s.done(transfer); s.done(transfer);
isQuery = transfer.readBoolean(); isQuery = transfer.readBoolean();
readonly = transfer.readBoolean(); readonly = transfer.readBoolean();
cmdType = v16 && createParams ? transfer.readInt() : UNKNOWN;
int paramCount = transfer.readInt(); int paramCount = transfer.readInt();
if (createParams) { if (createParams) {
parameters.clear(); parameters.clear();
...@@ -203,8 +213,10 @@ public class CommandRemote implements CommandInterface { ...@@ -203,8 +213,10 @@ public class CommandRemote implements CommandInterface {
} }
private void checkParameters() { private void checkParameters() {
for (ParameterInterface p : parameters) { if (cmdType != EXPLAIN) {
p.checkSet(); for (ParameterInterface p : parameters) {
p.checkSet();
}
} }
} }
...@@ -212,7 +224,12 @@ public class CommandRemote implements CommandInterface { ...@@ -212,7 +224,12 @@ public class CommandRemote implements CommandInterface {
int len = parameters.size(); int len = parameters.size();
transfer.writeInt(len); transfer.writeInt(len);
for (ParameterInterface p : parameters) { 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 { ...@@ -260,7 +277,7 @@ public class CommandRemote implements CommandInterface {
@Override @Override
public int getCommandType() { public int getCommandType() {
return UNKNOWN; return cmdType;
} }
} }
...@@ -58,6 +58,13 @@ public class Explain extends Prepared { ...@@ -58,6 +58,13 @@ public class Explain extends Prepared {
return query(-1); return query(-1);
} }
@Override
protected void checkParameters() {
// Check params only in case of EXPLAIN ANALYZE
if (executeCommand)
super.checkParameters();
}
@Override @Override
public ResultInterface query(int maxrows) { public ResultInterface query(int maxrows) {
Column column = new Column("PLAN", Value.STRING); Column column = new Column("PLAN", Value.STRING);
...@@ -146,6 +153,6 @@ public class Explain extends Prepared { ...@@ -146,6 +153,6 @@ public class Explain extends Prepared {
@Override @Override
public int getType() { public int getType() {
return CommandInterface.EXPLAIN; return executeCommand ? CommandInterface.EXPLAIN_ANALYZE : CommandInterface.EXPLAIN;
} }
} }
...@@ -96,6 +96,11 @@ public class Constants { ...@@ -96,6 +96,11 @@ public class Constants {
*/ */
public static final int TCP_PROTOCOL_VERSION_15 = 15; 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. * The major version of this database.
*/ */
......
...@@ -59,6 +59,7 @@ public class SessionRemote extends SessionWithState implements DataHandler { ...@@ -59,6 +59,7 @@ public class SessionRemote extends SessionWithState implements DataHandler {
public static final int SESSION_SET_AUTOCOMMIT = 15; public static final int SESSION_SET_AUTOCOMMIT = 15;
public static final int SESSION_HAS_PENDING_TRANSACTION = 16; public static final int SESSION_HAS_PENDING_TRANSACTION = 16;
public static final int LOB_READ = 17; 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_ERROR = 0;
public static final int STATUS_OK = 1; public static final int STATUS_OK = 1;
...@@ -118,7 +119,7 @@ public class SessionRemote extends SessionWithState implements DataHandler { ...@@ -118,7 +119,7 @@ public class SessionRemote extends SessionWithState implements DataHandler {
trans.setSSL(ci.isSSL()); trans.setSSL(ci.isSSL());
trans.init(); trans.init();
trans.writeInt(Constants.TCP_PROTOCOL_VERSION_6); 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(db);
trans.writeString(ci.getOriginalURL()); trans.writeString(ci.getOriginalURL());
trans.writeString(ci.getUserName()); trans.writeString(ci.getUserName());
...@@ -217,6 +218,10 @@ public class SessionRemote extends SessionWithState implements DataHandler { ...@@ -217,6 +218,10 @@ public class SessionRemote extends SessionWithState implements DataHandler {
} }
} }
public int getClientVersion() {
return clientVersion;
}
@Override @Override
public boolean getAutoCommit() { public boolean getAutoCommit() {
return autoCommit; return autoCommit;
......
...@@ -16,6 +16,7 @@ import java.sql.SQLException; ...@@ -16,6 +16,7 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Command; import org.h2.command.Command;
import org.h2.command.CommandInterface;
import org.h2.engine.ConnectionInfo; import org.h2.engine.ConnectionInfo;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Engine; import org.h2.engine.Engine;
...@@ -85,13 +86,13 @@ public class TcpServerThread implements Runnable { ...@@ -85,13 +86,13 @@ public class TcpServerThread implements Runnable {
if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) { if (minClientVersion < Constants.TCP_PROTOCOL_VERSION_6) {
throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2,
"" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_6); "" + 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, throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2,
"" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_15); "" + clientVersion, "" + Constants.TCP_PROTOCOL_VERSION_16);
} }
int maxClientVersion = transfer.readInt(); int maxClientVersion = transfer.readInt();
if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_15) { if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_16) {
clientVersion = Constants.TCP_PROTOCOL_VERSION_15; clientVersion = Constants.TCP_PROTOCOL_VERSION_16;
} else { } else {
clientVersion = minClientVersion; clientVersion = minClientVersion;
} }
...@@ -256,6 +257,7 @@ public class TcpServerThread implements Runnable { ...@@ -256,6 +257,7 @@ public class TcpServerThread implements Runnable {
int operation = transfer.readInt(); int operation = transfer.readInt();
switch (operation) { switch (operation) {
case SessionRemote.SESSION_PREPARE_READ_PARAMS: case SessionRemote.SESSION_PREPARE_READ_PARAMS:
case SessionRemote.SESSION_PREPARE_READ_PARAMS2:
case SessionRemote.SESSION_PREPARE: { case SessionRemote.SESSION_PREPARE: {
int id = transfer.readInt(); int id = transfer.readInt();
String sql = transfer.readString(); String sql = transfer.readString();
...@@ -264,10 +266,19 @@ public class TcpServerThread implements Runnable { ...@@ -264,10 +266,19 @@ public class TcpServerThread implements Runnable {
boolean readonly = command.isReadOnly(); boolean readonly = command.isReadOnly();
cache.addObject(id, command); cache.addObject(id, command);
boolean isQuery = command.isQuery(); boolean isQuery = command.isQuery();
ArrayList<? extends ParameterInterface> params = command.getParameters();
transfer.writeInt(getState(old)).writeBoolean(isQuery). transfer.writeInt(getState(old)).writeBoolean(isQuery).
writeBoolean(readonly).writeInt(params.size()); writeBoolean(readonly);
if (operation == SessionRemote.SESSION_PREPARE_READ_PARAMS) {
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) { for (ParameterInterface p : params) {
ParameterRemote.writeMetaData(transfer, p); ParameterRemote.writeMetaData(transfer, p);
} }
......
...@@ -238,7 +238,7 @@ public class FileLock implements Runnable { ...@@ -238,7 +238,7 @@ public class FileLock implements Runnable {
transfer.setSocket(socket); transfer.setSocket(socket);
transfer.init(); transfer.init();
transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_6); 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(null); transfer.writeString(null);
transfer.writeString(id); transfer.writeString(id);
......
...@@ -75,6 +75,8 @@ public class TestCases extends TestBase { ...@@ -75,6 +75,8 @@ public class TestCases extends TestBase {
testDeleteGroup(); testDeleteGroup();
testDisconnect(); testDisconnect();
testExecuteTrace(); testExecuteTrace();
testExplain();
testExplainAnalyze();
if (config.memory) { if (config.memory) {
return; return;
} }
...@@ -897,6 +899,225 @@ public class TestCases extends TestBase { ...@@ -897,6 +899,225 @@ public class TestCases extends TestBase {
conn.close(); 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 { private void testAlterTableReconnect() throws SQLException {
deleteDb("cases"); deleteDb("cases");
Connection conn = getConnection("cases"); Connection conn = getConnection("cases");
...@@ -1530,4 +1751,4 @@ public class TestCases extends TestBase { ...@@ -1530,4 +1751,4 @@ public class TestCases extends TestBase {
conn.close(); conn.close();
} }
} }
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论