Unverified 提交 c877fe32 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #646 from stumc/Issue#645

Issue#646 NPE in CREATE VIEW WITH RECURSIVE & NON_RECURSIVE CTE
...@@ -14,3 +14,4 @@ test.out.txt ...@@ -14,3 +14,4 @@ test.out.txt
*.log *.log
target/ target/
src/main/org/h2/res/help.csv src/main/org/h2/res/help.csv
_tmp*
...@@ -43,7 +43,6 @@ import org.h2.command.ddl.CreateSchema; ...@@ -43,7 +43,6 @@ import org.h2.command.ddl.CreateSchema;
import org.h2.command.ddl.CreateSequence; import org.h2.command.ddl.CreateSequence;
import org.h2.command.ddl.CreateSynonym; import org.h2.command.ddl.CreateSynonym;
import org.h2.command.ddl.CreateTable; import org.h2.command.ddl.CreateTable;
import org.h2.command.ddl.CreateTableData;
import org.h2.command.ddl.CreateTrigger; import org.h2.command.ddl.CreateTrigger;
import org.h2.command.ddl.CreateUser; import org.h2.command.ddl.CreateUser;
import org.h2.command.ddl.CreateUserDataType; import org.h2.command.ddl.CreateUserDataType;
...@@ -143,7 +142,6 @@ import org.h2.table.Table; ...@@ -143,7 +142,6 @@ import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.table.TableFilter.TableFilterVisitor; import org.h2.table.TableFilter.TableFilterVisitor;
import org.h2.table.TableView; import org.h2.table.TableView;
import org.h2.util.ColumnNamer;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
...@@ -172,6 +170,8 @@ import org.h2.value.ValueTimestampTimeZone; ...@@ -172,6 +170,8 @@ import org.h2.value.ValueTimestampTimeZone;
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class Parser { public class Parser {
public static final String WITH_STATEMENT_SUPPORTS_LIMITED_SUB_STATEMENTS =
"WITH statement supports only SELECT, CREATE TABLE, INSERT, UPDATE, MERGE or DELETE statements";
// used during the tokenizer phase // used during the tokenizer phase
private static final int CHAR_END = 1, CHAR_VALUE = 2, CHAR_QUOTED = 3; private static final int CHAR_END = 1, CHAR_VALUE = 2, CHAR_QUOTED = 3;
...@@ -199,8 +199,6 @@ public class Parser { ...@@ -199,8 +199,6 @@ public class Parser {
return o1 == o2 ? 0 : compareTableFilters(o1, o2); return o1 == o2 ? 0 : compareTableFilters(o1, o2);
} }
}; };
public static final String WITH_STATEMENT_SUPPORTS_LIMITED_STATEMENTS =
"WITH statement supports only SELECT, CREATE TABLE, INSERT, UPDATE, MERGE or DELETE statements";
private final Database database; private final Database database;
private final Session session; private final Session session;
...@@ -724,7 +722,7 @@ public class Parser { ...@@ -724,7 +722,7 @@ public class Parser {
* *
*/ */
private Schema getSchemaWithDefault() { private Schema getSchemaWithDefault() {
if(schemaName==null){ if (schemaName == null) {
schemaName = session.getCurrentSchemaName(); schemaName = session.getCurrentSchemaName();
} }
return getSchema(schemaName); return getSchema(schemaName);
...@@ -847,13 +845,13 @@ public class Parser { ...@@ -847,13 +845,13 @@ public class Parser {
currentSelect, orderInFrom, null); currentSelect, orderInFrom, null);
} }
private TableFilter readSimpleTableFilterWithAliasExcludes(int orderInFrom,Collection<String> excludeTokens) { private TableFilter readSimpleTableFilterWithAliasExcludes(int orderInFrom, Collection<String> excludeTokens) {
Table table = readTableOrView(); Table table = readTableOrView();
String alias = null; String alias = null;
if (readIf("AS")) { if (readIf("AS")) {
alias = readAliasIdentifier(); alias = readAliasIdentifier();
} else if (currentTokenType == IDENTIFIER) { } else if (currentTokenType == IDENTIFIER) {
if (!equalsTokenIgnoreCase(currentToken,"SET") && !isTokenInList(excludeTokens)) { if (!equalsTokenIgnoreCase(currentToken, "SET") && !isTokenInList(excludeTokens)) {
// SET is not a keyword (PostgreSQL supports it as a table name) // SET is not a keyword (PostgreSQL supports it as a table name)
alias = readAliasIdentifier(); alias = readAliasIdentifier();
} }
...@@ -1051,7 +1049,7 @@ public class Parser { ...@@ -1051,7 +1049,7 @@ public class Parser {
} }
} }
private static Prepared prepare(Session s, String sql, public static Prepared prepare(Session s, String sql,
ArrayList<Value> paramValues) { ArrayList<Value> paramValues) {
Prepared prep = s.prepare(sql); Prepared prep = s.prepare(sql);
ArrayList<Parameter> params = prep.getParameters(); ArrayList<Parameter> params = prep.getParameters();
...@@ -1082,13 +1080,13 @@ public class Parser { ...@@ -1082,13 +1080,13 @@ public class Parser {
currentPrepared = command; currentPrepared = command;
int start = lastParseIndex; int start = lastParseIndex;
read("INTO"); read("INTO");
List<String> excludeIdentifiers = Arrays.asList("USING","KEY","VALUES"); List<String> excludeIdentifiers = Arrays.asList("USING", "KEY", "VALUES");
TableFilter targetTableFilter = readSimpleTableFilterWithAliasExcludes(0,excludeIdentifiers); TableFilter targetTableFilter = readSimpleTableFilterWithAliasExcludes(0, excludeIdentifiers);
command.setTargetTableFilter(targetTableFilter); command.setTargetTableFilter(targetTableFilter);
Table table = command.getTargetTable(); Table table = command.getTargetTable();
if (readIf("USING")){ if (readIf("USING")) {
return parseMergeUsing(command,start); return parseMergeUsing(command, start);
} }
if (readIf("(")) { if (readIf("(")) {
if (isSelect()) { if (isSelect()) {
...@@ -1138,29 +1136,30 @@ public class Parser { ...@@ -1138,29 +1136,30 @@ public class Parser {
command.setQueryAlias(readFromAlias(null, Arrays.asList("ON"))); command.setQueryAlias(readFromAlias(null, Arrays.asList("ON")));
String[] querySQLOutput = new String[]{null}; String[] querySQLOutput = new String[]{null};
List<Column> columnTemplateList = createQueryColumnTemplateList(null, command.getQuery(), querySQLOutput); List<Column> columnTemplateList = TableView.createQueryColumnTemplateList(null, command.getQuery(), querySQLOutput);
TableView temporarySourceTableView = createTemporarySessionView( TableView temporarySourceTableView = createCTEView(
command.getQueryAlias(), querySQLOutput[0], command.getQueryAlias(), querySQLOutput[0],
columnTemplateList, false/* no recursion */, columnTemplateList, false/* no recursion */,
false/* do not add to session */); false/* do not add to session */,
false /* isPersistent */,
session);
TableFilter sourceTableFilter = new TableFilter(session, TableFilter sourceTableFilter = new TableFilter(session,
temporarySourceTableView, command.getQueryAlias(), temporarySourceTableView, command.getQueryAlias(),
rightsChecked, (Select) command.getQuery(), 0, null); rightsChecked, (Select) command.getQuery(), 0, null);
command.setSourceTableFilter(sourceTableFilter); command.setSourceTableFilter(sourceTableFilter);
} } else {
else{
/* Its a table name, simulate a query by building a select query for the table */ /* Its a table name, simulate a query by building a select query for the table */
List<String> excludeIdentifiers = Arrays.asList("ON"); List<String> excludeIdentifiers = Arrays.asList("ON");
TableFilter sourceTableFilter = readSimpleTableFilterWithAliasExcludes(0,excludeIdentifiers); TableFilter sourceTableFilter = readSimpleTableFilterWithAliasExcludes(0, excludeIdentifiers);
command.setSourceTableFilter(sourceTableFilter); command.setSourceTableFilter(sourceTableFilter);
StringBuilder buff = new StringBuilder( StringBuilder buff = new StringBuilder(
"SELECT * FROM "+sourceTableFilter.getTable().getName()); "SELECT * FROM "+sourceTableFilter.getTable().getName());
if(sourceTableFilter.getTableAlias()!=null){ if (sourceTableFilter.getTableAlias() != null) {
buff.append(" AS "+sourceTableFilter.getTableAlias()); buff.append(" AS "+sourceTableFilter.getTableAlias());
} }
Prepared preparedQuery = prepare(session, buff.toString(), null/*paramValues*/); Prepared preparedQuery = prepare(session, buff.toString(), null/*paramValues*/);
command.setQuery((Select)preparedQuery); command.setQuery((Select) preparedQuery);
} }
read("ON"); read("ON");
...@@ -1169,27 +1168,27 @@ public class Parser { ...@@ -1169,27 +1168,27 @@ public class Parser {
command.setOnCondition(condition); command.setOnCondition(condition);
read(")"); read(")");
if(readIfAll("WHEN","MATCHED","THEN")){ if (readIfAll("WHEN", "MATCHED", "THEN")) {
int startMatched = lastParseIndex; int startMatched = lastParseIndex;
if (readIf("UPDATE")){ if (readIf("UPDATE")) {
Update updateCommand = new Update(session); Update updateCommand = new Update(session);
//currentPrepared = updateCommand; //currentPrepared = updateCommand;
TableFilter filter = command.getTargetTableFilter(); TableFilter filter = command.getTargetTableFilter();
updateCommand.setTableFilter(filter); updateCommand.setTableFilter(filter);
parseUpdateSetClause(updateCommand, filter,startMatched); parseUpdateSetClause(updateCommand, filter, startMatched);
command.setUpdateCommand(updateCommand); command.setUpdateCommand(updateCommand);
} }
startMatched = lastParseIndex; startMatched = lastParseIndex;
if (readIf("DELETE")){ if (readIf("DELETE")) {
Delete deleteCommand = new Delete(session); Delete deleteCommand = new Delete(session);
TableFilter filter = command.getTargetTableFilter(); TableFilter filter = command.getTargetTableFilter();
deleteCommand.setTableFilter(filter); deleteCommand.setTableFilter(filter);
parseDeleteGivenTable(deleteCommand,null,startMatched); parseDeleteGivenTable(deleteCommand, null, startMatched);
command.setDeleteCommand(deleteCommand); command.setDeleteCommand(deleteCommand);
} }
} }
if(readIfAll("WHEN","NOT","MATCHED","THEN")){ if (readIfAll("WHEN", "NOT", "MATCHED", "THEN")) {
if (readIf("INSERT")){ if (readIf("INSERT")) {
Insert insertCommand = new Insert(session); Insert insertCommand = new Insert(session);
insertCommand.setTable(command.getTargetTable()); insertCommand.setTable(command.getTargetTable());
parseInsertGivenTable(insertCommand, command.getTargetTable()); parseInsertGivenTable(insertCommand, command.getTargetTable());
...@@ -1222,7 +1221,7 @@ public class Parser { ...@@ -1222,7 +1221,7 @@ public class Parser {
Table table = readTableOrView(); Table table = readTableOrView();
command.setTable(table); command.setTable(table);
Insert returnedCommand = parseInsertGivenTable(command, table); Insert returnedCommand = parseInsertGivenTable(command, table);
if (returnedCommand!=null){ if (returnedCommand != null) {
return returnedCommand; return returnedCommand;
} }
if (database.getMode().onDuplicateKeyUpdate) { if (database.getMode().onDuplicateKeyUpdate) {
...@@ -1452,8 +1451,8 @@ public class Parser { ...@@ -1452,8 +1451,8 @@ public class Parser {
} }
} }
} }
// inherit alias for temporary views (usually CTE's) from table name // inherit alias for CTE as views from table name
if(table.isView() && table.isTemporary() && alias==null){ if (table.isView() && table.isTableExpression() && alias == null) {
alias = table.getName(); alias = table.getName();
} }
return new TableFilter(session, table, alias, rightsChecked, return new TableFilter(session, table, alias, rightsChecked,
...@@ -1489,7 +1488,7 @@ public class Parser { ...@@ -1489,7 +1488,7 @@ public class Parser {
private String readFromAlias(String alias) { private String readFromAlias(String alias) {
// left and right are not keywords (because they are functions as // left and right are not keywords (because they are functions as
// well) // well)
List<String> excludeIdentifiers = Arrays.asList("LEFT","RIGHT","FULL"); List<String> excludeIdentifiers = Arrays.asList("LEFT", "RIGHT", "FULL");
return readFromAlias(alias, excludeIdentifiers); return readFromAlias(alias, excludeIdentifiers);
} }
...@@ -1922,11 +1921,7 @@ public class Parser { ...@@ -1922,11 +1921,7 @@ public class Parser {
} }
private Query parseSelect() { private Query parseSelect() {
// This method and its subroutines sometimes resets the schema name - the try-finally block
// makes sure it is reverted if nulled
//String savedSchemaName = schemaName;
Query command = null; Query command = null;
//try{
int paramIndex = parameters.size(); int paramIndex = parameters.size();
command = parseSelectUnion(); command = parseSelectUnion();
ArrayList<Parameter> params = New.arrayList(); ArrayList<Parameter> params = New.arrayList();
...@@ -1935,12 +1930,6 @@ public class Parser { ...@@ -1935,12 +1930,6 @@ public class Parser {
} }
command.setParameterList(params); command.setParameterList(params);
command.init(); command.init();
//}
//finally{
//if(schemaName==null){
// schemaName = savedSchemaName;
//}
//}
return command; return command;
} }
...@@ -1952,7 +1941,7 @@ public class Parser { ...@@ -1952,7 +1941,7 @@ public class Parser {
params.add(parameters.get(i)); params.add(parameters.get(i));
} }
command.setParameterList(params); command.setParameterList(params);
if(command instanceof Query){ if (command instanceof Query) {
Query query = (Query) command; Query query = (Query) command;
query.init(); query.init();
} }
...@@ -2144,8 +2133,7 @@ public class Parser { ...@@ -2144,8 +2133,7 @@ public class Parser {
Query query = null; Query query = null;
try { try {
query = (Query) parseWith(); query = (Query) parseWith();
} } catch (ClassCastException e) {
catch(ClassCastException e){
throw DbException.get(ErrorCode.SYNTAX_ERROR_1, throw DbException.get(ErrorCode.SYNTAX_ERROR_1,
"WITH statement supports only SELECT (query) in this context"); "WITH statement supports only SELECT (query) in this context");
} }
...@@ -3499,8 +3487,8 @@ public class Parser { ...@@ -3499,8 +3487,8 @@ public class Parser {
* Reads passed token in list, in order and returns true on first match. * Reads passed token in list, in order and returns true on first match.
* If none of the token matches returns false * If none of the token matches returns false
*/ */
private boolean readIfOr(String ... tokens) { private boolean readIfOr(String... tokens) {
for(String token: tokens) { for (String token: tokens) {
if (readIf(token)) { if (readIf(token)) {
return true; return true;
} }
...@@ -3512,14 +3500,13 @@ public class Parser { ...@@ -3512,14 +3500,13 @@ public class Parser {
* Reads every token in list, in order - returns true if all are found. * Reads every token in list, in order - returns true if all are found.
* If any are not found, returns false - AND resets parsing back to state when called. * If any are not found, returns false - AND resets parsing back to state when called.
*/ */
private boolean readIfAll(String ... tokens) { private boolean readIfAll(String... tokens) {
// save parse location in case we have to fail this test // save parse location in case we have to fail this test
int start = lastParseIndex; int start = lastParseIndex;
for(String token: tokens){ for (String token: tokens) {
if (!currentTokenQuoted && equalsToken(token, currentToken)) { if (!currentTokenQuoted && equalsToken(token, currentToken)) {
read(); read();
} } else {
else{
// read failed - revert parse location to before when called // read failed - revert parse location to before when called
parseIndex = start; parseIndex = start;
read(); read();
...@@ -3561,7 +3548,7 @@ public class Parser { ...@@ -3561,7 +3548,7 @@ public class Parser {
return false; return false;
} }
private boolean isTokenInList(Collection<String> upperCaseTokenList){ private boolean isTokenInList(Collection<String> upperCaseTokenList) {
String upperCaseCurrentToken = currentToken.toUpperCase(); String upperCaseCurrentToken = currentToken.toUpperCase();
return upperCaseTokenList.contains(upperCaseCurrentToken); return upperCaseTokenList.contains(upperCaseCurrentToken);
} }
...@@ -4523,7 +4510,7 @@ public class Parser { ...@@ -4523,7 +4510,7 @@ public class Parser {
String enumerator0 = readString(); String enumerator0 = readString();
enumeratorList.add(enumerator0); enumeratorList.add(enumerator0);
original += "'" + enumerator0 + "'"; original += "'" + enumerator0 + "'";
while(readIf(",")) { while (readIf(",")) {
original += ','; original += ',';
String enumeratorN = readString(); String enumeratorN = readString();
original += "'" + enumeratorN + "'"; original += "'" + enumeratorN + "'";
...@@ -4535,7 +4522,7 @@ public class Parser { ...@@ -4535,7 +4522,7 @@ public class Parser {
} }
try { try {
ValueEnum.check(enumerators); ValueEnum.check(enumerators);
} catch(DbException e) { } catch (DbException e) {
throw e.addSQL(original); throw e.addSQL(original);
} }
} else if (readIf("(")) { } else if (readIf("(")) {
...@@ -5166,180 +5153,195 @@ public class Parser { ...@@ -5166,180 +5153,195 @@ public class Parser {
private Prepared parseWith() { private Prepared parseWith() {
List<TableView> viewsCreated = new ArrayList<>(); List<TableView> viewsCreated = new ArrayList<>();
readIf("RECURSIVE"); readIf("RECURSIVE");
// this WITH statement might not be a temporary view - allow optional keyword to tell us that
// this keyword. This feature will not be documented - H2 internal use only.
boolean isPersistent = readIf("PERSISTENT");
// this WITH statement is not a temporary view - it is part of a persistent view
// as in CREATE VIEW abc AS WITH my_cte - this auto detects that condition
if (session.isParsingCreateView()) {
isPersistent = true;
}
do { do {
viewsCreated.add(parseSingleCommonTableExpression()); viewsCreated.add(parseSingleCommonTableExpression(isPersistent));
} while (readIf(",")); } while (readIf(","));
Prepared p = null; Prepared p = null;
// reverse the order of constructed CTE views - as the destruction order
// (since later created view may depend on previously created views -
// we preserve that dependency order in the destruction sequence )
// used in setCteCleanups
Collections.reverse(viewsCreated);
if(isToken("SELECT")) { if (isToken("SELECT")) {
Query query = parseSelectUnion(); Query query = parseSelectUnion();
query.setPrepareAlways(true); query.setPrepareAlways(true);
query.setNeverLazy(true); query.setNeverLazy(true);
p = query; p = query;
} } else if (readIf("INSERT")) {
else if(readIf("INSERT")) {
p = parseInsert(); p = parseInsert();
p.setPrepareAlways(true); p.setPrepareAlways(true);
} } else if (readIf("UPDATE")) {
else if(readIf("UPDATE")) {
p = parseUpdate(); p = parseUpdate();
p.setPrepareAlways(true); p.setPrepareAlways(true);
} } else if (readIf("MERGE")) {
else if(readIf("MERGE")) {
p = parseMerge(); p = parseMerge();
p.setPrepareAlways(true); p.setPrepareAlways(true);
} } else if (readIf("DELETE")) {
else if(readIf("DELETE")) {
p = parseDelete(); p = parseDelete();
p.setPrepareAlways(true); p.setPrepareAlways(true);
} } else if (readIf("CREATE")) {
else if(readIf("CREATE")) { if (!isToken("TABLE")) {
if (!isToken("TABLE")){
throw DbException.get(ErrorCode.SYNTAX_ERROR_1, throw DbException.get(ErrorCode.SYNTAX_ERROR_1,
WITH_STATEMENT_SUPPORTS_LIMITED_STATEMENTS); WITH_STATEMENT_SUPPORTS_LIMITED_SUB_STATEMENTS);
} }
p = parseCreate(); p = parseCreate();
p.setPrepareAlways(true); p.setPrepareAlways(true);
} } else {
else {
throw DbException.get(ErrorCode.SYNTAX_ERROR_1, throw DbException.get(ErrorCode.SYNTAX_ERROR_1,
WITH_STATEMENT_SUPPORTS_LIMITED_STATEMENTS); WITH_STATEMENT_SUPPORTS_LIMITED_SUB_STATEMENTS);
} }
// clean up temp views starting with last to first (in case of // clean up temporary views starting with last to first (in case of
// dependencies) // dependencies) - but only if they are not persistent
Collections.reverse(viewsCreated); if (!isPersistent) {
p.setCteCleanups(viewsCreated); p.setCteCleanups(viewsCreated);
}
return p; return p;
} }
private TableView parseSingleCommonTableExpression() { private TableView parseSingleCommonTableExpression(boolean isPersistent) {
String tempViewName = readIdentifierWithSchema(); String cteViewName = readIdentifierWithSchema();
Schema schema = getSchema(); Schema schema = getSchema();
Table recursiveTable; Table recursiveTable = null;
ArrayList<Column> columns = New.arrayList(); ArrayList<Column> columns = New.arrayList();
String[] cols = null; String[] cols = null;
Database db = session.getDatabase();
// column names are now optional - they can be inferred from the named // column names are now optional - they can be inferred from the named
// query if not supplied // query, if not supplied by user
if (readIf("(")) { if (readIf("(")) {
cols = parseColumnList(); cols = parseColumnList();
for (String c : cols) { for (String c : cols) {
// we don't really know the type of the column, so string will // we don't really know the type of the column, so STRING will
// have to do // have to do, UNKNOWN does not work here
columns.add(new Column(c, Value.STRING)); columns.add(new Column(c, Value.STRING));
} }
} }
Table old = session.findLocalTempTable(tempViewName);
if (old != null) { Table oldViewFound = null;
if (!(old instanceof TableView)) { if (isPersistent) {
oldViewFound = getSchema().findTableOrView(session, cteViewName);
} else {
oldViewFound = session.findLocalTempTable(cteViewName);
}
// this persistent check conflicts with check 10 lines down
if (oldViewFound != null) {
if (!(oldViewFound instanceof TableView)) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
tempViewName); cteViewName);
} }
TableView tv = (TableView) old; TableView tv = (TableView) oldViewFound;
if (!tv.isTableExpression()) { if (!tv.isTableExpression()) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
tempViewName); cteViewName);
} }
session.removeLocalTempTable(old); if (isPersistent) {
oldViewFound.lock(session, true, true);
session.getDatabase().removeSchemaObject(session, oldViewFound);
} else {
session.removeLocalTempTable(oldViewFound);
}
oldViewFound = null;
} }
// this table is created as a work around because recursive // this table is created as a work around because recursive
// table expressions need to reference something that look like // table expressions need to reference something that look like
// themselves // themselves
// to work (its removed after creation in this method) // to work (its removed after creation in this method)
CreateTableData data = new CreateTableData(); // only create table data and table if we don't have a working CTE already
data.id = database.allocateObjectId(); if (oldViewFound == null) {
data.columns = columns; recursiveTable = TableView.createShadowTableForRecursiveTableExpression(isPersistent, session, cteViewName,
data.tableName = tempViewName; schema, columns, db);
data.temporary = true; }
data.persistData = true;
data.persistIndexes = false;
data.create = true;
data.session = session;
recursiveTable = schema.createTable(data);
session.addLocalTempTable(recursiveTable);
List<Column> columnTemplateList; List<Column> columnTemplateList;
String[] querySQLOutput = new String[]{null}; String[] querySQLOutput = new String[]{null};
try { try {
read("AS"); read("AS");
read("("); read("(");
Query withQuery = parseSelect(); Query withQuery = parseSelect();
if (isPersistent) {
withQuery.session = session;
}
read(")"); read(")");
columnTemplateList = createQueryColumnTemplateList(cols, withQuery, querySQLOutput); columnTemplateList = TableView.createQueryColumnTemplateList(cols, withQuery, querySQLOutput);
} finally { } finally {
session.removeLocalTempTable(recursiveTable); TableView.destroyShadowTableForRecursiveExpression(isPersistent, session, recursiveTable);
} }
TableView view = createTemporarySessionView(tempViewName,
TableView view = createCTEView(cteViewName,
querySQLOutput[0], columnTemplateList, querySQLOutput[0], columnTemplateList,
true/* allowRecursiveQueryDetection */, true); true/* allowRecursiveQueryDetection */,
true/* add to session */,
isPersistent, session);
return view; return view;
} }
/** private TableView createCTEView(String cteViewName, String querySQL,
* Creates a list of column templates from a query (usually from WITH query, List<Column> columnTemplateList, boolean allowRecursiveQueryDetection,
* but could be any query) boolean addViewToSession, boolean isPersistent, Session targetSession) {
* Database db = targetSession.getDatabase();
* @param cols - an optional list of column names (can be specified by WITH
* clause overriding usual select names)
* @param theQuery - the query object we want the column list for
* @param querySQLOutput - array of length 1 to receive extra 'output' field
* in addition to return value - containing the SQL query of the
* Query object
* @return a list of column object returned by withQuery
*/
private static List<Column> createQueryColumnTemplateList(String[] cols,
Query theQuery, String[] querySQLOutput) {
List<Column> columnTemplateList = new ArrayList<>();
theQuery.prepare();
// array of length 1 to receive extra 'output' field in addition to
// return value
querySQLOutput[0] = StringUtils.cache(theQuery.getPlanSQL());
ColumnNamer columnNamer = new ColumnNamer(theQuery.getSession());
ArrayList<Expression> withExpressions = theQuery.getExpressions();
for (int i = 0; i < withExpressions.size(); ++i) {
Expression columnExp = withExpressions.get(i);
// use the passed in column name if supplied, otherwise use alias
// (if found) otherwise use column name derived from column
// expression
String columnName = columnNamer.getColumnName(columnExp,i,cols);
columnTemplateList.add(new Column(columnName,
columnExp.getType()));
}
return columnTemplateList;
}
private TableView createTemporarySessionView(String tempViewName, String querySQL,
List<Column> columnTemplateList, boolean allowRecursiveQueryDetection, boolean addViewToSession) {
Schema schema = getSchemaWithDefault(); Schema schema = getSchemaWithDefault();
int id = database.allocateObjectId(); int id = db.allocateObjectId();
Column[] columnTemplateArray = columnTemplateList.toArray(new Column[0]);
// No easy way to determine if this is a recursive query up front, so we just compile // No easy way to determine if this is a recursive query up front, so we just compile
// it twice - once without the flag set, and if we didn't see a recursive term, // it twice - once without the flag set, and if we didn't see a recursive term,
// then we just compile it again. // then we just compile it again.
TableView view = new TableView(schema, id, tempViewName, querySQL, TableView view;
parameters, columnTemplateList.toArray(new Column[0]), session, synchronized (targetSession) {
allowRecursiveQueryDetection, false); view = new TableView(schema, id, cteViewName, querySQL,
parameters, columnTemplateArray, targetSession,
allowRecursiveQueryDetection, false /* literalsChecked */, true /* isTableExpression */, isPersistent);
if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) { if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) {
if (isPersistent) {
db.addSchemaObject(targetSession, view);
view.lock(targetSession, true, true);
targetSession.getDatabase().removeSchemaObject(targetSession, view);
} else {
session.removeLocalTempTable(view); session.removeLocalTempTable(view);
view = new TableView(schema, id, tempViewName, querySQL, parameters, }
columnTemplateList.toArray(new Column[0]), session, view = new TableView(schema, id, cteViewName, querySQL, parameters,
false/* recursive */, false); columnTemplateArray, targetSession,
false/* assume recursive */, false /* literalsChecked */, true /* isTableExpression */, isPersistent);
}
// both removeSchemaObject and removeLocalTempTable hold meta locks
targetSession.getDatabase().unlockMeta(targetSession);
} }
view.setTableExpression(true); view.setTableExpression(true);
view.setTemporary(true); view.setTemporary(!isPersistent);
view.setHidden(true); view.setHidden(true);
if(addViewToSession){
session.addLocalTempTable(view);
}
view.setOnCommitDrop(false); view.setOnCommitDrop(false);
if (addViewToSession) {
if (isPersistent) {
db.addSchemaObject(targetSession, view);
view.unlock(targetSession);
db.unlockMeta(targetSession);
} else {
targetSession.addLocalTempTable(view);
}
}
return view; return view;
} }
private CreateView parseCreateView(boolean force, boolean orReplace) { private CreateView parseCreateView(boolean force, boolean orReplace) {
boolean ifNotExists = readIfNotExists(); boolean ifNotExists = readIfNotExists();
boolean isTableExpression = readIf("TABLE_EXPRESSION");
String viewName = readIdentifierWithSchema(); String viewName = readIdentifierWithSchema();
CreateView command = new CreateView(session, getSchema()); CreateView command = new CreateView(session, getSchema());
this.createView = command; this.createView = command;
...@@ -5348,6 +5350,7 @@ public class Parser { ...@@ -5348,6 +5350,7 @@ public class Parser {
command.setComment(readCommentIf()); command.setComment(readCommentIf());
command.setOrReplace(orReplace); command.setOrReplace(orReplace);
command.setForce(force); command.setForce(force);
command.setTableExpression(isTableExpression);
if (readIf("(")) { if (readIf("(")) {
String[] cols = parseColumnList(); String[] cols = parseColumnList();
command.setColumnNames(cols); command.setColumnNames(cols);
...@@ -5357,12 +5360,12 @@ public class Parser { ...@@ -5357,12 +5360,12 @@ public class Parser {
read("AS"); read("AS");
try { try {
Query query; Query query;
session.setParsingView(true); session.setParsingCreateView(true, viewName);
try { try {
query = parseSelect(); query = parseSelect();
query.prepare(); query.prepare();
} finally { } finally {
session.setParsingView(false); session.setParsingCreateView(false, viewName);
} }
command.setSelect(query); command.setSelect(query);
} catch (DbException e) { } catch (DbException e) {
...@@ -6100,7 +6103,8 @@ public class Parser { ...@@ -6100,7 +6103,8 @@ public class Parser {
command.setType(CommandInterface.ALTER_TABLE_DROP_COLUMN); command.setType(CommandInterface.ALTER_TABLE_DROP_COLUMN);
ArrayList<Column> columnsToRemove = New.arrayList(); ArrayList<Column> columnsToRemove = New.arrayList();
Table table = tableIfTableExists(schema, tableName, ifTableExists); Table table = tableIfTableExists(schema, tableName, ifTableExists);
boolean openingBracketDetected = readIf("("); // For Oracle compatibility - open bracket required // For Oracle compatibility - open bracket required
boolean openingBracketDetected = readIf("(");
do { do {
String columnName = readColumnIdentifier(); String columnName = readColumnIdentifier();
if (table == null) { if (table == null) {
...@@ -6113,7 +6117,8 @@ public class Parser { ...@@ -6113,7 +6117,8 @@ public class Parser {
columnsToRemove.add(column); columnsToRemove.add(column);
} while (readIf(",")); } while (readIf(","));
if (openingBracketDetected) { if (openingBracketDetected) {
read(")"); // For Oracle compatibility - close bracket // For Oracle compatibility - close bracket
read(")");
} }
command.setTableName(tableName); command.setTableName(tableName);
command.setIfTableExists(ifTableExists); command.setIfTableExists(ifTableExists);
...@@ -6137,9 +6142,10 @@ public class Parser { ...@@ -6137,9 +6142,10 @@ public class Parser {
command.setNewColumnName(newColumnName); command.setNewColumnName(newColumnName);
return command; return command;
} else if (readIf("MODIFY")) { } else if (readIf("MODIFY")) {
// MySQL compatibility // MySQL compatibility (optional)
readIf("COLUMN"); // optional readIf("COLUMN");
boolean hasOpeningBracket = readIf("("); // Oracle specifies (but will not require) an opening parenthesis // Oracle specifies (but will not require) an opening parenthesis
boolean hasOpeningBracket = readIf("(");
String columnName = readColumnIdentifier(); String columnName = readColumnIdentifier();
AlterTableAlterColumn command = null; AlterTableAlterColumn command = null;
NullConstraintType nullConstraint = parseNotNullConstraint(); NullConstraintType nullConstraint = parseNotNullConstraint();
...@@ -6745,13 +6751,16 @@ public class Parser { ...@@ -6745,13 +6751,16 @@ public class Parser {
return command; return command;
} }
/**
* Enumeration describing null constraints
*/
private enum NullConstraintType { private enum NullConstraintType {
NULL_IS_ALLOWED, NULL_IS_NOT_ALLOWED, NO_NULL_CONSTRAINT_FOUND NULL_IS_ALLOWED, NULL_IS_NOT_ALLOWED, NO_NULL_CONSTRAINT_FOUND
} }
private NullConstraintType parseNotNullConstraint() { private NullConstraintType parseNotNullConstraint() {
NullConstraintType nullConstraint = NullConstraintType.NO_NULL_CONSTRAINT_FOUND; NullConstraintType nullConstraint = NullConstraintType.NO_NULL_CONSTRAINT_FOUND;
if ((isToken("NOT") || isToken("NULL"))) { if (isToken("NOT") || isToken("NULL")) {
if (readIf("NOT")) { if (readIf("NOT")) {
read("NULL"); read("NULL");
nullConstraint = NullConstraintType.NULL_IS_NOT_ALLOWED; nullConstraint = NullConstraintType.NULL_IS_NOT_ALLOWED;
...@@ -6761,15 +6770,20 @@ public class Parser { ...@@ -6761,15 +6770,20 @@ public class Parser {
} }
if (database.getMode().getEnum() == ModeEnum.Oracle) { if (database.getMode().getEnum() == ModeEnum.Oracle) {
if (readIf("ENABLE")) { if (readIf("ENABLE")) {
readIf("VALIDATE"); // Leave constraint 'as is' // Leave constraint 'as is'
if (readIf("NOVALIDATE")) { // Turn off constraint, allow NULLs readIf("VALIDATE");
// Turn off constraint, allow NULLs
if (readIf("NOVALIDATE")) {
nullConstraint = NullConstraintType.NULL_IS_ALLOWED; nullConstraint = NullConstraintType.NULL_IS_ALLOWED;
} }
} }
if (readIf("DISABLE")) { // Turn off constraint, allow NULLs // Turn off constraint, allow NULLs
if (readIf("DISABLE")) {
nullConstraint = NullConstraintType.NULL_IS_ALLOWED; nullConstraint = NullConstraintType.NULL_IS_ALLOWED;
readIf("VALIDATE"); // ignore validate // ignore validate
readIf("NOVALIDATE"); // ignore novalidate readIf("VALIDATE");
// ignore novalidate
readIf("NOVALIDATE");
} }
} }
} }
......
...@@ -34,6 +34,7 @@ public class CreateView extends SchemaCommand { ...@@ -34,6 +34,7 @@ public class CreateView extends SchemaCommand {
private String comment; private String comment;
private boolean orReplace; private boolean orReplace;
private boolean force; private boolean force;
private boolean isTableExpression;
public CreateView(Session session, Schema schema) { public CreateView(Session session, Schema schema) {
super(session, schema); super(session, schema);
...@@ -71,6 +72,10 @@ public class CreateView extends SchemaCommand { ...@@ -71,6 +72,10 @@ public class CreateView extends SchemaCommand {
this.force = force; this.force = force;
} }
public void setTableExpression(boolean isTableExpression) {
this.isTableExpression = isTableExpression;
}
@Override @Override
public int update() { public int update() {
session.commit(true); session.commit(true);
...@@ -98,17 +103,27 @@ public class CreateView extends SchemaCommand { ...@@ -98,17 +103,27 @@ public class CreateView extends SchemaCommand {
} }
querySQL = select.getPlanSQL(); querySQL = select.getPlanSQL();
} }
Column[] columnTemplates = null; Column[] columnTemplatesAsUnknowns = null;
Column[] columnTemplatesAsStrings = null;
if (columnNames != null) { if (columnNames != null) {
columnTemplates = new Column[columnNames.length]; columnTemplatesAsUnknowns = new Column[columnNames.length];
columnTemplatesAsStrings = new Column[columnNames.length];
for (int i = 0; i < columnNames.length; ++i) { for (int i = 0; i < columnNames.length; ++i) {
columnTemplates[i] = new Column(columnNames[i], Value.UNKNOWN); // non table expressions are fine to use unknown column type
columnTemplatesAsUnknowns[i] = new Column(columnNames[i], Value.UNKNOWN);
// table expressions can't have unknown types - so we use string instead
columnTemplatesAsStrings[i] = new Column(columnNames[i], Value.STRING);
} }
} }
if (view == null) { if (view == null) {
view = new TableView(getSchema(), id, viewName, querySQL, null, columnTemplates, session, false, false); if (isTableExpression) {
view = TableView.createTableViewMaybeRecursive(getSchema(), id, viewName, querySQL, null, columnTemplatesAsStrings, session, false /* literalsChecked */, isTableExpression, true /*isPersistent*/, db);
} else {
view = new TableView(getSchema(), id, viewName, querySQL, null, columnTemplatesAsUnknowns, session, false/* allow recursive */, false/* literalsChecked */, isTableExpression, true);
}
} else { } else {
view.replace(querySQL, columnTemplates, session, false, force, false); // TODO support isTableExpression in replace function...
view.replace(querySQL, columnTemplatesAsUnknowns, session, false, force, false);
view.setModified(); view.setModified();
} }
if (comment != null) { if (comment != null) {
...@@ -116,9 +131,14 @@ public class CreateView extends SchemaCommand { ...@@ -116,9 +131,14 @@ public class CreateView extends SchemaCommand {
} }
if (old == null) { if (old == null) {
db.addSchemaObject(session, view); db.addSchemaObject(session, view);
db.unlockMeta(session);
} else { } else {
db.updateMeta(session, view); db.updateMeta(session, view);
} }
// TODO: if we added any table expressions that aren't used by this view, detect them
// and drop them - otherwise they will leak and never get cleaned up.
return 0; return 0;
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
package org.h2.command.ddl; package org.h2.command.ddl;
import java.util.ArrayList;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
import org.h2.constraint.ConstraintReferential; import org.h2.constraint.ConstraintReferential;
...@@ -68,8 +69,26 @@ public class DropView extends SchemaCommand { ...@@ -68,8 +69,26 @@ public class DropView extends SchemaCommand {
} }
} }
// TODO: Where is the ConstraintReferential.CASCADE style drop processing ? It's
// supported from imported keys - but not for dependent db objects
TableView tableView = (TableView) view;
ArrayList<Table> copyOfDependencies = new ArrayList<Table>(tableView.getTables());
view.lock(session, true, true); view.lock(session, true, true);
session.getDatabase().removeSchemaObject(session, view); session.getDatabase().removeSchemaObject(session, view);
// remove dependent table expressions
for (Table childTable: copyOfDependencies) {
if (TableType.VIEW == childTable.getTableType()) {
TableView childTableView = (TableView) childTable;
if (childTableView.isTableExpression() && childTableView.getName() != null) {
session.getDatabase().removeSchemaObject(session, childTableView);
}
}
}
// make sure its all unlocked
session.getDatabase().unlockMeta(session);
} }
return 0; return 0;
} }
......
...@@ -53,7 +53,7 @@ public class Delete extends Prepared { ...@@ -53,7 +53,7 @@ public class Delete extends Prepared {
this.condition = condition; this.condition = condition;
} }
public Expression getCondition( ) { public Expression getCondition() {
return this.condition; return this.condition;
} }
...@@ -136,17 +136,16 @@ public class Delete extends Prepared { ...@@ -136,17 +136,16 @@ public class Delete extends Prepared {
public void prepare() { public void prepare() {
if (condition != null) { if (condition != null) {
condition.mapColumns(targetTableFilter, 0); condition.mapColumns(targetTableFilter, 0);
if(sourceTableFilter!=null){ if (sourceTableFilter != null) {
condition.mapColumns(sourceTableFilter, 0); condition.mapColumns(sourceTableFilter, 0);
} }
condition = condition.optimize(session); condition = condition.optimize(session);
condition.createIndexConditions(session, targetTableFilter); condition.createIndexConditions(session, targetTableFilter);
} }
TableFilter[] filters; TableFilter[] filters;
if(sourceTableFilter==null){ if (sourceTableFilter == null) {
filters = new TableFilter[] { targetTableFilter }; filters = new TableFilter[] { targetTableFilter };
} } else {
else{
filters = new TableFilter[] { targetTableFilter, sourceTableFilter }; filters = new TableFilter[] { targetTableFilter, sourceTableFilter };
} }
PlanItem item = targetTableFilter.getBestPlanItem(session, filters, 0, PlanItem item = targetTableFilter.getBestPlanItem(session, filters, 0,
......
...@@ -79,7 +79,8 @@ import org.h2.value.Value; ...@@ -79,7 +79,8 @@ import org.h2.value.Value;
* 4) Previously if neither UPDATE or DELETE clause is supplied, but INSERT is supplied - the INSERT * 4) Previously if neither UPDATE or DELETE clause is supplied, but INSERT is supplied - the INSERT
* action is always triggered. This is because the embedded UPDATE and DELETE statement's * action is always triggered. This is because the embedded UPDATE and DELETE statement's
* returned update row count is used to detect a matching join. * returned update row count is used to detect a matching join.
* If neither of the two the statements are provided, no matching join is EVER detected. * If neither of the two the statements are provided, no matching join is NEVER detected.
*
* A fix for this is now implemented as described below: * A fix for this is now implemented as described below:
* We now generate a "matchSelect" query and use that to always detect * We now generate a "matchSelect" query and use that to always detect
* a match join - rather than relying on UPDATE or DELETE statements. * a match join - rather than relying on UPDATE or DELETE statements.
...@@ -111,11 +112,12 @@ public class MergeUsing extends Prepared { ...@@ -111,11 +112,12 @@ public class MergeUsing extends Prepared {
private Delete deleteCommand; private Delete deleteCommand;
private Insert insertCommand; private Insert insertCommand;
private String queryAlias; private String queryAlias;
private int countUpdatedRows = 0; private int countUpdatedRows;
private Column[] sourceKeys; private Column[] sourceKeys;
private Select targetMatchQuery; private Select targetMatchQuery;
private final HashMap<Value, Integer> targetRowidsRemembered = new HashMap<>(); private final HashMap<Value, Integer> targetRowidsRemembered = new HashMap<>();
private int sourceQueryRowNumber = 0; private int sourceQueryRowNumber;
public MergeUsing(Merge merge) { public MergeUsing(Merge merge) {
super(merge.getSession()); super(merge.getSession());
......
...@@ -817,14 +817,14 @@ public class Select extends Query { ...@@ -817,14 +817,14 @@ public class Select extends Query {
sort = prepareOrder(orderList, expressions.size()); sort = prepareOrder(orderList, expressions.size());
orderList = null; orderList = null;
} }
ColumnNamer columnNamer= new ColumnNamer(session); ColumnNamer columnNamer = new ColumnNamer(session);
for (int i = 0; i < expressions.size(); i++) { for (int i = 0; i < expressions.size(); i++) {
Expression e = expressions.get(i); Expression e = expressions.get(i);
String proposedColumnName = e.getAlias(); String proposedColumnName = e.getAlias();
String columnName = columnNamer.getColumnName(e,i,proposedColumnName); String columnName = columnNamer.getColumnName(e, i, proposedColumnName);
// if the name changed, create an alias // if the name changed, create an alias
if(!columnName.equals(proposedColumnName)){ if (!columnName.equals(proposedColumnName)) {
e = new Alias(e,columnName,true); e = new Alias(e, columnName, true);
} }
expressions.set(i, e.optimize(session)); expressions.set(i, e.optimize(session));
} }
...@@ -852,7 +852,7 @@ public class Select extends Query { ...@@ -852,7 +852,7 @@ public class Select extends Query {
isQuickAggregateQuery = isEverything(optimizable); isQuickAggregateQuery = isEverything(optimizable);
} }
} }
cost = preparePlan(session.isParsingView()); cost = preparePlan(session.isParsingCreateView());
if (distinct && session.getDatabase().getSettings().optimizeDistinct && if (distinct && session.getDatabase().getSettings().optimizeDistinct &&
!isGroupQuery && filters.size() == 1 && !isGroupQuery && filters.size() == 1 &&
expressions.size() == 1 && condition == null) { expressions.size() == 1 && condition == null) {
...@@ -1060,7 +1060,14 @@ public class Select extends Query { ...@@ -1060,7 +1060,14 @@ public class Select extends Query {
StatementBuilder buff = new StatementBuilder(); StatementBuilder buff = new StatementBuilder();
for (TableFilter f : topFilters) { for (TableFilter f : topFilters) {
Table t = f.getTable(); Table t = f.getTable();
if (t.isView() && ((TableView) t).isRecursive()) { TableView tableView = t.isView() ? (TableView) t : null;
if (tableView != null && tableView.isRecursive() && tableView.isTableExpression()) {
if (tableView.isPersistent()) {
// skip the generation of plan SQL for this already recursive persistent ctes, since using a with
// statement will re-create the common table expression views.
continue;
} else {
buff.append("WITH RECURSIVE ").append(t.getName()).append('('); buff.append("WITH RECURSIVE ").append(t.getName()).append('(');
buff.resetCount(); buff.resetCount();
for (Column c : t.getColumns()) { for (Column c : t.getColumns()) {
...@@ -1070,6 +1077,7 @@ public class Select extends Query { ...@@ -1070,6 +1077,7 @@ public class Select extends Query {
buff.append(") AS ").append(t.getSQL()).append("\n"); buff.append(") AS ").append(t.getSQL()).append("\n");
} }
} }
}
buff.resetCount(); buff.resetCount();
buff.append("SELECT"); buff.append("SELECT");
if (distinct) { if (distinct) {
......
...@@ -91,6 +91,9 @@ public class Database implements DataHandler { ...@@ -91,6 +91,9 @@ public class Database implements DataHandler {
private static int initialPowerOffCount; private static int initialPowerOffCount;
private static final ThreadLocal<Session> META_LOCK_DEBUGGING = new ThreadLocal<Session>();
private static final ThreadLocal<Throwable> META_LOCK_DEBUGGING_STACK = new ThreadLocal<Throwable>();
/** /**
* The default name of the system user. This name is only used as long as * The default name of the system user. This name is only used as long as
* there is no administrator user registered. * there is no administrator user registered.
...@@ -296,7 +299,7 @@ public class Database implements DataHandler { ...@@ -296,7 +299,7 @@ public class Database implements DataHandler {
e.fillInStackTrace(); e.fillInStackTrace();
} }
boolean alreadyOpen = e instanceof DbException boolean alreadyOpen = e instanceof DbException
&& ((DbException)e).getErrorCode() == ErrorCode.DATABASE_ALREADY_OPEN_1; && ((DbException) e).getErrorCode() == ErrorCode.DATABASE_ALREADY_OPEN_1;
if (alreadyOpen) { if (alreadyOpen) {
stopServer(); stopServer();
} }
...@@ -899,9 +902,6 @@ public class Database implements DataHandler { ...@@ -899,9 +902,6 @@ public class Database implements DataHandler {
} }
} }
private static final ThreadLocal<Session> metaLockDebugging = new ThreadLocal<Session>();
private static final ThreadLocal<Throwable> metaLockDebuggingStack = new ThreadLocal<Throwable>();
/** /**
* Lock the metadata table for updates. * Lock the metadata table for updates.
* *
...@@ -917,16 +917,17 @@ public class Database implements DataHandler { ...@@ -917,16 +917,17 @@ public class Database implements DataHandler {
return true; return true;
} }
if (SysProperties.CHECK2) { if (SysProperties.CHECK2) {
final Session prev = metaLockDebugging.get(); final Session prev = META_LOCK_DEBUGGING.get();
if (prev == null) { if (prev == null) {
metaLockDebugging.set(session); META_LOCK_DEBUGGING.set(session);
metaLockDebuggingStack.set(new Throwable()); META_LOCK_DEBUGGING_STACK.set(new Throwable("Last meta lock granted in this stack trace, "+
"this is debug information for following IllegalStateException"));
} else if (prev != session) { } else if (prev != session) {
metaLockDebuggingStack.get().printStackTrace(); META_LOCK_DEBUGGING_STACK.get().printStackTrace();
throw new IllegalStateException("meta currently locked by " throw new IllegalStateException("meta currently locked by "
+ prev + prev +", sessionid="+ prev.getId()
+ " and trying to be locked by different session, " + " and trying to be locked by different session, "
+ session + " on same thread"); + session +", sessionid="+ session.getId() + " on same thread");
} }
} }
boolean wasLocked = meta.lock(session, true, true); boolean wasLocked = meta.lock(session, true, true);
...@@ -952,9 +953,9 @@ public class Database implements DataHandler { ...@@ -952,9 +953,9 @@ public class Database implements DataHandler {
*/ */
public void unlockMetaDebug(Session session) { public void unlockMetaDebug(Session session) {
if (SysProperties.CHECK2) { if (SysProperties.CHECK2) {
if (metaLockDebugging.get() == session) { if (META_LOCK_DEBUGGING.get() == session) {
metaLockDebugging.set(null); META_LOCK_DEBUGGING.set(null);
metaLockDebuggingStack.set(null); META_LOCK_DEBUGGING_STACK.set(null);
} }
} }
} }
...@@ -1911,13 +1912,14 @@ public class Database implements DataHandler { ...@@ -1911,13 +1912,14 @@ public class Database implements DataHandler {
t.getSQL()); t.getSQL());
} }
obj.removeChildrenAndResources(session); obj.removeChildrenAndResources(session);
} }
removeMeta(session, id); removeMeta(session, id);
} }
} }
/** /**
* Check if this database disk-based. * Check if this database is disk-based.
* *
* @return true if it is disk-based, false it it is in-memory only. * @return true if it is disk-based, false it it is in-memory only.
*/ */
......
...@@ -6,12 +6,14 @@ ...@@ -6,12 +6,14 @@
package org.h2.engine; package org.h2.engine;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.ArrayDeque;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Command; import org.h2.command.Command;
...@@ -121,6 +123,7 @@ public class Session extends SessionWithState { ...@@ -121,6 +123,7 @@ public class Session extends SessionWithState {
private long modificationMetaID = -1; private long modificationMetaID = -1;
private SubQueryInfo subQueryInfo; private SubQueryInfo subQueryInfo;
private int parsingView; private int parsingView;
private Deque<String> viewNameStack = new ArrayDeque<String>();
private int preparingQueryExpression; private int preparingQueryExpression;
private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache; private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache;
private HashMap<Object, ViewIndex> subQueryIndexCache; private HashMap<Object, ViewIndex> subQueryIndexCache;
...@@ -226,13 +229,25 @@ public class Session extends SessionWithState { ...@@ -226,13 +229,25 @@ public class Session extends SessionWithState {
return subQueryInfo; return subQueryInfo;
} }
public void setParsingView(boolean parsingView) { public void setParsingCreateView(boolean parsingView, String viewName) {
// It can be recursive, thus implemented as counter. // It can be recursive, thus implemented as counter.
this.parsingView += parsingView ? 1 : -1; this.parsingView += parsingView ? 1 : -1;
assert this.parsingView >= 0; assert this.parsingView >= 0;
if (parsingView) {
viewNameStack.push(viewName);
} else {
assert viewName.equals(viewNameStack.peek());
viewNameStack.pop();
}
}
public String getParsingCreateViewName() {
if (viewNameStack.size() == 0) {
return null;
}
return viewNameStack.peek();
} }
public boolean isParsingView() { public boolean isParsingCreateView() {
assert parsingView >= 0; assert parsingView >= 0;
return parsingView != 0; return parsingView != 0;
} }
...@@ -679,7 +694,8 @@ public class Session extends SessionWithState { ...@@ -679,7 +694,8 @@ public class Session extends SessionWithState {
for (Table table : tablesToAnalyze) { for (Table table : tablesToAnalyze) {
Analyze.analyzeTable(this, table, rows, false); Analyze.analyzeTable(this, table, rows, false);
} }
database.unlockMeta(this); // analyze can lock the meta // analyze can lock the meta
database.unlockMeta(this);
} }
tablesToAnalyze = null; tablesToAnalyze = null;
} }
......
...@@ -8,7 +8,6 @@ package org.h2.index; ...@@ -8,7 +8,6 @@ package org.h2.index;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Parser; import org.h2.command.Parser;
import org.h2.command.Prepared; import org.h2.command.Prepared;
...@@ -182,10 +181,10 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -182,10 +181,10 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
private Cursor findRecursive(SearchRow first, SearchRow last) { private Cursor findRecursive(SearchRow first, SearchRow last) {
assert recursive; assert recursive;
ResultInterface recResult = view.getRecursiveResult(); ResultInterface recursiveResult = view.getRecursiveResult();
if (recResult != null) { if (recursiveResult != null) {
recResult.reset(); recursiveResult.reset();
return new ViewCursor(this, recResult, first, last); return new ViewCursor(this, recursiveResult, first, last);
} }
if (query == null) { if (query == null) {
Parser parser = new Parser(createSession); Parser parser = new Parser(createSession);
...@@ -200,35 +199,39 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -200,35 +199,39 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
} }
SelectUnion union = (SelectUnion) query; SelectUnion union = (SelectUnion) query;
Query left = union.getLeft(); Query left = union.getLeft();
left.setNeverLazy(true);
// to ensure the last result is not closed // to ensure the last result is not closed
left.disableCache(); left.disableCache();
ResultInterface r = left.query(0); ResultInterface resultInterface = left.query(0);
LocalResult result = union.getEmptyResult(); LocalResult localResult = union.getEmptyResult();
// ensure it is not written to disk, // ensure it is not written to disk,
// because it is not closed normally // because it is not closed normally
result.setMaxMemoryRows(Integer.MAX_VALUE); localResult.setMaxMemoryRows(Integer.MAX_VALUE);
while (r.next()) { while (resultInterface.next()) {
result.addRow(r.currentRow()); Value[] cr = resultInterface.currentRow();
localResult.addRow(cr);
} }
Query right = union.getRight(); Query right = union.getRight();
r.reset(); right.setNeverLazy(true);
view.setRecursiveResult(r); resultInterface.reset();
view.setRecursiveResult(resultInterface);
// to ensure the last result is not closed // to ensure the last result is not closed
right.disableCache(); right.disableCache();
while (true) { while (true) {
r = right.query(0); resultInterface = right.query(0);
if (!r.hasNext()) { if (!resultInterface.hasNext()) {
break; break;
} }
while (r.next()) { while (resultInterface.next()) {
result.addRow(r.currentRow()); Value[] cr = resultInterface.currentRow();
localResult.addRow(cr);
} }
r.reset(); resultInterface.reset();
view.setRecursiveResult(r); view.setRecursiveResult(resultInterface);
} }
view.setRecursiveResult(null); view.setRecursiveResult(null);
result.done(); localResult.done();
return new ViewCursor(this, result, first, last); return new ViewCursor(this, localResult, first, last);
} }
/** /**
......
...@@ -48,7 +48,6 @@ import org.h2.value.Value; ...@@ -48,7 +48,6 @@ import org.h2.value.Value;
* A table stored in a MVStore. * A table stored in a MVStore.
*/ */
public class MVTable extends TableBase { public class MVTable extends TableBase {
/** /**
* The table name this thread is waiting to lock. * The table name this thread is waiting to lock.
*/ */
...@@ -64,6 +63,31 @@ public class MVTable extends TableBase { ...@@ -64,6 +63,31 @@ public class MVTable extends TableBase {
*/ */
public static final DebuggingThreadLocal<ArrayList<String>> SHARED_LOCKS; public static final DebuggingThreadLocal<ArrayList<String>> SHARED_LOCKS;
/**
* The type of trace lock events
*/
private enum TraceLockEvent{
TRACE_LOCK_OK("ok"),
TRACE_LOCK_WAITING_FOR("waiting for"),
TRACE_LOCK_REQUESTING_FOR("requesting for"),
TRACE_LOCK_TIMEOUT_AFTER("timeout after "),
TRACE_LOCK_UNLOCK("unlock"),
TRACE_LOCK_ADDED_FOR("added for"),
TRACE_LOCK_ADD_UPGRADED_FOR("add (upgraded) for ");
private final String eventText;
TraceLockEvent(String eventText) {
this.eventText = eventText;
}
public String getEventText() {
return eventText;
}
}
private static final String NO_EXTRA_INFO = "";
static { static {
if (SysProperties.THREAD_DEADLOCK_DETECTOR) { if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
WAITING_FOR_LOCK = new DebuggingThreadLocal<>(); WAITING_FOR_LOCK = new DebuggingThreadLocal<>();
...@@ -192,7 +216,7 @@ public class MVTable extends TableBase { ...@@ -192,7 +216,7 @@ public class MVTable extends TableBase {
} }
private void doLock1(Session session, int lockMode, boolean exclusive) { private void doLock1(Session session, int lockMode, boolean exclusive) {
traceLock(session, exclusive, "requesting for"); traceLock(session, exclusive, TraceLockEvent.TRACE_LOCK_REQUESTING_FOR, NO_EXTRA_INFO);
// don't get the current time unless necessary // don't get the current time unless necessary
long max = 0; long max = 0;
boolean checkDeadlock = false; boolean checkDeadlock = false;
...@@ -219,11 +243,11 @@ public class MVTable extends TableBase { ...@@ -219,11 +243,11 @@ public class MVTable extends TableBase {
max = now + TimeUnit.MILLISECONDS.toNanos(session.getLockTimeout()); max = now + TimeUnit.MILLISECONDS.toNanos(session.getLockTimeout());
} else if (now >= max) { } else if (now >= max) {
traceLock(session, exclusive, traceLock(session, exclusive,
"timeout after " + session.getLockTimeout()); TraceLockEvent.TRACE_LOCK_TIMEOUT_AFTER, NO_EXTRA_INFO+session.getLockTimeout());
throw DbException.get(ErrorCode.LOCK_TIMEOUT_1, getName()); throw DbException.get(ErrorCode.LOCK_TIMEOUT_1, getName());
} }
try { try {
traceLock(session, exclusive, "waiting for"); traceLock(session, exclusive, TraceLockEvent.TRACE_LOCK_WAITING_FOR, NO_EXTRA_INFO);
if (database.getLockMode() == Constants.LOCK_MODE_TABLE_GC) { if (database.getLockMode() == Constants.LOCK_MODE_TABLE_GC) {
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
long free = Runtime.getRuntime().freeMemory(); long free = Runtime.getRuntime().freeMemory();
...@@ -251,7 +275,7 @@ public class MVTable extends TableBase { ...@@ -251,7 +275,7 @@ public class MVTable extends TableBase {
if (exclusive) { if (exclusive) {
if (lockExclusiveSession == null) { if (lockExclusiveSession == null) {
if (lockSharedSessions.isEmpty()) { if (lockSharedSessions.isEmpty()) {
traceLock(session, exclusive, "added for"); traceLock(session, exclusive, TraceLockEvent.TRACE_LOCK_ADDED_FOR, NO_EXTRA_INFO);
session.addLock(this); session.addLock(this);
lockExclusiveSession = session; lockExclusiveSession = session;
if (SysProperties.THREAD_DEADLOCK_DETECTOR) { if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
...@@ -263,7 +287,7 @@ public class MVTable extends TableBase { ...@@ -263,7 +287,7 @@ public class MVTable extends TableBase {
return true; return true;
} else if (lockSharedSessions.size() == 1 && } else if (lockSharedSessions.size() == 1 &&
lockSharedSessions.containsKey(session)) { lockSharedSessions.containsKey(session)) {
traceLock(session, exclusive, "add (upgraded) for "); traceLock(session, exclusive, TraceLockEvent.TRACE_LOCK_ADD_UPGRADED_FOR, NO_EXTRA_INFO);
lockExclusiveSession = session; lockExclusiveSession = session;
if (SysProperties.THREAD_DEADLOCK_DETECTOR) { if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
if (EXCLUSIVE_LOCKS.get() == null) { if (EXCLUSIVE_LOCKS.get() == null) {
...@@ -289,7 +313,7 @@ public class MVTable extends TableBase { ...@@ -289,7 +313,7 @@ public class MVTable extends TableBase {
} }
} }
if (!lockSharedSessions.containsKey(session)) { if (!lockSharedSessions.containsKey(session)) {
traceLock(session, exclusive, "ok"); traceLock(session, exclusive, TraceLockEvent.TRACE_LOCK_OK, NO_EXTRA_INFO);
session.addLock(this); session.addLock(this);
lockSharedSessions.put(session, session); lockSharedSessions.put(session, session);
if (SysProperties.THREAD_DEADLOCK_DETECTOR) { if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
...@@ -387,10 +411,10 @@ public class MVTable extends TableBase { ...@@ -387,10 +411,10 @@ public class MVTable extends TableBase {
} }
} }
private void traceLock(Session session, boolean exclusive, String s) { private void traceLock(Session session, boolean exclusive, TraceLockEvent eventEnum, String extraInfo) {
if (traceLock.isDebugEnabled()) { if (traceLock.isDebugEnabled()) {
traceLock.debug("{0} {1} {2} {3}", session.getId(), traceLock.debug("{0} {1} {2} {3}", session.getId(),
exclusive ? "exclusive write lock" : "shared read lock", s, exclusive ? "exclusive write lock" : "shared read lock", eventEnum.getEventText(),
getName()); getName());
} }
} }
...@@ -408,7 +432,7 @@ public class MVTable extends TableBase { ...@@ -408,7 +432,7 @@ public class MVTable extends TableBase {
@Override @Override
public void unlock(Session s) { public void unlock(Session s) {
if (database != null) { if (database != null) {
traceLock(s, lockExclusiveSession == s, "unlock"); traceLock(s, lockExclusiveSession == s, TraceLockEvent.TRACE_LOCK_UNLOCK, NO_EXTRA_INFO);
if (lockExclusiveSession == s) { if (lockExclusiveSession == s) {
lockExclusiveSession = null; lockExclusiveSession = null;
if (SysProperties.THREAD_DEADLOCK_DETECTOR) { if (SysProperties.THREAD_DEADLOCK_DETECTOR) {
......
...@@ -86,6 +86,7 @@ public abstract class Table extends SchemaObjectBase { ...@@ -86,6 +86,7 @@ public abstract class Table extends SchemaObjectBase {
private boolean checkForeignKeyConstraints = true; private boolean checkForeignKeyConstraints = true;
private boolean onCommitDrop, onCommitTruncate; private boolean onCommitDrop, onCommitTruncate;
private volatile Row nullRow; private volatile Row nullRow;
private boolean tableExpression;
public Table(Schema schema, int id, String name, boolean persistIndexes, public Table(Schema schema, int id, String name, boolean persistIndexes,
boolean persistData) { boolean persistData) {
...@@ -195,7 +196,6 @@ public abstract class Table extends SchemaObjectBase { ...@@ -195,7 +196,6 @@ public abstract class Table extends SchemaObjectBase {
* @param operation the operation * @param operation the operation
* @param row the row * @param row the row
*/ */
@SuppressWarnings("unused")
public void commit(short operation, Row row) { public void commit(short operation, Row row) {
// nothing to do // nothing to do
} }
...@@ -233,7 +233,6 @@ public abstract class Table extends SchemaObjectBase { ...@@ -233,7 +233,6 @@ public abstract class Table extends SchemaObjectBase {
* @param allColumnsSet all columns * @param allColumnsSet all columns
* @return the scan index * @return the scan index
*/ */
@SuppressWarnings("unused")
public Index getScanIndex(Session session, int[] masks, public Index getScanIndex(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder, TableFilter[] filters, int filter, SortOrder sortOrder,
HashSet<Column> allColumnsSet) { HashSet<Column> allColumnsSet) {
...@@ -465,7 +464,6 @@ public abstract class Table extends SchemaObjectBase { ...@@ -465,7 +464,6 @@ public abstract class Table extends SchemaObjectBase {
* @param session the session * @param session the session
* @return true if it is * @return true if it is
*/ */
@SuppressWarnings("unused")
public boolean isLockedExclusivelyBy(Session session) { public boolean isLockedExclusivelyBy(Session session) {
return false; return false;
} }
...@@ -836,7 +834,7 @@ public abstract class Table extends SchemaObjectBase { ...@@ -836,7 +834,7 @@ public abstract class Table extends SchemaObjectBase {
} }
/** /**
* Remove the given view from the list. * Remove the given view from the dependent views list.
* *
* @param view the view to remove * @param view the view to remove
*/ */
...@@ -1166,7 +1164,6 @@ public abstract class Table extends SchemaObjectBase { ...@@ -1166,7 +1164,6 @@ public abstract class Table extends SchemaObjectBase {
* @return an object array with the sessions involved in the deadlock, or * @return an object array with the sessions involved in the deadlock, or
* null * null
*/ */
@SuppressWarnings("unused")
public ArrayList<Session> checkDeadlock(Session session, Session clash, public ArrayList<Session> checkDeadlock(Session session, Session clash,
Set<Session> visited) { Set<Session> visited) {
return null; return null;
...@@ -1243,4 +1240,11 @@ public abstract class Table extends SchemaObjectBase { ...@@ -1243,4 +1240,11 @@ public abstract class Table extends SchemaObjectBase {
return false; return false;
} }
public void setTableExpression(boolean tableExpression) {
this.tableExpression = tableExpression;
}
public boolean isTableExpression() {
return tableExpression;
}
} }
...@@ -8,9 +8,11 @@ package org.h2.table; ...@@ -8,9 +8,11 @@ package org.h2.table;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Prepared; import org.h2.command.Prepared;
import org.h2.command.ddl.CreateTableData;
import org.h2.command.dml.Query; import org.h2.command.dml.Query;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
...@@ -50,21 +52,22 @@ public class TableView extends Table { ...@@ -50,21 +52,22 @@ public class TableView extends Table {
private Column[] columnTemplates; private Column[] columnTemplates;
private Query viewQuery; private Query viewQuery;
private ViewIndex index; private ViewIndex index;
private boolean recursive; private boolean allowRecursive;
private DbException createException; private DbException createException;
private long lastModificationCheck; private long lastModificationCheck;
private long maxDataModificationId; private long maxDataModificationId;
private User owner; private User owner;
private Query topQuery; private Query topQuery;
private ResultInterface recursiveResult; private ResultInterface recursiveResult;
private boolean tableExpression;
private boolean isRecursiveQueryDetected; private boolean isRecursiveQueryDetected;
private boolean isTableExpression;
private boolean isPersistent;
public TableView(Schema schema, int id, String name, String querySQL, public TableView(Schema schema, int id, String name, String querySQL,
ArrayList<Parameter> params, Column[] columnTemplates, Session session, ArrayList<Parameter> params, Column[] columnTemplates, Session session,
boolean recursive, boolean literalsChecked) { boolean allowRecursive, boolean literalsChecked, boolean isTableExpression, boolean isPersistent) {
super(schema, id, name, false, true); super(schema, id, name, false, true);
init(querySQL, params, columnTemplates, session, recursive, literalsChecked); init(querySQL, params, columnTemplates, session, allowRecursive, literalsChecked, isTableExpression, isPersistent);
} }
/** /**
...@@ -80,41 +83,50 @@ public class TableView extends Table { ...@@ -80,41 +83,50 @@ public class TableView extends Table {
boolean recursive, boolean force, boolean literalsChecked) { boolean recursive, boolean force, boolean literalsChecked) {
String oldQuerySQL = this.querySQL; String oldQuerySQL = this.querySQL;
Column[] oldColumnTemplates = this.columnTemplates; Column[] oldColumnTemplates = this.columnTemplates;
boolean oldRecursive = this.recursive; boolean oldRecursive = this.allowRecursive;
init(querySQL, null, init(querySQL, null,
newColumnTemplates == null ? this.columnTemplates newColumnTemplates == null ? this.columnTemplates
: newColumnTemplates, : newColumnTemplates,
session, recursive, literalsChecked); session, recursive, literalsChecked, isTableExpression, isPersistent);
DbException e = recompile(session, force, true); DbException e = recompile(session, force, true);
if (e != null) { if (e != null) {
init(oldQuerySQL, null, oldColumnTemplates, session, oldRecursive, literalsChecked); init(oldQuerySQL, null, oldColumnTemplates, session, oldRecursive,
literalsChecked, isTableExpression, isPersistent);
recompile(session, true, false); recompile(session, true, false);
throw e; throw e;
} }
} }
private synchronized void init(String querySQL, ArrayList<Parameter> params, private synchronized void init(String querySQL, ArrayList<Parameter> params,
Column[] columnTemplates, Session session, boolean recursive, boolean literalsChecked) { Column[] columnTemplates, Session session, boolean allowRecursive, boolean literalsChecked,
boolean isTableExpression, boolean isPersistent) {
this.querySQL = querySQL; this.querySQL = querySQL;
this.columnTemplates = columnTemplates; this.columnTemplates = columnTemplates;
this.recursive = recursive; this.allowRecursive = allowRecursive;
this.isRecursiveQueryDetected = false; this.isRecursiveQueryDetected = false;
index = new ViewIndex(this, querySQL, params, recursive); this.isTableExpression = isTableExpression;
this.isPersistent = isPersistent;
index = new ViewIndex(this, querySQL, params, allowRecursive);
initColumnsAndTables(session, literalsChecked); initColumnsAndTables(session, literalsChecked);
} }
private static Query compileViewQuery(Session session, String sql, boolean literalsChecked) { private Query compileViewQuery(Session session, String sql, boolean literalsChecked, String viewName) {
Prepared p; Prepared p;
session.setParsingView(true); session.setParsingCreateView(true, viewName);
try { try {
p = session.prepare(sql, false, literalsChecked); p = session.prepare(sql, false, literalsChecked);
} finally { } finally {
session.setParsingView(false); session.setParsingCreateView(false, viewName);
} }
if (!(p instanceof Query)) { if (!(p instanceof Query)) {
throw DbException.getSyntaxError(sql, 0); throw DbException.getSyntaxError(sql, 0);
} }
return (Query) p; Query q = (Query) p;
// only potentially recursive cte queries need to be non-lazy
if (isTableExpression && allowRecursive) {
q.setNeverLazy(true);
}
return q;
} }
/** /**
...@@ -129,7 +141,7 @@ public class TableView extends Table { ...@@ -129,7 +141,7 @@ public class TableView extends Table {
public synchronized DbException recompile(Session session, boolean force, public synchronized DbException recompile(Session session, boolean force,
boolean clearIndexCache) { boolean clearIndexCache) {
try { try {
compileViewQuery(session, querySQL, false); compileViewQuery(session, querySQL, false, getName());
} catch (DbException e) { } catch (DbException e) {
if (!force) { if (!force) {
return e; return e;
...@@ -151,15 +163,16 @@ public class TableView extends Table { ...@@ -151,15 +163,16 @@ public class TableView extends Table {
private void initColumnsAndTables(Session session, boolean literalsChecked) { private void initColumnsAndTables(Session session, boolean literalsChecked) {
Column[] cols; Column[] cols;
removeDependentViewFromTables(); removeCurrentViewFromOtherTables();
setTableExpression(isTableExpression);
try { try {
Query query = compileViewQuery(session, querySQL, literalsChecked); Query compiledQuery = compileViewQuery(session, querySQL, literalsChecked, getName());
this.querySQL = query.getPlanSQL(); this.querySQL = compiledQuery.getPlanSQL();
tables = New.arrayList(query.getTables()); tables = New.arrayList(compiledQuery.getTables());
ArrayList<Expression> expressions = query.getExpressions(); ArrayList<Expression> expressions = compiledQuery.getExpressions();
ArrayList<Column> list = New.arrayList(); ArrayList<Column> list = New.arrayList();
ColumnNamer columnNamer= new ColumnNamer(session); ColumnNamer columnNamer = new ColumnNamer(session);
for (int i = 0, count = query.getColumnCount(); i < count; i++) { for (int i = 0, count = compiledQuery.getColumnCount(); i < count; i++) {
Expression expr = expressions.get(i); Expression expr = expressions.get(i);
String name = null; String name = null;
int type = Value.UNKNOWN; int type = Value.UNKNOWN;
...@@ -170,7 +183,7 @@ public class TableView extends Table { ...@@ -170,7 +183,7 @@ public class TableView extends Table {
if (name == null) { if (name == null) {
name = expr.getAlias(); name = expr.getAlias();
} }
name = columnNamer.getColumnName(expr,i,name); name = columnNamer.getColumnName(expr, i, name);
if (type == Value.UNKNOWN) { if (type == Value.UNKNOWN) {
type = expr.getType(); type = expr.getType();
} }
...@@ -200,7 +213,7 @@ public class TableView extends Table { ...@@ -200,7 +213,7 @@ public class TableView extends Table {
} }
cols = list.toArray(new Column[0]); cols = list.toArray(new Column[0]);
createException = null; createException = null;
viewQuery = query; viewQuery = compiledQuery;
} catch (DbException e) { } catch (DbException e) {
e.addSQL(getCreateSQL()); e.addSQL(getCreateSQL());
createException = e; createException = e;
...@@ -214,7 +227,7 @@ public class TableView extends Table { ...@@ -214,7 +227,7 @@ public class TableView extends Table {
} }
tables = New.arrayList(); tables = New.arrayList();
cols = new Column[0]; cols = new Column[0];
if (recursive && columnTemplates != null) { if (allowRecursive && columnTemplates != null) {
cols = new Column[columnTemplates.length]; cols = new Column[columnTemplates.length];
for (int i = 0; i < columnTemplates.length; i++) { for (int i = 0; i < columnTemplates.length; i++) {
cols[i] = columnTemplates[i].getClone(); cols[i] = columnTemplates[i].getClone();
...@@ -318,6 +331,9 @@ public class TableView extends Table { ...@@ -318,6 +331,9 @@ public class TableView extends Table {
buff.append("FORCE "); buff.append("FORCE ");
} }
buff.append("VIEW "); buff.append("VIEW ");
if (isTableExpression) {
buff.append("TABLE_EXPRESSION ");
}
buff.append(quotedName); buff.append(quotedName);
if (comment != null) { if (comment != null) {
buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment)); buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment));
...@@ -416,7 +432,7 @@ public class TableView extends Table { ...@@ -416,7 +432,7 @@ public class TableView extends Table {
@Override @Override
public void removeChildrenAndResources(Session session) { public void removeChildrenAndResources(Session session) {
removeDependentViewFromTables(); removeCurrentViewFromOtherTables();
super.removeChildrenAndResources(session); super.removeChildrenAndResources(session);
database.removeMeta(session, getId()); database.removeMeta(session, getId());
querySQL = null; querySQL = null;
...@@ -438,7 +454,7 @@ public class TableView extends Table { ...@@ -438,7 +454,7 @@ public class TableView extends Table {
@Override @Override
public String getSQL() { public String getSQL() {
if (isTemporary() && querySQL!=null) { if (isTemporary() && querySQL != null) {
return "(\n" + StringUtils.indent(querySQL) + ")"; return "(\n" + StringUtils.indent(querySQL) + ")";
} }
return super.getSQL(); return super.getSQL();
...@@ -500,7 +516,7 @@ public class TableView extends Table { ...@@ -500,7 +516,7 @@ public class TableView extends Table {
return null; return null;
} }
private void removeDependentViewFromTables() { private void removeCurrentViewFromOtherTables() {
if (tables != null) { if (tables != null) {
for (Table t : tables) { for (Table t : tables) {
t.removeDependentView(this); t.removeDependentView(this);
...@@ -538,8 +554,9 @@ public class TableView extends Table { ...@@ -538,8 +554,9 @@ public class TableView extends Table {
Schema mainSchema = session.getDatabase().getSchema(Constants.SCHEMA_MAIN); Schema mainSchema = session.getDatabase().getSchema(Constants.SCHEMA_MAIN);
String querySQL = query.getPlanSQL(); String querySQL = query.getPlanSQL();
TableView v = new TableView(mainSchema, 0, name, TableView v = new TableView(mainSchema, 0, name,
querySQL, query.getParameters(), null, session, querySQL, query.getParameters(), null /* column templates */, session,
false, true /* literals have already been checked when parsing original query */); false/* allow recursive */, true /* literals have already been checked when parsing original query */,
false /* is table expression */, false/* is persistent*/);
if (v.createException != null) { if (v.createException != null) {
throw v.createException; throw v.createException;
} }
...@@ -586,12 +603,12 @@ public class TableView extends Table { ...@@ -586,12 +603,12 @@ public class TableView extends Table {
} }
public boolean isRecursive() { public boolean isRecursive() {
return recursive; return allowRecursive;
} }
@Override @Override
public boolean isDeterministic() { public boolean isDeterministic() {
if (recursive || viewQuery == null) { if (allowRecursive || viewQuery == null) {
return false; return false;
} }
return viewQuery.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR); return viewQuery.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR);
...@@ -608,14 +625,6 @@ public class TableView extends Table { ...@@ -608,14 +625,6 @@ public class TableView extends Table {
return recursiveResult; return recursiveResult;
} }
public void setTableExpression(boolean tableExpression) {
this.tableExpression = tableExpression;
}
public boolean isTableExpression() {
return tableExpression;
}
@Override @Override
public void addDependencies(HashSet<DbObject> dependencies) { public void addDependencies(HashSet<DbObject> dependencies) {
super.addDependencies(dependencies); super.addDependencies(dependencies);
...@@ -697,4 +706,144 @@ public class TableView extends Table { ...@@ -697,4 +706,144 @@ public class TableView extends Table {
return true; return true;
} }
public List<Table> getTables() {
return tables;
}
public boolean isPersistent() {
return isPersistent;
}
public static TableView createTableViewMaybeRecursive(Schema schema, int id, String name, String querySQL,
ArrayList<Parameter> parameters, Column[] columnTemplates, Session session,
boolean literalsChecked, boolean isTableExpression, boolean isPersistent, Database db) {
Table recursiveTable = TableView.createShadowTableForRecursiveTableExpression(isPersistent, session, name,
schema, Arrays.asList(columnTemplates), db);
List<Column> columnTemplateList;
String[] querySQLOutput = new String[]{null};
ArrayList<String> columnNames = new ArrayList<String>();
for (Column columnTemplate: columnTemplates) {
columnNames.add(columnTemplate.getName());
}
try {
Prepared withQuery = session.prepare(querySQL, false, false);
if (isPersistent) {
withQuery.setSession(session);
}
columnTemplateList = TableView.createQueryColumnTemplateList(columnNames.toArray(new String[1]),
(Query) withQuery, querySQLOutput);
} finally {
TableView.destroyShadowTableForRecursiveExpression(isPersistent, session, recursiveTable);
}
// build with recursion turned on
TableView view = new TableView(schema, id, name, querySQL,
parameters, columnTemplateList.toArray(columnTemplates), session,
true/* try recursive */, literalsChecked, isTableExpression, isPersistent);
// is recursion really detected ? if not - recreate it without recursion flag and no recursive index
if (!view.isRecursiveQueryDetected()) {
if (isPersistent) {
db.addSchemaObject(session, view);
view.lock(session, true, true);
session.getDatabase().removeSchemaObject(session, view);
// during database startup - this method does not normally get called - and it needs to be
// to correctly un-register the table which the table expression uses...
view.removeChildrenAndResources(session);
} else {
session.removeLocalTempTable(view);
}
view = new TableView(schema, id, name, querySQL, parameters,
columnTemplates, session,
false/* detected not recursive */, literalsChecked, isTableExpression, isPersistent);
}
return view;
}
/**
* Creates a list of column templates from a query (usually from WITH query,
* but could be any query)
*
* @param cols - an optional list of column names (can be specified by WITH
* clause overriding usual select names)
* @param theQuery - the query object we want the column list for
* @param querySQLOutput - array of length 1 to receive extra 'output' field
* in addition to return value - containing the SQL query of the
* Query object
* @return a list of column object returned by withQuery
*/
public static List<Column> createQueryColumnTemplateList(String[] cols,
Query theQuery, String[] querySQLOutput) {
List<Column> columnTemplateList = new ArrayList<>();
theQuery.prepare();
// String array of length 1 is to receive extra 'output' field in addition to
// return value
querySQLOutput[0] = StringUtils.cache(theQuery.getPlanSQL());
ColumnNamer columnNamer = new ColumnNamer(theQuery.getSession());
ArrayList<Expression> withExpressions = theQuery.getExpressions();
for (int i = 0; i < withExpressions.size(); ++i) {
Expression columnExp = withExpressions.get(i);
// use the passed in column name if supplied, otherwise use alias
// (if found) otherwise use column name derived from column
// expression
String columnName = columnNamer.getColumnName(columnExp, i, cols);
columnTemplateList.add(new Column(columnName,
columnExp.getType()));
}
return columnTemplateList;
}
public static Table createShadowTableForRecursiveTableExpression(boolean isPersistent, Session targetSession,
String cteViewName, Schema schema, List<Column> columns, Database db) {
// create table data object
CreateTableData recursiveTableData = new CreateTableData();
recursiveTableData.id = db.allocateObjectId();
recursiveTableData.columns = new ArrayList<Column>(columns);
recursiveTableData.tableName = cteViewName;
recursiveTableData.temporary = !isPersistent;
recursiveTableData.persistData = true;
recursiveTableData.persistIndexes = isPersistent;
recursiveTableData.create = true;
recursiveTableData.session = targetSession;
// this gets a meta table lock that is not released
Table recursiveTable = schema.createTable(recursiveTableData);
if (isPersistent) {
// this unlock is to prevent lock leak from schema.createTable()
db.unlockMeta(targetSession);
synchronized (targetSession) {
db.addSchemaObject(targetSession, recursiveTable);
}
} else {
targetSession.addLocalTempTable(recursiveTable);
}
return recursiveTable;
}
public static void destroyShadowTableForRecursiveExpression(boolean isPersistent, Session targetSession,
Table recursiveTable) {
if (recursiveTable != null) {
if (isPersistent) {
recursiveTable.lock(targetSession, true, true);
targetSession.getDatabase().removeSchemaObject(targetSession, recursiveTable);
} else {
targetSession.removeLocalTempTable(recursiveTable);
}
// both removeSchemaObject and removeLocalTempTable hold meta locks - release them here
targetSession.getDatabase().unlockMeta(targetSession);
}
}
} }
...@@ -54,6 +54,7 @@ import org.h2.test.db.TestOpenClose; ...@@ -54,6 +54,7 @@ import org.h2.test.db.TestOpenClose;
import org.h2.test.db.TestOptimizations; import org.h2.test.db.TestOptimizations;
import org.h2.test.db.TestOptimizerHints; import org.h2.test.db.TestOptimizerHints;
import org.h2.test.db.TestOutOfMemory; import org.h2.test.db.TestOutOfMemory;
import org.h2.test.db.TestPersistentCommonTableExpressions;
import org.h2.test.db.TestPowerOff; import org.h2.test.db.TestPowerOff;
import org.h2.test.db.TestQueryCache; import org.h2.test.db.TestQueryCache;
import org.h2.test.db.TestReadOnly; import org.h2.test.db.TestReadOnly;
...@@ -762,6 +763,10 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -762,6 +763,10 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestReadOnly()); addTest(new TestReadOnly());
addTest(new TestRecursiveQueries()); addTest(new TestRecursiveQueries());
addTest(new TestGeneralCommonTableQueries()); addTest(new TestGeneralCommonTableQueries());
if (!memory) {
// requires persistent store for reconnection tests
addTest(new TestPersistentCommonTableExpressions());
}
addTest(new TestRights()); addTest(new TestRights());
addTest(new TestRunscript()); addTest(new TestRunscript());
addTest(new TestSQLInjection()); addTest(new TestSQLInjection());
......
...@@ -124,7 +124,6 @@ public abstract class TestBase { ...@@ -124,7 +124,6 @@ public abstract class TestBase {
* *
* @param seed the random seed value * @param seed the random seed value
*/ */
@SuppressWarnings("unused")
public void testCase(int seed) throws Exception { public void testCase(int seed) throws Exception {
// do nothing // do nothing
} }
......
package org.h2.test.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.test.TestBase;
/**
* Base class for common table expression tests
*/
public abstract class AbstractBaseForCommonTableExpressions extends TestBase {
protected void testRepeatedQueryWithSetup(int maxRetries, String[] expectedRowData, String[] expectedColumnNames, int expectedNumbeOfRows, String setupSQL,
String withQuery, int closeAndReopenDatabaseConnectionOnIteration, String[] expectedColumnTypes) throws SQLException {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
for (int queryRunTries = 1; queryRunTries <= maxRetries; queryRunTries++) {
Statement stat = conn.createStatement();
stat.execute(setupSQL);
stat.close();
// close and re-open connection for one iteration to make sure the query work between connections
if (queryRunTries == closeAndReopenDatabaseConnectionOnIteration) {
conn.close();
conn = getConnection("commonTableExpressionQueries");
}
prep = conn.prepareStatement(withQuery);
rs = prep.executeQuery();
for (int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++) {
assertTrue(rs.getMetaData().getColumnLabel(columnIndex) != null);
assertEquals(expectedColumnNames[columnIndex - 1], rs.getMetaData().getColumnLabel(columnIndex));
assertEquals("wrongly type column "+rs.getMetaData().getColumnLabel(columnIndex)+" on iteration#"+queryRunTries,
expectedColumnTypes[columnIndex - 1], rs.getMetaData().getColumnTypeName(columnIndex));
}
int rowNdx = 0;
while (rs.next()) {
StringBuffer buf = new StringBuffer();
for (int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++) {
buf.append("|"+rs.getString(columnIndex));
}
assertEquals(expectedRowData[rowNdx], buf.toString());
rowNdx++;
}
assertEquals(expectedNumbeOfRows, rowNdx);
rs.close();
prep.close();
}
conn.close();
deleteDb("commonTableExpressionQueries");
}
}
...@@ -10,12 +10,13 @@ import java.sql.PreparedStatement; ...@@ -10,12 +10,13 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.Statement; import java.sql.Statement;
import org.h2.jdbc.JdbcSQLException; import org.h2.jdbc.JdbcSQLException;
import org.h2.test.TestAll;
import org.h2.test.TestBase; import org.h2.test.TestBase;
/** /**
* Test non-recursive queries using WITH, but more than one common table defined. * Test non-recursive queries using WITH, but more than one common table defined.
*/ */
public class TestGeneralCommonTableQueries extends TestBase { public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExpressions {
/** /**
* Run just this test. * Run just this test.
...@@ -41,7 +42,9 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -41,7 +42,9 @@ public class TestGeneralCommonTableQueries extends TestBase {
testMerge(); testMerge();
testCreateTable(); testCreateTable();
testNestedSQL(); testNestedSQL();
testRecursiveTable(); testSimple4RowRecursiveQuery();
testSimple2By4RowRecursiveQuery();
testSimple3RowRecursiveQueryWithLazyEval();
} }
private void testSimpleSelect() throws Exception { private void testSimpleSelect() throws Exception {
...@@ -52,18 +55,18 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -52,18 +55,18 @@ public class TestGeneralCommonTableQueries extends TestBase {
ResultSet rs; ResultSet rs;
stat = conn.createStatement(); stat = conn.createStatement();
final String simple_two_column_query = "with " + final String simpleTwoColumnQuery = "with " +
"t1(n) as (select 1 as first) " + "t1(n) as (select 1 as first) " +
",t2(n) as (select 2 as first) " + ",t2(n) as (select 2 as first) " +
"select * from t1 union all select * from t2"; "select * from t1 union all select * from t2";
rs = stat.executeQuery(simple_two_column_query); rs = stat.executeQuery(simpleTwoColumnQuery);
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(1, rs.getInt(1)); assertEquals(1, rs.getInt(1));
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(1));
assertFalse(rs.next()); assertFalse(rs.next());
prep = conn.prepareStatement(simple_two_column_query); prep = conn.prepareStatement(simpleTwoColumnQuery);
rs = prep.executeQuery(); rs = prep.executeQuery();
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(1, rs.getInt(1)); assertEquals(1, rs.getInt(1));
...@@ -75,7 +78,8 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -75,7 +78,8 @@ public class TestGeneralCommonTableQueries extends TestBase {
"t1(n) as (select 2 as first) " + "t1(n) as (select 2 as first) " +
",t2(n) as (select 3 as first) " + ",t2(n) as (select 3 as first) " +
"select * from t1 union all select * from t2 where n<>?"); "select * from t1 union all select * from t2 where n<>?");
prep.setInt(1, 0); // omit no lines since zero is not in list
prep.setInt(1, 0);
rs = prep.executeQuery(); rs = prep.executeQuery();
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(1));
...@@ -88,7 +92,8 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -88,7 +92,8 @@ public class TestGeneralCommonTableQueries extends TestBase {
",t2(n) as (select 3 as first) " + ",t2(n) as (select 3 as first) " +
",t3(n) as (select 4 as first) " + ",t3(n) as (select 4 as first) " +
"select * from t1 union all select * from t2 union all select * from t3 where n<>?"); "select * from t1 union all select * from t2 union all select * from t3 where n<>?");
prep.setInt(1, 4); // omit 4 line (last)
prep.setInt(1, 4);
rs = prep.executeQuery(); rs = prep.executeQuery();
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(1));
...@@ -111,15 +116,16 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -111,15 +116,16 @@ public class TestGeneralCommonTableQueries extends TestBase {
",t2 as (select first_col+1 from t1) " + ",t2 as (select first_col+1 from t1) " +
",t3 as (select 4 as first_col) " + ",t3 as (select 4 as first_col) " +
"select * from t1 union all select * from t2 union all select * from t3 where first_col<>?"); "select * from t1 union all select * from t2 union all select * from t3 where first_col<>?");
prep.setInt(1, 4); // omit 4 line (last)
prep.setInt(1, 4);
rs = prep.executeQuery(); rs = prep.executeQuery();
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(1));
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(3, rs.getInt("FIRST_COL")); assertEquals(3, rs.getInt("FIRST_COL"));
assertFalse(rs.next()); assertFalse(rs.next());
assertEquals(rs.getMetaData().getColumnCount(),1); assertEquals(rs.getMetaData().getColumnCount(), 1);
assertEquals("FIRST_COL",rs.getMetaData().getColumnLabel(1)); assertEquals("FIRST_COL", rs.getMetaData().getColumnLabel(1));
conn.close(); conn.close();
deleteDb("commonTableExpressionQueries"); deleteDb("commonTableExpressionQueries");
...@@ -171,7 +177,7 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -171,7 +177,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
prep.setInt(6, 6); prep.setInt(6, 6);
rs = prep.executeQuery(); rs = prep.executeQuery();
for(int n: new int[]{1,2,3,4,5,6} ){ for (int n: new int[]{1, 2, 3, 4, 5, 6}) {
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(n, rs.getInt(1)); assertEquals(n, rs.getInt(1));
} }
...@@ -180,7 +186,7 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -180,7 +186,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
// call it twice // call it twice
rs = prep.executeQuery(); rs = prep.executeQuery();
for(int n: new int[]{1,2,3,4,5,6} ){ for (int n: new int[]{1, 2, 3, 4, 5, 6}) {
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(n, rs.getInt(1)); assertEquals(n, rs.getInt(1));
} }
...@@ -217,21 +223,20 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -217,21 +223,20 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(n, rs.getInt(1)); assertEquals(n, rs.getInt(1));
} }
assertEquals("X",rs.getMetaData().getColumnLabel(1)); assertEquals("X", rs.getMetaData().getColumnLabel(1));
assertEquals("'T1'",rs.getMetaData().getColumnLabel(2)); assertEquals("'T1'", rs.getMetaData().getColumnLabel(2));
assertFalse(rs.next()); assertFalse(rs.next());
try{ try {
prep = conn.prepareStatement("SELECT * FROM t1 UNION ALL SELECT * FROM t2 "+ prep = conn.prepareStatement("SELECT * FROM t1 UNION ALL SELECT * FROM t2 "+
"UNION ALL SELECT X, 'Q' FROM SYSTEM_RANGE(5,6)"); "UNION ALL SELECT X, 'Q' FROM SYSTEM_RANGE(5,6)");
rs = prep.executeQuery(); rs = prep.executeQuery();
fail("Temp view T1 was accessible after previous WITH statement finished "+ fail("Temp view T1 was accessible after previous WITH statement finished "+
"- but should not have been."); "- but should not have been.");
} } catch (JdbcSQLException e) {
catch(JdbcSQLException e){
// ensure the T1 table has been removed even without auto commit // ensure the T1 table has been removed even without auto commit
assertContains(e.getMessage(),"Table \"T1\" not found;"); assertContains(e.getMessage(), "Table \"T1\" not found;");
} }
conn.close(); conn.close();
...@@ -290,13 +295,13 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -290,13 +295,13 @@ public class TestGeneralCommonTableQueries extends TestBase {
prep.setInt(2, 2); prep.setInt(2, 2);
rowCount = prep.executeUpdate(); rowCount = prep.executeUpdate();
assertEquals(2,rowCount); assertEquals(2, rowCount);
rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); rs = stat.executeQuery("SELECT ID, X,Y FROM T1");
for (int n : new int[] { 1, 2 }) { for (int n : new int[] { 1, 2 }) {
assertTrue(rs.next()); assertTrue(rs.next());
assertTrue(rs.getInt(1)!=0); assertTrue(rs.getInt(1) != 0);
assertEquals(n, rs.getInt(2)); assertEquals(n, rs.getInt(2));
assertEquals("Y1", rs.getString(3)); assertEquals("Y1", rs.getString(3));
} }
...@@ -321,7 +326,7 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -321,7 +326,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
+"DELETE FROM T1 WHERE X IN ( SELECT v1.X FROM v1 )"); +"DELETE FROM T1 WHERE X IN ( SELECT v1.X FROM v1 )");
rowCount = prep.executeUpdate(); rowCount = prep.executeUpdate();
assertEquals(2,rowCount); assertEquals(2, rowCount);
rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); rs = stat.executeQuery("SELECT ID, X,Y FROM T1");
...@@ -348,13 +353,13 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -348,13 +353,13 @@ public class TestGeneralCommonTableQueries extends TestBase {
+"MERGE INTO T1 KEY(ID) SELECT v1.X AS ID, v1.X, v1.Y FROM v1"); +"MERGE INTO T1 KEY(ID) SELECT v1.X AS ID, v1.X, v1.Y FROM v1");
rowCount = prep.executeUpdate(); rowCount = prep.executeUpdate();
assertEquals(3,rowCount); assertEquals(3, rowCount);
rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); rs = stat.executeQuery("SELECT ID, X,Y FROM T1");
for (int n : new int[] { 1, 2, 3 }) { for (int n : new int[] { 1, 2, 3 }) {
assertTrue(rs.next()); assertTrue(rs.next());
assertTrue(rs.getInt(1)!=0); assertTrue(rs.getInt(1) != 0);
assertEquals(n, rs.getInt(2)); assertEquals(n, rs.getInt(2));
assertEquals("X1", rs.getString(3)); assertEquals("X1", rs.getString(3));
} }
...@@ -377,13 +382,13 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -377,13 +382,13 @@ public class TestGeneralCommonTableQueries extends TestBase {
+"CREATE TABLE IF NOT EXISTS T1 AS SELECT v1.X AS ID, v1.X, v1.Y FROM v1"); +"CREATE TABLE IF NOT EXISTS T1 AS SELECT v1.X AS ID, v1.X, v1.Y FROM v1");
success = prep.execute(); success = prep.execute();
assertEquals(false,success); assertEquals(false, success);
rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); rs = stat.executeQuery("SELECT ID, X,Y FROM T1");
for (int n : new int[] { 1, 2, 3 }) { for (int n : new int[] { 1, 2, 3 }) {
assertTrue(rs.next()); assertTrue(rs.next());
assertTrue(rs.getInt(1)!=0); assertTrue(rs.getInt(1) != 0);
assertEquals(n, rs.getInt(2)); assertEquals(n, rs.getInt(2));
assertEquals("X1", rs.getString(3)); assertEquals("X1", rs.getString(3));
} }
...@@ -432,9 +437,9 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -432,9 +437,9 @@ public class TestGeneralCommonTableQueries extends TestBase {
for (String keyLetter : new String[] { "a", "b" }) { for (String keyLetter : new String[] { "a", "b" }) {
assertTrue(rs.next()); assertTrue(rs.next());
assertContains("ab",rs.getString(1)); assertContains("ab", rs.getString(1));
assertEquals(rs.getString(1),keyLetter); assertEquals(rs.getString(1), keyLetter);
assertTrue(rs.getInt(2)!=0); assertTrue(rs.getInt(2) != 0);
} }
conn.close(); conn.close();
deleteDb("commonTableExpressionQueries"); deleteDb("commonTableExpressionQueries");
...@@ -459,10 +464,10 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -459,10 +464,10 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertEquals(n, rs.getInt(1)); assertEquals(n, rs.getInt(1));
assertEquals(n, rs.getInt(4)); assertEquals(n, rs.getInt(4));
} }
assertEquals("ONE",rs.getMetaData().getColumnLabel(1)); assertEquals("ONE", rs.getMetaData().getColumnLabel(1));
assertEquals("TWO",rs.getMetaData().getColumnLabel(2)); assertEquals("TWO", rs.getMetaData().getColumnLabel(2));
assertEquals("THREE",rs.getMetaData().getColumnLabel(3)); assertEquals("THREE", rs.getMetaData().getColumnLabel(3));
assertEquals("X",rs.getMetaData().getColumnLabel(4)); assertEquals("X", rs.getMetaData().getColumnLabel(4));
assertFalse(rs.next()); assertFalse(rs.next());
...@@ -470,97 +475,81 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -470,97 +475,81 @@ public class TestGeneralCommonTableQueries extends TestBase {
deleteDb("commonTableExpressionQueries"); deleteDb("commonTableExpressionQueries");
} }
private void testRecursiveTable() throws Exception { private void testSimple4RowRecursiveQuery() throws Exception {
String[] expectedRowData =new String[]{"|meat|null","|fruit|3","|veg|2"};
String[] expectedColumnNames =new String[]{"VAL",
"SUM(SELECT\n" +
" X\n" +
"FROM PUBLIC.\"\" BB\n" +
" /* SELECT\n" +
" SUM(1) AS X,\n" +
" A\n" +
" FROM PUBLIC.B\n" +
" /++ PUBLIC.B.tableScan ++/\n" +
" /++ WHERE A IS ?1\n" +
" ++/\n" +
" /++ scanCount: 4 ++/\n" +
" INNER JOIN PUBLIC.C\n" +
" /++ PUBLIC.C.tableScan ++/\n" +
" ON 1=1\n" +
" WHERE (A IS ?1)\n" +
" AND (B.VAL = C.B)\n" +
" GROUP BY A: A IS A.VAL\n" +
" */\n" +
" /* scanCount: 1 */\n" +
"WHERE BB.A IS A.VAL)"};
deleteDb("commonTableExpressionQueries"); String[] expectedRowData = new String[]{"|1", "|2", "|3"};
Connection conn = getConnection("commonTableExpressionQueries"); String[] expectedColumnTypes = new String[]{"INTEGER"};
PreparedStatement prep; String[] expectedColumnNames = new String[]{"N"};
ResultSet rs;
String SETUP_SQL = String setupSQL = "-- do nothing";
"DROP TABLE IF EXISTS A; " String withQuery = "with recursive r(n) as (\n"+
+"DROP TABLE IF EXISTS B; " "(select 1) union all (select n+1 from r where n < 3)\n"+
+"DROP TABLE IF EXISTS C; " ")\n"+
+"CREATE TABLE A(VAL VARCHAR(255)); " "select n from r";
+"CREATE TABLE B(A VARCHAR(255), VAL VARCHAR(255)); "
+"CREATE TABLE C(B VARCHAR(255), VAL VARCHAR(255)); "
+" "
+"INSERT INTO A VALUES('fruit'); "
+"INSERT INTO B VALUES('fruit','apple'); "
+"INSERT INTO B VALUES('fruit','banana'); "
+"INSERT INTO C VALUES('apple', 'golden delicious');"
+"INSERT INTO C VALUES('apple', 'granny smith'); "
+"INSERT INTO C VALUES('apple', 'pippin'); "
+"INSERT INTO A VALUES('veg'); "
+"INSERT INTO B VALUES('veg', 'carrot'); "
+"INSERT INTO C VALUES('carrot', 'nantes'); "
+"INSERT INTO C VALUES('carrot', 'imperator'); "
+"INSERT INTO C VALUES(null, 'banapple'); "
+"INSERT INTO A VALUES('meat'); "
;
String WITH_QUERY = "WITH BB as (SELECT \n" +
"sum(1) as X, \n" +
"a \n" +
"FROM B \n" +
"JOIN C ON B.val=C.b \n" +
"GROUP BY a) \n" +
"SELECT \n" +
"A.val, \n" +
"sum(SELECT X FROM BB WHERE BB.a IS A.val)\n" +
// AS SUM_X
"FROM A \n" + "GROUP BY A.val";
for(int queryRunTries=1;queryRunTries<4;queryRunTries++){
Statement stat = conn.createStatement();
stat.execute(SETUP_SQL);
stat.close();
prep = conn.prepareStatement(WITH_QUERY);
rs = prep.executeQuery(); int maxRetries = 3;
for(int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++){ int expectedNumberOfRows = expectedRowData.length;
// previously the column label was null or had \n or \r in the string
assertTrue(rs.getMetaData().getColumnLabel(columnIndex)!=null); testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
assertEquals(expectedColumnNames[columnIndex-1],rs.getMetaData().getColumnLabel(columnIndex)); withQuery, maxRetries - 1, expectedColumnTypes);
}
int rowNdx=0;
while (rs.next()) {
StringBuilder buf = new StringBuilder();
for(int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++){
buf.append("|"+rs.getString(columnIndex));
} }
assertEquals(expectedRowData[rowNdx], buf.toString());
rowNdx++; private void testSimple2By4RowRecursiveQuery() throws Exception {
String[] expectedRowData = new String[]{"|0|1|10", "|1|2|11", "|2|3|12", "|3|4|13"};
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER"};
String[] expectedColumnNames = new String[]{"K", "N", "N2"};
String setupSQL = "-- do nothing";
String withQuery = "with \n"+
"r1(n,k) as ((select 1, 0) union all (select n+1,k+1 from r1 where n <= 3)),"+
"r2(n,k) as ((select 10,0) union all (select n+1,k+1 from r2 where n <= 13))"+
"select r1.k, r1.n, r2.n AS n2 from r1 inner join r2 ON r1.k= r2.k ";
int maxRetries = 3;
int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
} }
assertEquals(3,rowNdx);
rs.close(); private void testSimple3RowRecursiveQueryWithLazyEval() throws Exception {
prep.close();
String[] expectedRowData = new String[]{"|6"};
String[] expectedColumnTypes = new String[]{"BIGINT"};
String[] expectedColumnNames = new String[]{"SUM(N)"};
// back up the config - to restore it after this test
TestAll backupConfig = config;
config = new TestAll();
try {
//Test with settings: lazy mvStore memory mvcc multiThreaded
// connection url is =mem:script;MV_STORE=true;LOG=1;LOCK_TIMEOUT=50;MVCC=TRUE;MULTI_THREADED=TRUE;LAZY_QUERY_EXECUTION=1
config.lazy = true;
config.mvStore = true;
config.memory = true;
config.mvcc = true;
config.multiThreaded = true;
String setupSQL = "--no config set";
String withQuery = "select sum(n) from (\n"
+" with recursive r(n) as (\n"
+" (select 1) union all (select n+1 from r where n < 3) \n"
+" )\n"
+" select n from r \n"
+")\n";
int maxRetries = 10;
int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
} finally {
config = backupConfig;
} }
conn.close();
deleteDb("commonTableExpressionQueries");
} }
} }
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.db;
import org.h2.test.TestBase;
/**
* Test persistent common table expressions queries using WITH.
*/
public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonTableExpressions {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
// persistent cte tests - also tests reconnects and database reloading...
testRecursiveTable();
testPersistentNonRecursiveTableInCreateView();
testPersistentRecursiveTableInCreateView();
}
private void testRecursiveTable() throws Exception {
String[] expectedRowData = new String[]{"|meat|null", "|fruit|3", "|veg|2"};
String[] expectedColumnTypes = new String[]{"VARCHAR", "DECIMAL"};
String[] expectedColumnNames = new String[]{"VAL",
"SUM(SELECT\n" +
" X\n" +
"FROM PUBLIC.\"\" BB\n" +
" /* SELECT\n" +
" SUM(1) AS X,\n" +
" A\n" +
" FROM PUBLIC.B\n" +
" /++ PUBLIC.B.tableScan ++/\n" +
" /++ WHERE A IS ?1\n" +
" ++/\n" +
" /++ scanCount: 4 ++/\n" +
" INNER JOIN PUBLIC.C\n" +
" /++ PUBLIC.C.tableScan ++/\n" +
" ON 1=1\n" +
" WHERE (A IS ?1)\n" +
" AND (B.VAL = C.B)\n" +
" GROUP BY A: A IS A.VAL\n" +
" */\n" +
" /* scanCount: 1 */\n" +
"WHERE BB.A IS A.VAL)"};
String setupSQL =
"DROP TABLE IF EXISTS A; "
+"DROP TABLE IF EXISTS B; "
+"DROP TABLE IF EXISTS C; "
+"CREATE TABLE A(VAL VARCHAR(255)); "
+"CREATE TABLE B(A VARCHAR(255), VAL VARCHAR(255)); "
+"CREATE TABLE C(B VARCHAR(255), VAL VARCHAR(255)); "
+" "
+"INSERT INTO A VALUES('fruit'); "
+"INSERT INTO B VALUES('fruit','apple'); "
+"INSERT INTO B VALUES('fruit','banana'); "
+"INSERT INTO C VALUES('apple', 'golden delicious');"
+"INSERT INTO C VALUES('apple', 'granny smith'); "
+"INSERT INTO C VALUES('apple', 'pippin'); "
+"INSERT INTO A VALUES('veg'); "
+"INSERT INTO B VALUES('veg', 'carrot'); "
+"INSERT INTO C VALUES('carrot', 'nantes'); "
+"INSERT INTO C VALUES('carrot', 'imperator'); "
+"INSERT INTO C VALUES(null, 'banapple'); "
+"INSERT INTO A VALUES('meat'); ";
String withQuery = "WITH BB as (SELECT \n" +
"sum(1) as X, \n" +
"a \n" +
"FROM B \n" +
"JOIN C ON B.val=C.b \n" +
"GROUP BY a) \n" +
"SELECT \n" +
"A.val, \n" +
"sum(SELECT X FROM BB WHERE BB.a IS A.val)\n" +
"FROM A \n" + "GROUP BY A.val";
int maxRetries = 3;
int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
}
private void testPersistentRecursiveTableInCreateView() throws Exception {
String setuoSQL = "--SET TRACE_LEVEL_SYSTEM_OUT 3;\n"
+"DROP TABLE IF EXISTS my_tree; \n"
+"DROP VIEW IF EXISTS v_my_tree; \n"
+"CREATE TABLE my_tree ( \n"
+" id INTEGER, \n"
+" parent_fk INTEGER \n"
+"); \n"
+" \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 1, NULL ); \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 11, 1 ); \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 111, 11 ); \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 12, 1 ); \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 121, 12 ); \n"
+" \n"
+"CREATE OR REPLACE VIEW v_my_tree AS \n"
+"WITH RECURSIVE tree_cte (sub_tree_root_id, tree_level, parent_fk, child_fk) AS ( \n"
+" SELECT mt.ID AS sub_tree_root_id, CAST(0 AS INT) AS tree_level, mt.parent_fk, mt.id \n"
+" FROM my_tree mt \n"
+" UNION ALL \n"
+" SELECT sub_tree_root_id, mtc.tree_level + 1 AS tree_level, mtc.parent_fk, mt.id \n"
+" FROM my_tree mt \n"
+"INNER JOIN tree_cte mtc ON mtc.child_fk = mt.parent_fk \n"
+"), \n"
+"unused_cte AS ( SELECT 1 AS unUsedColumn ) \n"
+"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte; \n";
String withQuery = "SELECT * FROM v_my_tree";
int maxRetries = 4;
String[] expectedRowData = new String[]{"|1|0|null|1",
"|11|0|1|11",
"|111|0|11|111",
"|12|0|1|12",
"|121|0|12|121",
"|1|1|null|11",
"|11|1|1|111",
"|1|1|null|12",
"|12|1|1|121",
"|1|2|null|111",
"|1|2|null|121"
};
String[] expectedColumnNames = new String[]{"SUB_TREE_ROOT_ID", "TREE_LEVEL", "PARENT_FK", "CHILD_FK"};
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 11;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setuoSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
}
private void testPersistentNonRecursiveTableInCreateView() throws Exception {
String setupSQL = ""
+"DROP VIEW IF EXISTS v_my_nr_tree; \n"
+"DROP TABLE IF EXISTS my_table; \n"
+"CREATE TABLE my_table ( \n"
+" id INTEGER, \n"
+" parent_fk INTEGER \n"
+"); \n"
+" \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 1, NULL ); \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 11, 1 ); \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 111, 11 ); \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 12, 1 ); \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 121, 12 ); \n"
+" \n"
+"CREATE OR REPLACE VIEW v_my_nr_tree AS \n"
+"WITH tree_cte_nr (sub_tree_root_id, tree_level, parent_fk, child_fk) AS ( \n"
+" SELECT mt.ID AS sub_tree_root_id, CAST(0 AS INT) AS tree_level, mt.parent_fk, mt.id \n"
+" FROM my_table mt \n"
+"), \n"
+"unused_cte AS ( SELECT 1 AS unUsedColumn ) \n"
+"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte_nr; \n";
String withQuery = "SELECT * FROM v_my_nr_tree";
int maxRetries = 6;
String[] expectedRowData = new String[]{
"|1|0|null|1",
"|11|0|1|11",
"|111|0|11|111",
"|12|0|1|12",
"|121|0|12|121",
};
String[] expectedColumnNames = new String[]{"SUB_TREE_ROOT_ID", "TREE_LEVEL", "PARENT_FK", "CHILD_FK"};
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 5;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
}
}
...@@ -20,6 +20,10 @@ import org.h2.util.IOUtils; ...@@ -20,6 +20,10 @@ import org.h2.util.IOUtils;
*/ */
public class TestMvccMultiThreaded2 extends TestBase { public class TestMvccMultiThreaded2 extends TestBase {
private static final int TEST_THREAD_COUNT = 100;
private static final int TEST_TIME_SECONDS = 60;
private static final boolean DISPLAY_STATS = false;
private static final String URL = ";MVCC=TRUE;LOCK_TIMEOUT=120000;MULTI_THREADED=TRUE"; private static final String URL = ";MVCC=TRUE;LOCK_TIMEOUT=120000;MULTI_THREADED=TRUE";
/** /**
...@@ -62,22 +66,50 @@ public class TestMvccMultiThreaded2 extends TestBase { ...@@ -62,22 +66,50 @@ public class TestMvccMultiThreaded2 extends TestBase {
conn.commit(); conn.commit();
ArrayList<SelectForUpdate> threads = new ArrayList<>(); ArrayList<SelectForUpdate> threads = new ArrayList<>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < TEST_THREAD_COUNT; i++) {
SelectForUpdate sfu = new SelectForUpdate(); SelectForUpdate sfu = new SelectForUpdate();
sfu.setName("Test SelectForUpdate Thread#"+i);
threads.add(sfu); threads.add(sfu);
sfu.start(); sfu.start();
} }
// give any of the 100 threads a chance to start by yielding the processor to them
Thread.yield();
// gather stats on threads after they finished
@SuppressWarnings("unused")
int minProcessed = Integer.MAX_VALUE, maxProcessed = 0, totalProcessed = 0;
for (SelectForUpdate sfu : threads) { for (SelectForUpdate sfu : threads) {
// make sure all threads have stopped by joining with them
sfu.join(); sfu.join();
totalProcessed += sfu.iterationsProcessed;
if (sfu.iterationsProcessed > maxProcessed) {
maxProcessed = sfu.iterationsProcessed;
}
if (sfu.iterationsProcessed < minProcessed) {
minProcessed = sfu.iterationsProcessed;
}
}
if (DISPLAY_STATS) {
System.out.println(String.format("+ INFO: TestMvccMultiThreaded2 RUN STATS threads=%d, minProcessed=%d, maxProcessed=%d, "+
"totalProcessed=%d, averagePerThread=%d, averagePerThreadPerSecond=%d\n",
TEST_THREAD_COUNT, minProcessed, maxProcessed, totalProcessed, totalProcessed/TEST_THREAD_COUNT,
totalProcessed/(TEST_THREAD_COUNT * TEST_TIME_SECONDS)));
} }
IOUtils.closeSilently(conn); IOUtils.closeSilently(conn);
deleteDb(getTestName()); deleteDb(getTestName());
} }
/**
* Worker test thread selecting for update
*/
private class SelectForUpdate extends Thread { private class SelectForUpdate extends Thread {
public int iterationsProcessed;
@Override @Override
public void run() { public void run() {
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
...@@ -86,6 +118,10 @@ public class TestMvccMultiThreaded2 extends TestBase { ...@@ -86,6 +118,10 @@ public class TestMvccMultiThreaded2 extends TestBase {
try { try {
conn = getConnection(getTestName() + URL); conn = getConnection(getTestName() + URL);
conn.setAutoCommit(false); conn.setAutoCommit(false);
// give the other threads a chance to start up before going into our work loop
Thread.yield();
while (!done) { while (!done) {
try { try {
PreparedStatement ps = conn.prepareStatement( PreparedStatement ps = conn.prepareStatement(
...@@ -97,16 +133,21 @@ public class TestMvccMultiThreaded2 extends TestBase { ...@@ -97,16 +133,21 @@ public class TestMvccMultiThreaded2 extends TestBase {
assertTrue(rs.getInt(2) == 100); assertTrue(rs.getInt(2) == 100);
conn.commit(); conn.commit();
iterationsProcessed++;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (now - start > 1000 * 60) if (now - start > 1000 * TEST_TIME_SECONDS) {
done = true; done = true;
}
} catch (JdbcSQLException e1) { } catch (JdbcSQLException e1) {
throw e1; throw e1;
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
TestBase.logError("error", e); TestBase.logError("SQL error from thread "+getName(), e);
} catch (Exception e) {
TestBase.logError("General error from thread "+getName(), e);
throw e;
} }
IOUtils.closeSilently(conn); IOUtils.closeSilently(conn);
} }
......
...@@ -133,6 +133,9 @@ public class TestScript extends TestBase { ...@@ -133,6 +133,9 @@ public class TestScript extends TestBase {
"parsedatetime", "quarter", "second", "week", "year" }) { "parsedatetime", "quarter", "second", "week", "year" }) {
testScript("functions/timeanddate/" + s + ".sql"); testScript("functions/timeanddate/" + s + ".sql");
} }
for (String s : new String[] { "with", "mergeUsing" }) {
testScript("dml/" + s + ".sql");
}
deleteDb("script"); deleteDb("script");
System.out.flush(); System.out.flush();
} }
......
-- Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
CREATE TABLE PARENT(ID INT, NAME VARCHAR, PRIMARY KEY(ID) );
> ok
MERGE INTO PARENT AS P
USING (SELECT X AS ID, 'Coco'||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);
> update count: 2
SELECT * FROM PARENT;
> ID NAME
> -- -----
> 1 Coco1
> 2 Coco2
EXPLAIN PLAN
MERGE INTO PARENT AS P
USING (SELECT X AS ID, 'Coco'||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);
> PLAN
> ---------------------------------------------------------------------------------------------------------------------------------
> MERGE INTO PUBLIC.PARENT(ID, NAME) KEY(ID) SELECT X AS ID, ('Coco' || X) AS NAME FROM SYSTEM_RANGE(1, 2) /* PUBLIC.RANGE_INDEX */
\ No newline at end of file
-- Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
explain with recursive r(n) as (
(select 1) union all (select n+1 from r where n < 3)
)
select n from r;
> PLAN
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> WITH RECURSIVE R(N) AS ( (SELECT 1 FROM SYSTEM_RANGE(1, 1) /* PUBLIC.RANGE_INDEX */) UNION ALL (SELECT (N + 1) FROM PUBLIC.R /* PUBLIC.R.tableScan */ WHERE N < 3) ) SELECT N FROM R R /* null */
> rows: 1
select sum(n) from (
with recursive r(n) as (
(select 1) union all (select n+1 from r where n < 3)
)
select n from r
);
> SUM(N)
> ------
> 6
> rows: 1
select sum(n) from (select 0) join (
with recursive r(n) as (
(select 1) union all (select n+1 from r where n < 3)
)
select n from r
) on 1=1;
> SUM(N)
> ------
> 6
> rows: 1
select 0 from (
select 0 where 0 in (
with recursive r(n) as (
(select 1) union all (select n+1 from r where n < 3)
)
select n from r
)
);
> 0
> -
> rows: 0
with
r0(n,k) as (select -1, 0),
r1(n,k) as ((select 1, 0) union all (select n+1,k+1 from r1 where n <= 3)),
r2(n,k) as ((select 10,0) union all (select n+1,k+1 from r2 where n <= 13))
select r1.k, r0.n as N0, r1.n AS N1, r2.n AS n2 from r0 inner join r1 ON r1.k= r0.k inner join r2 ON r1.k= r2.k;
> K N0 N1 N2
> - -- -- --
> 0 -1 1 10
> rows: 1
\ No newline at end of file
...@@ -9369,49 +9369,6 @@ select 0 from (( ...@@ -9369,49 +9369,6 @@ select 0 from ((
}; };
> update count: 0 > update count: 0
explain with recursive r(n) as (
(select 1) union all (select n+1 from r where n < 3)
)
select n from r;
> PLAN
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> WITH RECURSIVE R(N) AS ( (SELECT 1 FROM SYSTEM_RANGE(1, 1) /* PUBLIC.RANGE_INDEX */) UNION ALL (SELECT (N + 1) FROM PUBLIC.R /* PUBLIC.R.tableScan */ WHERE N < 3) ) SELECT N FROM R R /* null */
> rows: 1
select sum(n) from (
with recursive r(n) as (
(select 1) union all (select n+1 from r where n < 3)
)
select n from r
);
> SUM(N)
> ------
> 6
> rows: 1
select sum(n) from (select 0) join (
with recursive r(n) as (
(select 1) union all (select n+1 from r where n < 3)
)
select n from r
) on 1=1;
> SUM(N)
> ------
> 6
> rows: 1
select 0 from (
select 0 where 0 in (
with recursive r(n) as (
(select 1) union all (select n+1 from r where n < 3)
)
select n from r
)
);
> 0
> -
> rows: 0
create table x(id int not null); create table x(id int not null);
> ok > ok
......
...@@ -51,11 +51,11 @@ public class TestMathUtils extends TestBase { ...@@ -51,11 +51,11 @@ public class TestMathUtils extends TestBase {
private void testNextPowerOf2Int() { private void testNextPowerOf2Int() {
// the largest power of two that fits into an integer // the largest power of two that fits into an integer
final int LARGEST_POW2 = 0x40000000; final int largestPower2 = 0x40000000;
int[] testValues = { 0, 1, 2, 3, 4, 12, 17, 500, 1023, int[] testValues = { 0, 1, 2, 3, 4, 12, 17, 500, 1023,
LARGEST_POW2-500, LARGEST_POW2 }; largestPower2 - 500, largestPower2 };
int[] resultValues = { 1, 1, 2, 4, 4, 16, 32, 512, 1024, int[] resultValues = { 1, 1, 2, 4, 4, 16, 32, 512, 1024,
LARGEST_POW2, LARGEST_POW2 }; largestPower2, largestPower2 };
for (int i = 0; i < testValues.length; i++) { for (int i = 0; i < testValues.length; i++) {
assertEquals(resultValues[i], MathUtils.nextPowerOf2(testValues[i])); assertEquals(resultValues[i], MathUtils.nextPowerOf2(testValues[i]));
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论