From 9315e96160610ac86f02c2b0edec26798aa1c798 Mon Sep 17 00:00:00 2001 From: Alex Kasko Date: Sat, 10 Oct 2026 15:28:21 +0100 Subject: [PATCH] Report NULL ordering in JDBC metadata (2.0) This is a backport of the PR #915 to `v2.0-cyanoptera` branch. Co-Authored-By: hutiefang76 <137664623+hutiefang76@users.noreply.github.com> --- .../org/duckdb/DuckDBDatabaseMetaData.java | 16 ++++-- src/test/java/org/duckdb/TestClosure.java | 57 +++++++++---------- src/test/java/org/duckdb/TestMetadata.java | 29 ++++++++++ .../java/org/duckdb/TestScalarFunctions.java | 4 +- 4 files changed, 70 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 1cfee691a..23311b138 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -61,24 +61,32 @@ public boolean isReadOnly() throws SQLException { return conn.isReadOnly(); } + private String getDefaultNullOrder() throws SQLException { + try (Statement s = conn.createStatement(); + ResultSet rs = s.executeQuery("SELECT current_setting('default_null_order')")) { + rs.next(); + return rs.getString(1); + } + } + @Override public boolean nullsAreSortedHigh() throws SQLException { - return true; + return "NULLS_LAST_ON_ASC_FIRST_ON_DESC".equals(getDefaultNullOrder()); } @Override public boolean nullsAreSortedLow() throws SQLException { - return false; + return "NULLS_FIRST_ON_ASC_LAST_ON_DESC".equals(getDefaultNullOrder()); } @Override public boolean nullsAreSortedAtStart() throws SQLException { - return true; + return "NULLS_FIRST".equals(getDefaultNullOrder()); } @Override public boolean nullsAreSortedAtEnd() throws SQLException { - return false; + return "NULLS_LAST".equals(getDefaultNullOrder()); } @Override diff --git a/src/test/java/org/duckdb/TestClosure.java b/src/test/java/org/duckdb/TestClosure.java index 076a77270..f4e4c2a0d 100644 --- a/src/test/java/org/duckdb/TestClosure.java +++ b/src/test/java/org/duckdb/TestClosure.java @@ -683,44 +683,41 @@ public static void test_open_connection_factories_return_objects() throws Except } public static void test_closed_connection_getMetaData_throws() throws Exception { - try (Connection conn = DriverManager.getConnection(JDBC_URL)) { - conn.close(); - assertTrue(conn.isClosed()); - try { - conn.getMetaData(); - fail("getMetaData() on a closed connection should throw SQLException"); - } catch (SQLException e) { - assertEquals(e.getMessage(), "Connection was closed"); - assertEquals(e.getSQLState(), "08003"); - } + Connection conn = DriverManager.getConnection(JDBC_URL); + conn.close(); + assertTrue(conn.isClosed()); + try { + conn.getMetaData(); + fail("getMetaData() on a closed connection should throw SQLException"); + } catch (SQLException e) { + assertEquals(e.getMessage(), "Connection was closed"); + assertEquals(e.getSQLState(), "08003"); } } public static void test_closed_connection_createArrayOf_throws() throws Exception { - try (Connection conn = DriverManager.getConnection(JDBC_URL)) { - conn.close(); - assertTrue(conn.isClosed()); - try { - conn.createArrayOf("INTEGER", new Object[] {1, 2}); - fail("createArrayOf() on a closed connection should throw SQLException"); - } catch (SQLException e) { - assertEquals(e.getMessage(), "Connection was closed"); - assertEquals(e.getSQLState(), "08003"); - } + Connection conn = DriverManager.getConnection(JDBC_URL); + conn.close(); + assertTrue(conn.isClosed()); + try { + conn.createArrayOf("INTEGER", new Object[] {1, 2}); + fail("createArrayOf() on a closed connection should throw SQLException"); + } catch (SQLException e) { + assertEquals(e.getMessage(), "Connection was closed"); + assertEquals(e.getSQLState(), "08003"); } } public static void test_closed_connection_createStruct_throws() throws Exception { - try (Connection conn = DriverManager.getConnection(JDBC_URL)) { - conn.close(); - assertTrue(conn.isClosed()); - try { - conn.createStruct("STRUCT(a INTEGER, b INTEGER)", new Object[] {1, 2}); - fail("createStruct() on a closed connection should throw SQLException"); - } catch (SQLException e) { - assertEquals(e.getMessage(), "Connection was closed"); - assertEquals(e.getSQLState(), "08003"); - } + Connection conn = DriverManager.getConnection(JDBC_URL); + conn.close(); + assertTrue(conn.isClosed()); + try { + conn.createStruct("STRUCT(a INTEGER, b INTEGER)", new Object[] {1, 2}); + fail("createStruct() on a closed connection should throw SQLException"); + } catch (SQLException e) { + assertEquals(e.getMessage(), "Connection was closed"); + assertEquals(e.getSQLState(), "08003"); } } diff --git a/src/test/java/org/duckdb/TestMetadata.java b/src/test/java/org/duckdb/TestMetadata.java index 8e864241b..57900f1a0 100644 --- a/src/test/java/org/duckdb/TestMetadata.java +++ b/src/test/java/org/duckdb/TestMetadata.java @@ -1203,4 +1203,33 @@ public static void test_metadata_variant_class() throws Exception { assertEquals(rows, 3); } } + + public static void test_metadata_null_ordering() throws Exception { + String[] options = {"NULLS_LAST", "NULLS_FIRST", "NULLS_FIRST_ON_ASC_LAST_ON_DESC", + "NULLS_LAST_ON_ASC_FIRST_ON_DESC"}; + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE test_null_order (i INTEGER)"); + stmt.execute("INSERT INTO test_null_order VALUES (1), (2), (NULL)"); + + DatabaseMetaData md = conn.getMetaData(); + for (String option : options) { + stmt.execute("SET default_null_order = '" + option + "'"); + + boolean ascNullFirst = isNullFirst(stmt, "ASC"); + boolean descNullFirst = isNullFirst(stmt, "DESC"); + + assertEquals(md.nullsAreSortedHigh(), !ascNullFirst && descNullFirst); + assertEquals(md.nullsAreSortedLow(), ascNullFirst && !descNullFirst); + assertEquals(md.nullsAreSortedAtStart(), ascNullFirst && descNullFirst); + assertEquals(md.nullsAreSortedAtEnd(), !ascNullFirst && !descNullFirst); + } + } + } + + private static boolean isNullFirst(Statement stmt, String direction) throws Exception { + try (ResultSet rs = stmt.executeQuery("SELECT i FROM test_null_order ORDER BY i " + direction)) { + assertTrue(rs.next()); + return rs.getObject(1) == null; + } + } } diff --git a/src/test/java/org/duckdb/TestScalarFunctions.java b/src/test/java/org/duckdb/TestScalarFunctions.java index 90b805477..2d23a86a2 100644 --- a/src/test/java/org/duckdb/TestScalarFunctions.java +++ b/src/test/java/org/duckdb/TestScalarFunctions.java @@ -2578,8 +2578,8 @@ public static void test_scalar_function_vectorized_parallel_validity_rmw() throw .register(conn); for (int repetition = 0; repetition < 3; repetition++) { try (ResultSet rs = - stmt.executeQuery("SELECT java_validity_rmw(x, x + 1) FROM range(0, (1<<16)) r(x)")) { - for (long i = 0; i < (1 << 16); i++) { + stmt.executeQuery("SELECT java_validity_rmw(x, x + 1) FROM range(0, (1<<12)) r(x)")) { + for (long i = 0; i < (1 << 12); i++) { assertTrue(rs.next()); if (i % 2 == 0) { assertNullRow(rs);