提交 563d2a5f authored 作者: Owner's avatar Owner

Finished set of test cases

上级 ab444a3f
...@@ -7,10 +7,13 @@ package org.h2.command.dml; ...@@ -7,10 +7,13 @@ package org.h2.command.dml;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.command.Prepared; import org.h2.command.Prepared;
import org.h2.engine.DbObject;
import org.h2.engine.Right; import org.h2.engine.Right;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.engine.UndoLogRecord; import org.h2.engine.UndoLogRecord;
...@@ -44,6 +47,8 @@ public class MergeUsing extends Merge { ...@@ -44,6 +47,8 @@ public class MergeUsing extends Merge {
private String queryAlias; private String queryAlias;
private TableView temporarySourceTableView; private TableView temporarySourceTableView;
private int countUpdatedRows=0; private int countUpdatedRows=0;
private Column[] sourceKeys;
private HashMap<List<Value>,Integer> sourceKeysRemembered = new HashMap<List<Value>,Integer>();
public MergeUsing(Merge merge) { public MergeUsing(Merge merge) {
super(merge.getSession()); super(merge.getSession());
...@@ -57,6 +62,8 @@ public class MergeUsing extends Merge { ...@@ -57,6 +62,8 @@ public class MergeUsing extends Merge {
@Override @Override
public int update() { public int update() {
sourceKeysRemembered.clear();
System.out.println("update using:"+temporarySourceTableView); System.out.println("update using:"+temporarySourceTableView);
if(targetTableFilter!=null){ if(targetTableFilter!=null){
...@@ -84,10 +91,31 @@ public class MergeUsing extends Merge { ...@@ -84,10 +91,31 @@ public class MergeUsing extends Merge {
Row sourceRow = new RowImpl(sourceRowValues,0); Row sourceRow = new RowImpl(sourceRowValues,0);
System.out.println(("currentRowValues="+Arrays.toString(sourceRowValues))); System.out.println(("currentRowValues="+Arrays.toString(sourceRowValues)));
Row newTargetRow = targetTable.getTemplateRow(); Row newTargetRow = targetTable.getTemplateRow();
ArrayList<Value> sourceKeyValuesList = new ArrayList<Value>();
setCurrentRowNumber(countInputRows); setCurrentRowNumber(countInputRows);
System.out.println("columns="+Arrays.toString(columns)); System.out.println("columns="+Arrays.toString(columns));
// computer the new target row columns values // isolate the source row key columns values
for (int j = 0; j < sourceKeys.length; j++) {
Column c = sourceKeys[j];
try {
Value v = c.convert(sourceRowValues[j]);
sourceKeyValuesList.add(v);
} catch (DbException ex) {
throw setRow(ex, countInputRows, getSQL(sourceRowValues));
}
}
if(sourceKeysRemembered.containsKey(sourceKeyValuesList)){
throw DbException.get(ErrorCode.DUPLICATE_KEY_1, "Merge using ON column expression, duplicates values found:keys"
+Arrays.asList(sourceKeys).toString()+":values:"+sourceKeyValuesList.toString()
+":from:"+sourceTableFilter.getTable()+":alias:"+sourceTableFilter.getTableAlias()+":current row number:"+countInputRows
+":conflicting row number:"+sourceKeysRemembered.get(sourceKeyValuesList));
}else{
sourceKeysRemembered.put(sourceKeyValuesList,countInputRows);
}
// compute the new target row columns values
for (int j = 0; j < columns.length; j++) { for (int j = 0; j < columns.length; j++) {
Column c = columns[j]; Column c = columns[j];
int index = c.getColumnId(); int index = c.getColumnId();
...@@ -324,7 +352,7 @@ public class MergeUsing extends Merge { ...@@ -324,7 +352,7 @@ public class MergeUsing extends Merge {
System.out.println("prepare:sourceTableFilterAlias="+sourceTableFilter.getTableAlias()); System.out.println("prepare:sourceTableFilterAlias="+sourceTableFilter.getTableAlias());
System.out.println("prepare:onConditions="+onCondition); System.out.println("prepare:onConditions="+onCondition);
TableFilter[] filters = new TableFilter[] { sourceTableFilter, targetTableFilter }; //TableFilter[] filters = new TableFilter[] { sourceTableFilter, targetTableFilter };
System.out.println("onCondition="+onCondition+":op="+onCondition.getClass().getSimpleName()); System.out.println("onCondition="+onCondition+":op="+onCondition.getClass().getSimpleName());
...@@ -332,6 +360,18 @@ public class MergeUsing extends Merge { ...@@ -332,6 +360,18 @@ public class MergeUsing extends Merge {
onCondition.addFilterConditions(targetTableFilter, true); onCondition.addFilterConditions(targetTableFilter, true);
onCondition.mapColumns(sourceTableFilter, 2); onCondition.mapColumns(sourceTableFilter, 2);
onCondition.mapColumns(targetTableFilter, 1); onCondition.mapColumns(targetTableFilter, 1);
if (keys == null) {
HashSet<Column> targetColumns = buildColumnListFromOnCondition(targetTableFilter);
keys = targetColumns.toArray(new Column[1]);
}
if (sourceKeys == null) {
HashSet<Column> sourceColumns = buildColumnListFromOnCondition(sourceTableFilter);
sourceKeys = sourceColumns.toArray(new Column[1]);
}
// only do the optimise now - before we have already gathered the unoptimized column data
onCondition = onCondition.optimize(session); onCondition = onCondition.optimize(session);
onCondition.createIndexConditions(session, sourceTableFilter); onCondition.createIndexConditions(session, sourceTableFilter);
//optional //optional
...@@ -370,21 +410,7 @@ public class MergeUsing extends Merge { ...@@ -370,21 +410,7 @@ public class MergeUsing extends Merge {
// } // }
// keys = idx.getColumns(); // keys = idx.getColumns();
// } // }
if (keys == null) {
HashSet<Column> targetColumns = new HashSet<Column>();
HashSet<Column> columns = new HashSet<Column>();
ExpressionVisitor visitor = ExpressionVisitor.getColumnsVisitor(columns);
onCondition.isEverything(visitor);
for(Column c: columns){
if(c.getTable()==targetTable){
targetColumns.add(c);
}
}
if (targetColumns.isEmpty()) {
throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, "ON (condition) target columns missing");
}
keys = targetColumns.toArray(new Column[1]);
}
String sql = buildPreparedSQL(); String sql = buildPreparedSQL();
update = session.prepare(sql); update = session.prepare(sql);
...@@ -407,6 +433,24 @@ public class MergeUsing extends Merge { ...@@ -407,6 +433,24 @@ public class MergeUsing extends Merge {
} }
private HashSet<Column> buildColumnListFromOnCondition(TableFilter targetTableFilter) {
HashSet<Column> targetColumns = new HashSet<Column>();
HashSet<Column> columns = new HashSet<Column>();
ExpressionVisitor visitor = ExpressionVisitor.getColumnsVisitor(columns);
onCondition.isEverything(visitor);
for(Column c: columns){
if(c.getTable()==targetTableFilter.getTable()){
targetColumns.add(c);
}
}
System.out.println("columnsVisitedForTable"+targetTableFilter.getTable()+"="+targetColumns);
if (targetColumns.isEmpty()) {
throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, "ON (condition) target columns missing");
}
return targetColumns;
}
private Expression appendOnCondition(Update updateCommand) { private Expression appendOnCondition(Update updateCommand) {
if (updateCommand.getCondition()==null){ if (updateCommand.getCondition()==null){
return onCondition; return onCondition;
......
...@@ -78,8 +78,64 @@ public class TestMergeUsing extends TestBase { ...@@ -78,8 +78,64 @@ public class TestMergeUsing extends TestBase {
"SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)", "SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)",
3 3
); );
// No updates happen: No insert defined, no update or delete happens due to ON condition failing always, target table missing PK
testMergeUsing(
"CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );",
"MERGE INTO PARENT AS P USING (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) ) AS S ON (P.ID = S.ID AND 1=0) WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE P.ID = 2 DELETE WHERE P.ID = 1",
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2)",
0
);
// One insert, one update one delete happens, target table missing PK
testMergeUsing(
"CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );"+
"CREATE TABLE SOURCE AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) );",
"MERGE INTO PARENT AS P USING SOURCE AS S ON (P.ID = S.ID) WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE P.ID = 2 DELETE WHERE P.ID = 1 WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (S.ID, S.NAME)",
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)",
3
);
// One insert, one update one delete happens, target table missing PK, no source alias
testMergeUsing(
"CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );"+
"CREATE TABLE SOURCE AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) );",
"MERGE INTO PARENT AS P USING SOURCE ON (P.ID = SOURCE.ID) WHEN MATCHED THEN UPDATE SET P.NAME = SOURCE.NAME||SOURCE.ID WHERE P.ID = 2 DELETE WHERE P.ID = 1 WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (SOURCE.ID, SOURCE.NAME)",
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)",
3
);
// One insert, one update one delete happens, target table missing PK, no source or target alias
testMergeUsing(
"CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );"+
"CREATE TABLE SOURCE AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) );",
"MERGE INTO PARENT USING SOURCE ON (PARENT.ID = SOURCE.ID) WHEN MATCHED THEN UPDATE SET PARENT.NAME = SOURCE.NAME||SOURCE.ID WHERE PARENT.ID = 2 DELETE WHERE PARENT.ID = 1 WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (SOURCE.ID, SOURCE.NAME)",
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)",
3
);
// Duplicate source keys: SQL standard says duplicate or repeated updates in same statement should cause errors
// One insert, one update one delete happens, target table missing PK, no source or target alias
testMergeUsingException(
"CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,1) );"+
"CREATE TABLE SOURCE AS (SELECT 1 AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );",
"MERGE INTO PARENT USING SOURCE ON (PARENT.ID = SOURCE.ID) WHEN MATCHED THEN UPDATE SET PARENT.NAME = SOURCE.NAME||SOURCE.ID WHERE PARENT.ID = 2 DELETE WHERE PARENT.ID = 1 WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (SOURCE.ID, SOURCE.NAME)",
GATHER_ORDERED_RESULTS_SQL,
"SELECT 1 AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(1,1)",
3,
"Unique index or primary key violation: \"Merge using ON column expression, duplicates values found:keys[ID]:values:[1]:from:PUBLIC.SOURCE:alias:SOURCE:current row number:2:conflicting row number:1"
);
} }
/**
* Run a test case of the merge using syntax
* @param setupSQL - one or more SQL statements to setup the case
* @param statementUnderTest - the merge statement being tested
* @param gatherResultsSQL - a select which gathers the results of the merge from the target table
* @param expectedResultsSQL - a select which returns the expected results in the target table
* @param expectedRowUpdateCount - how many updates should be expected from the merge using
* @throws Exception
*/
private void testMergeUsing(String setupSQL, String statementUnderTest, String gatherResultsSQL, private void testMergeUsing(String setupSQL, String statementUnderTest, String gatherResultsSQL,
String expectedResultsSQL, int expectedRowUpdateCount) throws Exception { String expectedResultsSQL, int expectedRowUpdateCount) throws Exception {
deleteDb("mergeUsingQueries"); deleteDb("mergeUsingQueries");
...@@ -95,6 +151,7 @@ public class TestMergeUsing extends TestBase { ...@@ -95,6 +151,7 @@ public class TestMergeUsing extends TestBase {
prep = conn.prepareStatement(statementUnderTest); prep = conn.prepareStatement(statementUnderTest);
rowCountUpdate = prep.executeUpdate(); rowCountUpdate = prep.executeUpdate();
// compare actual results from SQL resultsset with expected results - by diffing (aka set MINUS operation)
rs = stat.executeQuery("( "+gatherResultsSQL+" ) MINUS ( "+expectedResultsSQL+" )"); rs = stat.executeQuery("( "+gatherResultsSQL+" ) MINUS ( "+expectedResultsSQL+" )");
int rowCount = 0; int rowCount = 0;
...@@ -102,15 +159,37 @@ public class TestMergeUsing extends TestBase { ...@@ -102,15 +159,37 @@ public class TestMergeUsing extends TestBase {
while (rs.next()) { while (rs.next()) {
rowCount++; rowCount++;
diffBuffer.append("|"); diffBuffer.append("|");
for(int ndx = 0; ndx < rs.getMetaData().getColumnCount(); ndx++){ System.out.println("rs.getMetaData().getColumnCount()="+rs.getMetaData().getColumnCount());
for(int ndx = 1; ndx <= rs.getMetaData().getColumnCount(); ndx++){
diffBuffer.append(rs.getObject(ndx)); diffBuffer.append(rs.getObject(ndx));
diffBuffer.append("|\n"); diffBuffer.append("|\n");
} }
} }
assertEquals("Differences is expected output found:"+diffBuffer,0,rowCount); assertEquals("Differences between expected and actual output found:"+diffBuffer,0,rowCount);
assertEquals("Expected update counts differ",expectedRowUpdateCount,rowCountUpdate); assertEquals("Expected update counts differ",expectedRowUpdateCount,rowCountUpdate);
conn.close(); conn.close();
deleteDb("mergeUsingQueries"); deleteDb("mergeUsingQueries");
} }
/**
* Run a test case of the merge using syntax
* @param setupSQL - one or more SQL statements to setup the case
* @param statementUnderTest - the merge statement being tested
* @param gatherResultsSQL - a select which gathers the results of the merge from the target table
* @param expectedResultsSQL - a select which returns the expected results in the target table
* @param expectedRowUpdateCount - how many updates should be expected from the merge using
* @throws Exception
*/
private void testMergeUsingException(String setupSQL, String statementUnderTest, String gatherResultsSQL,
String expectedResultsSQL, int expectedRowUpdateCount, String exceptionMessage) throws Exception {
try{
testMergeUsing( setupSQL, statementUnderTest, gatherResultsSQL,
expectedResultsSQL, expectedRowUpdateCount);
}
catch(RuntimeException|org.h2.jdbc.JdbcSQLException e){
assertContains(e.getMessage(),exceptionMessage);
return;
}
fail("Failed to see exception with message:"+exceptionMessage);
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论