diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 7788bcceb..8fbe03e2f 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -19,6 +19,7 @@ - Fixed `getColumns()` flooding the `DriverManager` log writer with caught-and-recovered `Invalid column index` stack traces. - Fixed timezone-shifted TIMESTAMP values when retrieving nested complex types (STRUCT/ARRAY/MAP) with `EnableComplexDatatypeSupport=1`. - Fixed `DatabricksDatabaseMetaData.supportsBatchUpdates()` always returning `false`, which caused batch-aware JDBC clients (e.g. Apache Hop) to skip `executeBatch()` and fall back to one INSERT per row. It now returns `true` when `EnableBatchedInserts=1`, so those clients use the optimized multi-row INSERT path. +- Fixed `Connection.setReadOnly(true)` throwing `DatabricksSQLFeatureNotSupportedException`, which broke clients (e.g. Trino/Starburst GenericJDBC, HikariCP, DBCP) that call it during connection initialization. Per the JDBC spec, `setReadOnly` is a hint the driver may ignore; it is now a no-op and `isReadOnly()` continues to return `false`. --- *Note: When making changes, please add your change under the appropriate section diff --git a/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnection.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnection.java index ca39430b9..98f113908 100644 --- a/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnection.java +++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnection.java @@ -466,12 +466,14 @@ public DatabaseMetaData getMetaData() throws SQLException { @Override public void setReadOnly(boolean readOnly) throws SQLException { - LOGGER.debug("public void setReadOnly(boolean readOnly)"); + LOGGER.debug("public void setReadOnly(boolean readOnly = {})", readOnly); throwExceptionIfConnectionIsClosed(); - if (readOnly) { - throw new DatabricksSQLFeatureNotSupportedException( - "Databricks OSS JDBC does not support readOnly mode."); - } + // Per the JDBC spec, setReadOnly is a hint used to enable database optimizations. The + // Databricks + // backend does not enforce a connection-level read-only mode, so this is treated as a no-op + // rather than throwing. isReadOnly() continues to report false since the hint is not enforced. + // Throwing here breaks common clients (e.g. HikariCP, DBCP, Trino/Starburst) that call + // setReadOnly(true) during connection initialization. } @Override diff --git a/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionTest.java b/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionTest.java index 4828de2d0..670b17f8e 100644 --- a/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionTest.java +++ b/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionTest.java @@ -625,9 +625,11 @@ void testTranslationIsolation() throws SQLException { void testReadOnlyAndAbort() throws SQLException { connection = new DatabricksConnection(connectionContext, databricksClient); connection.open(); + // setReadOnly is a JDBC hint; the driver does not enforce read-only mode, so both values are + // accepted as no-ops (see JDBC spec). isReadOnly() continues to report false. assertDoesNotThrow(() -> connection.setReadOnly(false)); - assertThrows( - DatabricksSQLFeatureNotSupportedException.class, () -> connection.setReadOnly(true)); + assertDoesNotThrow(() -> connection.setReadOnly(true)); + assertFalse(connection.isReadOnly()); ExecutorService executorService = Executors.newFixedThreadPool(1); assertDoesNotThrow(() -> connection.abort(executorService)); connection.close();