diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html
index 5555992b27..ae2c8dc629 100644
--- a/h2/src/docsrc/html/changelog.html
+++ b/h2/src/docsrc/html/changelog.html
@@ -21,6 +21,11 @@
Change Log
Next Version (unreleased)
+- Issue #3987: Allow empty <with column list>
+
+- Issue #822: WITH clauses' column aliases are not maintained correctly when selecting from CTE from within a derived
+table
+
- Issue #910: Common Table Expressions (CTE) inside WITH should have their own identifier scope
- Issue #3981: Unexpected result when using trigonometric functions
diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java
index acd027fedc..1a11089a3d 100644
--- a/h2/src/main/org/h2/command/Parser.java
+++ b/h2/src/main/org/h2/command/Parser.java
@@ -1135,9 +1135,11 @@ private int parseSortType() {
private String[] parseColumnList() {
ArrayList 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]);
}
@@ -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() {
@@ -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)) {
@@ -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();
diff --git a/h2/src/main/org/h2/command/query/Query.java b/h2/src/main/org/h2/command/query/Query.java
index ae2ecf72df..481864c1d9 100644
--- a/h2/src/main/org/h2/command/query/Query.java
+++ b/h2/src/main/org/h2/command/query/Query.java
@@ -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;
@@ -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 withClause;
Query(SessionLocal session) {
@@ -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 withClause) {
this.withClause = withClause;
diff --git a/h2/src/main/org/h2/table/DerivedTable.java b/h2/src/main/org/h2/table/DerivedTable.java
index 06a5e6d710..99152746d8 100644
--- a/h2/src/main/org/h2/table/DerivedTable.java
+++ b/h2/src/main/org/h2/table/DerivedTable.java
@@ -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;
@@ -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();
+ }
+
}
diff --git a/h2/src/main/org/h2/table/QueryExpressionTable.java b/h2/src/main/org/h2/table/QueryExpressionTable.java
index 05679469b7..cc30862773 100644
--- a/h2/src/main/org/h2/table/QueryExpressionTable.java
+++ b/h2/src/main/org/h2/table/QueryExpressionTable.java
@@ -322,8 +322,6 @@ public final void addDependencies(HashSet dependencies) {
*
* @return the scope of this table
*/
- public QueryScope getQueryScope() {
- return null;
- }
+ public abstract QueryScope getQueryScope();
}
diff --git a/h2/src/test/org/h2/test/scripts/ddl/createView.sql b/h2/src/test/org/h2/test/scripts/ddl/createView.sql
index 3e1dbb919c..e3371355d4 100644
--- a/h2/src/test/org/h2/test/scripts/ddl/createView.sql
+++ b/h2/src/test/org/h2/test/scripts/ddl/createView.sql
@@ -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
diff --git a/h2/src/test/org/h2/test/scripts/dml/with.sql b/h2/src/test/org/h2/test/scripts/dml/with.sql
index fbb7ce8e38..a1388731d3 100644
--- a/h2/src/test/org/h2/test/scripts/dml/with.sql
+++ b/h2/src/test/org/h2/test/scripts/dml/with.sql
@@ -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