diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html
index 3d39f83112..6b1f8b26d4 100644
--- a/h2/src/docsrc/html/changelog.html
+++ b/h2/src/docsrc/html/changelog.html
@@ -21,6 +21,18 @@
Change Log
Next Version (unreleased)
+- Issue #3945: Column not found in correlated subquery, when referencing outer column from LEFT JOIN .. ON clause
+
+- Issue #4097: StackOverflowException when using multiple SELECT statements in one query (2.3.230)
+
+- Issue #3982: Potential issue when using ROUND
+
+- Issue #3894: Race condition causing stale data in query last result cache
+
+- Issue #4075: infinite loop in compact
+
+- Issue #4091: Wrong case with linked table to postgresql
+
- Issue #4088: BadGrammarException when the same alias is used within two different CTEs
- Issue #4079: [2.3.230] Regression in ORDER BY ... DESC on dates
diff --git a/h2/src/main/org/h2/command/query/Query.java b/h2/src/main/org/h2/command/query/Query.java
index 22dced7337..4b69239477 100644
--- a/h2/src/main/org/h2/command/query/Query.java
+++ b/h2/src/main/org/h2/command/query/Query.java
@@ -336,8 +336,10 @@ public TypeInfo getRowDataType() {
* @param level
* the subquery level (0 is the top level query, 1 is the first
* subquery level)
+ * @param outer
+ * whether this method was called from the outer query
*/
- public abstract void mapColumns(ColumnResolver resolver, int level);
+ public abstract void mapColumns(ColumnResolver resolver, int level, boolean outer);
/**
* Change the evaluatable flag. This is used when building the execution
diff --git a/h2/src/main/org/h2/command/query/Select.java b/h2/src/main/org/h2/command/query/Select.java
index d48cb377b0..385dd75abb 100644
--- a/h2/src/main/org/h2/command/query/Select.java
+++ b/h2/src/main/org/h2/command/query/Select.java
@@ -1155,7 +1155,7 @@ public void init() {
}
// map columns in select list and condition
for (TableFilter f : filters) {
- mapColumns(f, 0);
+ mapColumns(f, 0, false);
}
mapCondition(havingIndex);
mapCondition(qualifyIndex);
@@ -1596,13 +1596,16 @@ public void setForUpdate(ForUpdate b) {
}
@Override
- public void mapColumns(ColumnResolver resolver, int level) {
+ public void mapColumns(ColumnResolver resolver, int level, boolean outer) {
for (Expression e : expressions) {
e.mapColumns(resolver, level, Expression.MAP_INITIAL);
}
if (condition != null) {
condition.mapColumns(resolver, level, Expression.MAP_INITIAL);
}
+ for (TableFilter tableFilter : topFilters) {
+ tableFilter.mapColumns(resolver, level, outer);
+ }
}
@Override
diff --git a/h2/src/main/org/h2/command/query/SelectUnion.java b/h2/src/main/org/h2/command/query/SelectUnion.java
index acb8fa3ea6..d932473a08 100644
--- a/h2/src/main/org/h2/command/query/SelectUnion.java
+++ b/h2/src/main/org/h2/command/query/SelectUnion.java
@@ -303,9 +303,9 @@ public void setForUpdate(ForUpdate forUpdate) {
}
@Override
- public void mapColumns(ColumnResolver resolver, int level) {
- left.mapColumns(resolver, level);
- right.mapColumns(resolver, level);
+ public void mapColumns(ColumnResolver resolver, int level, boolean outer) {
+ left.mapColumns(resolver, level, outer);
+ right.mapColumns(resolver, level, outer);
}
@Override
diff --git a/h2/src/main/org/h2/command/query/TableValueConstructor.java b/h2/src/main/org/h2/command/query/TableValueConstructor.java
index 5a02db7289..2faef5bdc0 100644
--- a/h2/src/main/org/h2/command/query/TableValueConstructor.java
+++ b/h2/src/main/org/h2/command/query/TableValueConstructor.java
@@ -262,7 +262,7 @@ public void setForUpdate(ForUpdate forUpdate) {
}
@Override
- public void mapColumns(ColumnResolver resolver, int level) {
+ public void mapColumns(ColumnResolver resolver, int level, boolean outer) {
int columnCount = visibleColumnCount;
for (ArrayList row : rows) {
for (int i = 0; i < columnCount; i++) {
diff --git a/h2/src/main/org/h2/expression/ArrayConstructorByQuery.java b/h2/src/main/org/h2/expression/ArrayConstructorByQuery.java
index 32240676b9..f42f9216d1 100644
--- a/h2/src/main/org/h2/expression/ArrayConstructorByQuery.java
+++ b/h2/src/main/org/h2/expression/ArrayConstructorByQuery.java
@@ -65,7 +65,7 @@ public TypeInfo getType() {
@Override
public void mapColumns(ColumnResolver resolver, int level, int state) {
- query.mapColumns(resolver, level + 1);
+ query.mapColumns(resolver, level + 1, true);
}
@Override
diff --git a/h2/src/main/org/h2/expression/Subquery.java b/h2/src/main/org/h2/expression/Subquery.java
index 736cf080df..0709a7adbf 100644
--- a/h2/src/main/org/h2/expression/Subquery.java
+++ b/h2/src/main/org/h2/expression/Subquery.java
@@ -90,7 +90,7 @@ public TypeInfo getType() {
@Override
public void mapColumns(ColumnResolver resolver, int level, int state) {
outerResolvers.add(resolver);
- query.mapColumns(resolver, level + 1);
+ query.mapColumns(resolver, level + 1, true);
}
@Override
diff --git a/h2/src/main/org/h2/expression/condition/PredicateWithSubquery.java b/h2/src/main/org/h2/expression/condition/PredicateWithSubquery.java
index 04c9a32313..71d8d8a5e6 100644
--- a/h2/src/main/org/h2/expression/condition/PredicateWithSubquery.java
+++ b/h2/src/main/org/h2/expression/condition/PredicateWithSubquery.java
@@ -29,7 +29,7 @@ abstract class PredicateWithSubquery extends Condition {
@Override
public void mapColumns(ColumnResolver resolver, int level, int state) {
- query.mapColumns(resolver, level + 1);
+ query.mapColumns(resolver, level + 1, true);
}
@Override
diff --git a/h2/src/main/org/h2/table/TableFilter.java b/h2/src/main/org/h2/table/TableFilter.java
index d01f18c0e5..02966c0b00 100644
--- a/h2/src/main/org/h2/table/TableFilter.java
+++ b/h2/src/main/org/h2/table/TableFilter.java
@@ -635,7 +635,6 @@ public void addFilterCondition(Expression condition, boolean isJoin) {
*/
public void addJoin(TableFilter filter, boolean outer, Expression on) {
if (on != null) {
- on.mapColumns(this, 0, Expression.MAP_INITIAL);
TableFilterVisitor visitor = new MapColumnsVisitor(on);
visit(visitor);
filter.visit(visitor);
@@ -647,7 +646,7 @@ public void addJoin(TableFilter filter, boolean outer, Expression on) {
filter.visit(JOI_VISITOR);
}
if (on != null) {
- filter.mapAndAddFilter(on);
+ filter.addFilter(on);
}
} else {
join.addJoin(filter, outer, on);
@@ -664,18 +663,40 @@ public void setNestedJoin(TableFilter filter) {
}
/**
- * Map the columns and add the join condition.
+ * Add the join condition.
*
* @param on the condition
*/
- public void mapAndAddFilter(Expression on) {
- on.mapColumns(this, 0, Expression.MAP_INITIAL);
+ public void addFilter(Expression on) {
addFilterCondition(on, true);
+ if (join != null) {
+ join.addFilter(on);
+ }
+ }
+
+ /**
+ * Map the columns to the given column resolver.
+ *
+ * @param resolver
+ * the resolver
+ * @param level
+ * the subquery level (0 is the top level query, 1 is the first
+ * subquery level)
+ * @param outer
+ * whether this method was called from the outer query
+ */
+ public void mapColumns(ColumnResolver resolver, int level, boolean outer) {
+ if (!outer && joinOuter) {
+ return;
+ }
+ if (joinCondition != null) {
+ joinCondition.mapColumns(resolver, level, Expression.MAP_INITIAL);
+ }
if (nestedJoin != null) {
- on.mapColumns(nestedJoin, 0, Expression.MAP_INITIAL);
+ nestedJoin.mapColumns(resolver, level, outer);
}
if (join != null) {
- join.mapAndAddFilter(on);
+ join.mapColumns(resolver, level, outer);
}
}
diff --git a/h2/src/test/org/h2/test/scripts/queries/joins.sql b/h2/src/test/org/h2/test/scripts/queries/joins.sql
index 955dcdcd19..4ab9f6e326 100644
--- a/h2/src/test/org/h2/test/scripts/queries/joins.sql
+++ b/h2/src/test/org/h2/test/scripts/queries/joins.sql
@@ -1044,3 +1044,55 @@ FROM (SELECT 1 A) X JOIN (
> - - -
> 1 1 1
> rows: 1
+
+EXPLAIN
+WITH TEST(ID) AS (VALUES 1)
+SELECT * FROM TEST A INNER JOIN TEST B ON TRUE LEFT OUTER JOIN TEST C ON C.ID = A.ID;
+> PLAN
+> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+> WITH "TEST"("ID") AS ( VALUES (1) ) SELECT "A"."ID", "B"."ID", "C"."ID" FROM "TEST" "A" /* VALUES (1) */ INNER JOIN "TEST" "B" /* VALUES (1) */ ON 1=1 LEFT OUTER JOIN "TEST" "C" /* VALUES (1) */ ON "C"."ID" = "A"."ID"
+> rows: 1
+
+-- Column A.ID cannot be referenced from this part of the query
+EXPLAIN
+WITH TEST(ID) AS (VALUES 1)
+SELECT * FROM TEST A INNER JOIN TEST B LEFT OUTER JOIN TEST C ON C.ID = A.ID ON TRUE;
+> exception COLUMN_NOT_FOUND_1
+
+WITH
+ A(A) AS (VALUES (1)),
+ B(B) AS (VALUES (1)),
+ C(C) AS (VALUES (1))
+SELECT
+ A.A,
+ (
+ SELECT B.B
+ FROM B
+ JOIN C
+ ON B.B = A.A
+ AND C.C = B.B
+ )
+FROM A;
+> A (SELECT B.B FROM B B INNER JOIN C C ON 1=1 WHERE (B.B = A.A) AND (C.C = B.B))
+> - -----------------------------------------------------------------------------
+> 1 1
+> rows: 1
+
+WITH
+ A(A) AS (VALUES (1)),
+ B(B) AS (VALUES (1)),
+ C(C) AS (VALUES (1))
+SELECT
+ A.A,
+ (
+ SELECT B.B
+ FROM B
+ LEFT JOIN C
+ ON B.B = A.A
+ AND C.C = B.B
+ )
+FROM A;
+> A (SELECT B.B FROM B B LEFT OUTER JOIN C C ON (B.B = A.A) AND (C.C = B.B))
+> - ------------------------------------------------------------------------
+> 1 1
+> rows: 1