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
1 change: 1 addition & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- **SQL Scripting support**: Added support for [SQL Scripting](https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-scripting)
- Added a client property `enableVolumeOperations` to enable GET/PUT/REMOVE volume operations on a stream. For backward compatibility, allowedVolumeIngestionPaths can also be used for REMOVE operation.
- Support for fetching schemas across all catalogs (when catalog is specified as null or a wildcard) in `DatabaseMetaData#getSchemas` API in SQL Execution mode.
- **Configurable SQL validation in isValid()**: Added `EnableSQLValidationForIsValid` connection property to control whether `isValid()` method executes an actual SQL query for server-side validation. Default value is 0.

### Updated
- Databricks SDK dependency upgraded to latest version 0.60.0
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Optional parameters:
- `OAuth2RedirectUrlPort` - Ports for redirect URL (default: 8020)
- `EnableOIDCDiscovery` - Enable OIDC discovery (default: 1)
- `OAuthDiscoveryURL` - OIDC discovery endpoint (default: /oidc/.well-known/oauth-authorization-server)
- `EnableSQLValidationForIsValid` - Enable SQL query based validation in `isValid()` connection checks (default: 0)

### Logging

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,21 @@ public SQLXML createSQLXML() throws SQLException {
@Override
public boolean isValid(int timeout) throws SQLException {
ValidationUtil.checkIfNonNegative(timeout, "timeout");
return !isClosed();
if (isClosed()) {
return false;
}
if (connectionContext.getEnableSQLValidationForIsValid()) {
try (Statement stmt = createStatement()) {
stmt.setQueryTimeout(timeout);
// This is a lightweight query to check if the connection is valid
stmt.execute("SELECT VERSION()");
return true;
} catch (Exception e) {
LOGGER.debug("Validation failed for isValid(): {}", e.getMessage());
return false;
}
}
return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ public String getHttpPath() {
return getParameter(DatabricksJdbcUrlParams.HTTP_PATH);
}

public boolean getEnableSQLValidationForIsValid() {
LOGGER.debug("String getEnableSQLValidationForIsValid()");
return getParameter(DatabricksJdbcUrlParams.ENABLE_SQL_VALIDATION_FOR_IS_VALID, "0")
.equals("1");
}

@Override
public String getHostForOAuth() {
return this.host;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ public boolean toBoolean(Object object) throws DatabricksSQLException {
return Boolean.parseBoolean((String) object);
}
throw new DatabricksSQLException(
"Unsupported type for conversion to BIT: " + object.getClass(),
"Unsupported type for conversion to BIT: " + (object == null ? "null" : object.getClass()),
DatabricksDriverErrorCode.UNSUPPORTED_OPERATION);
}

@Override
public String toString(Object object) throws DatabricksSQLException {
if (object instanceof Boolean) {
return object.toString();
}
// For other types, fall back to the default behavior
throw new DatabricksSQLException(
"Unsupported String conversion operation", DatabricksDriverErrorCode.UNSUPPORTED_OPERATION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ public interface IDatabricksConnectionContext {

String getHttpPath();

/** Returns the value of the EnableSQLValidationForIsValid connection property. */
boolean getEnableSQLValidationForIsValid();

String getProxyHost();

int getProxyPort();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ public enum DatabricksJdbcUrlParams {
HTTP_CONNECTION_REQUEST_TIMEOUT(
"HttpConnectionRequestTimeout", "HTTP connection request timeout in seconds"),
CLOUD_FETCH_SPEED_THRESHOLD(
"CloudFetchSpeedThreshold", "Minimum expected download speed in MB/s", "0.1");
"CloudFetchSpeedThreshold", "Minimum expected download speed in MB/s", "0.1"),
ENABLE_SQL_VALIDATION_FOR_IS_VALID(
"EnableSQLValidationForIsValid",
"Enable SQL query execution for connection validation in isValid() method",
"0");

private final String paramName;
private final String defaultValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public class DatabricksDriverPropertyUtil {
DatabricksJdbcUrlParams.ROWS_FETCHED_PER_BLOCK,
DatabricksJdbcUrlParams.DEFAULT_STRING_COLUMN_LENGTH,
DatabricksJdbcUrlParams.SOCKET_TIMEOUT,
DatabricksJdbcUrlParams.ENABLE_TOKEN_CACHE);
DatabricksJdbcUrlParams.ENABLE_TOKEN_CACHE,
DatabricksJdbcUrlParams.ENABLE_SQL_VALIDATION_FOR_IS_VALID);

public static List<DriverPropertyInfo> getMissingProperties(String url, Properties info)
throws DatabricksParsingException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,4 +483,38 @@ public void testQueryTagsInSessionConfigs() throws SQLException {
assertTrue(sessionConfigs.containsKey("query_tags"));
assertEquals("team:marketing,dashboard:abc123", sessionConfigs.get("query_tags"));
}

@Test
public void testIsValidWithSQLValidationEnabled() throws SQLException {
String jdbcUrlWithValidation = CATALOG_SCHEMA_JDBC_URL + ";EnableSQLValidationForIsValid=1";
IDatabricksConnectionContext connectionContextWithValidation =
DatabricksConnectionContext.parse(jdbcUrlWithValidation, new Properties());
when(databricksClient.createSession(
new Warehouse(WAREHOUSE_ID), CATALOG, SCHEMA, new HashMap<>()))
.thenReturn(IMMUTABLE_SESSION_INFO);
connection = new DatabricksConnection(connectionContextWithValidation, databricksClient);
connection.open();
DatabricksConnection spyConnection = spy(connection);
DatabricksStatement mockStatement = mock(DatabricksStatement.class);
doReturn(mockStatement).when(spyConnection).createStatement();
doNothing().when(mockStatement).setQueryTimeout(anyInt());
when(mockStatement.execute("SELECT VERSION()")).thenReturn(true);

assertTrue(spyConnection.isValid(5));
verify(spyConnection).createStatement();
verify(mockStatement).setQueryTimeout(5);
verify(mockStatement).execute("SELECT VERSION()");

DatabricksStatement mockStatementFail = mock(DatabricksStatement.class);
doReturn(mockStatementFail).when(spyConnection).createStatement();
doNothing().when(mockStatementFail).setQueryTimeout(anyInt());
when(mockStatementFail.execute("SELECT VERSION()"))
.thenThrow(new SQLException("Connection lost"));

assertFalse(spyConnection.isValid(5));
verify(mockStatementFail).setQueryTimeout(5);
verify(mockStatementFail).execute("SELECT VERSION()");

connection.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.databricks.jdbc.api.impl.converters;

import static org.junit.jupiter.api.Assertions.*;

import com.databricks.jdbc.exception.DatabricksSQLException;
import org.junit.jupiter.api.Test;

public class BitConverterTest {

private final BitConverter bitConverter = new BitConverter();

@Test
public void testToStringWithBooleanTrue() throws DatabricksSQLException {
assertEquals("true", bitConverter.toString(true));
}

@Test
public void testToStringWithBooleanFalse() throws DatabricksSQLException {
assertEquals("false", bitConverter.toString(false));
}

@Test
public void testToStringWithBooleanObject() throws DatabricksSQLException {
Boolean trueObject = Boolean.TRUE;
Boolean falseObject = Boolean.FALSE;

assertEquals("true", bitConverter.toString(trueObject));
assertEquals("false", bitConverter.toString(falseObject));
}

@Test
public void testToStringWithUnsupportedType() {
DatabricksSQLException exception =
assertThrows(DatabricksSQLException.class, () -> bitConverter.toString("not a boolean"));

assertTrue(exception.getMessage().contains("Unsupported String conversion operation"));
assertEquals("UNSUPPORTED_OPERATION", exception.getSQLState());
}

@Test
public void testToStringWithNumber() {
DatabricksSQLException exception =
assertThrows(DatabricksSQLException.class, () -> bitConverter.toString(123));

assertTrue(exception.getMessage().contains("Unsupported String conversion operation"));
assertEquals("UNSUPPORTED_OPERATION", exception.getSQLState());
}

@Test
public void testToBooleanWithBooleanTrue() throws DatabricksSQLException {
assertTrue(bitConverter.toBoolean(true));
}

@Test
public void testToBooleanWithBooleanFalse() throws DatabricksSQLException {
assertFalse(bitConverter.toBoolean(false));
}

@Test
public void testToBooleanWithBooleanObject() throws DatabricksSQLException {
Boolean trueObject = Boolean.TRUE;
Boolean falseObject = Boolean.FALSE;

assertTrue(bitConverter.toBoolean(trueObject));
assertFalse(bitConverter.toBoolean(falseObject));
}

@Test
public void testToBooleanWithNumberZero() throws DatabricksSQLException {
assertFalse(bitConverter.toBoolean(0));
assertFalse(bitConverter.toBoolean(0L));
assertFalse(bitConverter.toBoolean(0.0f));
assertFalse(bitConverter.toBoolean(0.0));
}

@Test
public void testToBooleanWithNumberNonZero() throws DatabricksSQLException {
assertTrue(bitConverter.toBoolean(1));
assertTrue(bitConverter.toBoolean(-1));
assertTrue(bitConverter.toBoolean(42L));
assertTrue(bitConverter.toBoolean(3.14f));
assertTrue(bitConverter.toBoolean(2.71));
}

@Test
public void testToBooleanWithStringTrue() throws DatabricksSQLException {
assertTrue(bitConverter.toBoolean("true"));
assertTrue(bitConverter.toBoolean("TRUE"));
assertTrue(bitConverter.toBoolean("True"));
}

@Test
public void testToBooleanWithStringFalse() throws DatabricksSQLException {
assertFalse(bitConverter.toBoolean("false"));
assertFalse(bitConverter.toBoolean("FALSE"));
assertFalse(bitConverter.toBoolean("False"));
assertFalse(bitConverter.toBoolean("anything else"));
}

@Test
public void testToBooleanWithUnsupportedType() {
DatabricksSQLException exception =
assertThrows(DatabricksSQLException.class, () -> bitConverter.toBoolean(new Object()));

assertTrue(exception.getMessage().contains("Unsupported type for conversion to BIT"));
assertTrue(exception.getMessage().contains("Object"));
assertEquals("UNSUPPORTED_OPERATION", exception.getSQLState());
}

@Test
public void testToBooleanWithNull() {
DatabricksSQLException exception =
assertThrows(DatabricksSQLException.class, () -> bitConverter.toBoolean(null));

assertTrue(exception.getMessage().contains("Unsupported type for conversion to BIT"));
assertTrue(exception.getMessage().contains("null"));
assertEquals("UNSUPPORTED_OPERATION", exception.getSQLState());
}
}
Loading