diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java b/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java index c546fa03c856..41391acda0c3 100644 --- a/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java +++ b/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java @@ -76,6 +76,7 @@ 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.appsmith.external.models.Connection.Mode.READ_ONLY; import static com.external.plugins.constants.MssqlPluginConstants.GENERATE_CRUD_PAGE_SELECT_QUERY; import static com.external.plugins.exceptions.MssqlErrorMessages.CONNECTION_CLOSED_ERROR_MSG; import static com.external.plugins.exceptions.MssqlErrorMessages.CONNECTION_INVALID_ERROR_MSG; @@ -626,6 +627,12 @@ private static HikariDataSource createConnectionPool( addSslOptionsToUrlBuilder(datasourceConfiguration, urlBuilder); + com.appsmith.external.models.Connection configurationConnection = datasourceConfiguration.getConnection(); + if (configurationConnection != null && configurationConnection.getMode() == READ_ONLY) { + urlBuilder.append("ApplicationIntent=ReadOnly;"); + hikariConfig.setReadOnly(true); + } + hikariConfig.setJdbcUrl(urlBuilder.toString()); try { diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/form.json index 7731ac9421aa..457d02d3ab2f 100644 --- a/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/form.json +++ b/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/form.json @@ -9,6 +9,7 @@ "configProperty": "datasourceConfiguration.connection.mode", "controlType": "SEGMENTED_CONTROL", "initialValue": "READ_WRITE", + "tooltipText": "Read-only adds ApplicationIntent=ReadOnly to the JDBC URL. With Always On Availability Groups, SQL Server can route this session to a secondary replica. On a single instance or other topologies, the server may still accept writes—use database roles and permissions if you need strict read-only enforcement.", "options": [ { "label": "Read / Write", diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java b/app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java index 7be16681867a..a53fae958603 100755 --- a/app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java +++ b/app/server/appsmith-plugins/mssqlPlugin/src/test/java/com/external/plugins/MssqlPluginTest.java @@ -6,6 +6,7 @@ import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionRequest; import com.appsmith.external.models.ActionExecutionResult; +import com.appsmith.external.models.Connection; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceTestResult; @@ -44,11 +45,14 @@ import java.util.stream.Stream; import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY; +import static com.appsmith.external.models.Connection.Mode.READ_ONLY; +import static com.appsmith.external.models.Connection.Mode.READ_WRITE; import static com.external.plugins.MssqlTestDBContainerManager.createDatasourceConfiguration; import static com.external.plugins.MssqlTestDBContainerManager.mssqlPluginExecutor; import static com.external.plugins.MssqlTestDBContainerManager.runSQLQueryOnMssqlTestDB; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -773,4 +777,44 @@ public void testGetEndpointIdentifierForRateLimit_HostPresentPortAbsent_ReturnsC }) .verifyComplete(); } + + @Test + public void testReadOnlyConnectionMode() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(container); + + Connection connection = new Connection(); + connection.setMode(READ_ONLY); + dsConfig.setConnection(connection); + + HikariDataSource connectionPool = + mssqlPluginExecutor.datasourceCreate(dsConfig).block(); + + assertNotNull(connectionPool); + String jdbcUrl = connectionPool.getJdbcUrl(); + assertTrue( + jdbcUrl.contains("ApplicationIntent=ReadOnly"), + "JDBC URL should contain ApplicationIntent=ReadOnly parameter"); + + connectionPool.close(); + } + + @Test + public void testReadWriteConnectionMode() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(container); + + Connection connection = new Connection(); + connection.setMode(READ_WRITE); + dsConfig.setConnection(connection); + + HikariDataSource connectionPool = + mssqlPluginExecutor.datasourceCreate(dsConfig).block(); + + assertNotNull(connectionPool); + String jdbcUrl = connectionPool.getJdbcUrl(); + assertFalse( + jdbcUrl.contains("ApplicationIntent"), + "JDBC URL should not contain ApplicationIntent parameter for READ_WRITE mode"); + + connectionPool.close(); + } }