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
1 change: 1 addition & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Fixed `setCatalog()` and `setSchema()` producing invalid SQL (e.g. `SET CATALOG ``name``) when the catalog or schema name was passed already wrapped in backticks. Backticks are now stripped before wrapping, and `getCatalog()`/`getSchema()` return the bare identifier name.
- Fixed metadata SQL generation for catalog, schema, and table identifiers containing backticks.
- Fixed SEA result truncation when direct results are disabled. Large, highly-compressible results that span multiple chunks were delivered inline via the old hybrid path and truncated to the first chunk. The SQL Execution path now uses an async (`0s`) wait timeout when direct results are disabled, so results are returned via external links and fetched in full.
- Fixed `getColumns()` flooding the `DriverManager` log writer with caught-and-recovered `Invalid column index` stack traces.

---
*Note: When making changes, please add your change under the appropriate section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static java.sql.DatabaseMetaData.*;

import com.databricks.jdbc.api.impl.DatabricksResultSet;
import com.databricks.jdbc.api.impl.DatabricksResultSetMetaData;
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.api.internal.IDatabricksSession;
import com.databricks.jdbc.common.CommandName;
Expand Down Expand Up @@ -727,54 +728,41 @@ List<List<Object>> getRows(
object = null;
break;
default:
// If column does not match any of the special cases, try to get it from the ResultSet
try {
object = resultSet.getObject(mappedColumn.getResultSetColumnName());
if (mappedColumn.getColumnName().equals(IS_NULLABLE_COLUMN.getColumnName())) {
if (object == null || object.equals("true")) {
object = "YES";
} else {
object = "NO";
}
} else if (mappedColumn
.getColumnName()
.equals(DECIMAL_DIGITS_COLUMN.getColumnName())) {
object = getUpdatedDecimalDigits(stripBaseTypeName(typeVal), object);
} else if (mappedColumn
.getColumnName()
.equals(NUM_PREC_RADIX_COLUMN.getColumnName())) {
if (object == null) {
object = 0;
}
} else if (mappedColumn.getColumnName().equals(REMARKS_COLUMN.getColumnName())) {
if (object == null) {
object = "";
}
}
} catch (SQLException e) {
if (mappedColumn.getColumnName().equals(DATA_TYPE_COLUMN.getColumnName())) {
// Check if geospatial support is disabled and this is a geospatial type
if (!ctx.isGeoSpatialSupportEnabled() && isGeospatialType(typeVal)) {
object = Types.VARCHAR;
} else if (!ctx.isComplexDatatypeSupportEnabled() && isComplexType(typeVal)) {
object = Types.VARCHAR;
} else {
object = getCode(stripBaseTypeName(typeVal));
}
} else if (mappedColumn
.getColumnName()
.equals(CHAR_OCTET_LENGTH_COLUMN.getColumnName())) {
object = getCharOctetLength(typeVal);
if (object.equals(0)) {
object = null;
// If column does not match any of the special cases, try to get it from the ResultSet.
// When the column is known to be absent from the underlying result, compute the default
// directly instead of calling getObject() and catching "Invalid column index" — that
// throw is caught-and-recovered control flow, but its stack trace floods the
// DriverManager log writer (GitHub #1490).
if (isColumnAbsent(resultSet, mappedColumn.getResultSetColumnName())) {
object = getDefaultValueForMissingColumn(mappedColumn, typeVal);
} else {
try {
object = resultSet.getObject(mappedColumn.getResultSetColumnName());
if (mappedColumn.getColumnName().equals(IS_NULLABLE_COLUMN.getColumnName())) {
if (object == null || object.equals("true")) {
object = "YES";
} else {
object = "NO";
}
} else if (mappedColumn
.getColumnName()
.equals(DECIMAL_DIGITS_COLUMN.getColumnName())) {
object = getUpdatedDecimalDigits(stripBaseTypeName(typeVal), object);
} else if (mappedColumn
.getColumnName()
.equals(NUM_PREC_RADIX_COLUMN.getColumnName())) {
if (object == null) {
object = 0;
}
} else if (mappedColumn.getColumnName().equals(REMARKS_COLUMN.getColumnName())) {
if (object == null) {
object = "";
}
}
} else if (mappedColumn
.getColumnName()
.equals(BUFFER_LENGTH_COLUMN.getColumnName())) {
object = getBufferLength(typeVal);
} else {
// Handle other cases where the result set does not contain the expected column
object = null;
} catch (SQLException e) {
// Safety net: column resolved but value could not be read; fall back to the
// default.
object = getDefaultValueForMissingColumn(mappedColumn, typeVal);
}
}
if (mappedColumn.getColumnName().equals(NULLABLE_COLUMN.getColumnName())) {
Expand Down Expand Up @@ -823,6 +811,53 @@ List<List<Object>> getRows(
return rows;
}

/**
* Returns {@code true} when {@code columnName} is known to be absent from the underlying result
* set, resolved the same way {@link DatabricksResultSet#getObject(String)} resolves names. Used
* to avoid the "Invalid column index" throw for columns the server did not return (GitHub #1490).
*
* <p>Returns {@code false} when the column is present or when metadata is unavailable (e.g. test
* mocks), so callers fall back to the original {@code getObject()} path and behavior is
* unchanged.
*/
private boolean isColumnAbsent(DatabricksResultSet resultSet, String columnName) {
try {
ResultSetMetaData metaData = resultSet.getMetaData();
if (metaData instanceof DatabricksResultSetMetaData) {
// getColumnNameIndex returns a 1-based index, or -1 when the column is not present.
return ((DatabricksResultSetMetaData) metaData).getColumnNameIndex(columnName) <= 0;
}
} catch (SQLException e) {
// Metadata unavailable; preserve the legacy getObject() path.
}
return false;
}

/**
* Computes the default value for a column that is absent from the underlying result set. Mirrors
* the fallback that previously lived in the {@code catch} block of {@link #getRows}, so output is
* identical — only the triggering throw is avoided.
*/
private Object getDefaultValueForMissingColumn(ResultColumn mappedColumn, String typeVal) {
if (mappedColumn.getColumnName().equals(DATA_TYPE_COLUMN.getColumnName())) {
// Check if geospatial support is disabled and this is a geospatial type
if (!ctx.isGeoSpatialSupportEnabled() && isGeospatialType(typeVal)) {
return Types.VARCHAR;
} else if (!ctx.isComplexDatatypeSupportEnabled() && isComplexType(typeVal)) {
return Types.VARCHAR;
} else {
return getCode(stripBaseTypeName(typeVal));
}
} else if (mappedColumn.getColumnName().equals(CHAR_OCTET_LENGTH_COLUMN.getColumnName())) {
Object value = getCharOctetLength(typeVal);
return value.equals(0) ? null : value;
} else if (mappedColumn.getColumnName().equals(BUFFER_LENGTH_COLUMN.getColumnName())) {
return getBufferLength(typeVal);
}
// Result set does not contain the expected column and no special default applies.
return null;
}

/**
* Extracts the size from a SQL type definition in the format DATA_TYPE(size).
*
Expand Down
Loading