Adding microsoft SQL server plugin integration (#1374)
This commit is contained in:
parent
e9c40da8fd
commit
c6d4902e3d
|
|
@ -0,0 +1,171 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.external.plugins</groupId>
|
||||
<artifactId>mssqlPlugin</artifactId>
|
||||
<name>mssqlPlugin</name>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<transformers>
|
||||
<transformer>
|
||||
<manifestEntries>
|
||||
<Plugin-Id>${plugin.id}</Plugin-Id>
|
||||
<Plugin-Class>${plugin.class}</Plugin-Class>
|
||||
<Plugin-Version>${plugin.version}</Plugin-Version>
|
||||
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j-spring</artifactId>
|
||||
<version>0.6.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.appsmith</groupId>
|
||||
<artifactId>interfaces</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.8</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<version>1.15.0-rc2</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>duct-tape</artifactId>
|
||||
<groupId>org.rnorth.duct-tape</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>visible-assertions</artifactId>
|
||||
<groupId>org.rnorth.visible-assertions</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>docker-java-api</artifactId>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>docker-java-transport-zerodep</artifactId>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mssqlserver</artifactId>
|
||||
<version>1.15.0-rc2</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>jdbc</artifactId>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<version>3.2.11.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>byte-buddy-agent</artifactId>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>objenesis</artifactId>
|
||||
<groupId>org.objenesis</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<properties>
|
||||
<plugin.id>mssql-plugin</plugin.id>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<java.version>11</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<plugin.class>com.external.plugins.MssqlPlugin</plugin.class>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<plugin.version>1.0-SNAPSHOT</plugin.version>
|
||||
<plugin.provider>tech@appsmith.com</plugin.provider>
|
||||
</properties>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
plugin.id=mssql-plugin
|
||||
plugin.class=com.external.plugins.MssqlPlugin
|
||||
plugin.version=1.0-SNAPSHOT
|
||||
plugin.provider=tech@appsmith.com
|
||||
plugin.dependencies=
|
||||
146
app/server/appsmith-plugins/mssqlPlugin/pom.xml
Normal file
146
app/server/appsmith-plugins/mssqlPlugin/pom.xml
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.external.plugins</groupId>
|
||||
<artifactId>mssqlPlugin</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<name>mssqlPlugin</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>11</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<plugin.id>mssql-plugin</plugin.id>
|
||||
<plugin.class>com.external.plugins.MssqlPlugin</plugin.class>
|
||||
<plugin.version>1.0-SNAPSHOT</plugin.version>
|
||||
<plugin.provider>tech@appsmith.com</plugin.provider>
|
||||
<plugin.dependencies/>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j-spring</artifactId>
|
||||
<version>0.6.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.appsmith</groupId>
|
||||
<artifactId>interfaces</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.8</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>8.4.1.jre11</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<version>1.15.0-rc2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mssqlserver</artifactId>
|
||||
<version>1.15.0-rc2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<version>3.2.11.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<configuration>
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Plugin-Id>${plugin.id}</Plugin-Id>
|
||||
<Plugin-Class>${plugin.class}</Plugin-Class>
|
||||
<Plugin-Version>${plugin.version}</Plugin-Version>
|
||||
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
307
app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java
vendored
Normal file
307
app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java
vendored
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import com.appsmith.external.models.SSLDetails;
|
||||
import com.appsmith.external.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.external.pluginExceptions.StaleConnectionException;
|
||||
import com.appsmith.external.plugins.BasePlugin;
|
||||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.pf4j.Extension;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.external.models.Connection.Mode.READ_ONLY;
|
||||
|
||||
public class MssqlPlugin extends BasePlugin {
|
||||
|
||||
private static final String JDBC_DRIVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
|
||||
|
||||
private static final int VALIDITY_CHECK_TIMEOUT = 5;
|
||||
|
||||
private static final String DATE_COLUMN_TYPE_NAME = "date";
|
||||
|
||||
public MssqlPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* MsSQL plugin receives the query as json of the following format :
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Extension
|
||||
public static class MssqlPluginExecutor implements PluginExecutor<Connection> {
|
||||
|
||||
@Override
|
||||
public Mono<ActionExecutionResult> execute(Connection connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
try {
|
||||
if (connection == null || connection.isClosed() || !connection.isValid(VALIDITY_CHECK_TIMEOUT)) {
|
||||
log.info("Encountered stale connection in MsSQL plugin. Reporting back.");
|
||||
throw new StaleConnectionException();
|
||||
}
|
||||
} catch (SQLException error) {
|
||||
// This exception is thrown only when the timeout to `isValid` is negative. Since, that's not the case,
|
||||
// here, this should never happen.
|
||||
log.error("Error checking validity of MsSQL connection.", error);
|
||||
}
|
||||
|
||||
String query = actionConfiguration.getBody();
|
||||
|
||||
if (query == null) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Missing required parameter: Query."));
|
||||
}
|
||||
|
||||
List<Map<String, Object>> rowsList = new ArrayList<>(50);
|
||||
|
||||
Statement statement = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
statement = connection.createStatement();
|
||||
boolean isResultSet = statement.execute(query);
|
||||
|
||||
if (isResultSet) {
|
||||
resultSet = statement.getResultSet();
|
||||
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||
int colCount = metaData.getColumnCount();
|
||||
|
||||
while (resultSet.next()) {
|
||||
// Use `LinkedHashMap` here so that the column ordering is preserved in the response.
|
||||
Map<String, Object> row = new LinkedHashMap<>(colCount);
|
||||
|
||||
for (int i = 1; i <= colCount; i++) {
|
||||
Object value;
|
||||
final String typeName = metaData.getColumnTypeName(i);
|
||||
|
||||
if (resultSet.getObject(i) == null) {
|
||||
value = null;
|
||||
|
||||
} else if (DATE_COLUMN_TYPE_NAME.equalsIgnoreCase(typeName)) {
|
||||
value = DateTimeFormatter.ISO_DATE.format(resultSet.getDate(i).toLocalDate());
|
||||
|
||||
} else if ("timestamp".equalsIgnoreCase(typeName)) {
|
||||
value = DateTimeFormatter.ISO_DATE_TIME.format(
|
||||
LocalDateTime.of(
|
||||
resultSet.getDate(i).toLocalDate(),
|
||||
resultSet.getTime(i).toLocalTime()
|
||||
)
|
||||
) + "Z";
|
||||
|
||||
} else if ("timestamptz".equalsIgnoreCase(typeName)) {
|
||||
value = DateTimeFormatter.ISO_DATE_TIME.format(
|
||||
resultSet.getObject(i, OffsetDateTime.class)
|
||||
);
|
||||
|
||||
} else if ("time".equalsIgnoreCase(typeName) || "timetz".equalsIgnoreCase(typeName)) {
|
||||
value = resultSet.getString(i);
|
||||
|
||||
} else if ("interval".equalsIgnoreCase(typeName)) {
|
||||
value = resultSet.getObject(i).toString();
|
||||
|
||||
} else {
|
||||
value = resultSet.getObject(i);
|
||||
|
||||
}
|
||||
|
||||
row.put(metaData.getColumnName(i), value);
|
||||
}
|
||||
|
||||
rowsList.add(row);
|
||||
}
|
||||
|
||||
} else {
|
||||
rowsList.add(Map.of(
|
||||
"affectedRows",
|
||||
ObjectUtils.defaultIfNull(statement.getUpdateCount(), 0))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e.getMessage()));
|
||||
|
||||
} finally {
|
||||
if (resultSet != null) {
|
||||
try {
|
||||
resultSet.close();
|
||||
} catch (SQLException e) {
|
||||
log.warn("Error closing MsSQL ResultSet", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (statement != null) {
|
||||
try {
|
||||
statement.close();
|
||||
} catch (SQLException e) {
|
||||
log.warn("Error closing MsSQL Statement", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ActionExecutionResult result = new ActionExecutionResult();
|
||||
result.setBody(objectMapper.valueToTree(rowsList));
|
||||
result.setIsExecutionSuccess(true);
|
||||
log.debug("In the MssqlPlugin, got action execution result: " + result.toString());
|
||||
return Mono.just(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Connection> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
|
||||
try {
|
||||
Class.forName(JDBC_DRIVER);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return Mono.error(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
"Error loading MsSQL JDBC Driver class."
|
||||
));
|
||||
}
|
||||
|
||||
AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
|
||||
com.appsmith.external.models.Connection configurationConnection = datasourceConfiguration.getConnection();
|
||||
|
||||
final boolean isSslEnabled = configurationConnection != null
|
||||
&& configurationConnection.getSsl() != null
|
||||
&& !SSLDetails.AuthType.NO_SSL.equals(configurationConnection.getSsl().getAuthType());
|
||||
|
||||
StringBuilder urlBuilder = new StringBuilder("jdbc:sqlserver://");
|
||||
for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) {
|
||||
urlBuilder
|
||||
.append(endpoint.getHost())
|
||||
.append(":")
|
||||
.append(ObjectUtils.defaultIfNull(endpoint.getPort(), 5432L))
|
||||
.append(";");
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(authentication.getDatabaseName())) {
|
||||
urlBuilder
|
||||
.append("database=")
|
||||
.append(authentication.getDatabaseName())
|
||||
.append(";");
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(authentication.getUsername())) {
|
||||
urlBuilder
|
||||
.append("user=")
|
||||
.append(authentication.getUsername())
|
||||
.append(";");
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(authentication.getPassword())) {
|
||||
urlBuilder
|
||||
.append("password=")
|
||||
.append(authentication.getPassword())
|
||||
.append(";");
|
||||
}
|
||||
|
||||
urlBuilder
|
||||
.append("encrypt=")
|
||||
.append(isSslEnabled)
|
||||
.append(";");
|
||||
|
||||
try {
|
||||
Connection connection = DriverManager.getConnection(urlBuilder.toString());
|
||||
connection.setReadOnly(
|
||||
configurationConnection != null && READ_ONLY.equals(configurationConnection.getMode()));
|
||||
return Mono.just(connection);
|
||||
|
||||
} catch (SQLException e) {
|
||||
return Mono.error(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
"Error connecting to MsSQL: " + e.getMessage()
|
||||
));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void datasourceDestroy(Connection connection) {
|
||||
try {
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Error closing MsSQL Connection.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> validateDatasource(@NonNull DatasourceConfiguration datasourceConfiguration) {
|
||||
Set<String> invalids = new HashSet<>();
|
||||
|
||||
if (CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) {
|
||||
invalids.add("Missing endpoint.");
|
||||
}
|
||||
|
||||
if (datasourceConfiguration.getConnection() != null
|
||||
&& datasourceConfiguration.getConnection().getMode() == null) {
|
||||
invalids.add("Missing Connection Mode.");
|
||||
}
|
||||
|
||||
if (datasourceConfiguration.getAuthentication() == null) {
|
||||
invalids.add("Missing authentication details.");
|
||||
|
||||
} else {
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getUsername())) {
|
||||
invalids.add("Missing username for authentication.");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getPassword())) {
|
||||
invalids.add("Missing password for authentication.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return invalids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||
return datasourceCreate(datasourceConfiguration)
|
||||
.map(connection -> {
|
||||
try {
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.warn("Error closing MsSQL connection that was made for testing.", e);
|
||||
}
|
||||
|
||||
return new DatasourceTestResult();
|
||||
})
|
||||
.onErrorResume(error -> Mono.just(new DatasourceTestResult(error.getMessage())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"editor": [
|
||||
{
|
||||
"sectionName": "",
|
||||
"id": 1,
|
||||
"children": [
|
||||
{
|
||||
"label": "",
|
||||
"configProperty": "actionConfiguration.body",
|
||||
"controlType": "QUERY_DYNAMIC_TEXT"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
{
|
||||
"form": [
|
||||
{
|
||||
"sectionName": "Connection",
|
||||
"id": 1,
|
||||
"children": [
|
||||
{
|
||||
"label": "Connection Mode",
|
||||
"configProperty": "datasourceConfiguration.connection.mode",
|
||||
"controlType": "DROP_DOWN",
|
||||
"isRequired": true,
|
||||
"initialValue": "READ_WRITE",
|
||||
"options": [
|
||||
{
|
||||
"label": "Read Only",
|
||||
"value": "READ_ONLY"
|
||||
},
|
||||
{
|
||||
"label": "Read / Write",
|
||||
"value": "READ_WRITE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sectionName": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "Host Address",
|
||||
"configProperty": "datasourceConfiguration.endpoints[*].host",
|
||||
"controlType": "KEYVALUE_ARRAY",
|
||||
"validationMessage": "Please enter a valid host",
|
||||
"validationRegex": "^((?![/:]).)*$"
|
||||
},
|
||||
{
|
||||
"label": "Port",
|
||||
"configProperty": "datasourceConfiguration.endpoints[*].port",
|
||||
"dataType": "NUMBER",
|
||||
"controlType": "KEYVALUE_ARRAY"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Database Name",
|
||||
"configProperty": "datasourceConfiguration.authentication.databaseName",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"placeholderText": "Database name",
|
||||
"initialValue": "admin"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sectionName": "Authentication",
|
||||
"id": 2,
|
||||
"children": [
|
||||
{
|
||||
"sectionName": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "Username",
|
||||
"configProperty": "datasourceConfiguration.authentication.username",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"placeholderText": "Username"
|
||||
},
|
||||
{
|
||||
"label": "Password",
|
||||
"configProperty": "datasourceConfiguration.authentication.password",
|
||||
"dataType": "PASSWORD",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"placeholderText": "Password"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"sectionName": "SSL (optional)",
|
||||
"children": [
|
||||
{
|
||||
"label": "SSL Mode",
|
||||
"configProperty": "datasourceConfiguration.connection.ssl.authType",
|
||||
"controlType": "DROP_DOWN",
|
||||
"options": [
|
||||
{
|
||||
"label": "No SSL",
|
||||
"value": "NO_SSL"
|
||||
},
|
||||
{
|
||||
"label": "Allow",
|
||||
"value": "ALLOW"
|
||||
},
|
||||
{
|
||||
"label": "Prefer",
|
||||
"value": "PREFER"
|
||||
},
|
||||
{
|
||||
"label": "Require",
|
||||
"value": "REQUIRE"
|
||||
},
|
||||
{
|
||||
"label": "Disable",
|
||||
"value": "DISABLE"
|
||||
},
|
||||
{
|
||||
"label": "Verify-CA",
|
||||
"value": "VERIFY_CA"
|
||||
},
|
||||
{
|
||||
"label": "Verify-Full",
|
||||
"value": "VERIFY_FULL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sectionName": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "Key File",
|
||||
"configProperty": "datasourceConfiguration.connection.ssl.keyFile",
|
||||
"controlType": "FILE_PICKER"
|
||||
},
|
||||
{
|
||||
"label": "Certificate",
|
||||
"configProperty": "datasourceConfiguration.connection.ssl.certificateFile",
|
||||
"controlType": "FILE_PICKER"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sectionName": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "CA Certificate",
|
||||
"configProperty": "datasourceConfiguration.connection.ssl.caCertificateFile",
|
||||
"controlType": "FILE_PICKER"
|
||||
},
|
||||
{
|
||||
"label": "PEM Certificate",
|
||||
"configProperty": "datasourceConfiguration.connection.ssl.pemCertificate.file",
|
||||
"controlType": "FILE_PICKER"
|
||||
},
|
||||
{
|
||||
"label": "PEM Passphrase",
|
||||
"configProperty": "datasourceConfiguration.connection.ssl.pemCertificate.password",
|
||||
"dataType": "PASSWORD",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"placeholderText": "PEM Passphrase"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
INSERT INTO users
|
||||
(name, gender, email)
|
||||
VALUES
|
||||
(
|
||||
'{{ nameInput.text }}',
|
||||
'{{ genderDropdown.selectedOptionValue }}',
|
||||
'{{ nameInput.text }}'
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
DELETE FROM users WHERE id = '{{ usersTable.selectedRow.id }}';
|
||||
|
|
@ -0,0 +1 @@
|
|||
SELECT * FROM users where role = '{{ roleDropdown.selectedOptionValue }}' ORDER BY id LIMIT 10;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
UPDATE users
|
||||
SET status = 'APPROVED'
|
||||
WHERE id = '{{ usersTable.selectedRow.id }}';
|
||||
204
app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java
vendored
Normal file
204
app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java
vendored
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.testcontainers.containers.MSSQLServerContainer;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Unit tests for the PostgresPlugin
|
||||
*/
|
||||
@Slf4j
|
||||
public class MssqlPluginTest {
|
||||
|
||||
MssqlPlugin.MssqlPluginExecutor pluginExecutor = new MssqlPlugin.MssqlPluginExecutor();
|
||||
|
||||
@SuppressWarnings("rawtypes") // The type parameter for the container type is just itself and is pseudo-optional.
|
||||
@ClassRule
|
||||
public static final MSSQLServerContainer container =
|
||||
new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:2017-latest")
|
||||
.acceptLicense()
|
||||
.withExposedPorts(1433)
|
||||
.withPassword("Mssql123");
|
||||
|
||||
private static String address;
|
||||
private static Integer port;
|
||||
private static String username, password;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() throws SQLException {
|
||||
address = container.getContainerIpAddress();
|
||||
port = container.getMappedPort(1433);
|
||||
username = container.getUsername();
|
||||
password = container.getPassword();
|
||||
|
||||
try (Connection connection = DriverManager.getConnection(
|
||||
"jdbc:sqlserver://" + address + ":" + port + ";user=" + username + ";password=" + password
|
||||
)) {
|
||||
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("CREATE TABLE users (\n" +
|
||||
" id int identity (1, 1) NOT NULL,\n" +
|
||||
" username VARCHAR (50) UNIQUE NOT NULL,\n" +
|
||||
" password VARCHAR (50) NOT NULL,\n" +
|
||||
" email VARCHAR (355) UNIQUE NOT NULL,\n" +
|
||||
" spouse_dob DATE,\n" +
|
||||
" dob DATE NOT NULL,\n" +
|
||||
" time1 TIME NOT NULL,\n" +
|
||||
" constraint pk_users_id primary key (id)\n" +
|
||||
")");
|
||||
|
||||
statement.execute("CREATE TABLE possessions (\n" +
|
||||
" id int identity (1, 1) not null,\n" +
|
||||
" title VARCHAR (50) NOT NULL,\n" +
|
||||
" user_id int NOT NULL,\n" +
|
||||
" constraint pk_possessions_id primary key (id),\n" +
|
||||
" constraint user_fk foreign key (user_id) references users(id)\n" +
|
||||
")");
|
||||
}
|
||||
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("SET identity_insert users ON;");
|
||||
}
|
||||
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute(
|
||||
"INSERT INTO users (id, username, password, email, spouse_dob, dob, time1) VALUES (" +
|
||||
"1, 'Jack', 'jill', 'jack@exemplars.com', NULL, '2018-12-31'," +
|
||||
" '18:32:45'" +
|
||||
")");
|
||||
}
|
||||
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute(
|
||||
"INSERT INTO users (id, username, password, email, spouse_dob, dob, time1) VALUES (" +
|
||||
"2, 'Jill', 'jack', 'jill@exemplars.com', NULL, '2019-12-31'," +
|
||||
" '15:45:30'" +
|
||||
")");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private DatasourceConfiguration createDatasourceConfiguration() {
|
||||
AuthenticationDTO authDTO = new AuthenticationDTO();
|
||||
authDTO.setAuthType(AuthenticationDTO.Type.USERNAME_PASSWORD);
|
||||
authDTO.setUsername(username);
|
||||
authDTO.setPassword(password);
|
||||
|
||||
Endpoint endpoint = new Endpoint();
|
||||
endpoint.setHost(address);
|
||||
endpoint.setPort(port.longValue());
|
||||
|
||||
DatasourceConfiguration dsConfig = new DatasourceConfiguration();
|
||||
dsConfig.setAuthentication(authDTO);
|
||||
dsConfig.setEndpoints(List.of(endpoint));
|
||||
return dsConfig;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectPostgresContainer() {
|
||||
|
||||
DatasourceConfiguration dsConfig = createDatasourceConfiguration();
|
||||
|
||||
Mono<Connection> dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig);
|
||||
|
||||
StepVerifier.create(dsConnectionMono)
|
||||
.assertNext(Assert::assertNotNull)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAliasColumnNames() {
|
||||
DatasourceConfiguration dsConfig = createDatasourceConfiguration();
|
||||
Mono<Connection> dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig);
|
||||
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setBody("SELECT id as user_id FROM users WHERE id = 1");
|
||||
|
||||
Mono<ActionExecutionResult> executeMono = dsConnectionMono
|
||||
.flatMap(conn -> pluginExecutor.execute(conn, dsConfig, actionConfiguration));
|
||||
|
||||
StepVerifier.create(executeMono)
|
||||
.assertNext(result -> {
|
||||
final JsonNode node = ((ArrayNode) result.getBody()).get(0);
|
||||
assertArrayEquals(
|
||||
new String[]{
|
||||
"user_id"
|
||||
},
|
||||
new ObjectMapper()
|
||||
.convertValue(node, LinkedHashMap.class)
|
||||
.keySet()
|
||||
.toArray()
|
||||
);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute() {
|
||||
DatasourceConfiguration dsConfig = createDatasourceConfiguration();
|
||||
Mono<Connection> dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig);
|
||||
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setBody("SELECT * FROM users WHERE id = 1");
|
||||
|
||||
Mono<ActionExecutionResult> executeMono = dsConnectionMono
|
||||
.flatMap(conn -> pluginExecutor.execute(conn, dsConfig, actionConfiguration));
|
||||
|
||||
StepVerifier.create(executeMono)
|
||||
.assertNext(result -> {
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(result.getBody());
|
||||
|
||||
final JsonNode node = ((ArrayNode) result.getBody()).get(0);
|
||||
assertEquals("2018-12-31", node.get("dob").asText());
|
||||
assertEquals("18:32:45.0000000", node.get("time1").asText());
|
||||
assertTrue(node.get("spouse_dob").isNull());
|
||||
|
||||
// Check the order of the columns.
|
||||
assertArrayEquals(
|
||||
new String[]{
|
||||
"id",
|
||||
"username",
|
||||
"password",
|
||||
"email",
|
||||
"spouse_dob",
|
||||
"dob",
|
||||
"time1",
|
||||
},
|
||||
new ObjectMapper()
|
||||
.convertValue(node, LinkedHashMap.class)
|
||||
.keySet()
|
||||
.toArray()
|
||||
);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,5 +22,6 @@
|
|||
<module>elasticSearchPlugin</module>
|
||||
<module>dynamoPlugin</module>
|
||||
<module>redisPlugin</module>
|
||||
<module>mssqlPlugin</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
@ -994,6 +994,26 @@ public class DatabaseChangelog {
|
|||
installPluginToAllOrganizations(mongoTemplate, plugin1.getId());
|
||||
}
|
||||
|
||||
@ChangeSet(order = "030", id = "add-msSql-plugin", author = "")
|
||||
public void addMsSqlPlugin(MongoTemplate mongoTemplate) {
|
||||
Plugin plugin1 = new Plugin();
|
||||
plugin1.setName("MsSQL");
|
||||
plugin1.setType(PluginType.DB);
|
||||
plugin1.setPackageName("mssql-plugin");
|
||||
plugin1.setUiComponent("DbEditorForm");
|
||||
plugin1.setResponseType(Plugin.ResponseType.TABLE);
|
||||
plugin1.setIconLocation("https://s3.us-east-2.amazonaws.com/assets.appsmith.com/MsSQL.jpg");
|
||||
plugin1.setDocumentationLink("https://docs.appsmith.com/core-concepts/connecting-to-databases/querying-mssql");
|
||||
plugin1.setDefaultInstall(true);
|
||||
try {
|
||||
mongoTemplate.insert(plugin1);
|
||||
} catch (DuplicateKeyException e) {
|
||||
log.warn(plugin1.getPackageName() + " already present in database.");
|
||||
}
|
||||
|
||||
installPluginToAllOrganizations(mongoTemplate, plugin1.getId());
|
||||
}
|
||||
|
||||
private void installPluginToAllOrganizations(MongoTemplate mongoTemplate, String pluginId) {
|
||||
for (Organization organization : mongoTemplate.findAll(Organization.class)) {
|
||||
if (CollectionUtils.isEmpty(organization.getPlugins())) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user