提交 ab444a3f authored 作者: Owner's avatar Owner

Huge headway in testing

上级 bb5c0974
......@@ -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.
......
......@@ -1138,10 +1138,9 @@ public class Parser {
String[] querySQLOutput = new String[]{null};
List<Column> columnTemplateList = createQueryColumnTemplateList(null, command.getQuery(), querySQLOutput);
System.out.println("pre:alias="+command.getQueryAlias()+",sql="+querySQLOutput[0]+",ctlist="+columnTemplateList+",schema="+getSchema()+",schemaName="+schemaName);
TableView temporarySourceTableView = createTemporarySessionView(command.getQueryAlias(), querySQLOutput[0], columnTemplateList, false);
TableView temporarySourceTableView = createTemporarySessionView(command.getQueryAlias(), querySQLOutput[0], columnTemplateList, false, false);
command.setTemporaryTableView(temporarySourceTableView);
//Table tableOrView = command.getQuery().getTables().toArray(new Table[]{null})[0];
System.out.println("sourceTableFilter with tableOrView="+temporarySourceTableView);
System.out.println("sourceTableFilter rightsChecked="+rightsChecked);
TableFilter sourceTableFilter = new TableFilter(session, temporarySourceTableView, command.getQueryAlias(), rightsChecked,
......@@ -5190,7 +5189,7 @@ public class Parser {
} finally {
session.removeLocalTempTable(recursiveTable);
}
TableView view = createTemporarySessionView(tempViewName, querySQLOutput[0], columnTemplateList,true/*allowRecursiveQueryDetection*/);
TableView view = createTemporarySessionView(tempViewName, querySQLOutput[0], columnTemplateList,true/*allowRecursiveQueryDetection*/, true);
return view;
}
......@@ -5201,18 +5200,26 @@ public class Parser {
ArrayList<Expression> withExpressions = withQuery.getExpressions();
System.out.println("withExpressions="+withExpressions);
for (int i = 0; i < withExpressions.size(); ++i) {
Expression withExp = withExpressions.get(i);
System.out.println("withExp.alias="+withExp.getAlias()+",name="+withExp.getColumnName());
String columnName = cols != null ? cols[i]
: (withExp.getAlias()!=null ? withExp.getAlias() : withExp.getColumnName());
Expression columnExp = withExpressions.get(i);
// use the passed in column name if supplied, otherwise use alias (if used) otherwise use column name
// derived from column expression
String columnName;
if (cols != null){
columnName = cols[i];
} else if (columnExp.getAlias()!=null){
columnName = columnExp.getAlias();
}
else{
columnName = columnExp.getColumnName();
}
columnTemplateList.add(new Column(columnName,
withExpressions.get(i).getType()));
columnExp.getType()));
}
return columnTemplateList;
}
private TableView createTemporarySessionView(String tempViewName, String querySQL,
List<Column> columnTemplateList, boolean allowRecursiveQueryDetection) {
List<Column> columnTemplateList, boolean allowRecursiveQueryDetection, boolean addViewToSession) {
Schema schema = getSchema();
int id = database.allocateObjectId();
// No easy way to determine if this is a recursive query up front, so we just compile
......@@ -5229,7 +5236,9 @@ public class Parser {
}
view.setTableExpression(true);
view.setTemporary(true);
if(addViewToSession){
session.addLocalTempTable(view);
}
view.setOnCommitDrop(true);
return view;
}
......
......@@ -7,6 +7,7 @@ package org.h2.command.dml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.command.Prepared;
......@@ -42,6 +43,7 @@ public class MergeUsing extends Merge {
private Insert insertCommand;
private String queryAlias;
private TableView temporarySourceTableView;
private int countUpdatedRows=0;
public MergeUsing(Merge merge) {
super(merge.getSession());
......@@ -67,22 +69,22 @@ public class MergeUsing extends Merge {
sourceTableFilter.reset();
}
int count;
int countInputRows;
checkRights();
setCurrentRowNumber(0);
// process select query data for row creation
ResultInterface rows = query.query(0);
count = 0;
countInputRows = 0;
targetTable.fire(session, evaluateTriggerMasks(), true);
targetTable.lock(session, true, false);
while (rows.next()) {
count++;
countInputRows++;
Value[] sourceRowValues = rows.currentRow();
Row sourceRow = new RowImpl(sourceRowValues,0);
System.out.println(("currentRowValues="+Arrays.toString(sourceRowValues)));
Row newTargetRow = targetTable.getTemplateRow();
setCurrentRowNumber(count);
setCurrentRowNumber(countInputRows);
System.out.println("columns="+Arrays.toString(columns));
// computer the new target row columns values
......@@ -93,14 +95,14 @@ public class MergeUsing extends Merge {
Value v = c.convert(sourceRowValues[j]);
newTargetRow.setValue(index, v);
} catch (DbException ex) {
throw setRow(ex, count, getSQL(sourceRowValues));
throw setRow(ex, countInputRows, getSQL(sourceRowValues));
}
}
merge(sourceRow, sourceRowValues,newTargetRow);
}
rows.close();
targetTable.fire(session, evaluateTriggerMasks(), false);
return count;
return countUpdatedRows;
}
......@@ -136,31 +138,76 @@ public class MergeUsing extends Merge {
// put the column values into the table filter
sourceTableFilter.set(sourceRow);
// try and update
// try and perform an update
int count = 0;
if(updateCommand!=null){
System.out.println("onConditions="+onCondition.toString());
if(updateCommand!=null){
System.out.println("updatePlanSQL="+updateCommand.getPlanSQL());
count += updateCommand.update();
System.out.println("update.count="+count);
}
if(deleteCommand!=null && count==0){
System.out.println("deleteCommand="+deleteCommand.getPlanSQL());
count += deleteCommand.update();
System.out.println("delete.count="+count);
}
// if either updates do nothing, try an insert
if (count == 0) {
count+=addRowByCommandInsert(session,newTargetRow);
//addRowByAPIInsert(session,newTargetRow);
} else if (count != 1) {
throw DbException.get(ErrorCode.DUPLICATE_KEY_1, targetTable.getSQL());
}
countUpdatedRows+=count;
}
private void configPreparedParameters(Row newTargetRow, Prepared updatePrepared) {
ArrayList<Parameter> k = updatePrepared.getParameters();
// set each parameter in the updatePrepared with the real value from the source column
// 0 to columns.length-1
for (int i = 0; i < columns.length; i++) {
Column col = columns[i];
Value v = newTargetRow.getValue(col.getColumnId());
Parameter p = k.get(i);
p.setValue(v);
}
// columns.length to columns.length+keys.length-1
for (int i = 0; i < keys.length; i++) {
Column col = keys[i];
Value v = newTargetRow.getValue(col.getColumnId());
if (v == null) {
throw DbException.get(ErrorCode.COLUMN_CONTAINS_NULL_VALUES_1, col.getSQL());
}
Parameter p = k.get(columns.length + i);
p.setValue(v);
}
}
private int addRowByCommandInsert(Session session, Row newTargetRow) {
int localCount = 0;
if(insertCommand!=null){
System.out.println("insertPlanSQL="+insertCommand.getPlanSQL());
localCount += insertCommand.update();
}
System.out.println("insert.count="+localCount);
return localCount;
}
private int addRowByAPIInsert(Session session, Row newTargetRow) {
System.out.println("addRowByInsert=(hashcode)"+newTargetRow.hashCode());
try {
targetTable.validateConvertUpdateSequence(session, newTargetRow);
boolean done = targetTable.fireBeforeRow(session, null, newTargetRow);
if (!done) {
targetTable.lock(session, true, false);
//targetTable.addRow(session, row);
addRowByInsert(session,newTargetRow);
targetTable.addRow(session, newTargetRow);
session.log(targetTable, UndoLogRecord.INSERT, newTargetRow);
targetTable.fireAfterRow(session, null, newTargetRow, false);
return 1;
}
return 0;
} catch (DbException e) {
if (e.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) {
// possibly a concurrent merge or insert
......@@ -184,37 +231,6 @@ public class MergeUsing extends Merge {
}
throw e;
}
} else if (count != 1) {
throw DbException.get(ErrorCode.DUPLICATE_KEY_1, targetTable.getSQL());
}
}
private void configPreparedParameters(Row newTargetRow, Prepared updatePrepared) {
ArrayList<Parameter> k = updatePrepared.getParameters();
// set each parameter in the updatePrepared with the real value from the source column
// 0 to columns.length-1
for (int i = 0; i < columns.length; i++) {
Column col = columns[i];
Value v = newTargetRow.getValue(col.getColumnId());
Parameter p = k.get(i);
p.setValue(v);
}
// columns.length to columns.length+keys.length-1
for (int i = 0; i < keys.length; i++) {
Column col = keys[i];
Value v = newTargetRow.getValue(col.getColumnId());
if (v == null) {
throw DbException.get(ErrorCode.COLUMN_CONTAINS_NULL_VALUES_1, col.getSQL());
}
Parameter p = k.get(columns.length + i);
p.setValue(v);
}
}
private void addRowByInsert(Session session, Row row) {
System.out.println("addRowByInsert=(hashcode)"+row.hashCode());
targetTable.addRow(session, row);
}
......@@ -347,12 +363,27 @@ public class MergeUsing extends Merge {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
}
}
// if (keys == null) {
// Index idx = targetTable.getPrimaryKey();
// if (idx == null) {
// throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, "PRIMARY KEY");
// }
// keys = idx.getColumns();
// }
if (keys == null) {
Index idx = targetTable.getPrimaryKey();
if (idx == null) {
throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, "PRIMARY KEY");
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);
}
}
keys = idx.getColumns();
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();
update = session.prepare(sql);
......@@ -410,6 +441,44 @@ public class MergeUsing extends Merge {
String sql = buff.toString();
return sql;
}
private String buildPreparedSQLForMergeUsing() {
StatementBuilder buff = new StatementBuilder("MERGE INTO ");
buff.append(targetTable.getSQL());
if(targetTableFilter.getTableAlias()!=null){
buff.append(" AS "+targetTableFilter.getTableAlias()+"\n");
}
buff.append("USING \n");
buff.append(temporarySourceTableView.getSQL());
buff.append("\n");
if(sourceTableFilter.getTableAlias()!=null){
buff.append(" AS "+sourceTableFilter.getTableAlias()+"\n");
}
buff.append("ON (");
buff.append(onCondition.getSQL());
buff.append(" )");
if(updateCommand!=null || deleteCommand!=null){
buff.append("\nWHEN MATCHED\n");
if(updateCommand!=null){
buff.append(updateCommand.getPlanSQL());
}
if(deleteCommand!=null){
buff.append(deleteCommand.getPlanSQL());
}
}
if(insertCommand!=null){
buff.append("\nWHEN NOT MATCHED\n");
if(insertCommand!=null){
buff.append(insertCommand.getPlanSQL());
}
}
// buff.resetCount();
// for (Column c : keys) {
// buff.appendExceptFirst(" AND ");
// buff.append(c.getSQL()).append("=?");
// }
String sql = buff.toString();
return sql;
}
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);
......
......@@ -9,7 +9,6 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import org.h2.test.TestBase;
/**
* Test merge using syntax.
......@@ -17,6 +16,8 @@ import org.h2.test.TestBase;
public class TestMergeUsing extends TestBase {
private static final String GATHER_ORDERED_RESULTS_SQL = "SELECT ID, NAME FROM PARENT ORDER BY ID ASC";
/**
* Run just this test.
*
......@@ -28,35 +29,86 @@ public class TestMergeUsing extends TestBase {
@Override
public void test() throws Exception {
testMergeUsing();
// Simple ID,NAME inserts, target table with PK initially empty
testMergeUsing(
"CREATE TABLE PARENT(ID INT, NAME VARCHAR, PRIMARY KEY(ID) );",
"MERGE INTO PARENT AS P USING (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S ON (P.ID = S.ID AND 1=1 AND S.ID = P.ID) WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME WHERE 2 = 2 WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (S.ID, S.NAME)",
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2)",
2
);
// Simple NAME updates, 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,2) ) AS S ON (P.ID = S.ID AND 1=1 AND S.ID = P.ID) WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE 1 = 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(1,2)",
2
);
// No NAME updates, WHERE clause is always false, insert clause missing
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,2) ) AS S ON (P.ID = S.ID) WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE 1 = 2",
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2)",
0
);
// No NAME updates, no WHERE clause, insert clause missing
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,2) ) AS S ON (P.ID = S.ID) WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID",
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(1,2)",
2
);
// Two delete updates done, no WHERE clause, insert clause missing
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,2) ) AS S ON (P.ID = S.ID) WHEN MATCHED THEN DELETE",
GATHER_ORDERED_RESULTS_SQL,
"SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) WHERE 1=0",
2
);
// 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) );",
"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) 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
);
}
private void testMergeUsing() throws Exception {
private void testMergeUsing(String setupSQL, String statementUnderTest, String gatherResultsSQL,
String expectedResultsSQL, int expectedRowUpdateCount) throws Exception {
deleteDb("mergeUsingQueries");
Connection conn = getConnection("mergeUsingQueries");
Statement stat;
PreparedStatement prep;
ResultSet rs;
int rowCount;
int rowCountUpdate;
stat = conn.createStatement();
stat.execute("CREATE TABLE PARENT(ID INT, NAME VARCHAR, PRIMARY KEY(ID) );");
stat.execute(setupSQL);
prep = conn.prepareStatement("MERGE INTO PARENT AS P USING (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S ON (P.ID = S.ID AND 1=1 AND S.ID = P.ID) WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME WHERE 2 = 2 WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (S.ID, S.NAME)");
rowCount = prep.executeUpdate();
prep = conn.prepareStatement(statementUnderTest);
rowCountUpdate = prep.executeUpdate();
int[] rowArray = new int[] { 1,2 };
assertEquals(rowArray.length,rowCount);
rs = stat.executeQuery("( "+gatherResultsSQL+" ) MINUS ( "+expectedResultsSQL+" )");
rs = stat.executeQuery("SELECT ID, NAME FROM PARENT ORDER BY ID ASC");
for (int n : rowArray) {
assertTrue(rs.next());
assertEquals(n,rs.getInt(1));
assertEquals("Marcy"+n, rs.getString(2));
System.out.println("id="+rs.getInt(1)+",name="+rs.getString(2));
int rowCount = 0;
StringBuffer diffBuffer = new StringBuffer("");
while (rs.next()) {
rowCount++;
diffBuffer.append("|");
for(int ndx = 0; ndx < rs.getMetaData().getColumnCount(); ndx++){
diffBuffer.append(rs.getObject(ndx));
diffBuffer.append("|\n");
}
}
assertFalse(rs.next());
assertEquals("Differences is expected output found:"+diffBuffer,0,rowCount);
assertEquals("Expected update counts differ",expectedRowUpdateCount,rowCountUpdate);
conn.close();
deleteDb("mergeUsingQueries");
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论