diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java index a789099564..5aad875c65 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java @@ -155,6 +155,24 @@ public enum AppsmithPluginError implements BasePluginError { ErrorType.INTERNAL_ERROR, "{1}", "{2}"), + PLUGIN_GET_PREVIEW_DATA_ERROR( + 500, + AppsmithPluginErrorCode.PLUGIN_GET_PREVIEW_DATA_ERROR.getCode(), + AppsmithPluginErrorCode.PLUGIN_GET_PREVIEW_DATA_ERROR.getDescription(), + AppsmithErrorAction.DEFAULT, + "Failed to get preview data", + ErrorType.DATASOURCE_CONFIGURATION_ERROR, + "{0}", + "{1}"), + PLUGIN_UNSUPPORTED_OPERATION( + 500, + AppsmithPluginErrorCode.PLUGIN_UNSUPPORTED_OPERATION.getCode(), + AppsmithPluginErrorCode.PLUGIN_UNSUPPORTED_OPERATION.getDescription(), + AppsmithErrorAction.DEFAULT, + "Unsupported Operation", + ErrorType.INTERNAL_ERROR, + "{0}", + "{1}"), ; private final Integer httpErrorCode; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginErrorCode.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginErrorCode.java index 6510babf15..4a9ba3f733 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginErrorCode.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginErrorCode.java @@ -20,7 +20,9 @@ public enum AppsmithPluginErrorCode { PLUGIN_UQI_WHERE_CONDITION_UNKNOWN("PE-UQI-5000", "Where condition could not be parsed"), GENERIC_STALE_CONNECTION("PE-STC-5000", "Secondary stale connection error"), PLUGIN_EXECUTE_ARGUMENT_ERROR("PE-ARG-5000", "Wrong arguments provided"), - PLUGIN_VALIDATE_DATASOURCE_ERROR("PE-DSE-5005", "Failed to validate datasource"); + PLUGIN_VALIDATE_DATASOURCE_ERROR("PE-DSE-5005", "Failed to validate datasource"), + PLUGIN_GET_PREVIEW_DATA_ERROR("PE-DSE-5006", "Failed to get preview data"), + PLUGIN_UNSUPPORTED_OPERATION("PE-DSE-5007", "Unsupported Operation"); private final String code; private final String description; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java index 0a11ec5ded..59f96c76d1 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java @@ -8,6 +8,7 @@ import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceStructure; +import com.appsmith.external.models.DatasourceStructure.Template; import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.Param; import com.appsmith.external.models.TriggerRequestDTO; @@ -325,4 +326,12 @@ public interface PluginExecutor extends ExtensionPoint, CrudTemplateService { List actionConfigurationList, Object... args) { return Mono.empty(); } + + /* + * This method returns ActionConfiguration required in order to fetch preview data, + * that needs to be shown on datasource review page. + */ + default ActionConfiguration getSchemaPreviewActionConfig(Template queryTemplate) { + return null; + } } diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java index 1a333b2897..d57017becb 100644 --- a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java @@ -14,6 +14,7 @@ import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceStructure; +import com.appsmith.external.models.DatasourceStructure.Template; import com.appsmith.external.models.Endpoint; import com.appsmith.external.models.MustacheBindingToken; import com.appsmith.external.models.Param; @@ -60,6 +61,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.time.Duration; +import java.time.Instant; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; @@ -268,6 +270,21 @@ public class PostgresPlugin extends BasePlugin { explicitCastDataTypes); } + @Override + public ActionConfiguration getSchemaPreviewActionConfig(Template queryTemplate) { + ActionConfiguration actionConfig = new ActionConfiguration(); + // Sets query body + actionConfig.setBody(queryTemplate.getBody()); + + // Sets prepared statement to false + Property preparedStatement = new Property(); + preparedStatement.setValue(false); + List pluginSpecifiedTemplates = new ArrayList(); + pluginSpecifiedTemplates.add(preparedStatement); + actionConfig.setPluginSpecifiedTemplates(pluginSpecifiedTemplates); + return actionConfig; + } + private Mono executeCommon( HikariDataSource connection, DatasourceConfiguration datasourceConfiguration, @@ -285,6 +302,7 @@ public class PostgresPlugin extends BasePlugin { String transformedQuery = preparedStatement ? replaceQuestionMarkWithDollarIndex(query) : query; List requestParams = List.of(new RequestParamDTO(ACTION_CONFIGURATION_BODY, transformedQuery, null, null, psParams)); + Instant requestedAt = Instant.now(); return Mono.fromCallable(() -> { Connection connectionFromPool; @@ -544,6 +562,9 @@ public class PostgresPlugin extends BasePlugin { request.setQuery(query); request.setProperties(requestData); request.setRequestParams(requestParams); + if (request.getRequestedAt() == null) { + request.setRequestedAt(requestedAt); + } ActionExecutionResult result = actionExecutionResult; result.setRequest(request); return result; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/DatasourceControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/DatasourceControllerCE.java index 80863c6788..8219d82c03 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/DatasourceControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/DatasourceControllerCE.java @@ -1,8 +1,10 @@ package com.appsmith.server.controllers.ce; +import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceStorageDTO; import com.appsmith.external.models.DatasourceStructure; +import com.appsmith.external.models.DatasourceStructure.Template; import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.TriggerRequestDTO; import com.appsmith.external.models.TriggerResultDTO; @@ -206,4 +208,16 @@ public class DatasourceControllerCE { .trigger(datasourceId, environmentId, triggerRequestDTO) .map(triggerResultDTO -> new ResponseDTO<>(HttpStatus.OK.value(), triggerResultDTO, null)); } + + @JsonView(Views.Public.class) + @PostMapping("/{datasourceId}/schema-preview") + public Mono> getSchemaPreviewData( + @PathVariable String datasourceId, + @RequestBody Template template, + @RequestHeader(name = FieldName.ENVIRONMENT_ID, required = false) String environmentId) { + log.debug("Going to get schema preview data for datasource with id: '{}'.", datasourceId); + return datasourceStructureSolution + .getSchemaPreviewData(datasourceId, environmentId, template) + .map(actionExecutionResult -> new ResponseDTO<>(HttpStatus.OK.value(), actionExecutionResult, null)); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/DatasourceStructureSolutionCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/DatasourceStructureSolutionCE.java index ff763fe458..79f78f948e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/DatasourceStructureSolutionCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/DatasourceStructureSolutionCE.java @@ -1,7 +1,9 @@ package com.appsmith.server.solutions.ce; +import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DatasourceStorage; import com.appsmith.external.models.DatasourceStructure; +import com.appsmith.external.models.DatasourceStructure.Template; import reactor.core.publisher.Mono; public interface DatasourceStructureSolutionCE { @@ -9,4 +11,7 @@ public interface DatasourceStructureSolutionCE { Mono getStructure(String datasourceId, boolean ignoreCache, String environmentName); Mono getStructure(DatasourceStorage datasourceStorage, boolean ignoreCache); + + Mono getSchemaPreviewData( + String datasourceId, String environmentName, Template queryTemplate); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/DatasourceStructureSolutionCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/DatasourceStructureSolutionCEImpl.java index 1c8fa9b16c..c3b65765bb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/DatasourceStructureSolutionCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/DatasourceStructureSolutionCEImpl.java @@ -4,9 +4,13 @@ import com.appsmith.external.constants.AnalyticsEvents; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; +import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionExecutionResult; +import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceStorage; import com.appsmith.external.models.DatasourceStorageStructure; import com.appsmith.external.models.DatasourceStructure; +import com.appsmith.external.models.DatasourceStructure.Template; import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.server.constants.FieldName; import com.appsmith.server.exceptions.AppsmithError; @@ -172,4 +176,80 @@ public class DatasourceStructureSolutionCEImpl implements DatasourceStructureSol .switchIfEmpty(fetchAndStoreNewStructureMono) .defaultIfEmpty(new DatasourceStructure()); } + + @Override + public Mono getSchemaPreviewData( + String datasourceId, String environmentId, Template queryTemplate) { + return datasourceService + .findById(datasourceId, datasourcePermission.getExecutePermission()) + .zipWhen(datasource -> datasourceService.getTrueEnvironmentId( + datasource.getWorkspaceId(), + environmentId, + datasource.getPluginId(), + environmentPermission.getExecutePermission())) + .flatMap(tuple -> { + Datasource datasource = tuple.getT1(); + String trueEnvironmentId = tuple.getT2(); + return datasourceStorageService.findByDatasourceAndEnvironmentIdForExecution( + datasource, trueEnvironmentId); + }) + .flatMap(datasourceStorage -> getSchemaPreviewData(datasourceStorage, queryTemplate)) + .onErrorMap(e -> { + if (!(e instanceof AppsmithPluginException)) { + return new AppsmithPluginException( + AppsmithPluginError.PLUGIN_GET_PREVIEW_DATA_ERROR, e.getMessage()); + } + + return e; + }) + .onErrorResume(error -> { + ActionExecutionResult result = new ActionExecutionResult(); + result.setErrorInfo(error); + return Mono.just(result); + }); + } + + private Mono getSchemaPreviewData( + DatasourceStorage datasourceStorage, Template queryTemplate) { + if (Boolean.FALSE.equals(datasourceStorage.getIsValid())) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_DATASOURCE)); + } + + return pluginExecutorHelper + .getPluginExecutor(pluginService.findById(datasourceStorage.getPluginId())) + .switchIfEmpty(Mono.error(new AppsmithException( + AppsmithError.NO_RESOURCE_FOUND, FieldName.PLUGIN, datasourceStorage.getPluginId()))) + .flatMap(pluginExecutor -> { + ActionConfiguration actionConfig = + ((PluginExecutor) pluginExecutor).getSchemaPreviewActionConfig(queryTemplate); + // actionConfig will be null for plugins which do not have this functionality yet + // Currently its only implemented for PostgreSQL, to be added subsequently for MySQL as well + if (actionConfig != null) { + return datasourceContextService.retryOnce( + datasourceStorage, resourceContext -> ((PluginExecutor) pluginExecutor) + .executeParameterized( + resourceContext.getConnection(), + null, + datasourceStorage.getDatasourceConfiguration(), + actionConfig)); + } else { + return Mono.error( + new AppsmithPluginException(AppsmithPluginError.PLUGIN_UNSUPPORTED_OPERATION)); + } + }) + .onErrorMap( + StaleConnectionException.class, + error -> new AppsmithPluginException( + AppsmithPluginError.PLUGIN_ERROR, + "Appsmith server found a secondary stale connection. Please reach out to appsmith " + + "customer support to resolve this.")) + .onErrorMap(e -> { + log.error("In the datasourceStorage fetching preview data error mode.", e); + if (!(e instanceof AppsmithPluginException)) { + return new AppsmithPluginException( + AppsmithPluginError.PLUGIN_GET_PREVIEW_DATA_ERROR, e.getMessage()); + } + return e; + }); + } }