diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryConversionException.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryConversionException.java index ea78a4f11330..f8bc25442a78 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryConversionException.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryConversionException.java @@ -24,6 +24,9 @@ public class BigQueryConversionException extends SQLException { public BigQueryConversionException(String message, Throwable cause) { - super(BigQueryJdbcExceptionUtils.formatMessage(message, cause), cause); + super( + BigQueryJdbcExceptionUtils.formatMessage(message, cause), + BigQueryJdbcSqlStates.DATA_EXCEPTION, + cause); } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcCoercionException.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcCoercionException.java deleted file mode 100644 index defbbfcca784..000000000000 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcCoercionException.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.bigquery.exception; - -import com.google.api.core.InternalApi; - -/** - * Thrown to indicate that the coercion was attempted but couldn't be performed successfully because - * of some error. - */ -@InternalApi -public class BigQueryJdbcCoercionException extends RuntimeException { - - /** - * Construct a new exception with the specified cause. - * - * @param cause the actual cause which was thrown while performing the coercion. - */ - public BigQueryJdbcCoercionException(Exception cause) { - super(BigQueryJdbcExceptionUtils.formatMessage("Coercion error", cause), cause); - } -} diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcCoercionNotFoundException.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcCoercionNotFoundException.java deleted file mode 100644 index b4eafb2ee583..000000000000 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcCoercionNotFoundException.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.bigquery.exception; - -import com.google.api.core.InternalApi; - -/** - * Thrown to indicate that the current TypeCoercer can not perform the coercion as the Coercion - * implementation is not registered for the mentioned source and target type. - */ -@InternalApi -public class BigQueryJdbcCoercionNotFoundException extends RuntimeException { - - /** - * Construct a new exception. - * - * @param source the source type. - * @param target the target type. - */ - public BigQueryJdbcCoercionNotFoundException(Class> source, Class> target) { - super( - String.format( - "Coercion not found for [%s -> %s] conversion", - source.getCanonicalName(), target.getCanonicalName())); - } -} diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcException.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcException.java index a6262b167db4..f47770931c39 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcException.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcException.java @@ -28,7 +28,7 @@ public class BigQueryJdbcException extends SQLException { * @param message The detail message. */ public BigQueryJdbcException(String message) { - super(message); + super(message, BigQueryJdbcSqlStates.GENERAL_ERROR); } /** @@ -37,7 +37,7 @@ public BigQueryJdbcException(String message) { * @param ex The InterruptedException to be thrown. */ public BigQueryJdbcException(InterruptedException ex) { - super(ex); + super(ex.getMessage(), BigQueryJdbcSqlStates.QUERY_CANCELED, ex); } /** @@ -47,7 +47,10 @@ public BigQueryJdbcException(InterruptedException ex) { * @param ex The BigQueryException to be thrown. */ public BigQueryJdbcException(String message, BigQueryException ex) { - super(BigQueryJdbcExceptionUtils.formatMessage(message, ex), ex); + super( + BigQueryJdbcExceptionUtils.formatMessage(message, ex), + BigQueryJdbcExceptionUtils.sqlStateForCause(ex), + ex); this.bigQueryException = ex; } @@ -58,7 +61,10 @@ public BigQueryJdbcException(String message, BigQueryException ex) { * @param cause Throwable that is being converted. */ public BigQueryJdbcException(String message, Throwable cause) { - super(BigQueryJdbcExceptionUtils.formatMessage(message, cause), cause); + super( + BigQueryJdbcExceptionUtils.formatMessage(message, cause), + BigQueryJdbcExceptionUtils.sqlStateForCause(cause), + cause); } /** @@ -68,7 +74,10 @@ public BigQueryJdbcException(String message, Throwable cause) { * @param cause Throwable that is being converted. */ public BigQueryJdbcException(Throwable cause) { - super(cause); + super( + cause == null ? null : cause.getMessage(), + BigQueryJdbcExceptionUtils.sqlStateForCause(cause), + cause); } public BigQueryException getBigQueryException() { diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcExceptionUtils.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcExceptionUtils.java index 2ec3788d3c00..04632e1871a3 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcExceptionUtils.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcExceptionUtils.java @@ -16,6 +16,8 @@ package com.google.cloud.bigquery.exception; +import com.google.cloud.bigquery.BigQueryException; + /** Utility class for JDBC exceptions. */ final class BigQueryJdbcExceptionUtils { @@ -37,4 +39,49 @@ public static String formatMessage(String message, Throwable cause) { ? "\n" + (cause.getMessage() != null ? cause.getMessage() : cause.toString()) : ""); } + + /** + * Maps a cause to a standard SQL:2003 SQLState. + * + *
Returns {@code HY000} (general error) for anything unrecognised, so the result is always a + * valid 5-character state and never null. + * + * @param cause the underlying cause, may be null. + * @return a 5-character SQLState. + */ + static String sqlStateForCause(Throwable cause) { + if (!(cause instanceof BigQueryException)) { + return BigQueryJdbcSqlStates.GENERAL_ERROR; + } + String reason = ((BigQueryException) cause).getReason(); + if (reason == null) { + return BigQueryJdbcSqlStates.GENERAL_ERROR; + } + switch (reason) { + case "invalidQuery": + case "invalid": + case "badRequest": + return BigQueryJdbcSqlStates.SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION; + case "accessDenied": + return BigQueryJdbcSqlStates.INSUFFICIENT_PRIVILEGE; + case "invalidUser": + return BigQueryJdbcSqlStates.INVALID_AUTHORIZATION; + case "quotaExceeded": + case "rateLimitExceeded": + case "resourcesExceeded": + return BigQueryJdbcSqlStates.INSUFFICIENT_RESOURCES; + case "responseTooLarge": + return BigQueryJdbcSqlStates.PROGRAM_LIMIT_EXCEEDED; + case "stopped": + return BigQueryJdbcSqlStates.QUERY_CANCELED; + case "backendError": + case "internalError": + case "jobInternalError": + return BigQueryJdbcSqlStates.SYSTEM_ERROR; + case "notImplemented": + return BigQueryJdbcSqlStates.FEATURE_NOT_SUPPORTED; + default: + return BigQueryJdbcSqlStates.GENERAL_ERROR; + } + } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlFeatureNotSupportedException.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlFeatureNotSupportedException.java index 8039dcd53495..da84cc4abd52 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlFeatureNotSupportedException.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlFeatureNotSupportedException.java @@ -27,7 +27,7 @@ public class BigQueryJdbcSqlFeatureNotSupportedException extends SQLFeatureNotSu * @param message The detail message. */ public BigQueryJdbcSqlFeatureNotSupportedException(String message) { - super(message); + super(message, BigQueryJdbcSqlStates.FEATURE_NOT_SUPPORTED); } /** @@ -36,6 +36,6 @@ public BigQueryJdbcSqlFeatureNotSupportedException(String message) { * @param ex The BigQueryException to be thrown. */ public BigQueryJdbcSqlFeatureNotSupportedException(BigQueryException ex) { - super(ex); + super(ex.getMessage(), BigQueryJdbcSqlStates.FEATURE_NOT_SUPPORTED, ex); } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlStates.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlStates.java new file mode 100644 index 000000000000..ad7eb7e309c6 --- /dev/null +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlStates.java @@ -0,0 +1,64 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery.exception; + +/** + * Standard SQL:2003 SQLState codes used by the driver. + * + *
A SQLState is a 5-character code: a 2-character class followed by a 3-character subclass.
+ * These values are defined by the SQL standard and are portable across databases; nothing here is
+ * BigQuery-specific. {@code BigQueryDatabaseMetaData.getSQLStateType()} declares that the driver
+ * emits SQL:2003 states, so do not mix in X/Open or ODBC-only codes.
+ */
+final class BigQueryJdbcSqlStates {
+
+ /** 08 — connection exception. */
+ static final String CONNECTION_EXCEPTION = "08006";
+
+ /** 0A — feature not supported. */
+ static final String FEATURE_NOT_SUPPORTED = "0A000";
+
+ /** 22 — data exception (bad value, failed conversion). */
+ static final String DATA_EXCEPTION = "22000";
+
+ /** 28 — invalid authorization specification (authentication failed). */
+ static final String INVALID_AUTHORIZATION = "28000";
+
+ /** 42 — syntax error or access rule violation. */
+ static final String SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION = "42000";
+
+ static final String INSUFFICIENT_PRIVILEGE = "42501";
+
+ /** 53 — insufficient resources. */
+ static final String INSUFFICIENT_RESOURCES = "53000";
+
+ /** 54 — program limit exceeded. */
+ static final String PROGRAM_LIMIT_EXCEEDED = "54000";
+
+ /** 57 — operator intervention. */
+ static final String QUERY_CANCELED = "57014";
+
+ /** 58 — system error. */
+ static final String SYSTEM_ERROR = "58000";
+
+ /** HY — general error; the fallback when nothing more specific applies. */
+ static final String GENERAL_ERROR = "HY000";
+
+ private BigQueryJdbcSqlStates() {
+ // Utility class, prevent instantiation
+ }
+}
diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlSyntaxErrorException.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlSyntaxErrorException.java
index 6d51e656542a..4006b4187254 100644
--- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlSyntaxErrorException.java
+++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/exception/BigQueryJdbcSqlSyntaxErrorException.java
@@ -32,10 +32,13 @@ public class BigQueryJdbcSqlSyntaxErrorException extends SQLSyntaxErrorException
* @param ex The BigQueryException to be thrown.
*/
public BigQueryJdbcSqlSyntaxErrorException(BigQueryException ex) {
- super(ex.getMessage(), "Incorrect SQL syntax.");
+ super(ex.getMessage(), BigQueryJdbcSqlStates.SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION, ex);
}
public BigQueryJdbcSqlSyntaxErrorException(String message, BigQueryException ex) {
- super(BigQueryJdbcExceptionUtils.formatMessage(message, ex), ex);
+ super(
+ BigQueryJdbcExceptionUtils.formatMessage(message, ex),
+ BigQueryJdbcSqlStates.SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION,
+ ex);
}
}
diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcContextProxy.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcContextProxy.java
index 360fba0a8ed1..c6e25df46c46 100644
--- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcContextProxy.java
+++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcContextProxy.java
@@ -16,6 +16,7 @@
package com.google.cloud.bigquery.jdbc;
+import com.google.cloud.bigquery.jdbc.telemetry.v1.TelemetryManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -177,6 +178,11 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
LOG.severe("Exception occurred during " + methodName + ": " + errMsg, cause);
}
+ TelemetryManager.recordError(
+ TelemetryManager.extractErrorCode(cause),
+ TelemetryManager.extractXdbcCode(cause),
+ methodName);
+
throw cause;
}
}
diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/DriverEnvironmentBuilder.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/DriverEnvironmentDetector.java
similarity index 96%
rename from java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/DriverEnvironmentBuilder.java
rename to java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/DriverEnvironmentDetector.java
index 2e8fff6cf9f6..6479ebe7c688 100644
--- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/DriverEnvironmentBuilder.java
+++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/DriverEnvironmentDetector.java
@@ -27,17 +27,17 @@
import java.util.logging.Logger;
/** Utility builder for constructing {@link DriverEnvironment} telemetry protos. */
-final class DriverEnvironmentBuilder {
- private static final Logger logger = Logger.getLogger(DriverEnvironmentBuilder.class.getName());
+final class DriverEnvironmentDetector {
+ private static final Logger logger = Logger.getLogger(DriverEnvironmentDetector.class.getName());
- static final String DRIVER_NAME = "google-bigquery-jdbc-driver";
+ static final String DRIVER_NAME = "Google-BigQuery-JDBC-Driver";
static final String CLIENT_LANGUAGE = "java";
static final String DEFAULT_TELEMETRY_TAG_DIR = ".bigquery-jdbc";
static final String DEFAULT_TELEMETRY_TAG_FILE = "telemetry-tag";
static final String UNKNOWN = "unknown";
static final String RESTRICTED = "restricted";
- private DriverEnvironmentBuilder() {}
+ private DriverEnvironmentDetector() {}
static DriverEnvironment build() {
return build(null);
@@ -171,7 +171,7 @@ static String getOrCreateTelemetryTag(Path customFilePath) {
logger.log(Level.WARNING, "Failed to persist telemetry tag to file", e);
}
return newId;
- } catch (SecurityException e) {
+ } catch (RuntimeException e) {
return UUID.randomUUID().toString();
}
}
diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/TelemetryBatcher.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/TelemetryBatcher.java
index 39e28963cd02..acdb32efc582 100644
--- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/TelemetryBatcher.java
+++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/telemetry/v1/TelemetryBatcher.java
@@ -39,7 +39,10 @@
final class TelemetryBatcher implements AutoCloseable {
private static final Logger logger =
new BigQueryJdbcCustomLogger(TelemetryBatcher.class.getName());
- private static final int MAX_UNIQUE_PROFILES = 3000;
+ private static final int PROFILE_CAP_MULTIPLIER = 2;
+
+ /** Cap used when no usable threshold is configured. */
+ private static final int FALLBACK_PROFILE_CAP = 3000;
private final TelemetryConfiguration config;
private final ClearcutTransport transport;
@@ -52,6 +55,12 @@ final class TelemetryBatcher implements AutoCloseable {
private ConcurrentHashMap Returns {@code 0} when no SQLState is present or none is numeric, matching the proto default
+ * for {@code error_xdbc_code} so that unset and unmappable are indistinguishable downstream.
+ */
+ public static int extractXdbcCode(Throwable t) {
+ int depth = 0;
+ while (t != null && depth++ < 20) {
+ if (t instanceof SQLException) {
+ String sqlState = ((SQLException) t).getSQLState();
+ if (sqlState != null && !sqlState.isEmpty()) {
+ try {
+ return Integer.parseInt(sqlState);
+ } catch (NumberFormatException ignored) {
+ // Alphabetic SQLState such as HY000 — no numeric form, keep walking the chain.
+ }
+ }
+ }
+ t = t.getCause();
+ }
+ return 0;
+ }
+
/**
* Registers the JVM shutdown hook that flushes pending telemetry.
*
diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/exception/BigQueryJdbcExceptionTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/exception/BigQueryJdbcExceptionTest.java
index f13ace2f140f..7e051c2275ea 100644
--- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/exception/BigQueryJdbcExceptionTest.java
+++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/exception/BigQueryJdbcExceptionTest.java
@@ -40,9 +40,6 @@ static Stream