diff --git a/dialect/db/h2/src/main/java/org/eclipse/daanse/sql/dialect/db/h2/H2Dialect.java b/dialect/db/h2/src/main/java/org/eclipse/daanse/sql/dialect/db/h2/H2Dialect.java index 20cd640..f6a8b3c 100644 --- a/dialect/db/h2/src/main/java/org/eclipse/daanse/sql/dialect/db/h2/H2Dialect.java +++ b/dialect/db/h2/src/main/java/org/eclipse/daanse/sql/dialect/db/h2/H2Dialect.java @@ -188,12 +188,17 @@ public java.util.Optional generateListAgg(CharSequence operand, boolean return java.util.Optional.of((buf).toString()); } - /** The n-th value of the group's rows in their order: an ordered array, its n-th element (V-82). */ + /** + * The n-th value of the group's rows in their order: an ordered array, its n-th element (V-82). H2 fails an + * element reference past the end ("Array element error", also ARRAY_GET), so the element is sliced out and a NULL + * appended: a group with fewer values gives NULL, as the aggregate promises. + */ @Override public java.util.Optional generateNthValueAgg(CharSequence operand, boolean ignoreNulls, Integer n, List columns) { - return java.util.Optional.of(buildNthValueOfArray("ARRAY_AGG", operand, ignoreNulls, columns, "(", - ")[" + AggregationGenerator.nthIndex(n) + "]").toString()); + int index = AggregationGenerator.nthIndex(n); + return java.util.Optional.of(buildNthValueOfArray("ARRAY_AGG", operand, ignoreNulls, columns, "(ARRAY_SLICE(", + ", " + index + ", " + index + ") || ARRAY[NULL])[1]").toString()); } @Override diff --git a/dialect/db/h2/src/test/java/org/eclipse/daanse/sql/dialect/db/h2/H2NthValuePastTheEndTest.java b/dialect/db/h2/src/test/java/org/eclipse/daanse/sql/dialect/db/h2/H2NthValuePastTheEndTest.java new file mode 100644 index 0000000..cbb51d9 --- /dev/null +++ b/dialect/db/h2/src/test/java/org/eclipse/daanse/sql/dialect/db/h2/H2NthValuePastTheEndTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * SmartCity Jena - initial + */ +package org.eclipse.daanse.sql.dialect.db.h2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.daanse.sql.dialect.api.DialectInitData; +import org.eclipse.daanse.sql.model.sql.OrderedColumn; +import org.eclipse.daanse.sql.model.sql.SortDirection; +import org.junit.jupiter.api.Test; + +/** + * The n-th value aggregate runs on H2 and gives NULL for a group with fewer values: H2 fails an array element past + * the end, so the n-th value of four values asked for the fifth broke the query (TheNthValueIsPerCellTest on H2, + * database run 18.10.2026). + */ +class H2NthValuePastTheEndTest { + + private static List nth(int n, String rows) throws SQLException { + try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:nth" + n)) { + H2Dialect dialect = new H2Dialect(DialectInitData.fromConnection(connection)); + String nth = dialect.generateNthValueAgg("\"x\"", false, n, + List.of(new OrderedColumn("x", null, SortDirection.DESC))).orElseThrow(); + List values = new ArrayList<>(); + try (ResultSet result = connection.createStatement() + .executeQuery("SELECT \"g\", " + nth + " FROM (VALUES " + rows + ") t(\"g\", \"x\") GROUP BY \"g\" ORDER BY \"g\"")) { + while (result.next()) { + values.add(result.getObject(2)); + } + } + return values; + } + } + + @Test + void theNthValueOfEachGroupInTheOrderOfTheColumns() throws SQLException { + assertEquals(List.of(2, 7), nth(2, "(1, 1), (1, 2), (1, 3), (2, 7), (2, 8)")); + } + + @Test + void aGroupWithFewerValuesGivesNull() throws SQLException { + List values = nth(5, "(1, 1), (1, 2), (1, 3), (1, 4)"); + assertEquals(1, values.size()); + assertNull(values.get(0)); + } +} diff --git a/dialect/db/oracle/src/main/java/org/eclipse/daanse/sql/dialect/db/oracle/OracleDialect.java b/dialect/db/oracle/src/main/java/org/eclipse/daanse/sql/dialect/db/oracle/OracleDialect.java index f66f2b5..a423f16 100644 --- a/dialect/db/oracle/src/main/java/org/eclipse/daanse/sql/dialect/db/oracle/OracleDialect.java +++ b/dialect/db/oracle/src/main/java/org/eclipse/daanse/sql/dialect/db/oracle/OracleDialect.java @@ -237,9 +237,13 @@ public boolean supportsCreateOrReplaceTrigger() { return true; } + /** + * From Oracle 23 ({@code DROP TABLE IF EXISTS} came with 23ai); before, a drop of a missing table fails with + * ORA-00942. A dialect made without a connection knows no version and says no. + */ @Override public boolean supportsDropTableIfExists() { - return false; + return dialectVersion.atLeast(23, 0); } @Override diff --git a/dialect/db/oracle/src/test/java/org/eclipse/daanse/sql/dialect/db/oracle/OracleDropTableIfExistsTest.java b/dialect/db/oracle/src/test/java/org/eclipse/daanse/sql/dialect/db/oracle/OracleDropTableIfExistsTest.java new file mode 100644 index 0000000..14e22b4 --- /dev/null +++ b/dialect/db/oracle/src/test/java/org/eclipse/daanse/sql/dialect/db/oracle/OracleDropTableIfExistsTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * SmartCity Jena - initial + */ +package org.eclipse.daanse.sql.dialect.db.oracle; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.eclipse.daanse.sql.dialect.api.DialectInitData; +import org.junit.jupiter.api.Test; + +/** + * Oracle drops a table only if it exists from version 23 on: before, the plain {@code DROP TABLE} of a missing table + * fails with ORA-00942, which aborted every CSV fixture of the ROLAP suite on Oracle Free 23ai (database run + * 18.10.2026). + */ +class OracleDropTableIfExistsTest { + + private static OracleDialect dialect(int major) { + return new OracleDialect(DialectInitData.ansiDefaults().withVersion(major, 0)); + } + + @Test + void oracle23DropsATableIfItExists() { + assertThat(dialect(23).supportsDropTableIfExists()).isTrue(); + assertThat(dialect(23).dropTable("S", "T", true)).isEqualTo("DROP TABLE IF EXISTS \"S\".\"T\""); + } + + @Test + void oracle19AndADialectWithoutAVersionDropPlainly() { + assertThat(dialect(19).supportsDropTableIfExists()).isFalse(); + assertThat(new OracleDialect().supportsDropTableIfExists()).isFalse(); + } +}