-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Postgres source: handle terminate connection exception with test. (#1…
…9887) * Source postgres: catch termination connection PSQLException * Source postgres: move common code to util method * Source postgres: clean code * Source postgres: review fixes and added unit tests * Source postgres: clean code * Source postgres: bump version * Source postgres: format * Source postgres: format * Source postgres: bump version * Source postgres: fix failing test and bump version * Source mysql: make message visible for tests * Source postgres: reformat import Co-authored-by: VitaliiMaltsev <39538064+VitaliiMaltsev@users.noreply.github.com> Co-authored-by: Akash Kulkarni <113392464+akashkulk@users.noreply.github.com> Co-authored-by: Rodi Reich Zilberman <867491+rodireich@users.noreply.github.com>
- Loading branch information
1 parent
4429968
commit 293075e
Showing
6 changed files
with
194 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
...ns/bases/base-java/src/main/java/io/airbyte/integrations/util/ConnectorExceptionUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright (c) 2022 Airbyte, Inc., all rights reserved. | ||
*/ | ||
|
||
package io.airbyte.integrations.util; | ||
|
||
import io.airbyte.commons.exceptions.ConfigErrorException; | ||
import io.airbyte.commons.exceptions.ConnectionErrorException; | ||
import io.airbyte.integrations.base.errors.messages.ErrorMessage; | ||
import java.sql.SQLException; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.function.Predicate; | ||
|
||
/** | ||
* Utility class defining methods for handling configuration exceptions in connectors. | ||
*/ | ||
public class ConnectorExceptionUtil { | ||
|
||
public static final String COMMON_EXCEPTION_MESSAGE_TEMPLATE = "Could not connect with provided configuration. Error: %s"; | ||
static final String RECOVERY_CONNECTION_ERROR_MESSAGE = | ||
"We're having issues syncing from a Postgres replica that is configured as a hot standby server. " + | ||
"Please see https://docs.airbyte.com/integrations/sources/postgres/#sync-data-from-postgres-hot-standby-server for options and workarounds"; | ||
private static final List<Predicate<Throwable>> configErrorPredicates = | ||
List.of(getConfigErrorPredicate(), getConnectionErrorPredicate(), isRecoveryConnectionExceptionPredicate()); | ||
|
||
public static boolean isConfigError(final Throwable e) { | ||
return configErrorPredicates.stream().anyMatch(predicate -> predicate.test(e)); | ||
} | ||
|
||
public static String getDisplayMessage(final Throwable e) { | ||
if (e instanceof ConfigErrorException) { | ||
return ((ConfigErrorException) e).getDisplayMessage(); | ||
} else if (e instanceof ConnectionErrorException) { | ||
final ConnectionErrorException connEx = (ConnectionErrorException) e; | ||
return ErrorMessage.getErrorMessage(connEx.getStateCode(), connEx.getErrorCode(), connEx.getExceptionMessage(), connEx); | ||
} else if (isRecoveryConnectionExceptionPredicate().test(e)) { | ||
return RECOVERY_CONNECTION_ERROR_MESSAGE; | ||
} else { | ||
return String.format(COMMON_EXCEPTION_MESSAGE_TEMPLATE, e.getMessage() != null ? e.getMessage() : ""); | ||
} | ||
} | ||
|
||
/** | ||
* Returns the first instance of an exception associated with a configuration error (if it exists). | ||
* Otherwise, the original exception is returned. | ||
*/ | ||
public static Throwable getRootConfigError(final Exception e) { | ||
Throwable current = e; | ||
while (current != null) { | ||
if (ConnectorExceptionUtil.isConfigError(current)) { | ||
return current; | ||
} else { | ||
current = current.getCause(); | ||
} | ||
} | ||
return e; | ||
} | ||
|
||
private static Predicate<Throwable> getConfigErrorPredicate() { | ||
return e -> e instanceof ConfigErrorException; | ||
} | ||
|
||
private static Predicate<Throwable> getConnectionErrorPredicate() { | ||
return e -> e instanceof ConnectionErrorException; | ||
} | ||
|
||
private static Predicate<Throwable> isRecoveryConnectionExceptionPredicate() { | ||
return e -> e instanceof SQLException && e.getMessage() | ||
.toLowerCase(Locale.ROOT) | ||
.contains("due to conflict with recovery"); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
...ases/base-java/src/test/java/io/airbyte/integrations/util/ConnectorExceptionUtilTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* | ||
* Copyright (c) 2022 Airbyte, Inc., all rights reserved. | ||
*/ | ||
|
||
package io.airbyte.integrations.util; | ||
|
||
import static io.airbyte.integrations.util.ConnectorExceptionUtil.COMMON_EXCEPTION_MESSAGE_TEMPLATE; | ||
import static io.airbyte.integrations.util.ConnectorExceptionUtil.RECOVERY_CONNECTION_ERROR_MESSAGE; | ||
import static org.junit.jupiter.api.Assertions.*; | ||
|
||
import io.airbyte.commons.exceptions.ConfigErrorException; | ||
import io.airbyte.commons.exceptions.ConnectionErrorException; | ||
import java.sql.SQLException; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class ConnectorExceptionUtilTest { | ||
|
||
public static final String CONFIG_EXCEPTION_MESSAGE = "test message"; | ||
public static final String RECOVERY_EXCEPTION_MESSAGE = "FATAL: terminating connection due to conflict with recovery"; | ||
public static final String COMMON_EXCEPTION_MESSAGE = "something happens with connection"; | ||
public static final String CONNECTION_ERROR_MESSAGE_TEMPLATE = "State code: %s; Error code: %s; Message: %s"; | ||
|
||
@Test() | ||
void isConfigErrorForConfigException() { | ||
ConfigErrorException configErrorException = new ConfigErrorException(CONFIG_EXCEPTION_MESSAGE); | ||
assertTrue(ConnectorExceptionUtil.isConfigError(configErrorException)); | ||
|
||
} | ||
|
||
@Test | ||
void isConfigErrorForConnectionException() { | ||
ConnectionErrorException connectionErrorException = new ConnectionErrorException(CONFIG_EXCEPTION_MESSAGE); | ||
assertTrue(ConnectorExceptionUtil.isConfigError(connectionErrorException)); | ||
} | ||
|
||
@Test | ||
void isConfigErrorForRecoveryPSQLException() { | ||
SQLException recoveryPSQLException = new SQLException(RECOVERY_EXCEPTION_MESSAGE); | ||
assertTrue(ConnectorExceptionUtil.isConfigError(recoveryPSQLException)); | ||
} | ||
|
||
@Test | ||
void isConfigErrorForCommonSQLException() { | ||
SQLException recoveryPSQLException = new SQLException(COMMON_EXCEPTION_MESSAGE); | ||
assertFalse(ConnectorExceptionUtil.isConfigError(recoveryPSQLException)); | ||
} | ||
|
||
@Test | ||
void isConfigErrorForCommonException() { | ||
assertFalse(ConnectorExceptionUtil.isConfigError(new Exception())); | ||
} | ||
|
||
@Test | ||
void getDisplayMessageForConfigException() { | ||
ConfigErrorException configErrorException = new ConfigErrorException(CONFIG_EXCEPTION_MESSAGE); | ||
String actualDisplayMessage = ConnectorExceptionUtil.getDisplayMessage(configErrorException); | ||
assertEquals(CONFIG_EXCEPTION_MESSAGE, actualDisplayMessage); | ||
} | ||
|
||
@Test | ||
void getDisplayMessageForConnectionError() { | ||
String testCode = "test code"; | ||
int errorCode = -1; | ||
ConnectionErrorException connectionErrorException = new ConnectionErrorException(testCode, errorCode, CONFIG_EXCEPTION_MESSAGE, new Exception()); | ||
String actualDisplayMessage = ConnectorExceptionUtil.getDisplayMessage(connectionErrorException); | ||
assertEquals(String.format(CONNECTION_ERROR_MESSAGE_TEMPLATE, testCode, errorCode, CONFIG_EXCEPTION_MESSAGE), actualDisplayMessage); | ||
} | ||
|
||
@Test | ||
void getDisplayMessageForRecoveryException() { | ||
SQLException recoveryException = new SQLException(RECOVERY_EXCEPTION_MESSAGE); | ||
String actualDisplayMessage = ConnectorExceptionUtil.getDisplayMessage(recoveryException); | ||
assertEquals(RECOVERY_CONNECTION_ERROR_MESSAGE, actualDisplayMessage); | ||
} | ||
|
||
@Test | ||
void getDisplayMessageForCommonException() { | ||
Exception exception = new SQLException(COMMON_EXCEPTION_MESSAGE); | ||
String actualDisplayMessage = ConnectorExceptionUtil.getDisplayMessage(exception); | ||
assertEquals(String.format(COMMON_EXCEPTION_MESSAGE_TEMPLATE, COMMON_EXCEPTION_MESSAGE), actualDisplayMessage); | ||
} | ||
|
||
@Test | ||
void getRootConfigErrorFromConfigException() { | ||
ConfigErrorException configErrorException = new ConfigErrorException(CONFIG_EXCEPTION_MESSAGE); | ||
Exception exception = new Exception(COMMON_EXCEPTION_MESSAGE, configErrorException); | ||
|
||
Throwable actualRootConfigError = ConnectorExceptionUtil.getRootConfigError(exception); | ||
assertEquals(configErrorException, actualRootConfigError); | ||
} | ||
|
||
@Test | ||
void getRootConfigErrorFromRecoverySQLException() { | ||
SQLException recoveryException = new SQLException(RECOVERY_EXCEPTION_MESSAGE); | ||
RuntimeException runtimeException = new RuntimeException(COMMON_EXCEPTION_MESSAGE, recoveryException); | ||
Exception exception = new Exception(runtimeException); | ||
|
||
Throwable actualRootConfigError = ConnectorExceptionUtil.getRootConfigError(exception); | ||
assertEquals(recoveryException, actualRootConfigError); | ||
} | ||
|
||
@Test | ||
void getRootConfigErrorFromNonConfigException() { | ||
SQLException configErrorException = new SQLException(CONFIG_EXCEPTION_MESSAGE); | ||
Exception exception = new Exception(COMMON_EXCEPTION_MESSAGE, configErrorException); | ||
|
||
Throwable actualRootConfigError = ConnectorExceptionUtil.getRootConfigError(exception); | ||
assertEquals(exception, actualRootConfigError); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters