From 067f10cf66821f8ffc2d0b1d8bea80fabcac8fc8 Mon Sep 17 00:00:00 2001 From: hutiefang76 <137664623+hutiefang76@users.noreply.github.com> Date: Sat, 10 Oct 2026 09:06:25 +0800 Subject: [PATCH] Return false when array move out of bounds (2.0) This is a backport of the PR #912 to `v2.0-cyanoptera` branch. --- .../java/org/duckdb/DuckDBArrayResultSet.java | 18 ++- src/test/java/org/duckdb/TestDuckDBJDBC.java | 128 ++++++++++++++++++ 2 files changed, 139 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/duckdb/DuckDBArrayResultSet.java b/src/main/java/org/duckdb/DuckDBArrayResultSet.java index 9fdf3fdd8..6d235b00b 100644 --- a/src/main/java/org/duckdb/DuckDBArrayResultSet.java +++ b/src/main/java/org/duckdb/DuckDBArrayResultSet.java @@ -317,22 +317,22 @@ public BigDecimal getBigDecimal(String columnLabel) throws SQLException { @Override public boolean isBeforeFirst() throws SQLException { - return currentValueIndex < 0; + return length > 0 && currentValueIndex < 0; } @Override public boolean isAfterLast() throws SQLException { - return currentValueIndex >= length; + return length > 0 && currentValueIndex >= length; } @Override public boolean isFirst() throws SQLException { - return currentValueIndex == 0; + return length > 0 && currentValueIndex == 0; } @Override public boolean isLast() throws SQLException { - return currentValueIndex == length - 1; + return length > 0 && currentValueIndex == length - 1; } @Override @@ -365,7 +365,7 @@ public boolean last() throws SQLException { @Override public int getRow() throws SQLException { - return currentValueIndex + 1; + return isValidRow() ? currentValueIndex + 1 : 0; } @Override @@ -379,12 +379,16 @@ public boolean absolute(int row) throws SQLException { return checkBounds(); } + private boolean isValidRow() { + return currentValueIndex >= 0 && currentValueIndex < length; + } + private boolean checkBounds() { - if (currentValueIndex < -1) { + if (currentValueIndex < 0) { currentValueIndex = -1; return false; } - if (currentValueIndex > length) { + if (currentValueIndex >= length) { currentValueIndex = length; return false; } diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index a2aeb750b..c1279b63f 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -1405,6 +1405,134 @@ public static void test_array_resultset() throws Exception { } } + @SuppressWarnings("try") + public static void test_array_resultset_cursor() throws Exception { + try (Connection connection = DriverManager.getConnection(JDBC_URL); + Statement statement = connection.createStatement()) { + try (ResultSet rs = statement.executeQuery("select [10, 20, 30]")) { + assertTrue(rs.next()); + try (ResultSet arrayResultSet = rs.getArray(1).getResultSet()) { + // initial position: before first, not on a row + assertTrue(arrayResultSet.isBeforeFirst()); + assertFalse(arrayResultSet.isAfterLast()); + assertFalse(arrayResultSet.isFirst()); + assertFalse(arrayResultSet.isLast()); + assertEquals(arrayResultSet.getRow(), 0); + + // absolute positive within bounds + assertTrue(arrayResultSet.absolute(2)); + assertFalse(arrayResultSet.isBeforeFirst()); + assertFalse(arrayResultSet.isAfterLast()); + assertFalse(arrayResultSet.isFirst()); + assertFalse(arrayResultSet.isLast()); + assertEquals(arrayResultSet.getRow(), 2); + assertEquals(arrayResultSet.getInt(2), 20); + + assertTrue(arrayResultSet.absolute(1)); + assertTrue(arrayResultSet.isFirst()); + assertEquals(arrayResultSet.getRow(), 1); + + // absolute beyond last (length + 1) -> after last + assertFalse(arrayResultSet.absolute(4)); + assertTrue(arrayResultSet.isAfterLast()); + assertFalse(arrayResultSet.isBeforeFirst()); + assertEquals(arrayResultSet.getRow(), 0); + + // absolute(0) -> before first + assertFalse(arrayResultSet.absolute(0)); + assertTrue(arrayResultSet.isBeforeFirst()); + assertFalse(arrayResultSet.isAfterLast()); + assertEquals(arrayResultSet.getRow(), 0); + + // negative absolute counts from the end + assertTrue(arrayResultSet.absolute(-1)); + assertTrue(arrayResultSet.isLast()); + assertEquals(arrayResultSet.getRow(), 3); + assertEquals(arrayResultSet.getInt(2), 30); + + assertTrue(arrayResultSet.absolute(-3)); + assertTrue(arrayResultSet.isFirst()); + assertEquals(arrayResultSet.getRow(), 1); + + assertFalse(arrayResultSet.absolute(-4)); + assertTrue(arrayResultSet.isBeforeFirst()); + assertEquals(arrayResultSet.getRow(), 0); + + // relative edges + assertTrue(arrayResultSet.absolute(1)); + assertTrue(arrayResultSet.relative(2)); + assertTrue(arrayResultSet.isLast()); + assertFalse(arrayResultSet.relative(1)); + assertTrue(arrayResultSet.isAfterLast()); + assertFalse(arrayResultSet.relative(0)); + + assertTrue(arrayResultSet.absolute(1)); + assertTrue(arrayResultSet.relative(0)); + assertTrue(arrayResultSet.isFirst()); + + // previous from the first row -> before first + assertFalse(arrayResultSet.previous()); + assertTrue(arrayResultSet.isBeforeFirst()); + assertFalse(arrayResultSet.isAfterLast()); + assertEquals(arrayResultSet.getRow(), 0); + // previous again stays before first + assertFalse(arrayResultSet.previous()); + assertTrue(arrayResultSet.isBeforeFirst()); + assertEquals(arrayResultSet.getRow(), 0); + + // next walks 1 -> 2 -> 3 -> after last + assertTrue(arrayResultSet.next()); + assertTrue(arrayResultSet.isFirst()); + assertEquals(arrayResultSet.getRow(), 1); + assertEquals(arrayResultSet.getInt(2), 10); + + // second next -> second row, neither first nor last + assertTrue(arrayResultSet.next()); + assertFalse(arrayResultSet.isBeforeFirst()); + assertFalse(arrayResultSet.isFirst()); + assertFalse(arrayResultSet.isLast()); + assertEquals(arrayResultSet.getRow(), 2); + assertEquals(arrayResultSet.getInt(2), 20); + + // third next -> last row + assertTrue(arrayResultSet.next()); + assertFalse(arrayResultSet.isFirst()); + assertTrue(arrayResultSet.isLast()); + assertEquals(arrayResultSet.getRow(), 3); + assertEquals(arrayResultSet.getInt(2), 30); + + // fourth next -> after last + assertFalse(arrayResultSet.next()); + assertFalse(arrayResultSet.isBeforeFirst()); + assertTrue(arrayResultSet.isAfterLast()); + assertEquals(arrayResultSet.getRow(), 0); + } + } + + // empty array (0 values) + try (ResultSet rs = statement.executeQuery("select []::INTEGER[]")) { + assertTrue(rs.next()); + try (ResultSet empty = rs.getArray(1).getResultSet()) { + assertFalse(empty.isBeforeFirst()); + assertFalse(empty.isAfterLast()); + assertEquals(empty.getRow(), 0); + assertFalse(empty.first()); + assertFalse(empty.last()); + assertFalse(empty.next()); + assertFalse(empty.absolute(1)); + assertFalse(empty.absolute(-1)); + assertFalse(empty.absolute(0)); + assertFalse(empty.relative(1)); + assertFalse(empty.relative(0)); + assertFalse(empty.previous()); + assertFalse(empty.isBeforeFirst()); + assertFalse(empty.isAfterLast()); + assertEquals(empty.getRow(), 0); + } + } + } + } + @SuppressWarnings("unchecked") private static List arrayToList(Array array) throws SQLException { return arrayToList((T[]) array.getArray());