diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md
index ac81de5d9..9716a8d3b 100644
--- a/NEXT_CHANGELOG.md
+++ b/NEXT_CHANGELOG.md
@@ -2,6 +2,14 @@
## [Unreleased]
+### BREAKING CHANGES in 3.4.1
+
+1. **`getTables()`: Percent sign (`%`) in catalog argument is now treated as a literal character, not a wildcard.** Previously returned all tables; now returns zero rows unless a catalog named "%" exists. JDBC spec: catalog is an exact-match parameter, not a pattern. Migration: Pass `null` to search all catalogs.
+
+2. **`getColumnTypeName()`: DECIMAL columns now return `"DECIMAL"` without precision/scale** (e.g., `"DECIMAL"` not `"DECIMAL(10,2)"`). Use `getPrecision()` and `getScale()` for numeric constraints. JDBC spec: `getColumnTypeName()` returns the base type name only.
+
+3. **For DBSQL warehouses, metadata operations are now powered by SHOW SQL commands.** SQL Exec API mode already was powered by SHOW commands, now the same is true for Thrift server mode as well. To revert to native Thrift metadata RPCs, set `UseQueryForMetadata` to `0`.
+
### Added
### Updated
diff --git a/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnectionContext.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnectionContext.java
index ea9e15b26..159df78f0 100644
--- a/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnectionContext.java
+++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksConnectionContext.java
@@ -45,6 +45,9 @@ public class DatabricksConnectionContext implements IDatabricksConnectionContext
private static final String SQL_EXEC_FLAG_NAME =
"databricks.partnerplatform.clientConfigsFeatureFlags.enableSqlExecForJdbc";
+ private static final String USE_QUERY_FOR_THRIFT_FLAG_NAME =
+ "databricks.partnerplatform.clientConfigsFeatureFlags.enableUseQueryForThriftJdbc";
+
private final String host;
@VisibleForTesting final int port;
private final String schema;
@@ -1131,7 +1134,8 @@ public boolean enableShowCommandsForGetFunctions() {
@Override
public boolean useQueryForMetadata() {
- return getParameter(DatabricksJdbcUrlParams.USE_QUERY_FOR_METADATA).equals("1");
+ return resolveFeatureFlag(
+ DatabricksJdbcUrlParams.USE_QUERY_FOR_METADATA, USE_QUERY_FOR_THRIFT_FLAG_NAME);
}
@Override
@@ -1194,6 +1198,59 @@ private String getParameterIgnoreDefault(DatabricksJdbcUrlParams key) {
return this.parameters.getOrDefault(key.getParamName().toLowerCase(), null);
}
+ /**
+ * Resolves a boolean feature flag with client-side priority over server-side.
+ *
+ *
Priority order:
+ *
+ *
+ * - Client-side param (explicit user setting in JDBC URL) — honoured unconditionally
+ *
- Server-side feature flag (DBSQL warehouses only) — checked if user didn't set the param
+ *
- Default value from the param definition
+ *
+ *
+ * @param clientParam the JDBC URL parameter (e.g. USE_QUERY_FOR_METADATA)
+ * @param serverFlagName the server-side SAFE flag name
+ * @return true if the feature should be enabled
+ */
+ private boolean resolveFeatureFlag(DatabricksJdbcUrlParams clientParam, String serverFlagName) {
+ // 1. User explicitly set the param — honour it regardless of compute type
+ String explicitValue = getParameterIgnoreDefault(clientParam);
+ if (explicitValue != null) {
+ return explicitValue.equals("1");
+ }
+
+ // 2. No explicit setting + all-purpose cluster — always false
+ if (!(computeResource instanceof Warehouse)) {
+ return false;
+ }
+
+ // 3. No explicit setting + warehouse — enabled only when BOTH client default
+ // AND server-side flag agree. This gives a two-key rollout mechanism:
+ // flip the param default to "1" in the driver AND enable the server flag.
+ boolean clientDefault = getParameter(clientParam).equals("1");
+ boolean serverEnabled = false;
+ try {
+ serverEnabled =
+ DatabricksDriverFeatureFlagsContextFactory.getInstance(this)
+ .isFeatureEnabled(serverFlagName);
+ } catch (Exception e) {
+ LOGGER.debug("Failed to check server-side flag {}: {}", serverFlagName, e.getMessage());
+ }
+
+ if (clientDefault && serverEnabled) {
+ LOGGER.debug(
+ "Feature {} enabled for warehouse: client default={}, server flag {} ={}",
+ clientParam.getParamName(),
+ clientDefault,
+ serverFlagName,
+ serverEnabled);
+ return true;
+ }
+
+ return false;
+ }
+
private String getParameter(DatabricksJdbcUrlParams key, String defaultValue) {
return this.parameters.getOrDefault(key.getParamName().toLowerCase(), defaultValue);
}
diff --git a/src/main/java/com/databricks/jdbc/common/DatabricksJdbcUrlParams.java b/src/main/java/com/databricks/jdbc/common/DatabricksJdbcUrlParams.java
index 518676457..8f67fc0a2 100644
--- a/src/main/java/com/databricks/jdbc/common/DatabricksJdbcUrlParams.java
+++ b/src/main/java/com/databricks/jdbc/common/DatabricksJdbcUrlParams.java
@@ -173,7 +173,7 @@ public enum DatabricksJdbcUrlParams {
USE_QUERY_FOR_METADATA(
"UseQueryForMetadata",
"Use SQL SHOW commands instead of Thrift RPCs for metadata operations. When enabled, EnableShowCommandForGetFunctions is redundant",
- "0"),
+ "1"),
TREAT_METADATA_CATALOG_NAME_AS_PATTERN(
"TreatMetadataCatalogNameAsPattern",
"Treat catalog names as patterns in Thrift metadata RPCs. When disabled (default), wildcard characters in catalog names are escaped",
diff --git a/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java b/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java
index 3be5ae52f..41e54fc50 100644
--- a/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java
+++ b/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java
@@ -1456,7 +1456,8 @@ public void testDefaultGetterCoverage() throws DatabricksSQLException {
@Test
public void testUseQueryForMetadataDefaultFalseForWarehouse() throws DatabricksSQLException {
- // Warehouse URL without explicit UseQueryForMetadata — default is false (native RPCs)
+ // Warehouse without explicit setting — requires both client default AND server flag.
+ // Client default is "1" but no server flag set → false
IDatabricksConnectionContext ctx =
DatabricksConnectionContext.parse(TestConstants.VALID_URL_1, properties);
assertFalse(ctx.useQueryForMetadata());
@@ -1464,7 +1465,7 @@ public void testUseQueryForMetadataDefaultFalseForWarehouse() throws DatabricksS
@Test
public void testUseQueryForMetadataDefaultFalseForCluster() throws DatabricksSQLException {
- // Cluster URL without explicit UseQueryForMetadata — default is false
+ // Cluster without explicit setting — always false regardless of defaults
IDatabricksConnectionContext ctx =
DatabricksConnectionContext.parse(TestConstants.VALID_CLUSTER_URL, properties);
assertFalse(ctx.useQueryForMetadata());
@@ -1488,6 +1489,90 @@ public void testUseQueryForMetadataExplicitFalseOnWarehouse() throws DatabricksS
assertFalse(ctx.useQueryForMetadata());
}
+ @Test
+ public void testUseQueryForMetadata_serverFlagEnabled_warehouseReturnsTrue()
+ throws DatabricksSQLException {
+ // Warehouse without explicit setting — client default "1" + server flag enabled → true
+ DatabricksConnectionContext ctx =
+ (DatabricksConnectionContext)
+ DatabricksConnectionContext.parse(TestConstants.VALID_URL_1, properties);
+
+ Map flags = new HashMap<>();
+ flags.put(
+ "databricks.partnerplatform.clientConfigsFeatureFlags.enableUseQueryForThriftJdbc", "true");
+ DatabricksDriverFeatureFlagsContextFactory.setFeatureFlagsContext(ctx, flags);
+
+ assertTrue(ctx.useQueryForMetadata());
+ }
+
+ @Test
+ public void testUseQueryForMetadata_serverFlagDisabled_warehouseReturnsFalse()
+ throws DatabricksSQLException {
+ // Warehouse without explicit setting — client default "1" but server flag disabled → false
+ DatabricksConnectionContext ctx =
+ (DatabricksConnectionContext)
+ DatabricksConnectionContext.parse(TestConstants.VALID_URL_1, properties);
+
+ Map flags = new HashMap<>();
+ flags.put(
+ "databricks.partnerplatform.clientConfigsFeatureFlags.enableUseQueryForThriftJdbc",
+ "false");
+ DatabricksDriverFeatureFlagsContextFactory.setFeatureFlagsContext(ctx, flags);
+
+ assertFalse(ctx.useQueryForMetadata());
+ }
+
+ @Test
+ public void testUseQueryForMetadata_serverFlagEnabled_clusterIgnored()
+ throws DatabricksSQLException {
+ // All-purpose cluster — always false, server flag and client default both ignored
+ DatabricksConnectionContext ctx =
+ (DatabricksConnectionContext)
+ DatabricksConnectionContext.parse(TestConstants.VALID_CLUSTER_URL, properties);
+
+ Map flags = new HashMap<>();
+ flags.put(
+ "databricks.partnerplatform.clientConfigsFeatureFlags.enableUseQueryForThriftJdbc", "true");
+ DatabricksDriverFeatureFlagsContextFactory.setFeatureFlagsContext(ctx, flags);
+
+ assertFalse(ctx.useQueryForMetadata());
+ }
+
+ @Test
+ public void testUseQueryForMetadata_clientExplicit1_overridesServerFlagDisabled()
+ throws DatabricksSQLException {
+ // Client sets UseQueryForMetadata=1 — should be honoured even if server flag is disabled
+ DatabricksConnectionContext ctx =
+ (DatabricksConnectionContext)
+ DatabricksConnectionContext.parse(
+ TestConstants.VALID_URL_1 + ";UseQueryForMetadata=1", properties);
+
+ Map flags = new HashMap<>();
+ flags.put(
+ "databricks.partnerplatform.clientConfigsFeatureFlags.enableUseQueryForThriftJdbc",
+ "false");
+ DatabricksDriverFeatureFlagsContextFactory.setFeatureFlagsContext(ctx, flags);
+
+ assertTrue(ctx.useQueryForMetadata());
+ }
+
+ @Test
+ public void testUseQueryForMetadata_clientExplicit0_overridesServerFlagEnabled()
+ throws DatabricksSQLException {
+ // Client sets UseQueryForMetadata=0 — should be honoured even if server flag is enabled
+ DatabricksConnectionContext ctx =
+ (DatabricksConnectionContext)
+ DatabricksConnectionContext.parse(
+ TestConstants.VALID_URL_1 + ";UseQueryForMetadata=0", properties);
+
+ Map flags = new HashMap<>();
+ flags.put(
+ "databricks.partnerplatform.clientConfigsFeatureFlags.enableUseQueryForThriftJdbc", "true");
+ DatabricksDriverFeatureFlagsContextFactory.setFeatureFlagsContext(ctx, flags);
+
+ assertFalse(ctx.useQueryForMetadata());
+ }
+
// ---------------------------------------------------------------------------
// Geospatial flag independence from complex datatype flag
// ---------------------------------------------------------------------------
diff --git a/src/test/java/com/databricks/jdbc/api/impl/DatabricksSessionTest.java b/src/test/java/com/databricks/jdbc/api/impl/DatabricksSessionTest.java
index dad4727da..1d958a574 100644
--- a/src/test/java/com/databricks/jdbc/api/impl/DatabricksSessionTest.java
+++ b/src/test/java/com/databricks/jdbc/api/impl/DatabricksSessionTest.java
@@ -13,6 +13,7 @@
import com.databricks.jdbc.api.internal.IDatabricksConnectionContext;
import com.databricks.jdbc.common.DatabricksClientType;
import com.databricks.jdbc.common.DatabricksJdbcUrlParams;
+import com.databricks.jdbc.common.safe.DatabricksDriverFeatureFlagsContextFactory;
import com.databricks.jdbc.dbclient.impl.sqlexec.DatabricksMetadataQueryClient;
import com.databricks.jdbc.dbclient.impl.sqlexec.DatabricksSdkClient;
import com.databricks.jdbc.dbclient.impl.thrift.DatabricksThriftServiceClient;
@@ -22,6 +23,8 @@
import com.databricks.jdbc.model.client.thrift.generated.TSessionHandle;
import com.databricks.jdbc.telemetry.latency.DatabricksMetricsTimedProcessor;
import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Properties;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -47,6 +50,11 @@ public class DatabricksSessionTest {
static void setupWarehouse(boolean useThrift) throws SQLException {
String url = useThrift ? WAREHOUSE_JDBC_URL : WAREHOUSE_JDBC_URL_WITH_SEA;
connectionContext = DatabricksConnectionContext.parse(url, new Properties());
+ // Override feature flags with empty map to prevent test contamination from
+ // other test classes (e.g. DatabricksConnectionContextTest) that set flags
+ // on the shared static DatabricksDriverFeatureFlagsContextFactory.
+ DatabricksDriverFeatureFlagsContextFactory.setFeatureFlagsContext(
+ connectionContext, new HashMap<>());
}
private void setupCluster() throws SQLException {
@@ -328,6 +336,27 @@ public void testUseQueryForMetadataDisabledByDefaultForWarehouse() throws SQLExc
"Default UseQueryForMetadata=0: warehouse uses native Thrift RPCs for metadata");
}
+ @Test
+ public void testUseQueryForMetadataEnabledViaServerFlag() throws SQLException {
+ setupWarehouse(true /* useThrift */);
+ // Simulate server-side flag enabling SHOW commands for this warehouse
+ Map flags = new HashMap<>();
+ flags.put(
+ "databricks.partnerplatform.clientConfigsFeatureFlags.enableUseQueryForThriftJdbc", "true");
+ DatabricksDriverFeatureFlagsContextFactory.setFeatureFlagsContext(connectionContext, flags);
+
+ assertTrue(connectionContext.useQueryForMetadata());
+ DatabricksSession session = new DatabricksSession(connectionContext, thriftClient);
+ assertInstanceOf(
+ DatabricksMetadataQueryClient.class,
+ session.getDatabricksMetadataClient(),
+ "Server flag enabled: warehouse should use SHOW commands for metadata");
+
+ // Clean up so other tests are not affected
+ DatabricksDriverFeatureFlagsContextFactory.setFeatureFlagsContext(
+ connectionContext, new HashMap<>());
+ }
+
@Test
public void testUseQueryForMetadataDisabledByDefaultForCluster() throws SQLException {
connectionContext = DatabricksConnectionContext.parse(VALID_CLUSTER_URL, new Properties());