From 290e36639ec456de929bfb97d9517ed3c6c7817a Mon Sep 17 00:00:00 2001 From: Gopal Lal Date: Fri, 22 May 2026 16:46:49 +0530 Subject: [PATCH] Fix NPE when server returns null resultLinks for small CloudFetch results When canDownloadResult=true, the server may return URL_BASED_SET format but inline the actual data for small results, leaving resultLinks=null and putting data in arrowBatches instead. Two-layer fix: 1. ExecutionResultFactory: when URL_BASED_SET has no resultLinks but has inline arrowBatches, fall back to inline arrow path instead of creating RemoteChunkProvider. 2. AbstractRemoteChunkProvider: null guard on getResultLinks() as defense-in-depth for subsequent fetch responses. Reported by Fivetran partner via support ticket (driver 3.3.1). Co-authored-by: Isaac Signed-off-by: Gopal Lal --- NEXT_CHANGELOG.md | 1 + .../jdbc/api/impl/ExecutionResultFactory.java | 16 ++++++++++++++++ .../impl/arrow/AbstractRemoteChunkProvider.java | 6 +++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index ea019e959d..4693a38529 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -72,6 +72,7 @@ upgrading. These changes do not affect metadata on All-Purpose Clusters. - Bump shaded `netty-buffer`/`netty-common` from 4.2.12.Final to 4.2.13.Final to clear OWASP scanner reports for the May 2026 batch of netty codec CVEs (CVE-2026-42577/42579/42580/42581/42582/42583/42584/42585/42586/42587, CVE-2026-44248, CVE-2026-41417, CVE-2026-42578). The driver does not use any netty HTTP/codec components — these vulnerabilities are not exploitable in this usage — but the bump silences the false-positive CPE matches. - Bump shaded `commons-configuration2` from 2.10.1 to 2.15.0 to address [CVE-2026-45205](https://nvd.nist.gov/vuln/detail/CVE-2026-45205) (uncontrolled recursion when parsing untrusted YAML configurations). The driver does not parse untrusted YAML, so the practical risk is negligible. - Bump `lz4-java` from `org.lz4:lz4-java:1.8.1` to `at.yawk.lz4:lz4-java:1.10.1` to address [CVE-2025-66566](https://nvd.nist.gov/vuln/detail/CVE-2025-66566) (information leak via uncleared output buffers in the safe/unsafe Java decompressors). `org.lz4:lz4-java:1.8.1` is a relocation-only POM that resolves to `at.yawk.lz4:lz4-java:1.8.1`, so the published `databricks-jdbc-thin` artifact previously pulled the vulnerable fork transitively. The upstream `org.lz4` GA is no longer maintained; `at.yawk.lz4` is the fork that received the fix. Fixes #1455. +- Fixed `NullPointerException` in `AbstractRemoteChunkProvider.populateChunkIndexMap()` when the server returns `resultLinks=null` for small inline results delivered via CloudFetch format. This occurred when `canDownloadResult=true` but the server inlined the result due to its small size. Workaround: set `EnableQueryResultDownload=0`. - Fix `PreparedStatement.getMetaData()` crash (`IllegalArgumentException`) for SQL type aliases (VARCHAR, INTEGER, NUMERIC, DEC, REAL, NVARCHAR, NCHAR) returned by DESCRIBE QUERY - Fixed `DatabaseMetaData.getTables()` in Thrift mode returning rows when called with an empty `types` array. Per JDBC spec, empty types means "no types selected" and now correctly returns zero rows (matching SEA mode). - Fixed `?` characters inside SQL comments, string literals, and quoted identifiers being incorrectly counted as parameter placeholders when `supportManyParameters=1`. `SQLInterpolator` now uses `SqlCommentParser` to locate only real placeholders. Fixes #1331. diff --git a/src/main/java/com/databricks/jdbc/api/impl/ExecutionResultFactory.java b/src/main/java/com/databricks/jdbc/api/impl/ExecutionResultFactory.java index b9aec3fc1b..3860cc4c6d 100644 --- a/src/main/java/com/databricks/jdbc/api/impl/ExecutionResultFactory.java +++ b/src/main/java/com/databricks/jdbc/api/impl/ExecutionResultFactory.java @@ -14,6 +14,7 @@ import com.databricks.jdbc.log.JdbcLogger; import com.databricks.jdbc.log.JdbcLoggerFactory; import com.databricks.jdbc.model.client.thrift.generated.TFetchResultsResp; +import com.databricks.jdbc.model.client.thrift.generated.TRowSet; import com.databricks.jdbc.model.client.thrift.generated.TSparkRowSetType; import com.databricks.jdbc.model.core.ResultData; import com.databricks.jdbc.model.core.ResultManifest; @@ -103,6 +104,21 @@ private static IExecutionResult getResultHandler( case ARROW_BASED_SET: return createInlineArrowResult(resultsResp, parentStatement, session); case URL_BASED_SET: + // Server may declare URL_BASED_SET but inline the data for small results, + // leaving resultLinks=null and putting data in arrowBatches instead. + // Fall back to inline arrow path when no external links are present + // but inline arrow data is available. + TRowSet results = resultsResp.getResults(); + boolean hasResultLinks = + results != null + && results.getResultLinks() != null + && !results.getResultLinks().isEmpty(); + if (!hasResultLinks && results != null && results.isSetArrowBatches()) { + LOGGER.info( + "URL_BASED_SET format but no resultLinks with inline arrowBatches present" + + " — falling back to inline arrow path"); + return createInlineArrowResult(resultsResp, parentStatement, session); + } return new ArrowStreamResult(resultsResp, parentStatement, session); case ROW_BASED_SET: throw new DatabricksSQLFeatureNotSupportedException( diff --git a/src/main/java/com/databricks/jdbc/api/impl/arrow/AbstractRemoteChunkProvider.java b/src/main/java/com/databricks/jdbc/api/impl/arrow/AbstractRemoteChunkProvider.java index 4717852f48..421d8a5fb4 100644 --- a/src/main/java/com/databricks/jdbc/api/impl/arrow/AbstractRemoteChunkProvider.java +++ b/src/main/java/com/databricks/jdbc/api/impl/arrow/AbstractRemoteChunkProvider.java @@ -293,7 +293,11 @@ private ConcurrentMap initializeChunksMap( private void populateChunkIndexMap(TRowSet resultData, ConcurrentMap chunkIndexMap) throws DatabricksSQLException { rowCount += DatabricksThriftUtil.getRowCount(resultData); - for (TSparkArrowResultLink resultLink : resultData.getResultLinks()) { + java.util.List resultLinks = resultData.getResultLinks(); + if (resultLinks == null) { + return; + } + for (TSparkArrowResultLink resultLink : resultLinks) { LOGGER.debug( "Chunk information log - Row Offset: {}, Row Count: {}, Expiry Time: {}", resultLink.getStartRowOffset(),