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..fc724e53e543 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.extractSqlState(cause),
+ methodName);
+
throw cause;
}
}
diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java
index 1dab204b5b23..ef7c9363c68d 100644
--- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java
+++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java
@@ -177,7 +177,6 @@ protected boolean removeEldestEntry(Map.Entry Returns empty String when no SQLState is present.
+ */
+ public static String extractSqlState(Throwable t) {
+ int depth = 0;
+ while (t != null && depth++ < 20) {
+ if (t instanceof SQLException) {
+ String sqlState = ((SQLException) t).getSQLState();
+ if (sqlState != null && !sqlState.isEmpty()) {
+ return sqlState;
+ }
+ }
+ t = t.getCause();
+ }
+ return "";
+ }
+
/**
* Registers the JVM shutdown hook that flushes pending telemetry.
*
diff --git a/java-bigquery-jdbc/src/main/proto/google/cloud/bigquery/jdbc/telemetry/v1/telemetry.proto b/java-bigquery-jdbc/src/main/proto/google/cloud/bigquery/jdbc/telemetry/v1/telemetry.proto
index f4b41457eb2e..e469c6490f98 100644
--- a/java-bigquery-jdbc/src/main/proto/google/cloud/bigquery/jdbc/telemetry/v1/telemetry.proto
+++ b/java-bigquery-jdbc/src/main/proto/google/cloud/bigquery/jdbc/telemetry/v1/telemetry.proto
@@ -169,6 +169,7 @@ message ErrorMetric {
int32 error_xdbc_code = 2;
string method_name = 3;
int64 count = 4;
+ string error_sql_state = 5;
}
// High-level driver runtime features and capabilities tracked for 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