From e4f8ae8eefda05b7d08a2574cae12e4939ecc4fb Mon Sep 17 00:00:00 2001 From: ptiurin Date: Wed, 26 Jun 2024 15:08:32 +0100 Subject: [PATCH] feat: populating sqlstate --- .../firebolt/jdbc/client/FireboltClient.java | 13 +- .../jdbc/exception/FireboltException.java | 22 +++ .../com/firebolt/jdbc/exception/SQLState.java | 125 ++++++++++++++++++ .../FireboltAuthenticationService.java | 10 +- .../FireboltAuthenticationClientTest.java | 19 ++- .../FireboltAuthenticationServiceTest.java | 18 +++ 6 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/firebolt/jdbc/exception/SQLState.java diff --git a/src/main/java/com/firebolt/jdbc/client/FireboltClient.java b/src/main/java/com/firebolt/jdbc/client/FireboltClient.java index f7b59784..340fd4a1 100644 --- a/src/main/java/com/firebolt/jdbc/client/FireboltClient.java +++ b/src/main/java/com/firebolt/jdbc/client/FireboltClient.java @@ -3,6 +3,7 @@ import com.firebolt.jdbc.connection.CacheListener; import com.firebolt.jdbc.connection.FireboltConnection; import com.firebolt.jdbc.exception.FireboltException; +import com.firebolt.jdbc.exception.SQLState; import com.firebolt.jdbc.exception.ServerError; import com.firebolt.jdbc.exception.ServerError.Error.Location; import com.firebolt.jdbc.resultset.compress.LZ4InputStream; @@ -41,6 +42,7 @@ import java.util.stream.Collectors; import static java.lang.String.format; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.net.HttpURLConnection.HTTP_UNAVAILABLE; import static java.util.Optional.ofNullable; @@ -147,7 +149,8 @@ protected void validateResponse(String host, Response response, Boolean isCompre int statusCode = response.code(); if (!isCallSuccessful(statusCode)) { if (statusCode == HTTP_UNAVAILABLE) { - throw new FireboltException(format("Could not query Firebolt at %s. The engine is not running.", host), statusCode); + throw new FireboltException(format("Could not query Firebolt at %s. The engine is not running.", host), + statusCode, SQLState.CONNECTION_FAILURE); } String errorMessageFromServer = extractErrorMessage(response, isCompress); ServerError serverError = parseServerError(errorMessageFromServer); @@ -156,12 +159,16 @@ protected void validateResponse(String host, Response response, Boolean isCompre String errorResponseMessage = format( "Server failed to execute query with the following error:%n%s%ninternal error:%n%s", processedErrorMessage, getInternalErrorWithHeadersText(response)); - if (statusCode == HTTP_UNAUTHORIZED) { + if (statusCode == HTTP_UNAUTHORIZED || statusCode == HTTP_FORBIDDEN) { + // log the error message and throw an exception + log.log(Level.WARNING, "encountered unauthorized or forbidden error"); getConnection().removeExpiredTokens(); throw new FireboltException(format( "Could not query Firebolt at %s. The operation is not authorized or the token is expired and has been cleared from the cache.%n%s", - host, errorResponseMessage), statusCode, processedErrorMessage); + host, errorResponseMessage), statusCode, processedErrorMessage, + SQLState.INVALID_AUTHORIZATION_SPECIFICATION); } + log.log(Level.WARNING, "encountered some other error"); throw new FireboltException(errorResponseMessage, statusCode, processedErrorMessage); } } diff --git a/src/main/java/com/firebolt/jdbc/exception/FireboltException.java b/src/main/java/com/firebolt/jdbc/exception/FireboltException.java index 4c0ead1d..ede3445a 100644 --- a/src/main/java/com/firebolt/jdbc/exception/FireboltException.java +++ b/src/main/java/com/firebolt/jdbc/exception/FireboltException.java @@ -39,10 +39,20 @@ public FireboltException(String message, Integer httpStatusCode, String errorMes this.errorMessageFromServer = errorMessageFromServer; } + public FireboltException(String message, Integer httpStatusCode, String errorMessageFromServer, SQLState state) { + super(message, state.getCode()); + type = getExceptionType(httpStatusCode); + this.errorMessageFromServer = errorMessageFromServer; + } + public FireboltException(String message, Throwable cause) { this(message, cause, ExceptionType.ERROR); } + public FireboltException(String message, Throwable cause, SQLState state) { + this(message, cause, ExceptionType.ERROR, state); + } + public FireboltException(String message, ExceptionType type) { super(message); this.type = type; @@ -59,6 +69,18 @@ public FireboltException(String message, Throwable cause, ExceptionType type) { errorMessageFromServer = null; } + public FireboltException(String message, Throwable cause, ExceptionType type, SQLState state) { + super(message, state.getCode(), cause); + this.type = type; + errorMessageFromServer = null; + } + + public FireboltException(String message, int httpStatusCode, SQLState state) { + super(message, state.getCode()); + type = getExceptionType(httpStatusCode); + errorMessageFromServer = null; + } + private static ExceptionType getExceptionType(Integer httpStatusCode) { if (httpStatusCode == null) { return ERROR; diff --git a/src/main/java/com/firebolt/jdbc/exception/SQLState.java b/src/main/java/com/firebolt/jdbc/exception/SQLState.java new file mode 100644 index 00000000..038b2079 --- /dev/null +++ b/src/main/java/com/firebolt/jdbc/exception/SQLState.java @@ -0,0 +1,125 @@ +package com.firebolt.jdbc.exception; + +public enum SQLState { + SUCCESS("00000"), + WARNING("01000"), + NO_DATA("02000"), + STATEMENT_STRING_DATA_RIGHT_TRUNCATION("01004"), + NULL_VALUE_NO_INDICATOR_PARAMETER("22002"), + CONNECTION_EXCEPTION("08001"), + CONNECTION_DOES_NOT_EXIST("08003"), + CONNECTION_FAILURE("08006"), + TRANSACTION_RESOLUTION_UNKNOWN("08007"), + SQL_SYNTAX_ERROR("42000"), + SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION("42601"), + DUPLICATE_KEY_VALUE("23505"), + DATA_EXCEPTION("22000"), + CHARACTER_NOT_IN_REPERTOIRE("22021"), + STRING_DATA_RIGHT_TRUNCATION("22001"), + NUMERIC_VALUE_OUT_OF_RANGE("22003"), + INVALID_DATETIME_FORMAT("22007"), + INVALID_TIME_ZONE_DISPLACEMENT_VALUE("22009"), + INVALID_ESCAPE_CHARACTER("22019"), + INVALID_PARAMETER_VALUE("22023"), + INVALID_CURSOR_STATE("24000"), + INVALID_TRANSACTION_STATE("25000"), + INVALID_AUTHORIZATION_SPECIFICATION("28000"), + INVALID_SQL_STATEMENT_NAME("26000"), + INVALID_CURSOR_NAME("34000"), + INVALID_SCHEMA_NAME("3F000"), + TRANSACTION_ROLLBACK("40000"), + SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION_IN_DIRECT_STATEMENT("2F000"), + INVALID_SQL_DESCRIPTOR_NAME("33000"), + INVALID_CURSOR_POSITION("34000"), + INVALID_CONDITION_NUMBER("35000"), + INVALID_TRANSACTION_TERMINATION("2D000"), + INVALID_CONNECTION_NAME("2E000"), + INVALID_AUTHORIZATION_NAME("28000"), + INVALID_COLUMN_NAME("42703"), + INVALID_COLUMN_DEFINITION("42P16"), + INVALID_CURSOR_DEFINITION("42P11"), + INVALID_DATABASE_DEFINITION("42P15"), + INVALID_FUNCTION_DEFINITION("42P13"), + INVALID_PREPARED_STATEMENT_DEFINITION("42P12"), + INVALID_SCHEMA_DEFINITION("42P14"), + INVALID_TABLE_DEFINITION("42P01"), + INVALID_OBJECT_DEFINITION("42P17"), + WITH_CHECK_OPTION_VIOLATION("44000"), + INSUFFICIENT_RESOURCES("53000"), + DISK_FULL("53100"), + OUT_OF_MEMORY("53200"), + TOO_MANY_CONNECTIONS("53300"), + CONFIGURATION_LIMIT_EXCEEDED("53400"), + PROGRAM_LIMIT_EXCEEDED("54000"), + OBJECT_NOT_IN_PREREQUISITE_STATE("55000"), + OBJECT_IN_USE("55006"), + CANT_CHANGE_RUNTIME_PARAM("55P02"), + LOCK_NOT_AVAILABLE("55P03"), + OPERATOR_INTERVENTION("57000"), + QUERY_CANCELED("57014"), + ADMIN_SHUTDOWN("57P01"), + CRASH_SHUTDOWN("57P02"), + CANNOT_CONNECT_NOW("57P03"), + DATABASE_DROPPED("57P04"), + SYSTEM_ERROR("58000"), + IO_ERROR("58030"), + UNDEFINED_FILE("58P01"), + DUPLICATE_FILE("58P02"), + SNAPSHOT_TOO_OLD("72000"), + CONFIGURATION_FILE_ERROR("F0000"), + LOCK_FILE_EXISTS("F0001"), + FDW_ERROR("HV000"), + FDW_COLUMN_NAME_NOT_FOUND("HV005"), + FDW_DYNAMIC_PARAMETER_VALUE_NEEDED("HV002"), + FDW_FUNCTION_SEQUENCE_ERROR("HV010"), + FDW_INCONSISTENT_DESCRIPTOR_INFORMATION("HV021"), + FDW_INVALID_ATTRIBUTE_VALUE("HV024"), + FDW_INVALID_COLUMN_NAME("HV007"), + FDW_INVALID_COLUMN_NUMBER("HV008"), + FDW_INVALID_DATA_TYPE("HV004"), + FDW_INVALID_DATA_TYPE_DESCRIPTORS("HV006"), + FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER("HV091"), + FDW_INVALID_HANDLE("HV00B"), + FDW_INVALID_OPTION_INDEX("HV00C"), + FDW_INVALID_OPTION_NAME("HV00D"), + FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH("HV090"), + FDW_INVALID_STRING_FORMAT("HV00A"), + FDW_INVALID_USE_OF_NULL_POINTER("HV009"), + FDW_TOO_MANY_HANDLES("HV014"), + FDW_OUT_OF_MEMORY("HV001"), + FDW_NO_SCHEMAS("HV00P"), + FDW_OPTION_NAME_NOT_FOUND("HV00J"), + FDW_REPLY_HANDLE("HV00K"), + FDW_SCHEMA_NOT_FOUND("HV00Q"), + FDW_TABLE_NOT_FOUND("HV00R"), + FDW_UNABLE_TO_CREATE_EXECUTION("HV00L"), + FDW_UNABLE_TO_CREATE_REPLY("HV00M"), + FDW_UNABLE_TO_ESTABLISH_CONNECTION("HV00N"), + PLPGSQL_ERROR("P0000"), + RAISE_EXCEPTION("P0001"), + NO_DATA_FOUND("P0002"), + TOO_MANY_ROWS("P0003"), + ASSERT_FAILURE("P0004"), + INTERNAL_ERROR("XX000"), + DATA_CORRUPTED("XX001"), + INDEX_CORRUPTED("XX002"); + + private final String code; + + SQLState(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public static SQLState fromCode(String sqlState) { + for (SQLState state : SQLState.values()) { + if (state.code.equals(sqlState)) { + return state; + } + } + return null; + } +} diff --git a/src/main/java/com/firebolt/jdbc/service/FireboltAuthenticationService.java b/src/main/java/com/firebolt/jdbc/service/FireboltAuthenticationService.java index a540a115..323d20e4 100644 --- a/src/main/java/com/firebolt/jdbc/service/FireboltAuthenticationService.java +++ b/src/main/java/com/firebolt/jdbc/service/FireboltAuthenticationService.java @@ -4,6 +4,8 @@ import com.firebolt.jdbc.connection.FireboltConnectionTokens; import com.firebolt.jdbc.connection.settings.FireboltProperties; import com.firebolt.jdbc.exception.FireboltException; +import com.firebolt.jdbc.exception.SQLState; + import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import net.jodah.expiringmap.ExpiringMap; @@ -51,7 +53,11 @@ public FireboltConnectionTokens getConnectionTokens(String host, FireboltPropert } catch (FireboltException e) { log.log(Level.SEVERE, "Failed to connect to Firebolt", e); String msg = ofNullable(e.getErrorMessageFromServer()).map(m -> format(ERROR_MESSAGE_FROM_SERVER, m)).orElse(format(ERROR_MESSAGE, e.getMessage())); - throw new FireboltException(msg, e); + SQLState sqlState = SQLState.fromCode(e.getSQLState()); + if (sqlState == null) { + throw new FireboltException(msg, e); + } + throw new FireboltException(msg, e, sqlState); } catch (Exception e) { log.log(Level.SEVERE, "Failed to connect to Firebolt", e); throw new FireboltException(format(ERROR_MESSAGE, e.getMessage()), e); @@ -69,7 +75,7 @@ private long getCachingDurationInSeconds(long expireInSeconds) { /** * Removes connection tokens from the cache. - * + * * @param host host * @param loginProperties the login properties linked to the tokens */ diff --git a/src/test/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClientTest.java b/src/test/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClientTest.java index c120e337..a65a1ae2 100644 --- a/src/test/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClientTest.java +++ b/src/test/java/com/firebolt/jdbc/client/authentication/FireboltAuthenticationClientTest.java @@ -3,6 +3,8 @@ import com.firebolt.jdbc.connection.FireboltConnection; import com.firebolt.jdbc.connection.FireboltConnectionTokens; import com.firebolt.jdbc.exception.FireboltException; +import com.firebolt.jdbc.exception.SQLState; + import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -24,6 +26,7 @@ import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_UNAVAILABLE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -118,7 +121,21 @@ void shouldThrowExceptionWhenStatusCodeIsForbidden() throws Exception { when(response.code()).thenReturn(HTTP_FORBIDDEN); when(httpClient.newCall(any())).thenReturn(call); - assertThrows(FireboltException.class, + FireboltException ex = assertThrows(FireboltException.class, + () -> fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD, ENV)); + assertEquals(SQLState.INVALID_AUTHORIZATION_SPECIFICATION.getCode(), ex.getSQLState()); + } + + @Test + void shouldThrowExceptionWhenStatusCodeIsUnavailable() throws Exception { + Response response = mock(Response.class); + Call call = mock(Call.class); + when(call.execute()).thenReturn(response); + when(response.code()).thenReturn(HTTP_UNAVAILABLE); + when(httpClient.newCall(any())).thenReturn(call); + + FireboltException ex = assertThrows(FireboltException.class, () -> fireboltAuthenticationClient.postConnectionTokens(HOST, USER, PASSWORD, ENV)); + assertEquals(SQLState.CONNECTION_FAILURE.getCode(), ex.getSQLState()); } } diff --git a/src/test/java/com/firebolt/jdbc/service/FireboltAuthenticationServiceTest.java b/src/test/java/com/firebolt/jdbc/service/FireboltAuthenticationServiceTest.java index 38d63860..6c05ae6e 100644 --- a/src/test/java/com/firebolt/jdbc/service/FireboltAuthenticationServiceTest.java +++ b/src/test/java/com/firebolt/jdbc/service/FireboltAuthenticationServiceTest.java @@ -20,6 +20,7 @@ import com.firebolt.jdbc.connection.FireboltConnectionTokens; import com.firebolt.jdbc.connection.settings.FireboltProperties; import com.firebolt.jdbc.exception.FireboltException; +import com.firebolt.jdbc.exception.SQLState; @ExtendWith(MockitoExtension.class) class FireboltAuthenticationServiceTest { @@ -80,6 +81,21 @@ void shouldGetConnectionTokenAfterRemoving() throws SQLException, IOException { @Test void shouldThrowExceptionWithServerResponseWhenAResponseIsAvailable() throws SQLException, IOException { + String randomHost = UUID.randomUUID().toString(); + Mockito.when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD, ENV)) + .thenThrow(new FireboltException("An error happened during authentication", 403, "INVALID PASSWORD", + SQLState.INVALID_AUTHORIZATION_SPECIFICATION)); + + FireboltException ex = assertThrows(FireboltException.class, + () -> fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES)); + assertEquals( + "Failed to connect to Firebolt with the error from the server: INVALID PASSWORD, see logs for more info.", + ex.getMessage()); + assertEquals(SQLState.INVALID_AUTHORIZATION_SPECIFICATION.getCode(), ex.getSQLState()); + } + + @Test + void shouldThrowExceptionNoSQLStateWithServerResponseWhenAResponseIsAvailable() throws SQLException, IOException { String randomHost = UUID.randomUUID().toString(); Mockito.when(fireboltAuthenticationClient.postConnectionTokens(randomHost, USER, PASSWORD, ENV)) .thenThrow(new FireboltException("An error happened during authentication", 403, "INVALID PASSWORD")); @@ -89,6 +105,7 @@ void shouldThrowExceptionWithServerResponseWhenAResponseIsAvailable() throws SQL assertEquals( "Failed to connect to Firebolt with the error from the server: INVALID PASSWORD, see logs for more info.", ex.getMessage()); + assertEquals(null, ex.getSQLState()); } @Test @@ -100,6 +117,7 @@ void shouldThrowExceptionWithExceptionMessageWhenAResponseIsNotAvailable() throw FireboltException ex = assertThrows(FireboltException.class, () -> fireboltAuthenticationService.getConnectionTokens(randomHost, PROPERTIES)); assertEquals("Failed to connect to Firebolt with the error: NULL!, see logs for more info.", ex.getMessage()); + assertEquals(null, ex.getSQLState()); } }