Skip to content

Commit

Permalink
Merge pull request #3988 from katzyn/cte
Browse files Browse the repository at this point in the history
Fix references to CTEs from derived tables and allow empty column lists in WITH clause
  • Loading branch information
katzyn committed Jan 28, 2024
2 parents 494bd66 + 4505de3 commit 31cba65
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 37 deletions.
5 changes: 5 additions & 0 deletions h2/src/docsrc/html/changelog.html
Expand Up @@ -21,6 +21,11 @@ <h1>Change Log</h1>

<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #3987: Allow empty &lt;with column list&gt;
</li>
<li>Issue #822: WITH clauses' column aliases are not maintained correctly when selecting from CTE from within a derived
table
</li>
<li>Issue #910: Common Table Expressions (CTE) inside WITH should have their own identifier scope
</li>
<li>Issue #3981: Unexpected result when using trigonometric functions
Expand Down
61 changes: 28 additions & 33 deletions h2/src/main/org/h2/command/Parser.java
Expand Up @@ -1135,9 +1135,11 @@ private int parseSortType() {

private String[] parseColumnList() {
ArrayList<String> columns = Utils.newSmallArrayList();
do {
columns.add(readIdentifier());
} while (readIfMore());
if (!readIf(CLOSE_PAREN)) {
do {
columns.add(readIdentifier());
} while (readIfMore());
}
return columns.toArray(new String[0]);
}

Expand Down Expand Up @@ -2478,27 +2480,37 @@ private Query parseQuery() {

private Query parseQueryExpression() {
int start = tokenIndex;
QueryScope outerQueryScope = queryScope;
Query query;
if (readIf(WITH)) {
queryScope = new QueryScope(queryScope);
queryScope = new QueryScope(outerQueryScope);
try {
query = parseWith(start);
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));
query = parseQueryExpressionBodyAndEndOfQuery(start);
query.setPrepareAlways(true);
query.setNeverLazy(true);
query.setWithClause(queryScope.tableSubqeries);
} finally {
queryScope = queryScope.parent;
queryScope = outerQueryScope;
}
} else {
query = parseQueryExpressionBodyAndEndOfQuery();
query = parseQueryExpressionBodyAndEndOfQuery(start);
}
query.setOuterQueryScope(outerQueryScope);
return query;
}

private Query parseQueryExpressionBodyAndEndOfQuery() {
int start = tokenIndex;
Query command = parseQueryExpressionBody();
parseEndOfQuery(command);
setSQL(command, start);
return command;
private Query parseQueryExpressionBodyAndEndOfQuery(int start) {
Query query = parseQueryExpressionBody();
parseEndOfQuery(query);
setSQL(query, start);
return query;
}

private Query parseQueryExpressionBody() {
Expand Down Expand Up @@ -2661,9 +2673,10 @@ private void parseIsolationClause() {

private Query parseQueryPrimary() {
if (readIf(OPEN_PAREN)) {
Query command = parseQueryExpressionBodyAndEndOfQuery();
Query query = parseQueryExpressionBodyAndEndOfQuery(tokenIndex);
query.setOuterQueryScope(queryScope);
read(CLOSE_PAREN);
return command;
return query;
}
int start = tokenIndex;
if (readIf(SELECT)) {
Expand Down Expand Up @@ -6902,24 +6915,6 @@ private boolean isReservedFunctionName(String name) {
|| BuiltinFunctions.isBuiltinFunction(database, name) && !database.isAllowBuiltinAliasOverride();
}

private Query parseWith(int start) {
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.
final boolean isTemporary = !session.isParsingCreateView();

do {
parseSingleCommonTableExpression(isTemporary);
} while (readIf(COMMA));

Query query = parseQueryExpressionBodyAndEndOfQuery();
query.setPrepareAlways(true);
query.setNeverLazy(true);
setSQL(query, start);
return query;
}

private void parseSingleCommonTableExpression(boolean isTemporary) {
String cteViewName = readIdentifierWithSchema();
Schema schema = getSchema();
Expand Down
31 changes: 30 additions & 1 deletion h2/src/main/org/h2/command/query/Query.java
Expand Up @@ -16,6 +16,7 @@
import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface;
import org.h2.command.Prepared;
import org.h2.command.QueryScope;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
import org.h2.engine.SessionLocal;
Expand Down Expand Up @@ -155,6 +156,14 @@ static final class OffsetFetch {

boolean isPrepared;

/**
* The outer scope of this query.
*/
private QueryScope outerQueryScope;

/**
* The WITH clause of this query.
*/
private LinkedHashMap<String, Table> withClause;

Query(SessionLocal session) {
Expand Down Expand Up @@ -863,10 +872,30 @@ public final long getMaxDataModificationId() {
return Math.max(visitor.getMaxDataModificationId(), session.getSnapshotDataModificationId());
}

/**
* Returns the scope of the outer query.
*
* @return the scope of the outer query
*/
public QueryScope getOuterQueryScope() {
return outerQueryScope;
}

/**
* Sets the scope of the outer query.
*
* @param outerQueryScope
* the scope of the outer query
*/
public void setOuterQueryScope(QueryScope outerQueryScope) {
this.outerQueryScope = outerQueryScope;
}

/**
* Sets the WITH clause of this query.
*
* @param withClause the WITH clause of this query
* @param withClause
* the WITH clause of this query
*/
public void setWithClause(LinkedHashMap<String, Table> withClause) {
this.withClause = withClause;
Expand Down
6 changes: 6 additions & 0 deletions h2/src/main/org/h2/table/DerivedTable.java
Expand Up @@ -8,6 +8,7 @@
import java.util.ArrayList;

import org.h2.api.ErrorCode;
import org.h2.command.QueryScope;
import org.h2.command.query.Query;
import org.h2.engine.SessionLocal;
import org.h2.expression.ExpressionVisitor;
Expand Down Expand Up @@ -91,4 +92,9 @@ public StringBuilder getSQL(StringBuilder builder, int sqlFlags) {
return StringUtils.indent(builder.append("(\n"), querySQL, 4, true).append(')');
}

@Override
public QueryScope getQueryScope() {
return viewQuery.getOuterQueryScope();
}

}
4 changes: 1 addition & 3 deletions h2/src/main/org/h2/table/QueryExpressionTable.java
Expand Up @@ -322,8 +322,6 @@ public final void addDependencies(HashSet<DbObject> dependencies) {
*
* @return the scope of this table
*/
public QueryScope getQueryScope() {
return null;
}
public abstract QueryScope getQueryScope();

}
12 changes: 12 additions & 0 deletions h2/src/test/org/h2/test/scripts/ddl/createView.sql
Expand Up @@ -52,3 +52,15 @@ SELECT * FROM TEST_VIEW;

DROP TABLE TEST CASCADE;
> ok

CREATE VIEW V() AS SELECT;
> ok

TABLE V;
>
>
>
> rows: 1

DROP VIEW V;
> ok
20 changes: 20 additions & 0 deletions h2/src/test/org/h2/test/scripts/dml/with.sql
Expand Up @@ -239,3 +239,23 @@ WITH T(X) AS (SELECT 1)
> 2
> 3
> rows: 3

WITH T1(F1, F2) AS (SELECT 1, 2)
SELECT A1.F1, A1.F2 FROM (SELECT * FROM T1) A1;
> F1 F2
> -- --
> 1 2
> rows: 1

CREATE VIEW V AS
WITH A AS (SELECT) TABLE A;
> ok

TABLE V;
>
>
>
> rows: 1

DROP VIEW V;
> ok

0 comments on commit 31cba65

Please sign in to comment.