Skip to content
Open
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
3 changes: 3 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Added
- Added `EnableThriftNativeMetadata` to request and consume supported Thrift-native SEA metadata results.
- Added session-version exchange for SQL Exec API connections. On Lakehouse Real-Time, use
synchronous execution when subsequent statements depend on session changes; asynchronous
execution does not guarantee their visibility.

### Updated
- `UseBoundedSeaApi` and `EnableThriftNativeMetadata` now default to `1`; when unset, activation is controlled by the server-side `enableSqlExecForJdbc` rollout flag.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public interface IDatabricksStatement extends Statement {
* long-running queries. The actual results can be retrieved later using {@link
* #getExecutionResult()}.
*
* <p>On Lakehouse Real-Time, use synchronous execution when subsequent statements depend on
* session changes; asynchronous execution does not guarantee their visibility.
*
* @param sql The SQL command to be executed
* @return A {@link ResultSet} handle that can be used to track and retrieve the results
* @throws SQLException if a database access error occurs, this method is called on a closed
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/databricks/jdbc/api/impl/DatabricksSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.databricks.jdbc.exception.DatabricksTemporaryRedirectException;
import com.databricks.jdbc.log.JdbcLogger;
import com.databricks.jdbc.log.JdbcLoggerFactory;
import com.databricks.jdbc.model.core.SessionVersion;
import com.databricks.jdbc.model.telemetry.enums.DatabricksDriverErrorCode;
import com.databricks.jdbc.telemetry.TelemetryHelper;
import com.databricks.jdbc.telemetry.latency.DatabricksMetricsTimedProcessor;
Expand All @@ -29,6 +30,7 @@
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;

/**
Expand All @@ -43,6 +45,7 @@ public class DatabricksSession implements IDatabricksSession {
private final IDatabricksComputeResource computeResource;
private boolean isSessionOpen;
private ImmutableSessionInfo sessionInfo;
private final AtomicReference<Long> sessionVersion = new AtomicReference<>();

/** For context based commands */
private String catalog;
Expand Down Expand Up @@ -111,6 +114,32 @@ public ImmutableSessionInfo getSessionInfo() {
return sessionInfo;
}

@Nullable
@Override
public SessionVersion getSessionVersion() {
Long versionId = sessionVersion.get();
return versionId == null ? null : new SessionVersion().setVersionId(versionId);
}

@Override
public void updateSessionVersion(@Nullable SessionVersion newSessionVersion) {
if (newSessionVersion == null || newSessionVersion.getVersionId() == null) {
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low — The concurrency design mixes two mechanisms in a way that is redundant and slightly inconsistent:

  • updateSessionVersion already holds synchronized (this) before calling sessionVersion.accumulateAndGet(...). Since open() and close() also mutate sessionVersion only under the same monitor, the AtomicReference CAS loop inside the lock is redundant — a plain field guarded by the monitor would give the same monotonic-max guarantee.
  • forceClose() writes this.sessionVersion.set(null) outside the monitor. It is also redundant: forceClose() calls close(), whose finally already sets sessionVersion to null under the lock on every path (including when deleteSession throws). The only mutation that escapes the monitor is this one, which is why the AtomicReference is load-bearing today.

Not a correctness bug given the current call patterns, but consider standardizing on a single mechanism (either monitor-guarded plain field, or fully lock-free atomic with no synchronized) to avoid the mixed model. getSessionVersion() reading the atomic lock-free is fine either way.

synchronized (this) {
if (!isSessionOpen) {
return;
}
Long newVersionId = newSessionVersion.getVersionId();
sessionVersion.accumulateAndGet(
newVersionId,
(currentVersion, candidateVersion) ->
currentVersion == null || candidateVersion > currentVersion
? candidateVersion
: currentVersion);
}
}

@Override
public IDatabricksComputeResource getComputeResource() {
LOGGER.debug("public String getComputeResource()");
Expand Down Expand Up @@ -217,6 +246,7 @@ public void open() throws SQLException {
throw e;
}
}
this.sessionVersion.set(sessionInfo == null ? null : sessionInfo.sessionVersion());
this.isSessionOpen = true;
}
}
Expand All @@ -240,6 +270,7 @@ public void close() throws SQLException {
} finally {
// Always clean up local state
this.sessionInfo = null;
this.sessionVersion.set(null);
this.isSessionOpen = false;
}
}
Expand Down Expand Up @@ -406,6 +437,7 @@ public void forceClose() {
} catch (SQLException e) {
LOGGER.error("Error closing session resources, but marking the session as closed.");
} finally {
this.sessionVersion.set(null);
this.isSessionOpen = false;
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/databricks/jdbc/api/impl/SessionInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public interface SessionInfo {

IDatabricksComputeResource computeResource();

@Nullable
Long sessionVersion();

@Nullable
TSessionHandle sessionHandle(); // This field is set only for all-purpose cluster compute
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.databricks.jdbc.dbclient.IDatabricksClient;
import com.databricks.jdbc.dbclient.IDatabricksMetadataClient;
import com.databricks.jdbc.exception.DatabricksSQLException;
import com.databricks.jdbc.model.core.SessionVersion;
import java.sql.SQLException;
import java.util.Map;
import javax.annotation.Nullable;
Expand All @@ -24,6 +25,13 @@ public interface IDatabricksSession {
@Nullable
ImmutableSessionInfo getSessionInfo();

@Nullable
default SessionVersion getSessionVersion() {
return null;
}

default void updateSessionVersion(@Nullable SessionVersion sessionVersion) {}

/**
* Get the warehouse associated with the session.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import com.databricks.jdbc.model.core.ExternalLink;
import com.databricks.jdbc.model.core.ResultData;
import com.databricks.jdbc.model.core.ResultManifest;
import com.databricks.jdbc.model.core.SessionExecutionMode;
import com.databricks.jdbc.model.core.SessionVersion;
import com.databricks.jdbc.model.core.StatementStatus;
import com.databricks.jdbc.model.telemetry.enums.DatabricksDriverErrorCode;
import com.databricks.sdk.WorkspaceClient;
Expand Down Expand Up @@ -118,7 +120,9 @@ public ImmutableSessionInfo createSession(
schema,
sessionConf);
CreateSessionRequest request =
new CreateSessionRequest().setWarehouseId(((Warehouse) warehouse).getWarehouseId());
new CreateSessionRequest()
.setWarehouseId(((Warehouse) warehouse).getWarehouseId())
.setExecutionMode(SessionExecutionMode.FAST);
if (catalog != null) {
request.setCatalog(catalog);
}
Expand Down Expand Up @@ -155,11 +159,15 @@ public ImmutableSessionInfo createSession(
LOGGER.error(errorMessage, e);
throw new DatabricksSQLException(errorMessage, e, DatabricksDriverErrorCode.SDK_CLIENT_ERROR);
}
DatabricksThreadContextHolder.setSessionId(createSessionResponse.getSessionId());
return ImmutableSessionInfo.builder()
.computeResource(warehouse)
.sessionId(createSessionResponse.getSessionId())
.build();
String sessionId = createSessionResponse.getSessionId();
DatabricksThreadContextHolder.setSessionId(sessionId);
ImmutableSessionInfo.Builder sessionInfo =
ImmutableSessionInfo.builder().computeResource(warehouse).sessionId(sessionId);
SessionVersion initialVersion = createSessionResponse.getSessionVersion();
if (initialVersion != null && initialVersion.getVersionId() != null) {
sessionInfo.sessionVersion(initialVersion.getVersionId());
}
return sessionInfo.build();
}

@Override
Expand Down Expand Up @@ -229,6 +237,7 @@ public DatabricksResultSet executeStatement(
}
req.withHeaders(getHeaders("executeStatement", statementType, false, additionalHeaders));
response = apiClient.execute(req, ExecuteStatementResponse.class);
updateSessionVersion(session, response.getStatus());
} catch (IOException e) {
String errorMessage = "Error while processing the execute statement request";
LOGGER.error(errorMessage, e);
Expand Down Expand Up @@ -274,6 +283,7 @@ public DatabricksResultSet executeStatement(
TimeoutHandler.forStatement(timeoutInSeconds, typedStatementId, this, timeoutErrorCode);

StatementState responseState = response.getStatus().getState();
GetStatementRequest getStatementRequest = new GetStatementRequest().setStatementId(statementId);
while (responseState == StatementState.PENDING || responseState == StatementState.RUNNING) {
// Check for timeout
timeoutHandler.checkTimeout();
Expand All @@ -292,9 +302,11 @@ public DatabricksResultSet executeStatement(
}
String getStatusPath = String.format(STATEMENT_PATH_WITH_ID, statementId);
try {
Request req = new Request(Request.GET, getStatusPath, apiClient.serialize(request));
Request req =
new Request(Request.GET, getStatusPath, apiClient.serialize(getStatementRequest));
req.withHeaders(getHeaders("getStatement"));
response = wrapGetStatementResponse(apiClient.execute(req, GetStatementResponse.class));
updateSessionVersion(session, response.getStatus());
} catch (IOException e) {
String errorMessage = "Error while processing the get statement response";
LOGGER.error(errorMessage, e);
Expand Down Expand Up @@ -391,6 +403,7 @@ public DatabricksResultSet executeStatementAsync(
Request req = new Request(Request.POST, STATEMENT_PATH, apiClient.serialize(request));
req.withHeaders(getHeaders("executeStatement", statementType, true));
response = apiClient.execute(req, ExecuteStatementResponse.class);
updateSessionVersion(session, response.getStatus());
} catch (IOException e) {
String errorMessage = "Error while processing the execute statement async request";
LOGGER.error(errorMessage, e);
Expand Down Expand Up @@ -747,6 +760,10 @@ private ExecuteStatementRequest getRequest(
.setFormat(format)
.setResultCompression(compressionCodec)
.setParameters(parameterListItems);
SessionVersion sessionVersion = session.getSessionVersion();
if (sessionVersion != null) {
request.setSessionVersion(sessionVersion);
}
if (executeAsync) {
request.setWaitTimeout(ASYNC_TIMEOUT_VALUE);
} else {
Expand Down Expand Up @@ -829,6 +846,12 @@ private ExecuteStatementResponse wrapGetStatementResponse(
.setResult(getStatementResponse.getResult());
}

private void updateSessionVersion(IDatabricksSession session, StatementStatus status) {
if (session != null && status != null) {
session.updateSessionVersion(status.getSessionVersion());
}
}

/**
* Builds actionable error messages for SSL handshake failures. Returns a generic message if the
* error is not SSL-related.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.databricks.jdbc.model.client.sqlexec;

import com.databricks.jdbc.model.core.SessionExecutionMode;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;

Expand All @@ -19,6 +20,9 @@ public class CreateSessionRequest {
@JsonProperty("session_confs")
private Map<String, String> sessionConfigs;

@JsonProperty("execution_mode")
private SessionExecutionMode executionMode;

public CreateSessionRequest setWarehouseId(String warehouseId) {
this.warehouseId = warehouseId;
return this;
Expand Down Expand Up @@ -54,4 +58,13 @@ public CreateSessionRequest setSessionConfigs(Map<String, String> sessionConfigs
public Map<String, String> getSessionConfigs() {
return sessionConfigs;
}

public CreateSessionRequest setExecutionMode(SessionExecutionMode executionMode) {
this.executionMode = executionMode;
return this;
}

public SessionExecutionMode getExecutionMode() {
return executionMode;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.databricks.jdbc.model.client.sqlexec;

import com.databricks.jdbc.model.core.SessionVersion;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
Expand All @@ -13,6 +14,9 @@ public class CreateSessionResponse {
@JsonProperty("session_id")
private String sessionId;

@JsonProperty("session_version")
private SessionVersion sessionVersion;

public CreateSessionResponse setSessionId(String sessionId) {
this.sessionId = sessionId;
return this;
Expand All @@ -21,4 +25,13 @@ public CreateSessionResponse setSessionId(String sessionId) {
public String getSessionId() {
return sessionId;
}

public CreateSessionResponse setSessionVersion(SessionVersion sessionVersion) {
this.sessionVersion = sessionVersion;
return this;
}

public SessionVersion getSessionVersion() {
return sessionVersion;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.databricks.jdbc.common.CompressionCodec;
import com.databricks.jdbc.model.core.Disposition;
import com.databricks.jdbc.model.core.SessionVersion;
import com.databricks.sdk.service.sql.ExecuteStatementRequestOnWaitTimeout;
import com.databricks.sdk.service.sql.Format;
import com.databricks.sdk.service.sql.StatementParameterListItem;
Expand Down Expand Up @@ -46,6 +47,9 @@ public class ExecuteStatementRequest {
@JsonProperty("result_compression")
private CompressionCodec resultCompression;

@JsonProperty("session_version")
private SessionVersion sessionVersion;

public String getStatement() {
return statement;
}
Expand Down Expand Up @@ -86,6 +90,10 @@ public CompressionCodec getResultCompression() {
return resultCompression;
}

public SessionVersion getSessionVersion() {
return sessionVersion;
}

// Setters
public ExecuteStatementRequest setStatement(String statement) {
this.statement = statement;
Expand Down Expand Up @@ -138,6 +146,11 @@ public ExecuteStatementRequest setParameters(Collection<StatementParameterListIt
return this;
}

public ExecuteStatementRequest setSessionVersion(SessionVersion sessionVersion) {
this.sessionVersion = sessionVersion;
return this;
}

@Override
public String toString() {
return new ToStringer(ExecuteStatementRequest.class)
Expand All @@ -147,6 +160,7 @@ public String toString() {
.add("parameters", parameters)
.add("statement", statement)
.add("sessionId", sessionId)
.add("sessionVersion", sessionVersion)
.add("waitTimeout", waitTimeout)
.add("warehouseId", warehouseId)
.add("rowLimit", rowLimit)
Expand All @@ -161,6 +175,8 @@ public int hashCode() {
onWaitTimeout,
parameters,
rowLimit,
sessionId,
sessionVersion,
statement,
waitTimeout,
warehouseId);
Expand All @@ -181,6 +197,7 @@ public boolean equals(Object o) {
&& Objects.equals(statement, that.statement)
&& Objects.equals(waitTimeout, that.waitTimeout)
&& Objects.equals(sessionId, that.sessionId)
&& Objects.equals(sessionVersion, that.sessionVersion)
&& Objects.equals(warehouseId, that.warehouseId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.databricks.jdbc.model.core;

public enum SessionExecutionMode {
SESSION_EXECUTION_MODE_UNSPECIFIED,
DEFAULT,
FAST
}
Loading
Loading