fix: fix MySQL stale connection error (#24429)
## Description - Add changes to address the`StaleConnection` exception caused by MySQL plugin. - Update MySQL driver version. - Other refactor changes not related to the main issue: - Explicit empty constructor definition is replaced with Lombok annotation for all error messages class. - A base class is created for plugin error messages class to store all common error messages. - Fix Indentation.
This commit is contained in:
parent
4ce12be224
commit
98a509227f
|
|
@ -11,4 +11,24 @@ public interface PluginConstants {
|
|||
String AMAZON_S3_PLUGIN = "amazons3-plugin";
|
||||
String GOOGLE_SHEETS_PLUGIN = "google-sheets-plugin";
|
||||
}
|
||||
|
||||
interface PluginName {
|
||||
public static final String S3_PLUGIN_NAME = "S3";
|
||||
public static final String ARANGO_PLUGIN_NAME = "Arango";
|
||||
public static final String DYNAMO_PLUGIN_NAME = "Dynamo";
|
||||
public static final String ELASTIC_SEARCH_PLUGIN_NAME = "ElasticSearch";
|
||||
public static final String FIRESTORE_PLUGIN_NAME = "Firestore";
|
||||
public static final String GOOGLE_SHEETS_PLUGIN_NAME = "GoogleSheets";
|
||||
public static final String GRAPHQL_PLUGIN_NAME = "Graphql";
|
||||
public static final String MSSQL_PLUGIN_NAME = "Mssql";
|
||||
public static final String MYSQL_PLUGIN_NAME = "Mysql";
|
||||
public static final String ORACLE_PLUGIN_NAME = "Oracle";
|
||||
public static final String POSTGRES_PLUGIN_NAME = "Postgres";
|
||||
public static final String REDIS_PLUGIN_NAME = "Redis";
|
||||
public static final String REDSHIFT_PLUGIN_NAME = "Redshift";
|
||||
public static final String REST_API_PLUGIN_NAME = "RestApi";
|
||||
public static final String SAAS_PLUGIN_NAME = "Saas";
|
||||
public static final String SMTP_PLUGIN_NAME = "Smtp";
|
||||
public static final String SNOWFLAKE_PLUGIN_NAME = "Snowflake";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.appsmith.external.exceptions.pluginExceptions;
|
||||
|
||||
public abstract class BasePluginErrorMessages {
|
||||
public static final String CONNECTION_INVALID_ERROR_MSG = "Connection object is invalid.";
|
||||
public static final String CONNECTION_NULL_ERROR_MSG = "Connection object is null.";
|
||||
public static final String CONNECTION_CLOSED_ERROR_MSG = "Connection object is closed.";
|
||||
public static final String CONNECTION_POOL_NULL_ERROR_MSG = "Connection pool is null.";
|
||||
public static final String CONNECTION_POOL_CLOSED_ERROR_MSG = "Connection pool is closed.";
|
||||
public static final String CONNECTION_POOL_NOT_RUNNING_ERROR_MSG = "Connection pool is not running.";
|
||||
public static final String UNKNOWN_CONNECTION_ERROR_MSG = "Unknown connection error. Please reach out to Appsmith " +
|
||||
"customer support to resolve this.";
|
||||
public static final String JDBC_DRIVER_LOADING_ERROR_MSG = "Error loading JDBC Driver class.";
|
||||
}
|
||||
|
|
@ -1,14 +1,22 @@
|
|||
package com.appsmith.external.exceptions.pluginExceptions;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class StaleConnectionException extends RuntimeException {
|
||||
public StaleConnectionException() {
|
||||
}
|
||||
String message = "";
|
||||
|
||||
public StaleConnectionException(String message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public StaleConnectionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
|||
import com.appsmith.external.models.Condition;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
|
|||
|
|
@ -71,10 +71,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY;
|
||||
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_PATH;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_NULL_ERROR_MSG;
|
||||
import static com.appsmith.external.helpers.PluginUtils.OBJECT_TYPE;
|
||||
import static com.appsmith.external.helpers.PluginUtils.STRING_TYPE;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getDataValueSafelyFromFormData;
|
||||
|
|
@ -459,7 +459,7 @@ public class AmazonS3Plugin extends BasePlugin {
|
|||
* - If connection object is null, then assume stale connection.
|
||||
*/
|
||||
if (connection == null) {
|
||||
return Mono.error(new StaleConnectionException());
|
||||
return Mono.error(new StaleConnectionException(CONNECTION_NULL_ERROR_MSG));
|
||||
}
|
||||
|
||||
if (actionConfiguration == null) {
|
||||
|
|
@ -787,7 +787,7 @@ public class AmazonS3Plugin extends BasePlugin {
|
|||
}
|
||||
return Mono.just(actionResult);
|
||||
})
|
||||
.onErrorMap(IllegalStateException.class, error -> new StaleConnectionException())
|
||||
.onErrorMap(IllegalStateException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.flatMap(obj -> obj)
|
||||
.flatMap(result -> {
|
||||
ActionExecutionResult actionExecutionResult = new ActionExecutionResult();
|
||||
|
|
@ -986,8 +986,8 @@ public class AmazonS3Plugin extends BasePlugin {
|
|||
S3ErrorMessages.LIST_OF_BUCKET_FETCHING_ERROR_MSG,
|
||||
e.getMessage()
|
||||
);
|
||||
} catch (IllegalStateException s) {
|
||||
throw new StaleConnectionException();
|
||||
} catch (IllegalStateException e) {
|
||||
throw new StaleConnectionException(e.getMessage());
|
||||
}
|
||||
|
||||
return new DatasourceStructure(tableList);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class S3ErrorMessages {
|
||||
private S3ErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class S3ErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String FILE_CONTENT_FETCHING_ERROR_MSG = "Appsmith server has encountered an unexpected error when fetching file " +
|
||||
"content from AWS S3 server. Please reach out to Appsmith customer support to resolve this.";
|
||||
|
||||
|
|
@ -99,6 +101,4 @@ public class S3ErrorMessages {
|
|||
public static final String DS_MANDATORY_PARAMETER_ENDPOINT_URL_MISSING_ERROR_MSG = "Required parameter 'Endpoint URL' is empty. Did you forget to edit the 'Endpoint" +
|
||||
" URL' field in the datasource creation form ? You need to fill it with " +
|
||||
"the endpoint URL of your S3 instance.";
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import com.arangodb.model.CollectionsReadOptions;
|
|||
import com.external.plugins.exceptions.ArangoDBErrorMessages;
|
||||
import com.external.plugins.exceptions.ArangoDBPluginError;
|
||||
import com.external.utils.ArangoDBErrorUtils;
|
||||
import java.util.Properties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.pf4j.Extension;
|
||||
|
|
@ -51,6 +50,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY;
|
||||
import static com.appsmith.external.helpers.PluginUtils.MATCH_QUOTED_WORDS_REGEX;
|
||||
import static com.external.plugins.exceptions.ArangoDBErrorMessages.CONNECTION_INVALID_ERROR_MSG;
|
||||
import static com.external.plugins.exceptions.ArangoDBErrorMessages.DS_HOSTNAME_MISSING_OR_INVALID_ERROR_MSG;
|
||||
import static com.external.utils.SSLUtils.isCaCertificateAvailable;
|
||||
import static com.external.utils.SSLUtils.setSSLContext;
|
||||
|
|
@ -84,7 +84,7 @@ public class ArangoDBPlugin extends BasePlugin {
|
|||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
if (!isConnectionValid(db)) {
|
||||
return Mono.error(new StaleConnectionException());
|
||||
return Mono.error(new StaleConnectionException(CONNECTION_INVALID_ERROR_MSG));
|
||||
}
|
||||
|
||||
String query = actionConfiguration.getBody();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class ArangoDBErrorMessages {
|
||||
private ArangoDBErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class ArangoDBErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MISSING_QUERY_ERROR_MSG = "Missing required parameter: Query.";
|
||||
|
||||
public static final String QUERY_EXECUTION_FAILED_ERROR_MSG = "Your query failed to execute. Please check more information in the error details.";
|
||||
|
|
@ -20,7 +22,6 @@ public class ArangoDBErrorMessages {
|
|||
public static final String GET_STRUCTURE_ERROR_MSG = "Appsmith server has failed to fetch list of collections from database. Please check " +
|
||||
"if the database credentials are valid and/or you have the required permissions.";
|
||||
|
||||
|
||||
/*
|
||||
************************************************************************************************************************************************
|
||||
Error messages related to validation of datasource.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class DynamoErrorMessages {
|
||||
private DynamoErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class DynamoErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MISSING_ACTION_NAME_ERROR_MSG = "Missing action name (like `ListTables`, `GetItem` etc.).";
|
||||
|
||||
public static final String UNKNOWN_ACTION_NAME_ERROR_MSG = "Unknown action: `%s`. Note that action names are case-sensitive.";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class ElasticSearchErrorMessages {
|
||||
private ElasticSearchErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class ElasticSearchErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String ARRAY_TO_ND_JSON_ARRAY_CONVERSION_ERROR_MSG = "Error occurred while converting array to ND-JSON";
|
||||
|
||||
public static final String QUERY_EXECUTION_FAILED_ERROR_MSG = "Error occurred while executing Elasticsearch query.";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class FirestoreErrorMessages {
|
||||
private FirestoreErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class FirestoreErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MANDATORY_PARAM_COMMAND_MISSING_ERROR_MSG = "Mandatory parameter 'Command' is missing. Did you forget to select one of the commands" +
|
||||
" from the Command dropdown ?";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package com.external.constants;
|
||||
|
||||
public class ErrorMessages {
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class ErrorMessages extends BasePluginErrorMessages {
|
||||
|
||||
public static final String EMPTY_ROW_OBJECT_MESSAGE = "Row object(s) cannot be empty.";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class GraphQLErrorMessages {
|
||||
private GraphQLErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class GraphQLErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String URI_SYNTAX_WRONG_ERROR_MSG = "URI is invalid. Please rectify the URI and try again.";
|
||||
public static final String INVALID_CONTENT_TYPE_ERROR_MSG = "Invalid value for Content-Type.";
|
||||
public static final String NO_HTTP_METHOD_ERROR_MSG = "HTTPMethod must be set.";
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ import static com.external.plugins.constants.FieldName.SMART_SUBSTITUTION;
|
|||
import static com.external.plugins.constants.FieldName.SUCCESS;
|
||||
import static com.external.plugins.constants.FieldName.UPDATE_OPERATION;
|
||||
import static com.external.plugins.constants.FieldName.UPDATE_QUERY;
|
||||
import static com.external.plugins.exceptions.MongoPluginErrorMessages.MONGO_CLIENT_NULL_ERROR_MSG;
|
||||
import static com.external.plugins.utils.DatasourceUtils.KEY_PASSWORD;
|
||||
import static com.external.plugins.utils.DatasourceUtils.KEY_URI_DEFAULT_DBNAME;
|
||||
import static com.external.plugins.utils.DatasourceUtils.KEY_USERNAME;
|
||||
|
|
@ -134,7 +135,6 @@ import static com.external.plugins.utils.MongoPluginUtils.isRawCommand;
|
|||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.apache.logging.log4j.util.Strings.isBlank;
|
||||
import static org.apache.logging.log4j.util.Strings.isEmpty;
|
||||
|
||||
public class MongoPlugin extends BasePlugin {
|
||||
|
||||
|
|
@ -310,7 +310,7 @@ public class MongoPlugin extends BasePlugin {
|
|||
|
||||
if (mongoClient == null) {
|
||||
log.info("Encountered null connection in MongoDB plugin. Reporting back.");
|
||||
throw new StaleConnectionException();
|
||||
throw new StaleConnectionException(MONGO_CLIENT_NULL_ERROR_MSG);
|
||||
}
|
||||
Mono<Document> mongoOutputMono;
|
||||
ActionExecutionResult result = new ActionExecutionResult();
|
||||
|
|
@ -354,13 +354,13 @@ public class MongoPlugin extends BasePlugin {
|
|||
*/
|
||||
.onErrorMap(
|
||||
IllegalStateException.class,
|
||||
error -> new StaleConnectionException()
|
||||
error -> new StaleConnectionException(error.getMessage())
|
||||
)
|
||||
// This is an experimental fix to handle the scenario where after a period of inactivity, the mongo
|
||||
// database drops the connection which makes the client throw the following exception.
|
||||
.onErrorMap(
|
||||
MongoSocketWriteException.class,
|
||||
error -> new StaleConnectionException()
|
||||
error -> new StaleConnectionException(error.getMessage())
|
||||
)
|
||||
.flatMap(mongoOutput -> {
|
||||
try {
|
||||
|
|
@ -917,13 +917,13 @@ public class MongoPlugin extends BasePlugin {
|
|||
*/
|
||||
.onErrorMap(
|
||||
IllegalStateException.class,
|
||||
error -> new StaleConnectionException()
|
||||
error -> new StaleConnectionException(error.getMessage())
|
||||
)
|
||||
// This is an experimental fix to handle the scenario where after a period of inactivity, the mongo
|
||||
// database drops the connection which makes the client throw the following exception.
|
||||
.onErrorMap(
|
||||
MongoSocketWriteException.class,
|
||||
error -> new StaleConnectionException()
|
||||
error -> new StaleConnectionException(error.getMessage())
|
||||
)
|
||||
.onErrorMap(
|
||||
MongoCommandException.class,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ public class MongoPluginErrorMessages {
|
|||
|
||||
public static final String QUERY_INVALID_ERROR_MSG = "Your query is invalid";
|
||||
|
||||
public static final String MONGO_CLIENT_NULL_ERROR_MSG = "Mongo client object is null.";
|
||||
|
||||
/*
|
||||
************************************************************************************************************************************************
|
||||
Error messages related to validation of datasource.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>5.0.1</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
|
|
|
|||
|
|
@ -66,10 +66,13 @@ import java.util.Set;
|
|||
import java.util.stream.IntStream;
|
||||
|
||||
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY;
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.MSSQL_PLUGIN_NAME;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getIdenticalColumns;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getPSParamLabel;
|
||||
import static com.appsmith.external.helpers.SmartSubstitutionHelper.replaceQuestionMarkWithDollarIndex;
|
||||
import static com.external.plugins.utils.MssqlDatasourceUtils.getConnectionFromConnectionPool;
|
||||
import static com.external.plugins.exceptions.MssqlErrorMessages.CONNECTION_CLOSED_ERROR_MSG;
|
||||
import static com.external.plugins.exceptions.MssqlErrorMessages.CONNECTION_INVALID_ERROR_MSG;
|
||||
import static com.external.plugins.exceptions.MssqlErrorMessages.CONNECTION_NULL_ERROR_MSG;
|
||||
import static com.external.plugins.utils.MssqlDatasourceUtils.logHikariCPStatus;
|
||||
import static com.external.plugins.utils.MssqlExecuteUtils.closeConnectionPostExecution;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
|
|
@ -90,6 +93,8 @@ public class MssqlPlugin extends BasePlugin {
|
|||
|
||||
private static final long MS_SQL_DEFAULT_PORT = 1433L;
|
||||
|
||||
public static final MssqlDatasourceUtils mssqlDatasourceUtils = new MssqlDatasourceUtils();
|
||||
|
||||
public MssqlPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
|
@ -190,19 +195,36 @@ public class MssqlPlugin extends BasePlugin {
|
|||
final List<String> columnsList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
sqlConnectionFromPool = getConnectionFromConnectionPool(hikariDSConnection);
|
||||
sqlConnectionFromPool =
|
||||
mssqlDatasourceUtils.getConnectionFromHikariConnectionPool(hikariDSConnection,
|
||||
MSSQL_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
// The function can throw either StaleConnectionException or SQLException. The underlying hikari
|
||||
// library throws SQLException in case the pool is closed or there is an issue initializing
|
||||
// the connection pool which can also be translated in our world to StaleConnectionException
|
||||
// and should then trigger the destruction and recreation of the pool.
|
||||
return Mono.error(e instanceof StaleConnectionException ? e : new StaleConnectionException());
|
||||
return Mono.error(e instanceof StaleConnectionException ? e :
|
||||
new StaleConnectionException(e.getMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
if (sqlConnectionFromPool == null || sqlConnectionFromPool.isClosed() || !sqlConnectionFromPool.isValid(VALIDITY_CHECK_TIMEOUT)) {
|
||||
log.info("Encountered stale connection in MsSQL plugin. Reporting back.");
|
||||
return Mono.error(new StaleConnectionException());
|
||||
|
||||
if (sqlConnectionFromPool == null) {
|
||||
return Mono.error(new StaleConnectionException(CONNECTION_NULL_ERROR_MSG));
|
||||
}
|
||||
else if (sqlConnectionFromPool.isClosed()) {
|
||||
return Mono.error(new StaleConnectionException(CONNECTION_CLOSED_ERROR_MSG));
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Not adding explicit `!sqlConnectionFromPool.isValid(VALIDITY_CHECK_TIMEOUT)`
|
||||
* check here because this check may take few seconds to complete hence adding
|
||||
* extra time delay.
|
||||
*/
|
||||
return Mono.error(new StaleConnectionException(CONNECTION_INVALID_ERROR_MSG));
|
||||
}
|
||||
}
|
||||
} catch (SQLException error) {
|
||||
// This exception is thrown only when the timeout to `isValid` is negative. Since, that's not the case,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class MssqlErrorMessages {
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
private MssqlErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class MssqlErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MISSING_QUERY_ERROR_MSG = "Missing required parameter: Query.";
|
||||
|
||||
public static final String QUERY_EXECUTION_FAILED_ERROR_MSG = "Your query failed to execute. Please check more information in the error details.";
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import com.appsmith.external.models.DatasourceStructure;
|
|||
import com.external.plugins.exceptions.MssqlErrorMessages;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import com.zaxxer.hikari.HikariPoolMXBean;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -24,11 +22,16 @@ import java.util.LinkedHashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.MSSQL_PLUGIN_NAME;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_CLOSED_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NOT_RUNNING_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NULL_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.UNKNOWN_CONNECTION_ERROR_MSG;
|
||||
import static com.appsmith.external.helpers.PluginUtils.safelyCloseSingleConnectionFromHikariCP;
|
||||
import static com.external.plugins.MssqlPlugin.MssqlPluginExecutor.scheduler;
|
||||
import static com.external.plugins.MssqlPlugin.mssqlDatasourceUtils;
|
||||
|
||||
@Slf4j
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class MssqlDatasourceUtils {
|
||||
|
||||
public static final String PRIMARY_KEY_INDICATOR = "PRIMARY KEY";
|
||||
|
|
@ -94,13 +97,15 @@ public class MssqlDatasourceUtils {
|
|||
return Mono.fromSupplier(() -> {
|
||||
Connection connectionFromPool;
|
||||
try {
|
||||
connectionFromPool = getConnectionFromConnectionPool(connection);
|
||||
connectionFromPool = mssqlDatasourceUtils.getConnectionFromHikariConnectionPool(connection,
|
||||
MSSQL_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
// The function can throw either StaleConnectionException or SQLException. The
|
||||
// underlying hikari library throws SQLException in case the pool is closed or there is an issue
|
||||
// initializing the connection pool which can also be translated in our world to
|
||||
// StaleConnectionException and should then trigger the destruction and recreation of the pool.
|
||||
return Mono.error(e instanceof StaleConnectionException ? e : new StaleConnectionException());
|
||||
return Mono.error(e instanceof StaleConnectionException ? e :
|
||||
new StaleConnectionException(e.getMessage()));
|
||||
}
|
||||
|
||||
logHikariCPStatus("Before getting Mssql DB schema", connection);
|
||||
|
|
@ -137,21 +142,9 @@ public class MssqlDatasourceUtils {
|
|||
* First checks if the connection pool is still valid. If yes, we fetch a connection from the pool and return
|
||||
* In case a connection is not available in the pool, SQL Exception is thrown
|
||||
*
|
||||
* @param hikariDSConnectionPool
|
||||
* @param connectionPool
|
||||
* @return SQL Connection
|
||||
*/
|
||||
public static Connection getConnectionFromConnectionPool(HikariDataSource hikariDSConnectionPool) throws SQLException {
|
||||
|
||||
if (hikariDSConnectionPool == null || hikariDSConnectionPool.isClosed() || !hikariDSConnectionPool.isRunning()) {
|
||||
log.debug("Encountered stale connection pool in SQL Server plugin. Reporting back.");
|
||||
throw new StaleConnectionException();
|
||||
}
|
||||
|
||||
Connection sqlDataSourceConnection = hikariDSConnectionPool.getConnection();
|
||||
|
||||
return sqlDataSourceConnection;
|
||||
}
|
||||
|
||||
public static void logHikariCPStatus(String logPrefix, HikariDataSource connectionPool) {
|
||||
HikariPoolMXBean poolProxy = connectionPool.getHikariPoolMXBean();
|
||||
int idleConnections = poolProxy.getIdleConnections();
|
||||
|
|
@ -305,4 +298,36 @@ public class MssqlDatasourceUtils {
|
|||
return MessageFormat.format("{0}={1}", columnNameToSampleColumnDataMap.keySet().stream().findFirst().orElse(
|
||||
"id"), columnNameToSampleColumnDataMap.values().stream().findFirst().orElse("'uid'"));
|
||||
}
|
||||
|
||||
public void checkHikariCPConnectionPoolValidity(HikariDataSource connectionPool, String pluginName) throws StaleConnectionException {
|
||||
if (connectionPool == null || connectionPool.isClosed() || !connectionPool.isRunning()) {
|
||||
String printMessage = MessageFormat.format(Thread.currentThread().getName() +
|
||||
": Encountered stale connection pool in {0} plugin. Reporting back.", pluginName);
|
||||
System.out.println(printMessage);
|
||||
|
||||
if (connectionPool == null) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NULL_ERROR_MSG);
|
||||
}
|
||||
else if (connectionPool.isClosed()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_CLOSED_ERROR_MSG);
|
||||
}
|
||||
else if (!connectionPool.isRunning()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NOT_RUNNING_ERROR_MSG);
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Ideally, code flow is never expected to reach here. However, this section has been added to catch
|
||||
* those cases wherein a developer updates the parent if condition but does not update the nested
|
||||
* if else conditions.
|
||||
*/
|
||||
throw new StaleConnectionException(UNKNOWN_CONNECTION_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Connection getConnectionFromHikariConnectionPool(HikariDataSource connectionPool,
|
||||
String pluginName) throws SQLException {
|
||||
checkHikariCPConnectionPoolValidity(connectionPool, pluginName);
|
||||
return connectionPool.getConnection();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.appsmith.external.models.DBAuth;
|
|||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import com.appsmith.external.models.SSLDetails;
|
||||
import com.external.plugins.utils.MssqlDatasourceUtils;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.testcontainers.containers.MSSQLServerContainer;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
|
@ -12,13 +13,15 @@ import java.sql.SQLException;
|
|||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
import static com.external.plugins.utils.MssqlDatasourceUtils.getConnectionFromConnectionPool;
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.MSSQL_PLUGIN_NAME;
|
||||
import static com.external.plugins.utils.MssqlExecuteUtils.closeConnectionPostExecution;
|
||||
|
||||
public class MssqlTestDBContainerManager {
|
||||
|
||||
static MssqlPlugin.MssqlPluginExecutor mssqlPluginExecutor = new MssqlPlugin.MssqlPluginExecutor();
|
||||
|
||||
public static MssqlDatasourceUtils mssqlDatasourceUtils = new MssqlDatasourceUtils();
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static MSSQLServerContainer getMssqlDBForTest() {
|
||||
return new MSSQLServerContainer<>(
|
||||
|
|
@ -56,7 +59,8 @@ public class MssqlTestDBContainerManager {
|
|||
}
|
||||
|
||||
static void runSQLQueryOnMssqlTestDB(String sqlQuery, HikariDataSource sharedConnectionPool) throws SQLException {
|
||||
java.sql.Connection connectionFromPool = getConnectionFromConnectionPool(sharedConnectionPool);
|
||||
java.sql.Connection connectionFromPool =
|
||||
mssqlDatasourceUtils.getConnectionFromHikariConnectionPool(sharedConnectionPool, MSSQL_PLUGIN_NAME);
|
||||
Statement statement = connectionFromPool.createStatement();
|
||||
statement.execute(sqlQuery);
|
||||
closeConnectionPostExecution(null, statement, null, connectionFromPool);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<dependency>
|
||||
<groupId>org.mariadb</groupId>
|
||||
<artifactId>r2dbc-mariadb</artifactId>
|
||||
<version>1.1.3</version>
|
||||
<version>1.1.4</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import static com.appsmith.external.helpers.PluginUtils.MATCH_QUOTED_WORDS_REGEX
|
|||
import static com.appsmith.external.helpers.PluginUtils.getIdenticalColumns;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getPSParamLabel;
|
||||
import static com.appsmith.external.helpers.SmartSubstitutionHelper.replaceQuestionMarkWithDollarIndex;
|
||||
import static com.external.plugins.exceptions.MySQLErrorMessages.CONNECTION_VALIDITY_CHECK_FAILED_ERROR_MSG;
|
||||
import static com.external.utils.MySqlDatasourceUtils.getNewConnectionPool;
|
||||
import static com.external.utils.MySqlGetStructureUtils.getKeyInfo;
|
||||
import static com.external.utils.MySqlGetStructureUtils.getTableInfo;
|
||||
|
|
@ -256,9 +257,9 @@ public class MySqlPlugin extends BasePlugin {
|
|||
connectionPool.create(),
|
||||
connection -> {
|
||||
// TODO: add JUnit TC for the `connection.validate` check. Not sure how to do it at the moment.
|
||||
Flux<Result> resultFlux = Mono.from(connection.validate(ValidationDepth.REMOTE))
|
||||
Flux<Result> resultFlux = Mono.from(connection.validate(ValidationDepth.LOCAL))
|
||||
.timeout(Duration.ofSeconds(VALIDATION_CHECK_TIMEOUT))
|
||||
.onErrorMap(TimeoutException.class, error -> new StaleConnectionException())
|
||||
.onErrorMap(TimeoutException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.flatMapMany(isValid -> {
|
||||
if (isValid) {
|
||||
return createAndExecuteQueryFromConnection(finalQuery,
|
||||
|
|
@ -269,7 +270,7 @@ public class MySqlPlugin extends BasePlugin {
|
|||
requestData,
|
||||
psParams);
|
||||
}
|
||||
return Flux.error(new StaleConnectionException());
|
||||
return Flux.error(new StaleConnectionException(CONNECTION_VALIDITY_CHECK_FAILED_ERROR_MSG));
|
||||
});
|
||||
|
||||
Mono<List<Map<String, Object>>> resultMono;
|
||||
|
|
@ -348,9 +349,10 @@ public class MySqlPlugin extends BasePlugin {
|
|||
},
|
||||
Connection::close
|
||||
)
|
||||
.onErrorMap(TimeoutException.class, error -> new StaleConnectionException())
|
||||
.onErrorMap(PoolShutdownException.class, error -> new StaleConnectionException())
|
||||
.onErrorMap(R2dbcNonTransientResourceException.class, error -> new StaleConnectionException())
|
||||
.onErrorMap(TimeoutException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.onErrorMap(PoolShutdownException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.onErrorMap(R2dbcNonTransientResourceException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.onErrorMap(IllegalStateException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
|
|
@ -579,60 +581,58 @@ public class MySqlPlugin extends BasePlugin {
|
|||
|
||||
return Mono.usingWhen(
|
||||
connectionPool.create(),
|
||||
connection -> {
|
||||
return Mono.from(connection.validate(ValidationDepth.REMOTE))
|
||||
.timeout(Duration.ofSeconds(VALIDATION_CHECK_TIMEOUT))
|
||||
.onErrorMap(TimeoutException.class, error -> new StaleConnectionException())
|
||||
.flatMapMany(isValid -> {
|
||||
if (isValid) {
|
||||
return connection.createStatement(COLUMNS_QUERY).execute();
|
||||
} else {
|
||||
return Flux.error(new StaleConnectionException());
|
||||
}
|
||||
})
|
||||
.flatMap(result -> {
|
||||
return result.map((row, meta) -> {
|
||||
getTableInfo(row, meta, tablesByName);
|
||||
connection -> Mono.from(connection.validate(ValidationDepth.REMOTE))
|
||||
.timeout(Duration.ofSeconds(VALIDATION_CHECK_TIMEOUT))
|
||||
.onErrorMap(TimeoutException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.flatMapMany(isValid -> {
|
||||
if (isValid) {
|
||||
return connection.createStatement(COLUMNS_QUERY).execute();
|
||||
} else {
|
||||
return Flux.error(new StaleConnectionException(CONNECTION_VALIDITY_CHECK_FAILED_ERROR_MSG));
|
||||
}
|
||||
})
|
||||
.flatMap(result -> {
|
||||
return result.map((row, meta) -> {
|
||||
getTableInfo(row, meta, tablesByName);
|
||||
|
||||
return result;
|
||||
});
|
||||
})
|
||||
.collectList()
|
||||
.thenMany(Flux.from(connection.createStatement(KEYS_QUERY).execute()))
|
||||
.flatMap(result -> {
|
||||
return result.map((row, meta) -> {
|
||||
getKeyInfo(row, meta, tablesByName, keyRegistry);
|
||||
|
||||
return result;
|
||||
});
|
||||
})
|
||||
.collectList()
|
||||
.map(list -> {
|
||||
/* Get templates for each table and put those in. */
|
||||
getTemplates(tablesByName);
|
||||
structure.setTables(new ArrayList<>(tablesByName.values()));
|
||||
for (DatasourceStructure.Table table : structure.getTables()) {
|
||||
table.getKeys().sort(Comparator.naturalOrder());
|
||||
}
|
||||
|
||||
return structure;
|
||||
})
|
||||
.onErrorMap(e -> {
|
||||
if (!(e instanceof AppsmithPluginException) && !(e instanceof StaleConnectionException)) {
|
||||
return new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_GET_STRUCTURE_ERROR,
|
||||
MySQLErrorMessages.GET_STRUCTURE_ERROR_MSG,
|
||||
e.getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return e;
|
||||
return result;
|
||||
});
|
||||
},
|
||||
})
|
||||
.collectList()
|
||||
.thenMany(Flux.from(connection.createStatement(KEYS_QUERY).execute()))
|
||||
.flatMap(result -> {
|
||||
return result.map((row, meta) -> {
|
||||
getKeyInfo(row, meta, tablesByName, keyRegistry);
|
||||
|
||||
return result;
|
||||
});
|
||||
})
|
||||
.collectList()
|
||||
.map(list -> {
|
||||
/* Get templates for each table and put those in. */
|
||||
getTemplates(tablesByName);
|
||||
structure.setTables(new ArrayList<>(tablesByName.values()));
|
||||
for (DatasourceStructure.Table table : structure.getTables()) {
|
||||
table.getKeys().sort(Comparator.naturalOrder());
|
||||
}
|
||||
|
||||
return structure;
|
||||
})
|
||||
.onErrorMap(e -> {
|
||||
if (!(e instanceof AppsmithPluginException) && !(e instanceof StaleConnectionException)) {
|
||||
return new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_GET_STRUCTURE_ERROR,
|
||||
MySQLErrorMessages.GET_STRUCTURE_ERROR_MSG,
|
||||
e.getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return e;
|
||||
}),
|
||||
Connection::close
|
||||
)
|
||||
.onErrorMap(TimeoutException.class, error -> new StaleConnectionException())
|
||||
.onErrorMap(PoolShutdownException.class, error -> new StaleConnectionException())
|
||||
.onErrorMap(TimeoutException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.onErrorMap(PoolShutdownException.class, error -> new StaleConnectionException(error.getMessage()))
|
||||
.subscribeOn(scheduler);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class MySQLErrorMessages {
|
||||
private MySQLErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class MySQLErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MISSING_PARAMETER_QUERY_ERROR_MSG = "Missing required parameter: Query.";
|
||||
|
||||
public static final String IS_KEYWORD_NOT_SUPPORTED_IN_PS_ERROR_MSG = "Appsmith currently does not support the IS keyword with the prepared statement " +
|
||||
|
|
@ -32,7 +34,6 @@ public class MySQLErrorMessages {
|
|||
public static final String DS_MISSING_DATABASE_NAME_ERROR_MSG = "Missing database name.";
|
||||
public static final String DS_SSL_CONFIGURATION_FETCHING_FAILED_ERROR_MSG = "Appsmith server has failed to fetch SSL configuration from datasource configuration form. " +
|
||||
"Please reach out to Appsmith customer support to resolve this.";
|
||||
|
||||
|
||||
|
||||
public static final String CONNECTION_VALIDITY_CHECK_FAILED_ERROR_MSG = "Connection obtained from connection pool" +
|
||||
" is invalid.";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.appsmith.external.models.Endpoint;
|
|||
import com.appsmith.external.models.Property;
|
||||
import com.appsmith.external.models.SSLDetails;
|
||||
import com.external.plugins.exceptions.MySQLErrorMessages;
|
||||
import com.external.plugins.exceptions.MySQLPluginError;
|
||||
import io.r2dbc.pool.ConnectionPool;
|
||||
import io.r2dbc.pool.ConnectionPoolConfiguration;
|
||||
import io.r2dbc.spi.ConnectionFactoryOptions;
|
||||
|
|
@ -25,14 +24,40 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.r2dbc.pool.PoolingConnectionFactoryProvider.MAX_SIZE;
|
||||
import static io.r2dbc.spi.ConnectionFactoryOptions.SSL;
|
||||
|
||||
public class MySqlDatasourceUtils {
|
||||
|
||||
public static int MAX_CONNECTION_POOL_SIZE = 5;
|
||||
|
||||
private static final Duration MAX_IDLE_TIME = Duration.ofMinutes(10);
|
||||
/**
|
||||
* 1 sec is the recommended value as shown in the example here:
|
||||
* https://mariadb.com/docs/xpand/connect/programming-languages/java-r2dbc/native/connection-pools/
|
||||
*
|
||||
* Current understanding is that the issue mentioned in #17324 is because of at least one of the connections
|
||||
* malfunctioning and causing the reactor thread pool / scheduler to get stuck and not schedule new tasks.
|
||||
* Setting max idle time value to 1 sec could also be seen as a precaution move to make sure that we don't land
|
||||
* into a situation where an idle thread can malfunction.
|
||||
*/
|
||||
private static final Duration MAX_IDLE_TIME = Duration.ofSeconds(1);
|
||||
|
||||
/**
|
||||
* Current understanding is that the issue mentioned in #17324 is because of at least one of the connections
|
||||
* malfunctioning and causing the reactor thread pool / scheduler to get stuck and not schedule new tasks.
|
||||
* Setting max lifetime value to 5 min is a precaution move to make sure that we don't land into a situation
|
||||
* where an older connection can malfunction.
|
||||
* To understand what this config means please check here: https://github.com/r2dbc/r2dbc-pool
|
||||
*/
|
||||
private static final Duration MAX_LIFE_TIME = Duration.ofMinutes(5);
|
||||
|
||||
/**
|
||||
* Current understanding is that the issue mentioned in #17324 is because of at least one of the connections
|
||||
* malfunctioning and causing the reactor thread pool / scheduler to get stuck and not schedule new tasks.
|
||||
* Setting eviction time value to 5 min is a precaution move to make sure that we don't land into a situation
|
||||
* where an older connection can malfunction.
|
||||
* To understand what this config means please check here: https://github.com/r2dbc/r2dbc-pool
|
||||
*/
|
||||
public static final Duration BACKGROUND_EVICTION_TIME = Duration.ofMinutes(5);
|
||||
|
||||
public static ConnectionFactoryOptions.Builder getBuilder(DatasourceConfiguration datasourceConfiguration) {
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
|
|
@ -188,7 +213,10 @@ public class MySqlDatasourceUtils {
|
|||
ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory)
|
||||
.maxIdleTime(MAX_IDLE_TIME)
|
||||
.maxSize(MAX_CONNECTION_POOL_SIZE)
|
||||
.backgroundEvictionInterval(BACKGROUND_EVICTION_TIME)
|
||||
.maxLifeTime(MAX_LIFE_TIME)
|
||||
.build();
|
||||
|
||||
return new ConnectionPool(configuration);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,117 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.dtos.ExecuteActionDTO;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.external.utils.MySqlDatasourceUtils;
|
||||
import io.r2dbc.pool.ConnectionPool;
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.R2dbcNonTransientResourceException;
|
||||
import io.r2dbc.spi.ValidationDepth;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.pool.PoolShutdownException;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static com.external.plugins.exceptions.MySQLErrorMessages.CONNECTION_VALIDITY_CHECK_FAILED_ERROR_MSG;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MySqlStaleConnectionErrorMessageTest {
|
||||
static MySqlPlugin.MySqlPluginExecutor pluginExecutor = new MySqlPlugin.MySqlPluginExecutor();
|
||||
static MySqlDatasourceUtils mysqlDatasourceUtils = new MySqlDatasourceUtils();
|
||||
|
||||
@Test
|
||||
public void testStaleConnectionExceptionReturnsUpstreamErrorOnTimeoutError() throws TimeoutException {
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setBody("select 1;");
|
||||
ConnectionPool mockConnectionPool = mock(ConnectionPool.class);
|
||||
String expectedErrorMessage = "Timeout exception from MockConnectionPool";
|
||||
when(mockConnectionPool.create()).thenReturn(Mono.error(new TimeoutException(expectedErrorMessage)));
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = pluginExecutor.executeCommon(mockConnectionPool, actionConfiguration, false, List.of(),
|
||||
new ExecuteActionDTO(), new HashMap<>());
|
||||
StepVerifier.create(actionExecutionResultMono)
|
||||
.expectErrorSatisfies(error -> {
|
||||
assertTrue(error instanceof StaleConnectionException);
|
||||
assertEquals(expectedErrorMessage, error.getMessage());
|
||||
})
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleConnectionExceptionReturnsUpstreamErrorOnPoolShutdownError() throws TimeoutException {
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setBody("select 1;");
|
||||
ConnectionPool mockConnectionPool = mock(ConnectionPool.class);
|
||||
String expectedErrorMessage = "Timeout exception from MockConnectionPool";
|
||||
when(mockConnectionPool.create()).thenReturn(Mono.error(new PoolShutdownException(expectedErrorMessage)));
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = pluginExecutor.executeCommon(mockConnectionPool, actionConfiguration, false, List.of(),
|
||||
new ExecuteActionDTO(), new HashMap<>());
|
||||
StepVerifier.create(actionExecutionResultMono)
|
||||
.expectErrorSatisfies(error -> {
|
||||
assertTrue(error instanceof StaleConnectionException);
|
||||
assertEquals(expectedErrorMessage, error.getMessage());
|
||||
})
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleConnectionExceptionReturnsUpstreamErrorOnIllegalStateError() throws TimeoutException {
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setBody("select 1;");
|
||||
ConnectionPool mockConnectionPool = mock(ConnectionPool.class);
|
||||
String expectedErrorMessage = "Timeout exception from MockConnectionPool";
|
||||
when(mockConnectionPool.create()).thenReturn(Mono.error(new IllegalStateException(expectedErrorMessage)));
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = pluginExecutor.executeCommon(mockConnectionPool, actionConfiguration, false, List.of(),
|
||||
new ExecuteActionDTO(), new HashMap<>());
|
||||
StepVerifier.create(actionExecutionResultMono)
|
||||
.expectErrorSatisfies(error -> {
|
||||
assertTrue(error instanceof StaleConnectionException);
|
||||
assertEquals(expectedErrorMessage, error.getMessage());
|
||||
})
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleConnectionExceptionReturnsUpstreamErrorOnResourceError() throws TimeoutException {
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setBody("select 1;");
|
||||
ConnectionPool mockConnectionPool = mock(ConnectionPool.class);
|
||||
String expectedErrorMessage = "Timeout exception from MockConnectionPool";
|
||||
when(mockConnectionPool.create()).thenReturn(Mono.error(new R2dbcNonTransientResourceException(expectedErrorMessage)));
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = pluginExecutor.executeCommon(mockConnectionPool, actionConfiguration, false, List.of(),
|
||||
new ExecuteActionDTO(), new HashMap<>());
|
||||
StepVerifier.create(actionExecutionResultMono)
|
||||
.expectErrorSatisfies(error -> {
|
||||
assertTrue(error instanceof StaleConnectionException);
|
||||
assertEquals(expectedErrorMessage, error.getMessage());
|
||||
})
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleConnectionExceptionReturnsUpstreamErrorOnInvalidConnection() throws TimeoutException {
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setBody("select 1;");
|
||||
ConnectionPool mockConnectionPool = mock(ConnectionPool.class);
|
||||
Connection mockConnection = mock(Connection.class);
|
||||
when(mockConnectionPool.create()).thenReturn(Mono.just(mockConnection));
|
||||
when(mockConnection.validate(ValidationDepth.LOCAL)).thenReturn(Mono.just(false));
|
||||
when(mockConnection.close()).thenReturn(Mono.empty());
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = pluginExecutor.executeCommon(mockConnectionPool, actionConfiguration, false, List.of(),
|
||||
new ExecuteActionDTO(), new HashMap<>());
|
||||
StepVerifier.create(actionExecutionResultMono)
|
||||
.expectErrorSatisfies(error -> {
|
||||
assertTrue(error instanceof StaleConnectionException);
|
||||
assertEquals(CONNECTION_VALIDITY_CHECK_FAILED_ERROR_MSG, error.getMessage());
|
||||
})
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ import java.util.stream.IntStream;
|
|||
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY;
|
||||
import static com.appsmith.external.constants.CommonFieldName.BODY;
|
||||
import static com.appsmith.external.constants.CommonFieldName.PREPARED_STATEMENT;
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.ORACLE_PLUGIN_NAME;
|
||||
import static com.appsmith.external.helpers.PluginUtils.OBJECT_TYPE;
|
||||
import static com.appsmith.external.helpers.PluginUtils.STRING_TYPE;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getDataValueSafelyFromFormData;
|
||||
|
|
@ -67,7 +68,6 @@ import static com.appsmith.external.helpers.PluginUtils.setDataValueSafelyInForm
|
|||
import static com.appsmith.external.helpers.SmartSubstitutionHelper.replaceQuestionMarkWithDollarIndex;
|
||||
import static com.external.plugins.utils.OracleDatasourceUtils.JDBC_DRIVER;
|
||||
import static com.external.plugins.utils.OracleDatasourceUtils.createConnectionPool;
|
||||
import static com.external.plugins.utils.OracleDatasourceUtils.getConnectionFromConnectionPool;
|
||||
import static com.external.plugins.utils.OracleDatasourceUtils.logHikariCPStatus;
|
||||
import static com.external.plugins.utils.OracleExecuteUtils.closeConnectionPostExecution;
|
||||
import static com.external.plugins.utils.OracleExecuteUtils.isPLSQL;
|
||||
|
|
@ -79,6 +79,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
|
||||
@Slf4j
|
||||
public class OraclePlugin extends BasePlugin {
|
||||
public static final OracleDatasourceUtils oracleDatasourceUtils = new OracleDatasourceUtils();
|
||||
|
||||
public OraclePlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
|
|
@ -197,7 +198,9 @@ public class OraclePlugin extends BasePlugin {
|
|||
Connection connectionFromPool;
|
||||
|
||||
try {
|
||||
connectionFromPool = getConnectionFromConnectionPool(connectionPool);
|
||||
connectionFromPool =
|
||||
oracleDatasourceUtils.getConnectionFromHikariConnectionPool(connectionPool,
|
||||
ORACLE_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
// The function can throw either StaleConnectionException or SQLException. The underlying hikari
|
||||
// library throws SQLException in case the pool is closed or there is an issue initializing
|
||||
|
|
@ -205,7 +208,8 @@ public class OraclePlugin extends BasePlugin {
|
|||
// and should then trigger the destruction and recreation of the pool.
|
||||
log.debug("Exception Occurred while getting connection from pool" + e.getMessage());
|
||||
e.printStackTrace(System.out);
|
||||
return Mono.error(e instanceof StaleConnectionException ? e : new StaleConnectionException());
|
||||
return Mono.error(e instanceof StaleConnectionException ? e :
|
||||
new StaleConnectionException(e.getMessage()));
|
||||
}
|
||||
|
||||
List<Map<String, Object>> rowsList = new ArrayList<>(50);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class OracleErrorMessages {
|
||||
private OracleErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class OracleErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MISSING_QUERY_ERROR_MSG = "Missing required parameter: Query.";
|
||||
|
||||
public static final String QUERY_EXECUTION_FAILED_ERROR_MSG = "Your Oracle query failed to execute.";
|
||||
|
|
@ -34,8 +36,6 @@ public class OracleErrorMessages {
|
|||
|
||||
public static final String DS_INVALID_HOSTNAME_ERROR_MSG = "Host value cannot contain `/` or `:` characters. Found `%s`.";
|
||||
|
||||
public static final String DS_MISSING_CONNECTION_MODE_ERROR_MSG = "Missing connection mode.";
|
||||
|
||||
public static final String DS_MISSING_AUTHENTICATION_DETAILS_ERROR_MSG = "Missing authentication details.";
|
||||
|
||||
public static final String DS_MISSING_USERNAME_ERROR_MSG = "Missing username for authentication.";
|
||||
|
|
|
|||
|
|
@ -32,8 +32,14 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.ORACLE_PLUGIN_NAME;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_CLOSED_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NOT_RUNNING_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NULL_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.UNKNOWN_CONNECTION_ERROR_MSG;
|
||||
import static com.appsmith.external.helpers.PluginUtils.safelyCloseSingleConnectionFromHikariCP;
|
||||
import static com.external.plugins.OraclePlugin.OraclePluginExecutor.scheduler;
|
||||
import static com.external.plugins.OraclePlugin.oracleDatasourceUtils;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.springframework.util.CollectionUtils.isEmpty;
|
||||
|
||||
|
|
@ -155,13 +161,15 @@ public class OracleDatasourceUtils {
|
|||
return Mono.fromSupplier(() -> {
|
||||
Connection connectionFromPool;
|
||||
try {
|
||||
connectionFromPool = getConnectionFromConnectionPool(connectionPool);
|
||||
connectionFromPool =
|
||||
oracleDatasourceUtils.getConnectionFromHikariConnectionPool(connectionPool, ORACLE_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
// The function can throw either StaleConnectionException or SQLException. The
|
||||
// underlying hikari library throws SQLException in case the pool is closed or there is an issue
|
||||
// initializing the connection pool which can also be translated in our world to
|
||||
// StaleConnectionException and should then trigger the destruction and recreation of the pool.
|
||||
return Mono.error(e instanceof StaleConnectionException ? e : new StaleConnectionException());
|
||||
return Mono.error(e instanceof StaleConnectionException ? e :
|
||||
new StaleConnectionException(e.getMessage()));
|
||||
}
|
||||
|
||||
logHikariCPStatus("Before getting Oracle DB schema", connectionPool);
|
||||
|
|
@ -439,21 +447,6 @@ public class OracleDatasourceUtils {
|
|||
return datasource;
|
||||
}
|
||||
|
||||
/**
|
||||
* First checks if the connection pool is still valid. If yes, we fetch a connection from the pool and return
|
||||
* In case a connection is not available in the pool, SQL Exception is thrown
|
||||
*/
|
||||
public static java.sql.Connection getConnectionFromConnectionPool(HikariDataSource connectionPool) throws SQLException {
|
||||
|
||||
if (connectionPool == null || connectionPool.isClosed() || !connectionPool.isRunning()) {
|
||||
System.out.println(Thread.currentThread().getName() +
|
||||
": Encountered stale connection pool in Oracle plugin. Reporting back.");
|
||||
throw new StaleConnectionException();
|
||||
}
|
||||
|
||||
return connectionPool.getConnection();
|
||||
}
|
||||
|
||||
public static void logHikariCPStatus(String logPrefix, HikariDataSource connectionPool) {
|
||||
HikariPoolMXBean poolProxy = connectionPool.getHikariPoolMXBean();
|
||||
int idleConnections = poolProxy.getIdleConnections();
|
||||
|
|
@ -463,4 +456,36 @@ public class OracleDatasourceUtils {
|
|||
log.debug(MessageFormat.format("{0}: Hikari Pool stats : active - {1} , idle - {2}, awaiting - {3} , total - {4}",
|
||||
logPrefix, activeConnections, idleConnections, threadsAwaitingConnection, totalConnections));
|
||||
}
|
||||
|
||||
public void checkHikariCPConnectionPoolValidity(HikariDataSource connectionPool, String pluginName) throws StaleConnectionException {
|
||||
if (connectionPool == null || connectionPool.isClosed() || !connectionPool.isRunning()) {
|
||||
String printMessage = MessageFormat.format(Thread.currentThread().getName() +
|
||||
": Encountered stale connection pool in {0} plugin. Reporting back.", pluginName);
|
||||
System.out.println(printMessage);
|
||||
|
||||
if (connectionPool == null) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NULL_ERROR_MSG);
|
||||
}
|
||||
else if (connectionPool.isClosed()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_CLOSED_ERROR_MSG);
|
||||
}
|
||||
else if (!connectionPool.isRunning()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NOT_RUNNING_ERROR_MSG);
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Ideally, code flow is never expected to reach here. However, this section has been added to catch
|
||||
* those cases wherein a developer updates the parent if condition but does not update the nested
|
||||
* if else conditions.
|
||||
*/
|
||||
throw new StaleConnectionException(UNKNOWN_CONNECTION_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Connection getConnectionFromHikariConnectionPool(HikariDataSource connectionPool,
|
||||
String pluginName) throws SQLException {
|
||||
checkHikariCPConnectionPoolValidity(connectionPool, pluginName);
|
||||
return connectionPool.getConnection();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,23 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
|
||||
import com.external.plugins.exceptions.OraclePluginError;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_CLOSED_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NOT_RUNNING_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NULL_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.UNKNOWN_CONNECTION_ERROR_MSG;
|
||||
import static com.external.plugins.OracleTestDBContainerManager.oracleDatasourceUtils;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class OraclePluginErrorsTest {
|
||||
@Test
|
||||
public void verifyUniquenessOfOraclePluginErrorCode() {
|
||||
|
|
@ -15,4 +27,63 @@ public class OraclePluginErrorsTest {
|
|||
.filter(appErrorCode -> appErrorCode.length() != 11 || !appErrorCode.startsWith("PE-ORC"))
|
||||
.collect(Collectors.toList()).size() == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not repeating this test for other plugins because (1) their implementation is identical to this one. (2) We
|
||||
* want to re-factor this identical code into a common method in future (#24763) - could not be done right now because
|
||||
* of some package dependency issues.
|
||||
*/
|
||||
@Test
|
||||
public void testStaleConnectionErrorHasUpstreamErrorWhenConnectionPoolIsNull() {
|
||||
Exception exception = assertThrows(StaleConnectionException.class,
|
||||
() -> oracleDatasourceUtils.checkHikariCPConnectionPoolValidity(null
|
||||
, "pluginName"));
|
||||
String expectedErrorMessage = CONNECTION_POOL_NULL_ERROR_MSG;
|
||||
assertEquals(expectedErrorMessage, exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Not repeating this test for other plugins because (1) their implementation is identical to this one. (2) We
|
||||
* want to re-factor this identical code into a common method in future (#24763) - could not be done right now because
|
||||
* of some package dependency issues.
|
||||
*/
|
||||
@Test
|
||||
public void testStaleConnectionErrorHasUpstreamErrorWhenConnectionPoolIsClosed() {
|
||||
HikariDataSource mockConnectionPool = mock(HikariDataSource.class);
|
||||
when(mockConnectionPool.isClosed()).thenReturn(true).thenReturn(true);
|
||||
Exception exception = assertThrows(StaleConnectionException.class,
|
||||
() -> oracleDatasourceUtils.checkHikariCPConnectionPoolValidity(mockConnectionPool, "pluginName"));
|
||||
String expectedErrorMessage = CONNECTION_POOL_CLOSED_ERROR_MSG;
|
||||
assertEquals(expectedErrorMessage, exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Not repeating this test for other plugins because (1) their implementation is identical to this one. (2) We
|
||||
* want to re-factor this identical code into a common method in future (#24763) - could not be done right now because
|
||||
* of some package dependency issues.
|
||||
*/
|
||||
@Test
|
||||
public void testStaleConnectionErrorHasUpstreamErrorWhenConnectionPoolIsRunning() {
|
||||
HikariDataSource mockConnectionPool = mock(HikariDataSource.class);
|
||||
when(mockConnectionPool.isRunning()).thenReturn(false).thenReturn(false);
|
||||
Exception exception = assertThrows(StaleConnectionException.class,
|
||||
() -> oracleDatasourceUtils.checkHikariCPConnectionPoolValidity(mockConnectionPool, "pluginName"));
|
||||
String expectedErrorMessage = CONNECTION_POOL_NOT_RUNNING_ERROR_MSG;
|
||||
assertEquals(expectedErrorMessage, exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Not repeating this test for other plugins because (1) their implementation is identical to this one. (2) We
|
||||
* want to re-factor this identical code into a common method in future (#24763) - could not be done right now because
|
||||
* of some package dependency issues.
|
||||
*/
|
||||
@Test
|
||||
public void testStaleConnectionErrorHasDefaultUpstreamError() {
|
||||
HikariDataSource mockConnectionPool = mock(HikariDataSource.class);
|
||||
when(mockConnectionPool.isRunning()).thenReturn(false).thenReturn(true);
|
||||
Exception exception = assertThrows(StaleConnectionException.class,
|
||||
() -> oracleDatasourceUtils.checkHikariCPConnectionPoolValidity(mockConnectionPool, "pluginName"));
|
||||
String expectedErrorMessage = UNKNOWN_CONNECTION_ERROR_MSG;
|
||||
assertEquals(expectedErrorMessage, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.appsmith.external.models.DBAuth;
|
|||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import com.appsmith.external.models.SSLDetails;
|
||||
import com.external.plugins.utils.OracleDatasourceUtils;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.testcontainers.containers.OracleContainer;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ import java.sql.SQLException;
|
|||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static com.external.plugins.utils.OracleDatasourceUtils.getConnectionFromConnectionPool;
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.ORACLE_PLUGIN_NAME;
|
||||
import static com.external.plugins.utils.OracleExecuteUtils.closeConnectionPostExecution;
|
||||
|
||||
public class OracleTestDBContainerManager {
|
||||
|
|
@ -20,6 +21,8 @@ public class OracleTestDBContainerManager {
|
|||
public static final String ORACLE_PASSWORD = "testPassword";
|
||||
public static final String ORACLE_DB_NAME = "testDB";
|
||||
public static final String ORACLE_DOCKER_HUB_CONTAINER = "gvenzl/oracle-xe:21-slim-faststart";
|
||||
|
||||
public static OracleDatasourceUtils oracleDatasourceUtils = new OracleDatasourceUtils();
|
||||
static OraclePlugin.OraclePluginExecutor oraclePluginExecutor = new OraclePlugin.OraclePluginExecutor();
|
||||
|
||||
public static OracleContainer getOracleDBForTest() {
|
||||
|
|
@ -49,7 +52,9 @@ public class OracleTestDBContainerManager {
|
|||
}
|
||||
|
||||
static void runSQLQueryOnOracleTestDB(String sqlQuery, HikariDataSource sharedConnectionPool) throws SQLException {
|
||||
java.sql.Connection connectionFromPool = getConnectionFromConnectionPool(sharedConnectionPool);
|
||||
java.sql.Connection connectionFromPool =
|
||||
oracleDatasourceUtils.getConnectionFromHikariConnectionPool(sharedConnectionPool,
|
||||
ORACLE_PLUGIN_NAME);
|
||||
Statement statement = connectionFromPool.createStatement();
|
||||
statement.execute(sqlQuery);
|
||||
closeConnectionPostExecution(null, statement, null, connectionFromPool);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.appsmith.external.services.SharedConfig;
|
|||
import com.external.plugins.datatypes.PostgresSpecificDataTypes;
|
||||
import com.external.plugins.exceptions.PostgresErrorMessages;
|
||||
import com.external.plugins.exceptions.PostgresPluginError;
|
||||
import com.external.plugins.utils.PostgresDatasourceUtils;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import com.zaxxer.hikari.HikariPoolMXBean;
|
||||
|
|
@ -76,6 +77,7 @@ import java.util.stream.IntStream;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY;
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.POSTGRES_PLUGIN_NAME;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getColumnsListForJdbcPlugin;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getIdenticalColumns;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getPSParamLabel;
|
||||
|
|
@ -124,13 +126,14 @@ public class PostgresPlugin extends BasePlugin {
|
|||
|
||||
private static int MAX_SIZE_SUPPORTED;
|
||||
|
||||
public static PostgresDatasourceUtils postgresDatasourceUtils = new PostgresDatasourceUtils();
|
||||
|
||||
public PostgresPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
@Extension
|
||||
public static class PostgresPluginExecutor implements SmartSubstitutionInterface, PluginExecutor<HikariDataSource> {
|
||||
|
||||
private final Scheduler scheduler = Schedulers.boundedElastic();
|
||||
|
||||
private static final String TABLES_QUERY = "select a.attname as name,\n"
|
||||
|
|
@ -281,7 +284,8 @@ public class PostgresPlugin extends BasePlugin {
|
|||
Connection connectionFromPool;
|
||||
|
||||
try {
|
||||
connectionFromPool = getConnectionFromConnectionPool(connection, datasourceConfiguration);
|
||||
connectionFromPool = postgresDatasourceUtils.getConnectionFromHikariConnectionPool(connection,
|
||||
POSTGRES_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
// The function can throw either StaleConnectionException or SQLException. The
|
||||
// underlying hikari
|
||||
|
|
@ -290,7 +294,7 @@ public class PostgresPlugin extends BasePlugin {
|
|||
// the connection pool which can also be translated in our world to
|
||||
// StaleConnectionException
|
||||
// and should then trigger the destruction and recreation of the pool.
|
||||
return Mono.error(e instanceof StaleConnectionException ? e : new StaleConnectionException());
|
||||
return Mono.error(e instanceof StaleConnectionException ? e : new StaleConnectionException(e.getMessage()));
|
||||
}
|
||||
|
||||
List<Map<String, Object>> rowsList = new ArrayList<>(50);
|
||||
|
|
@ -631,7 +635,8 @@ public class PostgresPlugin extends BasePlugin {
|
|||
|
||||
Connection connectionFromPool;
|
||||
try {
|
||||
connectionFromPool = getConnectionFromConnectionPool(connection, datasourceConfiguration);
|
||||
connectionFromPool = postgresDatasourceUtils.getConnectionFromHikariConnectionPool(connection,
|
||||
POSTGRES_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
// The function can throw either StaleConnectionException or SQLException. The
|
||||
// underlying hikari
|
||||
|
|
@ -640,7 +645,8 @@ public class PostgresPlugin extends BasePlugin {
|
|||
// the connection pool which can also be translated in our world to
|
||||
// StaleConnectionException
|
||||
// and should then trigger the destruction and recreation of the pool.
|
||||
return Mono.error(e instanceof StaleConnectionException ? e : new StaleConnectionException());
|
||||
return Mono.error(e instanceof StaleConnectionException ? e :
|
||||
new StaleConnectionException(e.getMessage()));
|
||||
}
|
||||
|
||||
HikariPoolMXBean poolProxy = connection.getHikariPoolMXBean();
|
||||
|
|
@ -1101,30 +1107,4 @@ public class PostgresPlugin extends BasePlugin {
|
|||
|
||||
return datasource;
|
||||
}
|
||||
|
||||
/**
|
||||
* First checks if the connection pool is still valid. If yes, we fetch a
|
||||
* connection from the pool and return
|
||||
* In case a connection is not available in the pool, SQL Exception is thrown
|
||||
*
|
||||
* @param connectionPool
|
||||
* @return SQL Connection
|
||||
*/
|
||||
private static Connection getConnectionFromConnectionPool(HikariDataSource connectionPool,
|
||||
DatasourceConfiguration datasourceConfiguration) throws SQLException {
|
||||
|
||||
if (connectionPool == null || connectionPool.isClosed() || !connectionPool.isRunning()) {
|
||||
log.debug("Encountered stale connection pool in Postgres plugin. Reporting back.");
|
||||
throw new StaleConnectionException();
|
||||
}
|
||||
|
||||
Connection connection = connectionPool.getConnection();
|
||||
|
||||
com.appsmith.external.models.Connection configurationConnection = datasourceConfiguration.getConnection();
|
||||
if (configurationConnection == null) {
|
||||
return connection;
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class PostgresErrorMessages {
|
||||
private PostgresErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class PostgresErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MISSING_QUERY_ERROR_MSG = "Missing required parameter: Query.";
|
||||
|
||||
public static final String QUERY_EXECUTION_FAILED_ERROR_MSG = "Your PostgreSQL query failed to execute.";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
package com.external.plugins.utils;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_CLOSED_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NOT_RUNNING_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NULL_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.UNKNOWN_CONNECTION_ERROR_MSG;
|
||||
|
||||
public class PostgresDatasourceUtils {
|
||||
public void checkHikariCPConnectionPoolValidity(HikariDataSource connectionPool, String pluginName) throws StaleConnectionException {
|
||||
if (connectionPool == null || connectionPool.isClosed() || !connectionPool.isRunning()) {
|
||||
String printMessage = MessageFormat.format(Thread.currentThread().getName() +
|
||||
": Encountered stale connection pool in {0} plugin. Reporting back.", pluginName);
|
||||
System.out.println(printMessage);
|
||||
|
||||
if (connectionPool == null) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NULL_ERROR_MSG);
|
||||
}
|
||||
else if (connectionPool.isClosed()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_CLOSED_ERROR_MSG);
|
||||
}
|
||||
else if (!connectionPool.isRunning()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NOT_RUNNING_ERROR_MSG);
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Ideally, code flow is never expected to reach here. However, this section has been added to catch
|
||||
* those cases wherein a developer updates the parent if condition but does not update the nested
|
||||
* if else conditions.
|
||||
*/
|
||||
throw new StaleConnectionException(UNKNOWN_CONNECTION_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Connection getConnectionFromHikariConnectionPool(HikariDataSource connectionPool,
|
||||
String pluginName) throws SQLException {
|
||||
checkHikariCPConnectionPoolValidity(connectionPool, pluginName);
|
||||
return connectionPool.getConnection();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class RedisErrorMessages {
|
||||
private RedisErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class RedisErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String BODY_IS_NULL_OR_EMPTY_ERROR_MSG = "Body is null or empty [%s]";
|
||||
|
||||
public static final String QUERY_PARSING_FAILED_ERROR_MSG = "Appsmith server has failed to parse your Redis query. Are you sure it's" +
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
<groupId>com.amazon.redshift</groupId>
|
||||
<artifactId>redshift-jdbc42</artifactId>
|
||||
<version>2.1.0.9</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.appsmith.external.plugins.BasePlugin;
|
|||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
import com.external.plugins.exceptions.RedshiftErrorMessages;
|
||||
import com.external.plugins.exceptions.RedshiftPluginError;
|
||||
import com.external.utils.RedshiftDatasourceUtils;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import com.zaxxer.hikari.HikariPoolMXBean;
|
||||
import lombok.NonNull;
|
||||
|
|
@ -47,15 +48,17 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY;
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.REDSHIFT_PLUGIN_NAME;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.JDBC_DRIVER_LOADING_ERROR_MSG;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getColumnsListForJdbcPlugin;
|
||||
import static com.appsmith.external.helpers.PluginUtils.getIdenticalColumns;
|
||||
import static com.external.utils.RedshiftDatasourceUtils.createConnectionPool;
|
||||
import static com.external.utils.RedshiftDatasourceUtils.getConnectionFromConnectionPool;
|
||||
|
||||
@Slf4j
|
||||
public class RedshiftPlugin extends BasePlugin {
|
||||
public static final String JDBC_DRIVER = "com.amazon.redshift.jdbc.Driver";
|
||||
private static final String DATE_COLUMN_TYPE_NAME = "date";
|
||||
public static RedshiftDatasourceUtils redshiftDatasourceUtils = new RedshiftDatasourceUtils();
|
||||
|
||||
public RedshiftPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
|
|
@ -212,7 +215,7 @@ public class RedshiftPlugin extends BasePlugin {
|
|||
return Mono.fromCallable(() -> {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = getConnectionFromConnectionPool(connectionPool);
|
||||
connection = redshiftDatasourceUtils.getConnectionFromHikariConnectionPool(connectionPool, REDSHIFT_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
|
|
@ -230,13 +233,13 @@ public class RedshiftPlugin extends BasePlugin {
|
|||
// library throws SQLException in case the pool is closed or there is an issue initializing
|
||||
// the connection pool which can also be translated in our world to StaleConnectionException
|
||||
// and should then trigger the destruction and recreation of the pool.
|
||||
return Mono.error(new StaleConnectionException());
|
||||
return Mono.error(new StaleConnectionException(e.getMessage()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Keeping this print statement post call to getConnectionFromConnectionPool because it checks for
|
||||
* stale connection pool.
|
||||
* Keeping this print statement post call to getConnectionFromHikariConnectionPool because it
|
||||
* checks for stale connection pool.
|
||||
*/
|
||||
printConnectionPoolStatus(connectionPool, false);
|
||||
|
||||
|
|
@ -359,7 +362,7 @@ public class RedshiftPlugin extends BasePlugin {
|
|||
try {
|
||||
Class.forName(JDBC_DRIVER);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, RedshiftErrorMessages.JDBC_DRIVER_LOADING_ERROR_MSG, e.getMessage()));
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, JDBC_DRIVER_LOADING_ERROR_MSG, e.getMessage()));
|
||||
}
|
||||
|
||||
return Mono
|
||||
|
|
@ -569,7 +572,8 @@ public class RedshiftPlugin extends BasePlugin {
|
|||
return Mono.fromSupplier(() -> {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = getConnectionFromConnectionPool(connectionPool);
|
||||
connection = redshiftDatasourceUtils.getConnectionFromHikariConnectionPool(connectionPool
|
||||
, REDSHIFT_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
|
|
@ -587,7 +591,7 @@ public class RedshiftPlugin extends BasePlugin {
|
|||
// library throws SQLException in case the pool is closed or there is an issue initializing
|
||||
// the connection pool which can also be translated in our world to StaleConnectionException
|
||||
// and should then trigger the destruction and recreation of the pool.
|
||||
return Mono.error(new StaleConnectionException());
|
||||
return Mono.error(new StaleConnectionException(e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class RedshiftErrorMessages {
|
||||
private RedshiftErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class RedshiftErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String QUERY_PARAMETER_MISSING_ERROR_MSG = "Missing required parameter: Query.";
|
||||
|
||||
public static final String NULL_RESULTSET_ERROR_MSG = "Redshift driver failed to fetch result: resultSet is null.";
|
||||
|
|
@ -18,7 +19,5 @@ public class RedshiftErrorMessages {
|
|||
public static final String GET_STRUCTURE_ERROR_MSG = "Appsmith server has failed to fetch the structure of the database. "
|
||||
+ "Please check if the database credentials are valid and/or you have the required permissions.";
|
||||
|
||||
public static final String JDBC_DRIVER_LOADING_ERROR_MSG = "Error loading Redshift JDBC Driver class.";
|
||||
|
||||
public static final String CONNECTION_POOL_CREATION_FAILED_ERROR_MSG = "Exception occurred while creating connection pool. One or more arguments in the datasource configuration may be invalid. Please check your datasource configuration.";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,14 @@ import org.springframework.util.StringUtils;
|
|||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_CLOSED_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NOT_RUNNING_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NULL_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.UNKNOWN_CONNECTION_ERROR_MSG;
|
||||
import static com.external.plugins.RedshiftPlugin.JDBC_DRIVER;
|
||||
|
||||
public class RedshiftDatasourceUtils {
|
||||
|
|
@ -95,14 +100,35 @@ public class RedshiftDatasourceUtils {
|
|||
return datasource;
|
||||
}
|
||||
|
||||
public static Connection getConnectionFromConnectionPool(HikariDataSource connectionPool) throws SQLException {
|
||||
|
||||
public void checkHikariCPConnectionPoolValidity(HikariDataSource connectionPool, String pluginName) throws StaleConnectionException {
|
||||
if (connectionPool == null || connectionPool.isClosed() || !connectionPool.isRunning()) {
|
||||
System.out.println(Thread.currentThread().getName() +
|
||||
": Encountered stale connection pool in Redshift plugin. Reporting back.");
|
||||
throw new StaleConnectionException();
|
||||
}
|
||||
String printMessage = MessageFormat.format(Thread.currentThread().getName() +
|
||||
": Encountered stale connection pool in {0} plugin. Reporting back.", pluginName);
|
||||
System.out.println(printMessage);
|
||||
|
||||
if (connectionPool == null) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NULL_ERROR_MSG);
|
||||
}
|
||||
else if (connectionPool.isClosed()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_CLOSED_ERROR_MSG);
|
||||
}
|
||||
else if (!connectionPool.isRunning()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NOT_RUNNING_ERROR_MSG);
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Ideally, code flow is never expected to reach here. However, this section has been added to catch
|
||||
* those cases wherein a developer updates the parent if condition but does not update the nested
|
||||
* if else conditions.
|
||||
*/
|
||||
throw new StaleConnectionException(UNKNOWN_CONNECTION_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Connection getConnectionFromHikariConnectionPool(HikariDataSource connectionPool,
|
||||
String pluginName) throws SQLException {
|
||||
checkHikariCPConnectionPoolValidity(connectionPool, pluginName);
|
||||
return connectionPool.getConnection();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class RestApiErrorMessages {
|
||||
private RestApiErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class RestApiErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String URI_SYNTAX_WRONG_ERROR_MSG = "Invalid value of URI.";
|
||||
public static final String INVALID_CONTENT_TYPE_ERROR_MSG = "Invalid value for Content-Type.";
|
||||
public static final String NO_HTTP_METHOD_ERROR_MSG = "HTTPMethod must be set.";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class SaaSErrorMessages {
|
||||
private SaaSErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class SaaSErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MISSING_DATASOURCE_TEMPLATE_NAME_ERROR_MSG = "Missing template name for datasource";
|
||||
|
||||
public static final String MISSING_ACTION_TEMPLATE_NAME_ERROR_MSG = "Missing template name for action";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class SMTPErrorMessages {
|
||||
private SMTPErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class SMTPErrorMessages {
|
||||
public static final String RECIPIENT_ADDRESS_NOT_FOUND_ERROR_MSG = "Couldn't find a valid recipient address. Please check your action configuration.";
|
||||
|
||||
public static final String SENDER_ADDRESS_NOT_FOUND_ERROR_MSG = "Couldn't find a valid sender address. Please check your action configuration.";
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.appsmith.external.models.DatasourceTestResult;
|
|||
import com.appsmith.external.plugins.BasePlugin;
|
||||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
import com.external.plugins.exceptions.SnowflakeErrorMessages;
|
||||
import com.external.plugins.exceptions.SnowflakePluginError;
|
||||
import com.external.utils.SqlUtils;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
|
@ -22,7 +21,6 @@ import com.zaxxer.hikari.pool.HikariPool;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.Extension;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
|
|
@ -41,7 +39,9 @@ import java.util.Map;
|
|||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.external.constants.PluginConstants.PluginName.SNOWFLAKE_PLUGIN_NAME;
|
||||
import static com.external.utils.ExecutionUtils.getRowsFromQueryResult;
|
||||
import static com.external.utils.SnowflakeDatasourceUtils.getConnectionFromHikariConnectionPool;
|
||||
import static com.external.utils.ValidationUtils.validateWarehouseDatabaseSchema;
|
||||
|
||||
@Slf4j
|
||||
|
|
@ -83,12 +83,19 @@ public class SnowflakePlugin extends BasePlugin {
|
|||
Connection connectionFromPool;
|
||||
|
||||
try {
|
||||
connectionFromPool = getConnectionFromConnectionPool(connection);
|
||||
/**
|
||||
* The getConnectionFromHikariConnectionPool method used here is the duplicate of
|
||||
* method defined in PluginUtils.java and not the same one. Please check the comment on
|
||||
* the method definition to understand more.
|
||||
*/
|
||||
connectionFromPool =
|
||||
getConnectionFromHikariConnectionPool(connection,
|
||||
SNOWFLAKE_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
if (e instanceof StaleConnectionException) {
|
||||
throw e;
|
||||
} else {
|
||||
throw new StaleConnectionException();
|
||||
throw new StaleConnectionException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -275,7 +282,14 @@ public class SnowflakePlugin extends BasePlugin {
|
|||
|
||||
Connection connectionFromPool;
|
||||
try {
|
||||
connectionFromPool = getConnectionFromConnectionPool(connectionPool);
|
||||
/**
|
||||
* The getConnectionFromHikariConnectionPool method used here is the duplicate of
|
||||
* method defined in PluginUtils.java and not the same one. Please check the comment on
|
||||
* the method definition to understand more.
|
||||
*/
|
||||
connectionFromPool =
|
||||
getConnectionFromHikariConnectionPool(connectionPool,
|
||||
SNOWFLAKE_PLUGIN_NAME);
|
||||
return Mono.just(validateWarehouseDatabaseSchema(connectionFromPool));
|
||||
} catch (SQLException e) {
|
||||
// The function can throw either StaleConnectionException or SQLException. The underlying hikari
|
||||
|
|
@ -313,7 +327,13 @@ public class SnowflakePlugin extends BasePlugin {
|
|||
|
||||
Connection connectionFromPool;
|
||||
try {
|
||||
connectionFromPool = getConnectionFromConnectionPool(connection);
|
||||
/**
|
||||
* The getConnectionFromHikariConnectionPool method used here is the duplicate of
|
||||
* method defined in PluginUtils.java and not the same one. Please check the comment on
|
||||
* the method definition to understand more.
|
||||
*/
|
||||
connectionFromPool =
|
||||
getConnectionFromHikariConnectionPool(connection, SNOWFLAKE_PLUGIN_NAME);
|
||||
} catch (SQLException | StaleConnectionException e) {
|
||||
// The function can throw either StaleConnectionException or SQLException. The underlying hikari
|
||||
// library throws SQLException in case the pool is closed or there is an issue initializing
|
||||
|
|
@ -391,22 +411,5 @@ public class SnowflakePlugin extends BasePlugin {
|
|||
})
|
||||
.subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* First checks if the connection pool is still valid. If yes, we fetch a connection from the pool and return
|
||||
* In case a connection is not available in the pool, SQL Exception is thrown
|
||||
*
|
||||
* @param connectionPool
|
||||
* @return SQL Connection
|
||||
*/
|
||||
private static Connection getConnectionFromConnectionPool(HikariDataSource connectionPool) throws SQLException {
|
||||
|
||||
if (connectionPool == null || connectionPool.isClosed() || !connectionPool.isRunning()) {
|
||||
log.debug("Encountered stale connection pool in Snowflake plugin. Reporting back.");
|
||||
throw new StaleConnectionException();
|
||||
}
|
||||
|
||||
return connectionPool.getConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,15 @@
|
|||
package com.external.plugins.exceptions;
|
||||
|
||||
public class SnowflakeErrorMessages {
|
||||
private SnowflakeErrorMessages() {
|
||||
//Prevents instantiation
|
||||
}
|
||||
import com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) // To prevent instantiation
|
||||
public class SnowflakeErrorMessages extends BasePluginErrorMessages {
|
||||
public static final String MISSING_QUERY_ERROR_MSG = "Missing required parameter: Query.";
|
||||
|
||||
public static final String DRIVER_NOT_FOUND_ERROR_MSG = "Snowflake driver not found. Please reach out to Appsmith support to resolve this issue.";
|
||||
|
||||
public static final String QUERY_EXECUTION_FAILED_ERROR_MSG = "Your query failed to execute. Please check more information in the error details.";
|
||||
|
||||
public static final String CONNECTION_CREATION_FAILED_ERROR_MSG = "Error occurred while connecting to Snowflake endpoint: %s";
|
||||
|
||||
public static final String UNABLE_TO_CREATE_CONNECTION_ERROR_MSG = "Unable to create connection to Snowflake URL";
|
||||
|
||||
public static final String GET_STRUCTURE_ERROR_MSG = "Appsmith server has failed to fetch the structure of your schema. Please check more information in the error details.";
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import java.util.LinkedHashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.external.plugins.exceptions.SnowflakeErrorMessages.CONNECTION_INVALID_ERROR_MSG;
|
||||
|
||||
@Slf4j
|
||||
public class ExecutionUtils {
|
||||
/**
|
||||
|
|
@ -38,7 +40,7 @@ public class ExecutionUtils {
|
|||
// Instead for every execution, we check for connection validity,
|
||||
// and reset the connection if required
|
||||
if (!connection.isValid(30)) {
|
||||
throw new StaleConnectionException();
|
||||
throw new StaleConnectionException(CONNECTION_INVALID_ERROR_MSG);
|
||||
}
|
||||
|
||||
statement = connection.createStatement();
|
||||
|
|
@ -58,7 +60,7 @@ public class ExecutionUtils {
|
|||
}
|
||||
} catch (SQLException e) {
|
||||
if (e instanceof SnowflakeReauthenticationRequest) {
|
||||
throw new StaleConnectionException();
|
||||
throw new StaleConnectionException(e.getMessage());
|
||||
}
|
||||
log.error("Exception caught when executing Snowflake query. Cause: ", e);
|
||||
throw new AppsmithPluginException(SnowflakePluginError.QUERY_EXECUTION_FAILED, SnowflakeErrorMessages.QUERY_EXECUTION_FAILED_ERROR_MSG, e.getMessage(), "SQLSTATE: " + e.getSQLState() );
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
package com.external.utils;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_CLOSED_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NOT_RUNNING_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.CONNECTION_POOL_NULL_ERROR_MSG;
|
||||
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.UNKNOWN_CONNECTION_ERROR_MSG;
|
||||
|
||||
public class SnowflakeDatasourceUtils {
|
||||
public static void checkHikariCPConnectionPoolValidity(HikariDataSource connectionPool, String pluginName) throws StaleConnectionException {
|
||||
if (connectionPool == null || connectionPool.isClosed() || !connectionPool.isRunning()) {
|
||||
String printMessage = MessageFormat.format(Thread.currentThread().getName() +
|
||||
": Encountered stale connection pool in {0} plugin. Reporting back.", pluginName);
|
||||
System.out.println(printMessage);
|
||||
|
||||
if (connectionPool == null) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NULL_ERROR_MSG);
|
||||
}
|
||||
else if (connectionPool.isClosed()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_CLOSED_ERROR_MSG);
|
||||
}
|
||||
else if (!connectionPool.isRunning()) {
|
||||
throw new StaleConnectionException(CONNECTION_POOL_NOT_RUNNING_ERROR_MSG);
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Ideally, code flow is never expected to reach here. However, this section has been added to catch
|
||||
* those cases wherein a developer updates the parent if condition but does not update the nested
|
||||
* if else conditions.
|
||||
*/
|
||||
throw new StaleConnectionException(UNKNOWN_CONNECTION_ERROR_MSG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Connection getConnectionFromHikariConnectionPool(HikariDataSource connectionPool,
|
||||
String pluginName) throws SQLException {
|
||||
checkHikariCPConnectionPoolValidity(connectionPool, pluginName);
|
||||
return connectionPool.getConnection();
|
||||
}
|
||||
}
|
||||
|
|
@ -644,7 +644,7 @@ public class ActionExecutionSolutionCEImpl implements ActionExecutionSolutionCE
|
|||
return new AppsmithPluginException(AppsmithPluginError.PLUGIN_QUERY_TIMEOUT_ERROR,
|
||||
actionDTO.getName(), timeoutDuration);
|
||||
} else if (error instanceof StaleConnectionException e) {
|
||||
return new AppsmithPluginException(AppsmithPluginError.STALE_CONNECTION_ERROR);
|
||||
return new AppsmithPluginException(AppsmithPluginError.STALE_CONNECTION_ERROR, e.getMessage());
|
||||
} else {
|
||||
return error;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user