diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html
index 4ac74aed15..c4b9881605 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 #4033: Wrong array produced when using ARRAY_AGG() on UNNEST(ARRAY[CAST(? AS INT)]) expression
+in a PreparedStatement
+
+- Issue #3909: Maintenance taking too much resources since 2.2.222
+
- Issue #4010: org.h2.jdbc.JdbcConnection.getTypeMap() returns null
- PR #4007: Update pom.xml related to CVE-2024-1597
diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java
index 73e94fcac6..924c24baf7 100644
--- a/h2/src/main/org/h2/command/Parser.java
+++ b/h2/src/main/org/h2/command/Parser.java
@@ -4414,12 +4414,8 @@ private ArrayTableFunction readUnnestFunction() {
do {
Expression expr = readExpression();
TypeInfo columnType = TypeInfo.TYPE_NULL;
- boolean constant = expr.isConstant();
- if (constant || expr instanceof CastSpecification) {
- if (constant) {
- expr = expr.optimize(session);
- }
- TypeInfo exprType = expr.getType();
+ TypeInfo exprType = expr.getTypeIfStaticallyKnown(session);
+ if (exprType != null) {
switch (exprType.getValueType()) {
case Value.JSON:
columnType = TypeInfo.TYPE_JSON;
diff --git a/h2/src/main/org/h2/expression/Expression.java b/h2/src/main/org/h2/expression/Expression.java
index b7f741cdcc..841e133658 100644
--- a/h2/src/main/org/h2/expression/Expression.java
+++ b/h2/src/main/org/h2/expression/Expression.java
@@ -284,6 +284,17 @@ public Expression getNotIfPossible(@SuppressWarnings("unused") SessionLocal sess
return null;
}
+ /**
+ * Returns data type of this expression if it is statically known.
+ *
+ * @param session
+ * the session
+ * @return data type or {@code null}
+ */
+ public TypeInfo getTypeIfStaticallyKnown(SessionLocal session) {
+ return null;
+ }
+
/**
* Check if this expression will always return the same value.
*
diff --git a/h2/src/main/org/h2/expression/ExpressionList.java b/h2/src/main/org/h2/expression/ExpressionList.java
index be9efe436b..fcc0554305 100644
--- a/h2/src/main/org/h2/expression/ExpressionList.java
+++ b/h2/src/main/org/h2/expression/ExpressionList.java
@@ -114,6 +114,21 @@ public int getCost() {
return cost;
}
+ @Override
+ public TypeInfo getTypeIfStaticallyKnown(SessionLocal session) {
+ int count = list.length;
+ TypeInfo[] types = new TypeInfo[count];
+ for (int i = 0; i < count; i++) {
+ TypeInfo t = list[i].getTypeIfStaticallyKnown(session);
+ if (t == null) {
+ return null;
+ }
+ types[i] = t;
+ }
+ return isArray ? TypeInfo.getTypeInfo(Value.ARRAY, list.length, 0, TypeInfo.getHigherType(types))
+ : TypeInfo.getTypeInfo(Value.ROW, 0, 0, new ExtTypeInfoRow(types));
+ }
+
@Override
public boolean isConstant() {
for (Expression e : list) {
diff --git a/h2/src/main/org/h2/expression/Subquery.java b/h2/src/main/org/h2/expression/Subquery.java
index 99a1c7127c..736cf080df 100644
--- a/h2/src/main/org/h2/expression/Subquery.java
+++ b/h2/src/main/org/h2/expression/Subquery.java
@@ -161,6 +161,16 @@ public int getCost() {
return query.getCostAsExpression();
}
+ @Override
+ public TypeInfo getTypeIfStaticallyKnown(SessionLocal session) {
+ if (query.isConstantQuery()) {
+ query.prepare();
+ setType();
+ return expression.getType();
+ }
+ return null;
+ }
+
@Override
public boolean isConstant() {
return query.isConstantQuery();
diff --git a/h2/src/main/org/h2/expression/ValueExpression.java b/h2/src/main/org/h2/expression/ValueExpression.java
index 6428aa28c8..0831fd5a76 100644
--- a/h2/src/main/org/h2/expression/ValueExpression.java
+++ b/h2/src/main/org/h2/expression/ValueExpression.java
@@ -114,6 +114,11 @@ public Expression getNotIfPossible(SessionLocal session) {
return getBoolean(!value.getBoolean());
}
+ @Override
+ public TypeInfo getTypeIfStaticallyKnown(SessionLocal session) {
+ return value.getType();
+ }
+
@Override
public boolean isConstant() {
return true;
diff --git a/h2/src/main/org/h2/expression/function/CastSpecification.java b/h2/src/main/org/h2/expression/function/CastSpecification.java
index 7e3fd67d4e..3991313a51 100644
--- a/h2/src/main/org/h2/expression/function/CastSpecification.java
+++ b/h2/src/main/org/h2/expression/function/CastSpecification.java
@@ -90,6 +90,11 @@ public Expression optimize(SessionLocal session) {
return this;
}
+ @Override
+ public TypeInfo getTypeIfStaticallyKnown(SessionLocal session) {
+ return type;
+ }
+
@Override
public boolean isConstant() {
return left instanceof ValueExpression && (right == null || right.isConstant())
diff --git a/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java b/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java
index 78e0ec3757..e5eba7a375 100644
--- a/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java
+++ b/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java
@@ -1810,6 +1810,13 @@ private void testUnnestWithArrayParameter(Connection conn) throws SQLException {
}
assertFalse(rs.next());
}
+ prep = conn.prepareStatement(
+ "SELECT ARRAY_AGG(V) FROM UNNEST(ARRAY[CAST(? AS INTEGER), CAST(? AS INTEGER)]) T(V)");
+ prep.setInt(1, 1);
+ prep.setInt(2, 2);
+ ResultSet rs = prep.executeQuery();
+ assertTrue(rs.next());
+ assertEquals(new Integer[] { 1, 2 }, rs.getObject(1, Integer[].class));
}
}