提交 55e2d0c4 authored 作者: noelgrandin@gmail.com's avatar noelgrandin@gmail.com

Improved MS SQL Server and MySQL compatibility: support DELETE with TOP or LIMIT.

上级 fe9b5e70
...@@ -66,9 +66,10 @@ UPDATE PERSON P SET NAME=(SELECT A.NAME FROM ADDRESS A WHERE A.ID=P.ID); ...@@ -66,9 +66,10 @@ UPDATE PERSON P SET NAME=(SELECT A.NAME FROM ADDRESS A WHERE A.ID=P.ID);
" "
"Commands (DML)","DELETE"," "Commands (DML)","DELETE","
DELETE FROM tableName [ WHERE expression ] DELETE [ TOP term ] FROM tableName [ WHERE expression ] [ LIMIT term ]
"," ","
Deletes rows form a table. Deletes rows form a table.
If TOP or LIMIT is specified, at most the specified number of rows are deleted.
"," ","
DELETE FROM TEST WHERE ID=2 DELETE FROM TEST WHERE ID=2
" "
......
...@@ -19,7 +19,8 @@ Change Log ...@@ -19,7 +19,8 @@ Change Log
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul> <ul>
<li>Support for COSH, SINH, and TANH functions. <li>Improved MS SQL Server and MySQL compatibility: support DELETE with TOP or LIMIT.
</li><li>Support for COSH, SINH, and TANH functions.
</li><li>Support for the % operator (modulo) thanks to Noel Grandin. </li><li>Support for the % operator (modulo) thanks to Noel Grandin.
</li><li>Issue 288: Some right outer join queries failed to produce the correct result or </li><li>Issue 288: Some right outer join queries failed to produce the correct result or
threw exceptions such as "column x must be in the group by list". threw exceptions such as "column x must be in the group by list".
......
...@@ -302,7 +302,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>. ...@@ -302,7 +302,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Sequences: CURRVAL should be session specific. Compatibility with PostgreSQL. </li><li>Sequences: CURRVAL should be session specific. Compatibility with PostgreSQL.
</li><li>Auto-server: add option to define the IP address range or list. </li><li>Auto-server: add option to define the IP address range or list.
</li><li>Index creation using deterministic functions. </li><li>Index creation using deterministic functions.
</li><li>Support DELETE with TOP or LIMIT. See also: http://dev.mysql.com/doc/refman/5.1/de/delete.html
</li><li>ANALYZE: for unique indexes that allow null, count the number of null. </li><li>ANALYZE: for unique indexes that allow null, count the number of null.
</li><li>AUTO_SERVER: support changing IP addresses (disable a network while the database is open). </li><li>AUTO_SERVER: support changing IP addresses (disable a network while the database is open).
</li><li>Avoid using java.util.Calendar internally because it's slow, complicated, and buggy. </li><li>Avoid using java.util.Calendar internally because it's slow, complicated, and buggy.
......
...@@ -710,6 +710,10 @@ public class Parser { ...@@ -710,6 +710,10 @@ public class Parser {
private Delete parseDelete() { private Delete parseDelete() {
Delete command = new Delete(session); Delete command = new Delete(session);
Expression limit = null;
if (readIf("TOP")) {
limit = readTerm().optimize(session);
}
currentPrepared = command; currentPrepared = command;
int start = lastParseIndex; int start = lastParseIndex;
readIf("FROM"); readIf("FROM");
...@@ -719,6 +723,10 @@ public class Parser { ...@@ -719,6 +723,10 @@ public class Parser {
Expression condition = readExpression(); Expression condition = readExpression();
command.setCondition(condition); command.setCondition(condition);
} }
if (readIf("LIMIT") && limit == null) {
limit = readTerm().optimize(session);
}
command.setLimit(limit);
setSQL(command, "DELETE", start); setSQL(command, "DELETE", start);
return command; return command;
} }
......
...@@ -30,6 +30,11 @@ public class Delete extends Prepared { ...@@ -30,6 +30,11 @@ public class Delete extends Prepared {
private Expression condition; private Expression condition;
private TableFilter tableFilter; private TableFilter tableFilter;
/**
* The limit expression as specified in the LIMIT or TOP clause.
*/
private Expression limitExpr;
public Delete(Session session) { public Delete(Session session) {
super(session); super(session);
} }
...@@ -50,6 +55,10 @@ public class Delete extends Prepared { ...@@ -50,6 +55,10 @@ public class Delete extends Prepared {
table.fire(session, Trigger.DELETE, true); table.fire(session, Trigger.DELETE, true);
table.lock(session, true, false); table.lock(session, true, false);
RowList rows = new RowList(session); RowList rows = new RowList(session);
int limitRows = -1;
if (limitExpr != null) {
limitRows = limitExpr.getValue(session).getInt();
}
try { try {
setCurrentRowNumber(0); setCurrentRowNumber(0);
while (tableFilter.next()) { while (tableFilter.next()) {
...@@ -66,22 +75,36 @@ public class Delete extends Prepared { ...@@ -66,22 +75,36 @@ public class Delete extends Prepared {
} }
} }
int rowScanCount = 0; int rowScanCount = 0;
int rowsDeleted = 0;
for (rows.reset(); rows.hasNext();) { for (rows.reset(); rows.hasNext();) {
if ((++rowScanCount & 127) == 0) { if ((++rowScanCount & 127) == 0) {
checkCanceled(); checkCanceled();
} }
Row row = rows.next(); Row row = rows.next();
table.removeRow(session, row); table.removeRow(session, row);
rowsDeleted++;
session.log(table, UndoLogRecord.DELETE, row); session.log(table, UndoLogRecord.DELETE, row);
if (limitRows != -1) {
if (rowsDeleted == limitRows) {
break;
}
}
} }
if (table.fireRow()) { if (table.fireRow()) {
int count = 0;
for (rows.reset(); rows.hasNext();) { for (rows.reset(); rows.hasNext();) {
Row row = rows.next(); Row row = rows.next();
table.fireAfterRow(session, row, null, false); table.fireAfterRow(session, row, null, false);
count++;
if (limitRows != -1) {
if (count == limitRows) {
break;
}
}
} }
} }
table.fire(session, Trigger.DELETE, false); table.fire(session, Trigger.DELETE, false);
return rows.size(); return rowsDeleted;
} finally { } finally {
rows.close(); rows.close();
} }
...@@ -89,10 +112,14 @@ public class Delete extends Prepared { ...@@ -89,10 +112,14 @@ public class Delete extends Prepared {
public String getPlanSQL() { public String getPlanSQL() {
StringBuilder buff = new StringBuilder(); StringBuilder buff = new StringBuilder();
buff.append("DELETE FROM ").append(tableFilter.getPlanSQL(false)); buff.append("DELETE ");
buff.append("FROM ").append(tableFilter.getPlanSQL(false));
if (condition != null) { if (condition != null) {
buff.append("\nWHERE ").append(StringUtils.unEnclose(condition.getSQL())); buff.append("\nWHERE ").append(StringUtils.unEnclose(condition.getSQL()));
} }
if (limitExpr != null) {
buff.append("\nLIMIT (").append(StringUtils.unEnclose(limitExpr.getSQL())).append(")");
}
return buff.toString(); return buff.toString();
} }
...@@ -119,4 +146,8 @@ public class Delete extends Prepared { ...@@ -119,4 +146,8 @@ public class Delete extends Prepared {
return CommandInterface.DELETE; return CommandInterface.DELETE;
} }
public void setLimit(Expression limit) {
this.limitExpr = limit;
}
} }
...@@ -36,6 +36,7 @@ public class TestCases extends TestBase { ...@@ -36,6 +36,7 @@ public class TestCases extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
testDeleteTop();
testUnicode(); testUnicode();
testOuterJoin(); testOuterJoin();
testCommentOnColumnWithSchemaEqualDatabase(); testCommentOnColumnWithSchemaEqualDatabase();
...@@ -1175,4 +1176,46 @@ public class TestCases extends TestBase { ...@@ -1175,4 +1176,46 @@ public class TestCases extends TestBase {
} }
} }
private void testDeleteTop() throws SQLException {
deleteDb("cases");
Connection conn = getConnection("cases");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE TEST(id int) AS SELECT x FROM system_range(1, 100)");
stat.execute("DELETE TOP 10 FROM TEST");
ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST");
assertTrue(rs.next());
assertEquals(90, rs.getInt(1));
stat.execute("DELETE FROM TEST LIMIT ((SELECT COUNT(*) FROM TEST) / 10)");
rs = stat.executeQuery("SELECT COUNT(*) FROM TEST");
assertTrue(rs.next());
assertEquals(81, rs.getInt(1));
rs = stat.executeQuery("EXPLAIN DELETE FROM TEST LIMIT ((SELECT COUNT(*) FROM TEST) / 10)");
rs.next();
assertEquals("DELETE FROM PUBLIC.TEST\n" +
" /* PUBLIC.TEST.tableScan */\n" +
"LIMIT ((SELECT\n" +
" COUNT(*)\n" +
"FROM PUBLIC.TEST\n" +
" /* PUBLIC.TEST.tableScan */\n" +
"/* direct lookup */) / 10)",
rs.getString(1));
PreparedStatement prep;
prep = conn.prepareStatement("SELECT * FROM TEST LIMIT ?");
prep.setInt(1, 10);
prep.execute();
prep = conn.prepareStatement("DELETE FROM TEST LIMIT ?");
prep.setInt(1, 10);
prep.execute();
rs = stat.executeQuery("SELECT COUNT(*) FROM TEST");
assertTrue(rs.next());
assertEquals(71, rs.getInt(1));
conn.close();
}
} }
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论