From e903434c2994a6fa1e2b93c10b5c83a3944f2a43 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Tue, 25 Aug 2026 13:16:36 +0200 Subject: [PATCH] Fix NPE on materialising nested arrays or arrays of maps or structs with `NULL` elements. Null elements in strongly typed nested arrays are valid elements and must be returned as such. This fixes the instance of checking by adding a null-check on all nested branches (arrays of arrays, arrays of maps and arrays of structs). This closes #1658. Signed-off-by: Michael Simons --- .../jdbc/api/impl/DatabricksArray.java | 6 ++ .../e2e/ComplexTypeQueryTests.java | 83 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/main/java/com/databricks/jdbc/api/impl/DatabricksArray.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksArray.java index c914b33339..89dd6ab188 100644 --- a/src/main/java/com/databricks/jdbc/api/impl/DatabricksArray.java +++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksArray.java @@ -54,6 +54,8 @@ private Object[] convertElements(List elements, String elementType) { convertedElements[i] = new DatabricksStruct((Map) element, elementType); } else if (element instanceof DatabricksStruct) { convertedElements[i] = element; + } else if (element == null) { + convertedElements[i] = null; } else { throw new DatabricksDriverException( "Expected a Map for STRUCT but found: " + element.getClass().getSimpleName(), @@ -64,6 +66,8 @@ private Object[] convertElements(List elements, String elementType) { convertedElements[i] = new DatabricksArray((List) element, elementType); } else if (element instanceof DatabricksArray) { convertedElements[i] = element; + } else if (element == null) { + convertedElements[i] = null; } else { throw new DatabricksDriverException( "Expected a List for ARRAY but found: " + element.getClass().getSimpleName(), @@ -74,6 +78,8 @@ private Object[] convertElements(List elements, String elementType) { convertedElements[i] = new DatabricksMap<>((Map) element, elementType); } else if (element instanceof DatabricksMap) { convertedElements[i] = element; + } else if (element == null) { + convertedElements[i] = null; } else { throw new DatabricksDriverException( "Expected a Map for MAP but found: " + element.getClass().getSimpleName(), diff --git a/src/test/java/com/databricks/jdbc/integration/e2e/ComplexTypeQueryTests.java b/src/test/java/com/databricks/jdbc/integration/e2e/ComplexTypeQueryTests.java index 0ef768e291..9f9e9ed844 100644 --- a/src/test/java/com/databricks/jdbc/integration/e2e/ComplexTypeQueryTests.java +++ b/src/test/java/com/databricks/jdbc/integration/e2e/ComplexTypeQueryTests.java @@ -190,6 +190,89 @@ void testEmptyArray(int thriftVal, int complexSupport) throws SQLException { } } + @ParameterizedTest + @CsvSource({"0,0", "1,0", "0,1", "1,1"}) + void testNestedArrayWithNullElement(int thriftVal, int complexSupport) throws SQLException { + setupConnection(thriftVal, complexSupport); + + String sql = "SELECT array(null, array('Hello'))::ARRAY> AS arr"; + ResultSet rs = executeQuery(connection, sql); + assertNotNull(rs); + while (rs.next()) { + if (complexSupport == 1) { + Array arr = rs.getArray("arr"); + assertNotNull(arr); + Object[] elements = (Object[]) arr.getArray(); + assertEquals(2, elements.length); + assertNull(elements[0]); + assertInstanceOf(Array.class, elements[1]); + Object[] innerElements = (Object[]) ((Array) elements[1]).getArray(); + assertEquals(1, innerElements.length); + assertEquals("Hello", innerElements[0]); + } else { + assertThrows(SQLException.class, () -> rs.getArray("arr")); + Object obj = rs.getObject("arr"); + assertInstanceOf(String.class, obj); + String text = (String) obj; + assertFalse(text.isEmpty()); + } + } + } + + @ParameterizedTest + @CsvSource({"0,0", "1,0", "0,1", "1,1"}) + void testArrayOfMapsWithNullElement(int thriftVal, int complexSupport) throws SQLException { + setupConnection(thriftVal, complexSupport); + + String sql = + "SELECT array(null, map('red', 1, 'green', 2))::ARRAY> AS arr"; + ResultSet rs = executeQuery(connection, sql); + assertNotNull(rs); + while (rs.next()) { + if (complexSupport == 1) { + Array arr = rs.getArray("arr"); + assertNotNull(arr); + Object[] elements = (Object[]) arr.getArray(); + assertEquals(2, elements.length); + assertNull(elements[0]); + assertInstanceOf(Map.class, elements[1]); + } else { + assertThrows(SQLException.class, () -> rs.getArray("arr")); + Object obj = rs.getObject("arr"); + assertInstanceOf(String.class, obj); + String text = (String) obj; + assertFalse(text.isEmpty()); + } + } + } + + @ParameterizedTest + @CsvSource({"0,0", "1,0", "0,1", "1,1"}) + void testArrayOfStructsWithNullElement(int thriftVal, int complexSupport) throws SQLException { + setupConnection(thriftVal, complexSupport); + + String sql = + "SELECT array(null, struct('Spark', 5))::ARRAY> AS arr"; + ResultSet rs = executeQuery(connection, sql); + assertNotNull(rs); + while (rs.next()) { + if (complexSupport == 1) { + Array arr = rs.getArray("arr"); + assertNotNull(arr); + Object[] elements = (Object[]) arr.getArray(); + assertEquals(2, elements.length); + assertNull(elements[0]); + assertInstanceOf(Struct.class, elements[1]); + } else { + assertThrows(SQLException.class, () -> rs.getArray("arr")); + Object obj = rs.getObject("arr"); + assertInstanceOf(String.class, obj); + String text = (String) obj; + assertFalse(text.isEmpty()); + } + } + } + @ParameterizedTest @CsvSource({"0,0", "1,0", "0,1", "1,1"}) void testNullArray(int thriftVal, int complexSupport) throws SQLException {