Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Fixed

- Return SQLSTATE `HYT00` when a client-side query timeout expires.

---
*Note: When making changes, please add your change under the appropriate section
with a brief description.*
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,10 @@ DatabricksResultSet executeInternal(
LOGGER.error(timeoutErrorMessage);
futureResultSet.cancel(true); // Cancel execution run
throw new DatabricksTimeoutException(
timeoutErrorMessage, e, DatabricksDriverErrorCode.STATEMENT_EXECUTION_TIMEOUT);
timeoutErrorMessage,
TIMEOUT_EXPIRED_SQLSTATE,
e,
DatabricksDriverErrorCode.STATEMENT_EXECUTION_TIMEOUT);
} catch (InterruptedException | ExecutionException e) {
Throwable cause = e;
// Look for underlying SQLException (includes DatabricksSQLException and other SQL exceptions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ public final class DatabricksJdbcConstants {
public static final String DEFAULT_HTTP_EXCEPTION_SQLSTATE = OPERATION_ERROR_SQLSTATE;
public static final String QUERY_EXECUTION_TIMEOUT_SQLSTATE = "57KD0";

/** SQLSTATE for timeout expiration, matching the Databricks reference JDBC driver. */
public static final String TIMEOUT_EXPIRED_SQLSTATE = "HYT00";

/** Standard SQL state for syntax error or access rule violation (SQLSTATE 42000). */
public static final String SYNTAX_OR_ACCESS_VIOLATION_SQLSTATE = "42000";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.databricks.jdbc.dbclient.impl.common;

import static com.databricks.jdbc.common.DatabricksJdbcConstants.TIMEOUT_EXPIRED_SQLSTATE;

import com.databricks.jdbc.dbclient.IDatabricksClient;
import com.databricks.jdbc.exception.DatabricksTimeoutException;
import com.databricks.jdbc.log.JdbcLogger;
Expand Down Expand Up @@ -68,7 +70,8 @@ public void checkTimeout() throws DatabricksTimeoutException {
"Statement execution timed-out after %d seconds. Operation: %s",
timeoutSeconds, operationDescription);
LOGGER.error(timeoutErrorMessage);
throw new DatabricksTimeoutException(timeoutErrorMessage, /*cause*/ null, internalErrorCode);
throw new DatabricksTimeoutException(
timeoutErrorMessage, TIMEOUT_EXPIRED_SQLSTATE, /*cause*/ null, internalErrorCode);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.databricks.jdbc.api.impl;

import static com.databricks.jdbc.common.DatabricksJdbcConstants.TIMEOUT_EXPIRED_SQLSTATE;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
Expand All @@ -15,6 +16,7 @@
import com.databricks.jdbc.dbclient.impl.sqlexec.DatabricksSdkClient;
import com.databricks.jdbc.exception.DatabricksSQLException;
import com.databricks.jdbc.exception.DatabricksSQLFeatureNotSupportedException;
import com.databricks.jdbc.exception.DatabricksTimeoutException;
import com.databricks.jdbc.model.core.StatementStatus;
import com.databricks.jdbc.model.telemetry.enums.DatabricksDriverErrorCode;
import com.databricks.sdk.service.sql.StatementState;
Expand Down Expand Up @@ -330,6 +332,31 @@ public void testExecuteInternalWithZeroTimeout() throws Exception {
verify(mockFuture, never()).get(anyLong(), any(TimeUnit.class));
}

@Test
public void testExecuteInternalTimeoutUsesTimeoutExpiredSqlState() throws Exception {
IDatabricksConnectionContext connectionContext =
DatabricksConnectionContext.parse(JDBC_URL, new Properties());
DatabricksConnection mockConnection = mock(DatabricksConnection.class);
when(mockConnection.getConnectionContext()).thenReturn(connectionContext);
DatabricksStatement statement = spy(new DatabricksStatement(mockConnection));
statement.setQueryTimeout(1);

CompletableFuture<DatabricksResultSet> mockFuture = mock(CompletableFuture.class);
when(mockFuture.get(1, TimeUnit.SECONDS))
.thenThrow(new java.util.concurrent.TimeoutException());
doReturn(mockFuture).when(statement).getFutureResult(anyString(), anyMap(), any());

DatabricksTimeoutException exception =
assertThrows(
DatabricksTimeoutException.class,
() ->
statement.executeInternal(
"SELECT * FROM table", new HashMap<>(), StatementType.QUERY, false));

assertEquals(TIMEOUT_EXPIRED_SQLSTATE, exception.getSQLState());
verify(mockFuture).cancel(true);
}

@Test
public void testInputStreamForVolumeOperation() throws Exception {
IDatabricksConnectionContext connectionContext =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.databricks.jdbc.dbclient.impl.common;

import static com.databricks.jdbc.common.DatabricksJdbcConstants.TIMEOUT_EXPIRED_SQLSTATE;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

Expand Down Expand Up @@ -82,6 +83,7 @@ void testTimeoutActionExecuted() throws Exception {
// Verify exception message
assertTrue(exception.getMessage().contains("timed-out after 2 seconds"));
assertTrue(exception.getMessage().contains("Test operation"));
assertEquals(TIMEOUT_EXPIRED_SQLSTATE, exception.getSQLState());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.databricks.jdbc.TestConstants.TEST_STRING;
import static com.databricks.jdbc.common.DatabricksJdbcConstants.QUERY_EXECUTION_TIMEOUT_SQLSTATE;
import static com.databricks.jdbc.common.DatabricksJdbcConstants.TEMPORARY_REDIRECT_STATUS_CODE;
import static com.databricks.jdbc.common.DatabricksJdbcConstants.TIMEOUT_EXPIRED_SQLSTATE;
import static com.databricks.jdbc.dbclient.impl.sqlexec.PathConstants.*;
import static com.databricks.jdbc.model.core.ColumnInfoTypeName.DECIMAL;
import static com.databricks.jdbc.model.core.ColumnInfoTypeName.INT;
Expand Down Expand Up @@ -782,6 +783,7 @@ public void testExecuteStatementWithTimeoutExpired() throws Exception {
null));

assertTrue(exception.getMessage().contains("timed-out after 1 seconds"));
assertEquals(TIMEOUT_EXPIRED_SQLSTATE, exception.getSQLState());

// Verify cancel was called
verify(databricksSdkClient).cancelStatement(eq(STATEMENT_ID));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.databricks.jdbc.dbclient.impl.thrift;

import static com.databricks.jdbc.common.DatabricksJdbcConstants.QUERY_EXECUTION_TIMEOUT_SQLSTATE;
import static com.databricks.jdbc.common.DatabricksJdbcConstants.TIMEOUT_EXPIRED_SQLSTATE;
import static com.databricks.jdbc.common.EnvironmentVariables.DEFAULT_BYTE_LIMIT;
import static com.databricks.jdbc.common.EnvironmentVariables.DEFAULT_ROW_LIMIT_PER_BLOCK;
import static com.databricks.jdbc.model.telemetry.enums.DatabricksDriverErrorCode.EXECUTE_STATEMENT_FAILED;
Expand Down Expand Up @@ -954,6 +955,7 @@ void testExecuteWithTimeoutExpired() throws TException, SQLException {
() -> accessor.execute(request, parentStatement, session, StatementType.SQL));

assertTrue(exception.getMessage().contains("timed-out after 1 seconds"));
assertEquals(TIMEOUT_EXPIRED_SQLSTATE, exception.getSQLState());

// Verify that cancel was called
verify(thriftClient).CancelOperation(any(TCancelOperationReq.class));
Expand Down
Loading