提交 0c4ecec6 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #626 from stumc/Issue#589

Issue#589 Support MERGE INTO ... USING syntax
......@@ -21,7 +21,7 @@ OFFSET specified how many rows to skip.
Please note using high offset values should be avoided because it can cause performance problems.
SAMPLE_SIZE limits the number of rows read for aggregate queries.
Multiple set operators (UNION, INTERSECT, MINUS, EXPECT) are evaluated
Multiple set operators (UNION, INTERSECT, MINUS, EXCEPT) are evaluated
from left to right. For compatibility with other databases and future versions
of H2 please use parentheses.
......@@ -122,6 +122,37 @@ key is set to 0; otherwise it is set to the new key.
MERGE INTO TEST KEY(ID) VALUES(2, 'World')
"
"Commands (DML)","MERGE USING","
MERGE INTO targetTableName [ [AS] targetAlias]
USING { ( select ) | sourceTableName }[ [AS] sourceAlias ]
ON ( expression )
[ WHEN MATCHED THEN [ update ] [ delete] ]
[ WHEN NOT MATCHED THEN insert ]
","
Updates or deletes existing rows, and insert rows that don't exist. The ON clause
specifies the matching column expression and must be specified. If more than one row
is updated per input row, an exception is thrown.
If the source data contains duplicate rows (specifically those columns used in the
row matching ON clause), then an exception is thrown to prevent two updates applying
to the same target row. The embedded update, delete or insert statements can not re-specify
the target table name.
","
MERGE INTO TARGET_TABLE AS T USING SOURCE_TABLE AS S
ON (T.ID = S.ID)
WHEN MATCHED THEN
UPDATE SET T.COL1 = S.COL1 WHERE T.COL2<>'FINAL'
DELETE WHERE T.COL2='FINAL'
WHEN NOT MATCHED THEN
INSERT (ID,COL1,COL2) VALUES(S.ID,S.COL1,S.COL2)
MERGE INTO TARGET_TABLE AS T USING (SELECT * FROM SOURCE_TABLE) AS S
ON (T.ID = S.ID)
WHEN MATCHED THEN
UPDATE SET T.COL1 = S.COL1 WHERE T.COL2<>'FINAL'
DELETE WHERE T.COL2='FINAL'
WHEN NOT MATCHED THEN
INSERT (ID,COL1,COL2) VALUES(S.ID,S.COL1,S.COL2)
"
"Commands (DML)","RUNSCRIPT","
RUNSCRIPT FROM fileNameString scriptCompressionEncryption
[ CHARSET charsetString ]
......
......@@ -454,4 +454,8 @@ public abstract class Prepared {
public void setCteCleanups(List<TableView> cteCleanups) {
this.cteCleanups = cteCleanups;
}
public Session getSession() {
return session;
}
}
......@@ -30,30 +30,38 @@ import org.h2.value.ValueNull;
public class Delete extends Prepared {
private Expression condition;
private TableFilter tableFilter;
private TableFilter targetTableFilter;
/**
* The limit expression as specified in the LIMIT or TOP clause.
*/
private Expression limitExpr;
/**
* This table filter is for MERGE..USING support - not used in stand-alone DML
*/
private TableFilter sourceTableFilter;
public Delete(Session session) {
super(session);
}
public void setTableFilter(TableFilter tableFilter) {
this.tableFilter = tableFilter;
this.targetTableFilter = tableFilter;
}
public void setCondition(Expression condition) {
this.condition = condition;
}
public Expression getCondition( ) {
return this.condition;
}
@Override
public int update() {
tableFilter.startQuery(session);
tableFilter.reset();
Table table = tableFilter.getTable();
targetTableFilter.startQuery(session);
targetTableFilter.reset();
Table table = targetTableFilter.getTable();
session.getUser().checkRight(table, Right.DELETE);
table.fire(session, Trigger.DELETE, true);
table.lock(session, true, false);
......@@ -68,11 +76,11 @@ public class Delete extends Prepared {
try {
setCurrentRowNumber(0);
int count = 0;
while (limitRows != 0 && tableFilter.next()) {
while (limitRows != 0 && targetTableFilter.next()) {
setCurrentRowNumber(rows.size() + 1);
if (condition == null || Boolean.TRUE.equals(
condition.getBooleanValue(session))) {
Row row = tableFilter.get();
Row row = targetTableFilter.get();
boolean done = false;
if (table.fireRow()) {
done = table.fireBeforeRow(session, row, null);
......@@ -112,7 +120,7 @@ public class Delete extends Prepared {
public String getPlanSQL() {
StringBuilder buff = new StringBuilder();
buff.append("DELETE ");
buff.append("FROM ").append(tableFilter.getPlanSQL(false));
buff.append("FROM ").append(targetTableFilter.getPlanSQL(false));
if (condition != null) {
buff.append("\nWHERE ").append(StringUtils.unEnclose(
condition.getSQL()));
......@@ -127,15 +135,24 @@ public class Delete extends Prepared {
@Override
public void prepare() {
if (condition != null) {
condition.mapColumns(tableFilter, 0);
condition.mapColumns(targetTableFilter, 0);
if(sourceTableFilter!=null){
condition.mapColumns(sourceTableFilter, 0);
}
condition = condition.optimize(session);
condition.createIndexConditions(session, tableFilter);
condition.createIndexConditions(session, targetTableFilter);
}
TableFilter[] filters;
if(sourceTableFilter==null){
filters = new TableFilter[] { targetTableFilter };
}
else{
filters = new TableFilter[] { targetTableFilter, sourceTableFilter };
}
TableFilter[] filters = new TableFilter[] { tableFilter };
PlanItem item = tableFilter.getBestPlanItem(session, filters, 0,
PlanItem item = targetTableFilter.getBestPlanItem(session, filters, 0,
ExpressionVisitor.allColumnsForTableFilters(filters));
tableFilter.setPlanItem(item);
tableFilter.prepare();
targetTableFilter.setPlanItem(item);
targetTableFilter.prepare();
}
@Override
......@@ -162,4 +179,16 @@ public class Delete extends Prepared {
return true;
}
public void setSourceTableFilter(TableFilter sourceTableFilter) {
this.sourceTableFilter = sourceTableFilter;
}
public TableFilter getTableFilter() {
return targetTableFilter;
}
public TableFilter getSourceTableFilter() {
return sourceTableFilter;
}
}
......@@ -28,6 +28,7 @@ import org.h2.result.ResultTarget;
import org.h2.result.Row;
import org.h2.table.Column;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.value.Value;
......@@ -46,6 +47,10 @@ public class Insert extends Prepared implements ResultTarget {
private boolean sortedInsertMode;
private int rowNumber;
private boolean insertFromSelect;
/**
* This table filter is for MERGE..USING support - not used in stand-alone DML
*/
private TableFilter sourceTableFilter;
/**
* For MySQL-style INSERT ... ON DUPLICATE KEY UPDATE ....
......@@ -267,6 +272,9 @@ public class Insert extends Prepared implements ResultTarget {
for (int i = 0, len = expr.length; i < len; i++) {
Expression e = expr[i];
if (e != null) {
if(sourceTableFilter!=null){
e.mapColumns(sourceTableFilter, 0);
}
e = e.optimize(session);
if (e instanceof Parameter) {
Parameter p = (Parameter) e;
......@@ -395,4 +403,8 @@ public class Insert extends Prepared implements ResultTarget {
return condition;
}
public void setSourceTableFilter(TableFilter sourceTableFilter) {
this.sourceTableFilter = sourceTableFilter;
}
}
......@@ -6,7 +6,6 @@
package org.h2.command.dml;
import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.command.Command;
......@@ -23,6 +22,7 @@ import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.table.Column;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.value.Value;
......@@ -33,10 +33,11 @@ import org.h2.value.Value;
*/
public class Merge extends Prepared {
private Table table;
private Table targetTable;
private TableFilter targetTableFilter;
private Column[] columns;
private Column[] keys;
private final ArrayList<Expression[]> list = New.arrayList();
private final ArrayList<Expression[]> valuesExpressionList = New.arrayList();
private Query query;
private Prepared update;
......@@ -52,8 +53,8 @@ public class Merge extends Prepared {
}
}
public void setTable(Table table) {
this.table = table;
public void setTargetTable(Table targetTable) {
this.targetTable = targetTable;
}
public void setColumns(Column[] columns) {
......@@ -67,28 +68,29 @@ public class Merge extends Prepared {
public void setQuery(Query query) {
this.query = query;
}
/**
* Add a row to this merge statement.
*
* @param expr the list of values
*/
public void addRow(Expression[] expr) {
list.add(expr);
valuesExpressionList.add(expr);
}
@Override
public int update() {
int count;
session.getUser().checkRight(table, Right.INSERT);
session.getUser().checkRight(table, Right.UPDATE);
session.getUser().checkRight(targetTable, Right.INSERT);
session.getUser().checkRight(targetTable, Right.UPDATE);
setCurrentRowNumber(0);
if (list.size() > 0) {
if (valuesExpressionList.size() > 0) {
// process values in list
count = 0;
for (int x = 0, size = list.size(); x < size; x++) {
for (int x = 0, size = valuesExpressionList.size(); x < size; x++) {
setCurrentRowNumber(x + 1);
Expression[] expr = list.get(x);
Row newRow = table.getTemplateRow();
Expression[] expr = valuesExpressionList.get(x);
Row newRow = targetTable.getTemplateRow();
for (int i = 0, len = columns.length; i < len; i++) {
Column c = columns[i];
int index = c.getColumnId();
......@@ -107,14 +109,15 @@ public class Merge extends Prepared {
count++;
}
} else {
// process select data for list
ResultInterface rows = query.query(0);
count = 0;
table.fire(session, Trigger.UPDATE | Trigger.INSERT, true);
table.lock(session, true, false);
targetTable.fire(session, Trigger.UPDATE | Trigger.INSERT, true);
targetTable.lock(session, true, false);
while (rows.next()) {
count++;
Value[] r = rows.currentRow();
Row newRow = table.getTemplateRow();
Row newRow = targetTable.getTemplateRow();
setCurrentRowNumber(count);
for (int j = 0; j < columns.length; j++) {
Column c = columns[j];
......@@ -129,12 +132,12 @@ public class Merge extends Prepared {
merge(newRow);
}
rows.close();
table.fire(session, Trigger.UPDATE | Trigger.INSERT, false);
targetTable.fire(session, Trigger.UPDATE | Trigger.INSERT, false);
}
return count;
}
private void merge(Row row) {
protected void merge(Row row) {
ArrayList<Parameter> k = update.getParameters();
for (int i = 0; i < columns.length; i++) {
Column col = columns[i];
......@@ -151,16 +154,20 @@ public class Merge extends Prepared {
Parameter p = k.get(columns.length + i);
p.setValue(v);
}
// try and update
int count = update.update();
// if update fails try an insert
if (count == 0) {
try {
table.validateConvertUpdateSequence(session, row);
boolean done = table.fireBeforeRow(session, null, row);
targetTable.validateConvertUpdateSequence(session, row);
boolean done = targetTable.fireBeforeRow(session, null, row);
if (!done) {
table.lock(session, true, false);
table.addRow(session, row);
session.log(table, UndoLogRecord.INSERT, row);
table.fireAfterRow(session, null, row, false);
targetTable.lock(session, true, false);
targetTable.addRow(session, row);
session.log(targetTable, UndoLogRecord.INSERT, row);
targetTable.fireAfterRow(session, null, row, false);
}
} catch (DbException e) {
if (e.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) {
......@@ -179,21 +186,21 @@ public class Merge extends Prepared {
}
}
if (indexMatchesKeys) {
throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName());
throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, targetTable.getName());
}
}
}
throw e;
}
} else if (count != 1) {
throw DbException.get(ErrorCode.DUPLICATE_KEY_1, table.getSQL());
throw DbException.get(ErrorCode.DUPLICATE_KEY_1, targetTable.getSQL());
}
}
@Override
public String getPlanSQL() {
StatementBuilder buff = new StatementBuilder("MERGE INTO ");
buff.append(table.getSQL()).append('(');
buff.append(targetTable.getSQL()).append('(');
for (Column c : columns) {
buff.appendExceptFirst(", ");
buff.append(c.getSQL());
......@@ -209,10 +216,10 @@ public class Merge extends Prepared {
buff.append(')');
}
buff.append('\n');
if (list.size() > 0) {
if (valuesExpressionList.size() > 0) {
buff.append("VALUES ");
int row = 0;
for (Expression[] expr : list) {
for (Expression[] expr : valuesExpressionList) {
if (row++ > 0) {
buff.append(", ");
}
......@@ -237,15 +244,15 @@ public class Merge extends Prepared {
@Override
public void prepare() {
if (columns == null) {
if (list.size() > 0 && list.get(0).length == 0) {
if (valuesExpressionList.size() > 0 && valuesExpressionList.get(0).length == 0) {
// special case where table is used as a sequence
columns = new Column[0];
} else {
columns = table.getColumns();
columns = targetTable.getColumns();
}
}
if (list.size() > 0) {
for (Expression[] expr : list) {
if (valuesExpressionList.size() > 0) {
for (Expression[] expr : valuesExpressionList) {
if (expr.length != columns.length) {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
}
......@@ -263,14 +270,14 @@ public class Merge extends Prepared {
}
}
if (keys == null) {
Index idx = table.getPrimaryKey();
Index idx = targetTable.getPrimaryKey();
if (idx == null) {
throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, "PRIMARY KEY");
}
keys = idx.getColumns();
}
StatementBuilder buff = new StatementBuilder("UPDATE ");
buff.append(table.getSQL()).append(" SET ");
buff.append(targetTable.getSQL()).append(" SET ");
for (Column c : columns) {
buff.appendExceptFirst(", ");
buff.append(c.getSQL()).append("=?");
......@@ -305,4 +312,19 @@ public class Merge extends Prepared {
return true;
}
public Table getTargetTable() {
return targetTable;
}
public TableFilter getTargetTableFilter() {
return targetTableFilter;
}
public void setTargetTableFilter(TableFilter targetTableFilter) {
this.targetTableFilter = targetTableFilter;
setTargetTable(targetTableFilter.getTable());
}
}
差异被折叠。
......@@ -168,6 +168,10 @@ public class Select extends Query {
}
}
public Expression getCondition() {
return condition;
}
private LazyResult queryGroupSorted(int columnCount, ResultTarget result) {
LazyResultGroupSorted lazyResult = new LazyResultGroupSorted(expressionArray, columnCount);
if (result == null) {
......
......@@ -38,7 +38,11 @@ import org.h2.value.ValueNull;
public class Update extends Prepared {
private Expression condition;
private TableFilter tableFilter;
private TableFilter targetTableFilter;// target of update
/**
* This table filter is for MERGE..USING support - not used in stand-alone DML
*/
private TableFilter sourceTableFilter;
/** The limit expression as specified in the LIMIT clause. */
private Expression limitExpr;
......@@ -51,12 +55,16 @@ public class Update extends Prepared {
}
public void setTableFilter(TableFilter tableFilter) {
this.tableFilter = tableFilter;
this.targetTableFilter = tableFilter;
}
public void setCondition(Expression condition) {
this.condition = condition;
}
public Expression getCondition( ) {
return this.condition;
}
/**
* Add an assignment of the form column = expression.
......@@ -79,11 +87,11 @@ public class Update extends Prepared {
@Override
public int update() {
tableFilter.startQuery(session);
tableFilter.reset();
targetTableFilter.startQuery(session);
targetTableFilter.reset();
RowList rows = new RowList(session);
try {
Table table = tableFilter.getTable();
Table table = targetTableFilter.getTable();
session.getUser().checkRight(table, Right.UPDATE);
table.fire(session, Trigger.UPDATE, true);
table.lock(session, true, false);
......@@ -99,14 +107,14 @@ public class Update extends Prepared {
limitRows = v.getInt();
}
}
while (tableFilter.next()) {
while (targetTableFilter.next()) {
setCurrentRowNumber(count+1);
if (limitRows >= 0 && count >= limitRows) {
break;
}
if (condition == null ||
Boolean.TRUE.equals(condition.getBooleanValue(session))) {
Row oldRow = tableFilter.get();
Row oldRow = targetTableFilter.get();
Row newRow = table.getTemplateRow();
for (int i = 0; i < columnCount; i++) {
Expression newExpr = expressionMap.get(columns[i]);
......@@ -161,7 +169,7 @@ public class Update extends Prepared {
@Override
public String getPlanSQL() {
StatementBuilder buff = new StatementBuilder("UPDATE ");
buff.append(tableFilter.getPlanSQL(false)).append("\nSET\n ");
buff.append(targetTableFilter.getPlanSQL(false)).append("\nSET\n ");
for (int i = 0, size = columns.size(); i < size; i++) {
Column c = columns.get(i);
Expression e = expressionMap.get(c);
......@@ -181,21 +189,30 @@ public class Update extends Prepared {
@Override
public void prepare() {
if (condition != null) {
condition.mapColumns(tableFilter, 0);
condition.mapColumns(targetTableFilter, 0);
condition = condition.optimize(session);
condition.createIndexConditions(session, tableFilter);
condition.createIndexConditions(session, targetTableFilter);
}
for (int i = 0, size = columns.size(); i < size; i++) {
Column c = columns.get(i);
Expression e = expressionMap.get(c);
e.mapColumns(tableFilter, 0);
e.mapColumns(targetTableFilter, 0);
if (sourceTableFilter!=null){
e.mapColumns(sourceTableFilter, 0);
}
expressionMap.put(c, e.optimize(session));
}
TableFilter[] filters = new TableFilter[] { tableFilter };
PlanItem item = tableFilter.getBestPlanItem(session, filters, 0,
TableFilter[] filters;
if(sourceTableFilter==null){
filters = new TableFilter[] { targetTableFilter };
}
else{
filters = new TableFilter[] { targetTableFilter, sourceTableFilter };
}
PlanItem item = targetTableFilter.getBestPlanItem(session, filters, 0,
ExpressionVisitor.allColumnsForTableFilters(filters));
tableFilter.setPlanItem(item);
tableFilter.prepare();
targetTableFilter.setPlanItem(item);
targetTableFilter.prepare();
}
@Override
......@@ -222,4 +239,11 @@ public class Update extends Prepared {
return true;
}
public TableFilter getSourceTableFilter() {
return sourceTableFilter;
}
public void setSourceTableFilter(TableFilter sourceTableFilter) {
this.sourceTableFilter = sourceTableFilter;
}
}
......@@ -361,7 +361,7 @@ public class Session extends SessionWithState {
}
if (localTempTables.get(table.getName()) != null) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
table.getSQL());
table.getSQL()+" AS "+table.getName());
}
modificationId++;
localTempTables.put(table.getName(), table);
......
......@@ -103,7 +103,8 @@ public class JdbcConnection extends TraceObject implements Connection,
/**
* INTERNAL
*/
public JdbcConnection(ConnectionInfo ci, boolean useBaseDir)
@SuppressWarnings("resource")// the session closable object does not leak as Eclipse warns - due to the CloseWatcher
public JdbcConnection(ConnectionInfo ci, boolean useBaseDir)
throws SQLException {
try {
if (useBaseDir) {
......
......@@ -567,8 +567,6 @@ public class TableFilter implements ColumnResolver {
private void checkTimeout() {
session.checkCanceled();
// System.out.println(this.alias+ " " + table.getName() + ": " +
// scanCount);
}
/**
......
......@@ -45,6 +45,7 @@ import org.h2.test.db.TestLinkedTable;
import org.h2.test.db.TestListener;
import org.h2.test.db.TestLob;
import org.h2.test.db.TestMemoryUsage;
import org.h2.test.db.TestMergeUsing;
import org.h2.test.db.TestMultiConn;
import org.h2.test.db.TestMultiDimension;
import org.h2.test.db.TestMultiThread;
......@@ -749,6 +750,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestLinkedTable());
addTest(new TestListener());
addTest(new TestLob());
addTest(new TestMergeUsing());
addTest(new TestMultiConn());
addTest(new TestMultiDimension());
addTest(new TestMultiThreadedKernel());
......
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论