Skip to content

Commit

Permalink
Merge pull request #3989 from katzyn/cte
Browse files Browse the repository at this point in the history
Refactor CTE-related code and reduce recompilations
  • Loading branch information
katzyn committed Jan 29, 2024
2 parents 31cba65 + a7e96fb commit 2a29f47
Show file tree
Hide file tree
Showing 17 changed files with 622 additions and 596 deletions.
153 changes: 76 additions & 77 deletions h2/src/main/org/h2/command/Parser.java
Expand Up @@ -39,6 +39,9 @@
import static org.h2.command.Token.SMALLER_EQUAL;
import static org.h2.command.Token.SPATIAL_INTERSECTS;
import static org.h2.command.Token.TILDE;
import static org.h2.util.HasSQL.DEFAULT_SQL_FLAGS;
import static org.h2.util.HasSQL.QUOTE_ONLY_WHEN_REQUIRED;
import static org.h2.util.HasSQL.TRACE_SQL_FLAGS;
import static org.h2.util.ParserUtil.ALL;
import static org.h2.util.ParserUtil.AND;
import static org.h2.util.ParserUtil.ANY;
Expand Down Expand Up @@ -362,6 +365,7 @@
import org.h2.schema.Sequence;
import org.h2.schema.UserAggregate;
import org.h2.schema.UserDefinedFunction;
import org.h2.table.CTE;
import org.h2.table.Column;
import org.h2.table.DataChangeDeltaTable;
import org.h2.table.DataChangeDeltaTable.ResultOption;
Expand All @@ -376,7 +380,6 @@
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.table.TableView;
import org.h2.util.HasSQL;
import org.h2.util.IntervalUtils;
import org.h2.util.ParserUtil;
import org.h2.util.StringUtils;
Expand Down Expand Up @@ -428,6 +431,7 @@ public final class Parser extends ParserBase {
private int orderInFrom;
private boolean parseDomainConstraint;
private QueryScope queryScope;
private boolean parsingRecursiveWithList;

/**
* Creates a new instance of parser.
Expand Down Expand Up @@ -1200,7 +1204,7 @@ private Prepared parseShow() {
if (i > 0) {
searchPathBuff.append(", ");
}
ParserUtil.quoteIdentifier(searchPathBuff, searchPath[i], HasSQL.QUOTE_ONLY_WHEN_REQUIRED);
ParserUtil.quoteIdentifier(searchPathBuff, searchPath[i], QUOTE_ONLY_WHEN_REQUIRED);
}
}
StringUtils.quoteStringSQL(buff, searchPathBuff.toString());
Expand Down Expand Up @@ -1813,7 +1817,7 @@ private TableFilter readDerivedTableWithCorrelation() {
if (derivedColumnNames != null) {
query.init();
columnTemplates = QueryExpressionTable.createQueryColumnTemplateList(
derivedColumnNames.toArray(new String[0]), query, new String[1])
derivedColumnNames.toArray(new String[0]), query)
.toArray(new Column[0]);
}
table = query.toTable(alias, columnTemplates, queryParameters, createView != null, currentSelect);
Expand All @@ -1834,7 +1838,7 @@ private TableFilter buildTableFilter(Table table, String alias, ArrayList<String
discardWithTableHints();
}
// inherit alias for CTE as views from table name
if (alias == null && table.isView() && table.isTableExpression()) {
if (alias == null && table instanceof CTE) {
alias = table.getName();
}
TableFilter filter = new TableFilter(session, table, alias, rightsChecked,
Expand Down Expand Up @@ -2483,17 +2487,21 @@ private Query parseQueryExpression() {
QueryScope outerQueryScope = queryScope;
Query query;
if (readIf(WITH)) {
boolean oldRecursive = parsingRecursiveWithList;
boolean isPotentiallyRecursive = !oldRecursive && readIf("RECURSIVE");
queryScope = new QueryScope(outerQueryScope);
try {
readIf("RECURSIVE");
// 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.
boolean isTemporary = !session.isParsingCreateView();
do {
parseSingleCommonTableExpression(isTemporary);
} while (readIf(COMMA));
if (isPotentiallyRecursive) {
parsingRecursiveWithList = true;
}
try {
do {
parseSingleCommonTableExpression(isPotentiallyRecursive);
} while (readIf(COMMA));
} finally {
parsingRecursiveWithList = oldRecursive;
}
query = parseQueryExpressionBodyAndEndOfQuery(start);
query.setPrepareAlways(true);
query.setNeverLazy(true);
query.setWithClause(queryScope.tableSubqeries);
} finally {
Expand Down Expand Up @@ -3458,7 +3466,7 @@ private Expression readAggregate(AggregateType aggregateType, String aggregateNa
if (readIf(ORDER)) {
read("BY");
Expression expr2 = readExpression();
String sql = expr.getSQL(HasSQL.DEFAULT_SQL_FLAGS), sql2 = expr2.getSQL(HasSQL.DEFAULT_SQL_FLAGS);
String sql = expr.getSQL(DEFAULT_SQL_FLAGS), sql2 = expr2.getSQL(DEFAULT_SQL_FLAGS);
if (!sql.equals(sql2)) {
throw DbException.getSyntaxError(ErrorCode.IDENTICAL_EXPRESSIONS_SHOULD_BE_USED, sqlCommand,
token.start(), sql, sql2);
Expand Down Expand Up @@ -6915,84 +6923,75 @@ private boolean isReservedFunctionName(String name) {
|| BuiltinFunctions.isBuiltinFunction(database, name) && !database.isAllowBuiltinAliasOverride();
}

private void parseSingleCommonTableExpression(boolean isTemporary) {
String cteViewName = readIdentifierWithSchema();
Schema schema = getSchema();
private void parseSingleCommonTableExpression(boolean isPotentiallyRecursive) {
String cteName = readIdentifier();
ArrayList<Column> columns = Utils.newSmallArrayList();
String[] cols = null;

// column names are now optional - they can be inferred from the named
// query, if not supplied by user
if (readIf(OPEN_PAREN)) {
readColumns: {
if (isPotentiallyRecursive) {
read(OPEN_PAREN);
} else if (!readIf(OPEN_PAREN)) {
break readColumns;
}
cols = parseColumnList();
for (String c : cols) {
// we don't really know the type of the column, so STRING will
// have to do, UNKNOWN does not work here
columns.add(new Column(c, TypeInfo.TYPE_VARCHAR));
}
}
Table oldViewFound = getWithSubquery(cteViewName);
Table oldViewFound = getWithSubquery(cteName);
if (oldViewFound != null) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, cteViewName);
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, cteName);
}
/*
* This table is created as a workaround because recursive table
* expressions need to reference something that look like themselves to
* work (its removed after creation in this method). Only create table
* data and table if we don't have a working CTE already.
*/
Table recursiveTable = new ShadowTable(schema, cteViewName, columns.toArray(new Column[0]));
List<Column> columnTemplateList;
String[] querySQLOutput = new String[1];
BitSet outerUsedParameters = openParametersScope();
queryScope.tableSubqeries.put(cteViewName, recursiveTable);
read(AS);
read(OPEN_PAREN);
int index = tokenIndex;
setTokenIndex(index);
Query withQuery;
String sql;
ArrayList<Parameter> queryParameters;
try {
read(AS);
read(OPEN_PAREN);
Query withQuery = parseQuery();
if (!isTemporary) {
withQuery.session = session;
List<Column> columnTemplateList;
if (isPotentiallyRecursive) {
/*
* This table is created as a workaround because recursive table
* expressions need to reference something that look like themselves to
* work (its removed after creation in this method). Only create table
* data and table if we don't have a working CTE already.
*/
Table recursiveTable = new ShadowTable(database.getMainSchema(), cteName, columns.toArray(new Column[0]));
BitSet outerUsedParameters = openParametersScope();
queryScope.tableSubqeries.put(cteName, recursiveTable);
try {
withQuery = parseQuery();
} finally {
queryParameters = closeParametersScope(outerUsedParameters);
queryScope.tableSubqeries.remove(cteName);
}
read(CLOSE_PAREN);
columnTemplateList = QueryExpressionTable.createQueryColumnTemplateList(cols, withQuery, querySQLOutput);

} finally {
queryParameters = closeParametersScope(outerUsedParameters);
queryScope.tableSubqeries.remove(cteViewName);
}

createCTEView(cteViewName, querySQLOutput[0], queryParameters, columnTemplateList, isTemporary);
}

private void createCTEView(String cteViewName, String querySQL, ArrayList<Parameter> queryParameters,
List<Column> columnTemplateList, boolean isTemporary) {
Schema schema = getSchemaWithDefault();
Column[] columnTemplateArray = columnTemplateList.toArray(new Column[0]);

// 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,
// then we just compile it again.
TableView view;
session.lock();
try {
view = new TableView(schema, 0, cteViewName, querySQL,
queryParameters, columnTemplateArray, session,
true, false, true,
isTemporary, queryScope);
if (!view.isRecursiveQueryDetected()) {
view = new TableView(schema, 0, cteViewName, querySQL, queryParameters,
columnTemplateArray, session,
false/* assume recursive */, false, true,
isTemporary, queryScope);
columnTemplateList = QueryExpressionTable.createQueryColumnTemplateList(cols, withQuery);
sql = withQuery.getPlanSQL(DEFAULT_SQL_FLAGS);
try {
withQuery = (Query) session.prepare(sql, false, true, queryScope);
columnTemplateList = QueryExpressionTable.createQueryColumnTemplateList(cols, withQuery);
sql = withQuery.getPlanSQL(DEFAULT_SQL_FLAGS);
isPotentiallyRecursive = false;
} catch (DbException e) {
// Assume a recursive query
}
} finally {
session.unlock();
} else {
BitSet outerUsedParameters = openParametersScope();
try {
withQuery = parseQuery();
} finally {
queryParameters = closeParametersScope(outerUsedParameters);
}
columnTemplateList = QueryExpressionTable.createQueryColumnTemplateList(cols, withQuery);
sql = withQuery.getPlanSQL(DEFAULT_SQL_FLAGS);
}
view.setTableExpression(true);
view.setTemporary(isTemporary);
view.setOnCommitDrop(false);
queryScope.tableSubqeries.put(cteViewName, view);
read(CLOSE_PAREN);
queryScope.tableSubqeries.put(cteName, new CTE(cteName, withQuery, StringUtils.cache(sql),
queryParameters, columnTemplateList.toArray(new Column[0]), session, isPotentiallyRecursive,
queryScope));
}

private CreateView parseCreateView(boolean force, boolean orReplace) {
Expand Down Expand Up @@ -7279,7 +7278,7 @@ private boolean parseSequenceOptions(SequenceOptions options, CreateSequence com
TypeInfo dataType = parseDataType();
if (!DataType.isNumericType(dataType.getValueType())) {
throw DbException.getUnsupportedException(dataType
.getSQL(new StringBuilder("CREATE SEQUENCE AS "), HasSQL.TRACE_SQL_FLAGS).toString());
.getSQL(new StringBuilder("CREATE SEQUENCE AS "), TRACE_SQL_FLAGS).toString());
}
options.setDataType(dataType);
} else if (readIf("START", WITH)
Expand Down
5 changes: 2 additions & 3 deletions h2/src/main/org/h2/command/ddl/CreateView.java
Expand Up @@ -111,10 +111,9 @@ long update(Schema schema) {
}
}
if (view == null) {
view = new TableView(schema, id, viewName, querySQL, null, columnTemplatesAsUnknowns, session,
false, false, false, false, null);
view = new TableView(schema, id, viewName, querySQL, columnTemplatesAsUnknowns, session);
} else {
view.replace(querySQL, columnTemplatesAsUnknowns, session, false, force, false);
view.replace(querySQL, columnTemplatesAsUnknowns, session, force);
view.setModified();
}
if (comment != null) {
Expand Down
22 changes: 5 additions & 17 deletions h2/src/main/org/h2/command/query/Query.java
Expand Up @@ -31,13 +31,12 @@
import org.h2.result.ResultInterface;
import org.h2.result.ResultTarget;
import org.h2.result.SortOrder;
import org.h2.table.CTE;
import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.DerivedTable;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.table.TableView;
import org.h2.util.ParserUtil;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.ExtTypeInfoRow;
Expand Down Expand Up @@ -905,7 +904,7 @@ protected void writeWithList(StringBuilder builder, int sqlFlags) {
if (withClause != null) {
boolean recursive = false;
for (Table t : withClause.values()) {
if (t instanceof TableView && ((TableView) t).isRecursive()) {
if (((CTE) t).isRecursive()) {
recursive = true;
break;
}
Expand All @@ -921,25 +920,14 @@ protected void writeWithList(StringBuilder builder, int sqlFlags) {
} else {
builder.append(",\n");
}
writeWithListElement(builder, sqlFlags, table);
table.getSQL(builder, sqlFlags).append('(');
Column.writeColumns(builder, table.getColumns(), sqlFlags).append(") AS (\n");
StringUtils.indent(builder, ((CTE) table).getQuerySQL(), 4, true).append(')');
}
builder.append('\n');
}
}

protected static void writeWithListElement(StringBuilder builder, int sqlFlags, Table table) {
ParserUtil.quoteIdentifier(builder, table.getName(), sqlFlags).append('(');
Column.writeColumns(builder, table.getColumns(), sqlFlags).append(") AS ");
if (table instanceof TableView) {
String querySQL = ((TableView) table).getQuerySQL();
if (querySQL != null) {
StringUtils.indent(builder.append("(\n"), querySQL, 4, true).append(')');
return;
}
}
table.getSQL(builder, sqlFlags);
}

/**
* Appends ORDER BY, OFFSET, and FETCH clauses to the plan.
*
Expand Down
2 changes: 1 addition & 1 deletion h2/src/main/org/h2/index/QueryExpressionCursor.java
Expand Up @@ -47,7 +47,7 @@ public boolean next() {
while (true) {
boolean res = result.next();
if (!res) {
if (index.isRecursive()) {
if (index.getClass() == RecursiveIndex.class) {
result.reset();
} else {
result.close();
Expand Down

0 comments on commit 2a29f47

Please sign in to comment.