-
Notifications
You must be signed in to change notification settings - Fork 34
PLUGIN-1823: Retrying all SQLTransientExceptions #597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
7fcb0a0
to
9da75b8
Compare
9da75b8
to
ac813f0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, this looks everything is getting wrapped within Failsafe
where we might end up with having nested level retries, we need to ensure we add retries only where we are actually interacting with JDBC client and not top level functions.
For example adding retries to DriverManager.getConnection(connectionString, connectionProperties)
makes sense because you are actually interacting with the source db but adding retries to whole loadSchema(Connection connection, String query)
do not makes sense we need to be careful while adding such retries.
@@ -48,37 +50,46 @@ public DBRun(QueryConfig config, Class<? extends Driver> driverClass, Boolean en | |||
* to use and which connection string to use come from the plugin configuration. | |||
*/ | |||
public void run() throws SQLException, InstantiationException, IllegalAccessException { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove empty line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
database-commons/src/main/java/io/cdap/plugin/db/source/AbstractDBSource.java
Outdated
Show resolved
Hide resolved
catch (SQLException e) { | ||
// wrap exception to ensure SQLException-child instances not exposed to contexts without jdbc | ||
// driver in classpath | ||
String errorMessage = | ||
String.format("SQL Exception occurred: [Message='%s', SQLState='%s', ErrorCode='%s'].", | ||
e.getMessage(), | ||
e.getSQLState(), e.getErrorCode()); | ||
String errorMessageWithDetails = String.format("Error occurred while trying to" + | ||
" get schema from database." + | ||
"Error message: '%s'. Error code: '%s'. SQLState: '%s'", e.getMessage(), e.getErrorCode(), | ||
e.getSQLState()); | ||
String externalDocumentationLink = getExternalDocumentationLink(); | ||
if (!Strings.isNullOrEmpty(externalDocumentationLink)) { | ||
if (!errorMessage.endsWith(".")) { | ||
errorMessage = errorMessage + "."; | ||
} | ||
errorMessage = String.format("%s For more details, see %s", errorMessage, | ||
externalDocumentationLink); | ||
} | ||
throw ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN), | ||
errorMessage, errorMessageWithDetails, ErrorType.USER, false, ErrorCodeType.SQLSTATE, | ||
e.getSQLState(), externalDocumentationLink, new SQLException(e.getMessage(), | ||
e.getSQLState(), e.getErrorCode())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If loadSchemaFromDB(Connection connection, String query)
already includes retry logic, we don't need to add another layer here, as having nested retries can lead to undesirable behavior.
Also Failsafe
will wrap any non-retryable exception or an exception after all retries are exhausted in a FailsafeException
so where ever we are doing catch(SQLException)
it won't work, you might need to catch FailsafeException
& check cause.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the nested retry logic. Also, handled the FailsafeException
and the wrapped exceptions in a separate method : unwrapFailsafeException()
of RetryUtils
class as suggested in the other comment.
Please note E2E should not be modified and not fail with these changes. Otherwise, we have done something wrong which does not give expected failure messages. |
errorMessage, errorMessageWithDetails, ErrorType.USER, false, ErrorCodeType.SQLSTATE, | ||
e.getSQLState(), externalDocumentationLink, new SQLException(e.getMessage(), | ||
e.getSQLState(), e.getErrorCode())); | ||
throw e; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should throw cause
instead of FailsafeException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled the FailsafeException
in a separate method : unwrapFailsafeException
of RetryUtils
class as suggested in the other comment.
} | ||
throw e; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should throw cause
instead of FailsafeException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled the FailsafeException
in a separate method : unwrapFailsafeException
of RetryUtils
class as suggested in the other comment.
errorMessage, errorMessageWithDetails, ErrorType.USER, false, ErrorCodeType.SQLSTATE, | ||
e.getSQLState(), externalDocumentationLink, new SQLException(e.getMessage(), | ||
e.getSQLState(), e.getErrorCode())); | ||
throw e; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should throw cause
instead of FailsafeException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled the FailsafeException
in a separate method : unwrapFailsafeException
of RetryUtils
class as suggested in the other comment.
return Failsafe.with(retryPolicy).<Connection>get(() -> DriverManager | ||
.getConnection(connectionString, connectionProperties)); | ||
} | ||
|
||
private Statement createStatementWithRetry(Connection connection) { | ||
return Failsafe.with(retryPolicy).<Statement>get(() -> connection.createStatement()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should throw cause
instead of FailsafeException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled the FailsafeException
in a separate method : unwrapFailsafeException
of RetryUtils
class as suggested in the other comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see some level of duplication in both AbstractDBSource
& AbstractDBSink
, can we please move it to the common AbstractDBUtil
class?
return maxRetryCount == null ? DEFAULT_MAX_RETRY_COUNT : maxRetryCount; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove extra empty line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
cloudsql-mysql-plugin/src/test/java/io/cdap/plugin/cloudsql/mysql/CloudSQLMySQLSinkTest.java
Show resolved
Hide resolved
ResultSet resultSet = Failsafe.with(RetryPolicyUtil.getRetryPolicy(config.getInitialRetryDuration(), | ||
config.getMaxRetryDuration(), config.getMaxRetryCount())) | ||
.get(() -> statement.executeQuery(query)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should not throw FailsafeException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled the FailsafeException
in a separate method : unwrapFailsafeException
of RetryUtils
class as suggested in the other comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public final class RetryUtils {
public static Connection createConnectionWithRetry(RetryPolicy<?> retryPolicy, String connectionString,
Properties connectionProperties, String externalDocumentationLink) throws Exception {
try {
return Failsafe.with(retryPolicy).get(() ->
DriverManager.getConnection(connectionString, connectionProperties)
);
} catch (Exception e) {
throw unwrapFailsafeException(e, externalDocumentationLink);
}
}
public static Statement createStatementWithRetry(RetryPolicy<?> retryPolicy,
Connection connection, String externalDocumentationLink) throws Exception {
try {
return Failsafe.with(retryPolicy).get(connection::createStatement);
} catch (Exception e) {
throw unwrapFailsafeException(e, externalDocumentationLink);
}
}
public static PreparedStatement prepareStatementWithRetry(RetryPolicy<?> retryPolicy,
Connection connection,
String sqlQuery, String externalDocumentationLink) throws Exception {
try {
return Failsafe.with(retryPolicy).get(() ->
connection.prepareStatement(sqlQuery)
);
} catch (Exception e) {
throw unwrapFailsafeException(e, externalDocumentationLink);
}
}
public static ResultSet executeWithRetry(RetryPolicy<?> retryPolicy,
Connection connection,
String sqlQuery, String externalDocumentationLink) throws Exception {
try {
return Failsafe.with(retryPolicy).get(() -> connection.createStatement().executeQuery(sqlQuery));
} catch (Exception e) {
throw unwrapFailsafeException(e, externalDocumentationLink);
}
}
private static Exception unwrapFailsafeException(Exception e) {
if (e instanceof FailsafeException && e.getCause() instanceof Exception) {
if (e instanceOf SQLException) {
return programFailureException(e, externalDocumentationLink);
} else {
return (Exception) e.getCause();
}
}
return e;
}
private static ProgramFailureException programFailureException(SQLException e, String externalDocumentationLink) {
// wrap exception to ensure SQLException-child instances not exposed to contexts without jdbc
// driver in classpath
String errorMessage =
String.format("SQL Exception occurred: [Message='%s', SQLState='%s', ErrorCode='%s'].",
e.getMessage(), e.getSQLState(), e.getErrorCode());
String errorMessageWithDetails = String.format("Error occurred while trying to" +
" get schema from database." + "Error message: '%s'. Error code: '%s'. SQLState: '%s'", e.getMessage(),
e.getErrorCode(), e.getSQLState());
if (!Strings.isNullOrEmpty(externalDocumentationLink)) {
if (!errorMessage.endsWith(".")) {
errorMessage = errorMessage + ".";
}
errorMessage = String.format("%s For more details, see %s", errorMessage, externalDocumentationLink);
}
return ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN),
errorMessage, errorMessageWithDetails, ErrorType.USER, false, ErrorCodeType.SQLSTATE, e.getSQLState(),
externalDocumentationLink, e);
}
}
You can create a RetryUtils
like above which accepts connection params.
|
||
protected final T sourceConfig; | ||
protected Class<? extends Driver> driverClass; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove extra empty line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
Move retry logic into a separate class: RetryUtils and add exception handling
Refactored the code to add the retry logic only for the methods interacting with the JDBC client. |
private ProgramFailureException wrapException(SQLException e) { | ||
// wrap exception to ensure SQLException-child instances not exposed to contexts without jdbc | ||
// driver in classpath | ||
String errorMessage = | ||
String.format("SQL Exception occurred: [Message='%s', SQLState='%s', ErrorCode='%s'].", | ||
e.getMessage(), e.getSQLState(), e.getErrorCode()); | ||
String errorMessageWithDetails = String.format("Error occurred while trying to" + | ||
" get schema from database." + "Error message: '%s'. Error code: '%s'. SQLState: '%s'", e.getMessage(), | ||
e.getErrorCode(), e.getSQLState()); | ||
String externalDocumentationLink = getExternalDocumentationLink(); | ||
if (!Strings.isNullOrEmpty(externalDocumentationLink)) { | ||
if (!errorMessage.endsWith(".")) { | ||
errorMessage = errorMessage + "."; | ||
} | ||
errorMessage = String.format("%s For more details, see %s", errorMessage, externalDocumentationLink); | ||
} | ||
return ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN), | ||
errorMessage, errorMessageWithDetails, ErrorType.USER, false, ErrorCodeType.SQLSTATE, e.getSQLState(), | ||
externalDocumentationLink, new SQLException(e.getMessage(), e.getSQLState(), e.getErrorCode())); | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where this method used now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's unused now. Removed it. fc78d9c
errorMessage = String.format("%s For more details, see %s", errorMessage, externalDocumentationLink); | ||
} | ||
return ErrorUtils.getProgramFailureException(new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN), | ||
errorMessage, errorMessageWithDetails, ErrorType.USER, false, ErrorCodeType.SQLSTATE, e.getSQLState(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this cannot be USER
always, we should check based on SQLState
and dependency should be true
. Not sure how was this missed earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you give some pointers where to fetch all possible SQL States?
Should it be something like this?
String sqlState = e.getSQLState();
ErrorType errorType = getErrorTypeFromSQLState(sqlState);
private static ErrorType getErrorTypeFromSQLState(String sqlState) {
if (sqlState != null && sqlState.length() >= 2) {
String classCode = sqlState.substring(0, 2);
switch (classCode) {
case "22": // Data Exception
case "23": // Integrity Constraint Violation
case "28": // Invalid Authorization
case "42": // Syntax Error or Access Rule Violation
return ErrorType.USER;
case "08": // Connection Exception
case "53": // Insufficient Resources
case "57": // Operator Intervention
case "58": // System Error
return ErrorType.SYSTEM;
default:
// Check for the timeout codes
if ("HYT00".equals(sqlState) || "HYT01".equals(sqlState)) {
return ErrorType.SYSTEM;
}
break;
}
}
return ErrorType.UNKNOWN;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can directly use DBErrorDetailsProvider#getException#getProgramFailureException
: https://github.com/cdapio/hydrator-plugins/blob/b9723d1b24068347c60ad2b0489064902f335de9/hydrator-common/src/main/java/io/cdap/plugin/common/db/DBErrorDetailsProvider.java#L184
create an object of DBErrorDetailsProvider
in RetryUtils
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we directly use this method, then we wouldn't be able to pass the externalDocumentationLink
as an argument in the method call to DBErrorDetailsProvider#getException#getProgramFailureException
unless we create another overloaded method in the DBErrorDetailsProvider
class to accept this new parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did some more code analysis to find out that there's an alternate approach we can try where we'd need to create a method to return the instance of DBErrorDetailsProvider
in AbstractSource
/AbstractSink
classes and then override this method in the respective connectors' subclasses to return the instance of sub-classes of DBErrorDetailsProvider
and then call the below methods for
getExternalDocumentationLink()
getErrorTypeFromErrorCodeAndSqlState()
andgetErrorCategoryFromSqlState()
using the returned instance
AbstractDBSource
protected DBErrorDetailsProvider getDBErrorDetailsProvider() {
return new DBErrorDetailsProvider();
}
PostgresSource
@Override
protected PostgresErrorDetailsProvider getDBErrorDetailsProvider() {
return new PostgresErrorDetailsProvider();
}
To go with this approach, we'd need to make the access-modifier for the 3 methods mentioned above, from protected
to public
in the DBErrorDetailsProvider
class because Java uses static types at compile time. (Even though we can increase the visibility for the overriden methods from protected
to public
and JVM will resolve to the sub-class overridden methods for respective connectors at the run time)
Now, if we need to avoid this change for the access modifier, it will additionally require us to create methods for all the 3 methods mentioned above, in AbstractSource
/AbstractSink
and then override them with the connector specific implementation such as this
AbstractDBSource
protected String getExternalDocumentationLink() {
return "https://en.wikipedia.org/wiki/SQLSTATE"; //Default
}
PostgresSource
@Override
protected String getExternalDocumentationLink() {
return getDBErrorDetailsProvider().getExternalDocumentationLink();
}
This will be a significant change and may stretch this PR further.
private static final String NAME_INITIAL_RETRY_DURATION = "initialRetryDuration"; | ||
private static final String NAME_MAX_RETRY_DURATION = "maxRetryDuration"; | ||
private static final String NAME_MAX_RETRY_COUNT = "maxRetryCount"; | ||
public static final int DEFAULT_INITIAL_RETRY_DURATION_SECONDS = 5; | ||
public static final int DEFAULT_MAX_RETRY_COUNT = 5; | ||
public static final int DEFAULT_MAX_RETRY_DURATION_SECONDS = 80; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move all of this to RetryPolicyUtil
and use it everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved the constants from AbstractDBConnectorConfig
and ConnectionConfig
classes to RetryUtils
class 037a0d4
} catch (SQLException e) { | ||
throw new RuntimeException(String.format("Unable to validate schema due to: %s.", | ||
ExceptionUtils.getRootCauseMessage(e)), e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when it will be thrown if RetryUtils
will always throw ProgramFailureException
for every SQLException
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The try-with-resources
automatically closes any resources that implement AutoCloseable (like Connection, Statement, or ResultSet)
This SQLException
is thrown from the auto-closeable resource when we perform any SQL related operation such as creating a connection or creating statement in the try-with-resources
block. If we remove the try-wth-resources
, then the SQLException
is not thrown
In case we want to keep the try-with-resources
, then either we need to add throws SQLException
(or Exception
) in the enclosing method signature OR we need to catch the SQLException
and handle it.
@@ -168,12 +168,12 @@ public Schema getSchema() throws SQLException { | |||
} | |||
|
|||
private Schema loadSchemaFromDB(Connection connection, String query) throws SQLException { | |||
Statement statement = connection.createStatement(); | |||
Statement statement = RetryUtils.createStatementWithRetry((RetryPolicy<Statement>) retryPolicy, connection, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we use try with resources here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, added try-with-resource. 037a0d4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does it affect the UI if validateSchema
fails for DB sinks? Can we test before/after the change?
Moved all the retry constants to RetryUtils class, Added the methods to determine error type and error category methods from Error code and SQL State
Yes. Will check and update the behaviour here. I've reverted the changes done to handle Test Scenario: Validate the schema in PostgreSQL Sink Plugin, if connection is not active. (Postgres DB is down) [BEFORE CHANGES] Error message on the UI: [AFTER CHANGES] Error message on the UI: Is the new error message acceptable or should we revert the changes done to throw |
LGTM |
ERROR_CODE_TO_ERROR_TYPE.put("0W", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("0X", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("0Y", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("0Z", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("10", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("20", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("21", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("22", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("23", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("24", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("25", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("26", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("27", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("28", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("2B", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("2C", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("2D", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("2E", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("2F", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("2H", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("30", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("33", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("34", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("35", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("36", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("38", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("39", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("3B", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("3C", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("3D", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("3F", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("40", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("42", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("44", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("45", ErrorType.USER); | ||
ERROR_CODE_TO_ERROR_TYPE.put("46", ErrorType.SYSTEM); | ||
ERROR_CODE_TO_ERROR_TYPE.put("HW", ErrorType.SYSTEM); | ||
|
||
ERROR_CODE_TO_ERROR_CATEGORY = new HashMap<>(); | ||
ErrorCategory.ErrorCategoryEnum plugin = ErrorCategory.ErrorCategoryEnum.PLUGIN; | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("01", new ErrorCategory(plugin, "DB Warning")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("02", new ErrorCategory(plugin, "DB No Data")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("07", new ErrorCategory(plugin, "DB Dynamic SQL error")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("08", new ErrorCategory(plugin, "DB Connection Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("09", new ErrorCategory(plugin, "DB Triggered Action Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0A", new ErrorCategory(plugin, "DB Feature Not Supported")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0D", new ErrorCategory(plugin, "DB Invalid Target Type Specification")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0E", new ErrorCategory(plugin, "DB Invalid Schema Name List Specification")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0F", new ErrorCategory(plugin, "DB Locator Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0K", new ErrorCategory(plugin, "DB Resignal When Handler Not Active")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0L", new ErrorCategory(plugin, "DB Invalid Grantor")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0M", new ErrorCategory(plugin, "DB Invalid SQL-Invoked Procedure Reference")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0N", new ErrorCategory(plugin, "DB SQL/XML Mapping Error")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0P", new ErrorCategory(plugin, "DB Invalid Role Specification")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0S", | ||
new ErrorCategory(plugin, "DB Invalid Transform Group Name Specification")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0T", | ||
new ErrorCategory(plugin, "DB Target Table Disagrees With Cursor Specification")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0U", | ||
new ErrorCategory(plugin, "DB Attempt To Assign To Non-Updatable Column")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0V", new ErrorCategory(plugin, "DB Attempt To Assign To Ordering Column")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0W", new ErrorCategory(plugin, "DB Prohibited Statement Encountered")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0X", new ErrorCategory(plugin, "DB Invalid Foreign Server Specification")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0Y", new ErrorCategory(plugin, "DB Pass-Through Specific Condition")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("0Z", new ErrorCategory(plugin, "DB Diagnostics Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("10", new ErrorCategory(plugin, "DB XQuery Error")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("20", new ErrorCategory(plugin, "DB Case Not Found For Case Statement")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("21", new ErrorCategory(plugin, "DB Cardinality Violation")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("22", new ErrorCategory(plugin, "DB Data Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("23", new ErrorCategory(plugin, "DB Integrity Constraint Violation")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("24", new ErrorCategory(plugin, "DB Invalid Cursor State")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("25", new ErrorCategory(plugin, "DB Invalid Transaction State")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("26", new ErrorCategory(plugin, "DB Invalid SQL Statement Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("27", new ErrorCategory(plugin, "DB Triggered Data Change Violation")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("28", new ErrorCategory(plugin, "DB Invalid Authorization Specification")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("2B", | ||
new ErrorCategory(plugin, "DB Dependent Privilege Descriptors Still Exist")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("2C", new ErrorCategory(plugin, "DB Invalid Character Set Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("2D", new ErrorCategory(plugin, "DB Invalid Transaction Termination")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("2E", new ErrorCategory(plugin, "DB Invalid Connection Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("2F", new ErrorCategory(plugin, "DB SQL Routine Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("2H", new ErrorCategory(plugin, "DB Invalid Collation Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("30", new ErrorCategory(plugin, "DB Invalid SQL Statement Identifier")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("33", new ErrorCategory(plugin, "DB Invalid SQL Descriptor Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("34", new ErrorCategory(plugin, "DB Invalid Cursor Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("35", new ErrorCategory(plugin, "DB Invalid Condition Number")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("36", new ErrorCategory(plugin, "DB Cursor Sensitivity Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("38", new ErrorCategory(plugin, "DB External Routine Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("39", new ErrorCategory(plugin, "DB External Routine Invocation Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("3B", new ErrorCategory(plugin, "DB Savepoint Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("3C", new ErrorCategory(plugin, "DB Ambiguous Cursor Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("3D", new ErrorCategory(plugin, "DB Invalid Catalog Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("3F", new ErrorCategory(plugin, "DB Invalid Schema Name")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("40", new ErrorCategory(plugin, "DB Transaction Rollback")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("42", new ErrorCategory(plugin, "DB Syntax Error or Access Rule Violation")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("44", new ErrorCategory(plugin, "DB With Check Option Violation")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("45", new ErrorCategory(plugin, "DB Unhandled User-Defined Exception")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("46", new ErrorCategory(plugin, "DB JAVA DDL")); | ||
ERROR_CODE_TO_ERROR_CATEGORY.put("HW", new ErrorCategory(plugin, "DB Datalink Exception")); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not meant to be copied and maintained at two places.
We can just create an object of DBErrorDetailsProvider
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those maps are private and there are not public getter methods for them in the DBErrorDetailsProvider
. Are you saying that we can modify the class and raise a PR in thehydrator-plugins
repo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class:
does make use of some of the error codesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those maps are private and there are not public getter methods for them in the
DBErrorDetailsProvider
. Are you saying that we can modify the class and raise a PR in thehydrator-plugins
repo?
I specifically pointed out the method which can be called in earlier comment : https://github.com/cdapio/hydrator-plugins/blob/b9723d1b24068347c60ad2b0489064902f335de9/hydrator-common/src/main/java/io/cdap/plugin/common/db/DBErrorDetailsProvider.java#L184
private void executeWithRetry(FailureCollector failureCollector, SettableArguments settableArguments, | ||
Properties connectionProperties) throws SQLException { | ||
try (Connection connection = RetryUtils.createConnectionWithRetry((RetryPolicy<Connection>) retryPolicy, | ||
config.getConnectionString(), connectionProperties, null)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is externalDocumentationLink
passed as null
, this is not correct.
You can create a method at top level which returns generic link and others can override it.
See how we do it in OracleErrorDetailsProvider
, MysqlErrorDetailsProvider
, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use this link as default one "https://en.wikipedia.org/wiki/SQLSTATE"
as it is being used by DBErrorDetailsProvider
. similar comment for AbstractDBSource
& AbstractDBSink
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the default value for external documentation link in the above mentioned classes. 35537d7
String errorMessageWithDetails = String.format("Error occurred while trying to" + | ||
" get schema from database." + "Error message: '%s'. Error code: '%s'. SQLState: '%s'", e.getMessage(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not always fetching schema from database in above calls.
/** | ||
* Get the {@link ErrorCategory} for the given SQL state. | ||
* Implements generic error categories based on the SQL state. | ||
* See <a href="https://en.wikipedia.org/wiki/SQLSTATE">SQLSTATE</a> for more information. | ||
* Override this method to provide custom error categories based on the SQL state. | ||
* | ||
* @param sqlState The SQL state. | ||
* @return The {@link ErrorCategory} for the given SQL state. | ||
*/ | ||
private static ErrorCategory getErrorCategoryFromSqlState(String sqlState) { | ||
if (!Strings.isNullOrEmpty(sqlState) && sqlState.length() >= 2 && | ||
ERROR_CODE_TO_ERROR_CATEGORY.containsKey(sqlState.substring(0, 2))) { | ||
return ERROR_CODE_TO_ERROR_CATEGORY.get(sqlState.substring(0, 2)); | ||
} | ||
return new ErrorCategory(ErrorCategory.ErrorCategoryEnum.PLUGIN); | ||
} | ||
|
||
/** | ||
* Get the {@link ErrorType} for the given error code and SQL state. | ||
* Override this method to provide custom error types based on the error code and SQL state. | ||
* | ||
* @param errorCode The error code. | ||
* @param sqlState The SQL state. | ||
* @return The {@link ErrorType} for the given error code and SQL state. | ||
*/ | ||
private static ErrorType getErrorTypeFromErrorCodeAndSqlState(int errorCode, String sqlState) { | ||
if (!Strings.isNullOrEmpty(sqlState) && sqlState.length() >= 2 && | ||
ERROR_CODE_TO_ERROR_TYPE.containsKey(sqlState.substring(0, 2))) { | ||
return ERROR_CODE_TO_ERROR_TYPE.get(sqlState.substring(0, 2)); | ||
} | ||
return ErrorType.UNKNOWN; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said before this is not needed, we could just create an object of DBErrorDetailsProvider
and use DBErrorDetailsProvider#getProgramFailureException
to do all of this stuff.
PLUGIN-1823
Add Failsafe Retry poilcy to all the places in the database-plugins where
SQLTransientException
could be thrown.Added three new properties (hidden from UI)