From 53fef72b76f3a168664b2cca5e8a070ac4e13c21 Mon Sep 17 00:00:00 2001 From: Luis Ibarra Date: Mon, 16 Feb 2026 18:14:30 -0500 Subject: [PATCH 1/3] feat: add support for READ_ONLY connection mode in MSSQL plugin - Implemented connection mode configuration for READ_ONLY, appending "ApplicationIntent=ReadOnly" to the JDBC URL and setting the HikariCP configuration to read-only. - Updated form.json to include a tooltip explaining the implications of using read-only mode. - Added unit tests to verify the correct behavior of both READ_ONLY and READ_WRITE connection modes, ensuring the JDBC URL is constructed appropriately based on the selected mode. --- .../com/external/plugins/MssqlPlugin.java | 8 ++++ .../mssqlPlugin/src/main/resources/form.json | 1 + .../com/external/plugins/MssqlPluginTest.java | 48 +++++++++++++++++++ 3 files changed, 57 insertions(+) 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..be1e3d7677b3 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 @@ -626,6 +626,14 @@ private static HikariDataSource createConnectionPool( addSslOptionsToUrlBuilder(datasourceConfiguration, urlBuilder); + // Add connection mode configuration for READ_ONLY + if (datasourceConfiguration.getConnection() != null + && datasourceConfiguration.getConnection().getMode() + == com.appsmith.external.models.Connection.Mode.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..9cf247f923d8 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 mode uses ApplicationIntent=ReadOnly. Note: This only enforces read-only connections when using Always On Availability Groups.", "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..befcc76ff4d0 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 @@ -773,4 +773,52 @@ public void testGetEndpointIdentifierForRateLimit_HostPresentPortAbsent_ReturnsC }) .verifyComplete(); } + + @Test + public void testReadOnlyConnectionMode() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(container); + + // Set connection mode to READ_ONLY + com.appsmith.external.models.Connection connection = new com.appsmith.external.models.Connection(); + connection.setMode(com.appsmith.external.models.Connection.Mode.READ_ONLY); + dsConfig.setConnection(connection); + + // Create connection pool + HikariDataSource connectionPool = + mssqlPluginExecutor.datasourceCreate(dsConfig).block(); + + // Verify the JDBC URL contains ApplicationIntent=ReadOnly + assertNotNull(connectionPool); + String jdbcUrl = connectionPool.getJdbcUrl(); + assertTrue( + jdbcUrl.contains("ApplicationIntent=ReadOnly"), + "JDBC URL should contain ApplicationIntent=ReadOnly parameter"); + + // Cleanup + connectionPool.close(); + } + + @Test + public void testReadWriteConnectionMode() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(container); + + // Set connection mode to READ_WRITE + com.appsmith.external.models.Connection connection = new com.appsmith.external.models.Connection(); + connection.setMode(com.appsmith.external.models.Connection.Mode.READ_WRITE); + dsConfig.setConnection(connection); + + // Create connection pool + HikariDataSource connectionPool = + mssqlPluginExecutor.datasourceCreate(dsConfig).block(); + + // Verify the JDBC URL does NOT contain ApplicationIntent parameter + assertNotNull(connectionPool); + String jdbcUrl = connectionPool.getJdbcUrl(); + assertTrue( + !jdbcUrl.contains("ApplicationIntent"), + "JDBC URL should not contain ApplicationIntent parameter for READ_WRITE mode"); + + // Cleanup + connectionPool.close(); + } } From 19c8b4236e9e75704ea8b7d84c25294fe20d3343 Mon Sep 17 00:00:00 2001 From: Luis Ibarra Date: Fri, 27 Mar 2026 20:09:54 -0500 Subject: [PATCH 2/3] refactor: streamline READ_ONLY connection mode handling in MSSQL plugin - Simplified the connection mode check by using a local variable for the connection configuration. - Updated unit tests to utilize the new import for READ_ONLY and READ_WRITE modes, ensuring clarity and consistency in the test cases. - Enhanced readability of the code by reducing redundancy in connection mode checks. --- .../com/external/plugins/MssqlPlugin.java | 7 +++--- .../com/external/plugins/MssqlPluginTest.java | 24 ++++++++----------- 2 files changed, 13 insertions(+), 18 deletions(-) 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 be1e3d7677b3..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,10 +627,8 @@ private static HikariDataSource createConnectionPool( addSslOptionsToUrlBuilder(datasourceConfiguration, urlBuilder); - // Add connection mode configuration for READ_ONLY - if (datasourceConfiguration.getConnection() != null - && datasourceConfiguration.getConnection().getMode() - == com.appsmith.external.models.Connection.Mode.READ_ONLY) { + com.appsmith.external.models.Connection configurationConnection = datasourceConfiguration.getConnection(); + if (configurationConnection != null && configurationConnection.getMode() == READ_ONLY) { urlBuilder.append("ApplicationIntent=ReadOnly;"); hikariConfig.setReadOnly(true); } 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 befcc76ff4d0..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; @@ -778,23 +782,19 @@ public void testGetEndpointIdentifierForRateLimit_HostPresentPortAbsent_ReturnsC public void testReadOnlyConnectionMode() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(container); - // Set connection mode to READ_ONLY - com.appsmith.external.models.Connection connection = new com.appsmith.external.models.Connection(); - connection.setMode(com.appsmith.external.models.Connection.Mode.READ_ONLY); + Connection connection = new Connection(); + connection.setMode(READ_ONLY); dsConfig.setConnection(connection); - // Create connection pool HikariDataSource connectionPool = mssqlPluginExecutor.datasourceCreate(dsConfig).block(); - // Verify the JDBC URL contains ApplicationIntent=ReadOnly assertNotNull(connectionPool); String jdbcUrl = connectionPool.getJdbcUrl(); assertTrue( jdbcUrl.contains("ApplicationIntent=ReadOnly"), "JDBC URL should contain ApplicationIntent=ReadOnly parameter"); - // Cleanup connectionPool.close(); } @@ -802,23 +802,19 @@ public void testReadOnlyConnectionMode() { public void testReadWriteConnectionMode() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(container); - // Set connection mode to READ_WRITE - com.appsmith.external.models.Connection connection = new com.appsmith.external.models.Connection(); - connection.setMode(com.appsmith.external.models.Connection.Mode.READ_WRITE); + Connection connection = new Connection(); + connection.setMode(READ_WRITE); dsConfig.setConnection(connection); - // Create connection pool HikariDataSource connectionPool = mssqlPluginExecutor.datasourceCreate(dsConfig).block(); - // Verify the JDBC URL does NOT contain ApplicationIntent parameter assertNotNull(connectionPool); String jdbcUrl = connectionPool.getJdbcUrl(); - assertTrue( - !jdbcUrl.contains("ApplicationIntent"), + assertFalse( + jdbcUrl.contains("ApplicationIntent"), "JDBC URL should not contain ApplicationIntent parameter for READ_WRITE mode"); - // Cleanup connectionPool.close(); } } From c3d6070b9d3248f93b606790debd795aef149107 Mon Sep 17 00:00:00 2001 From: Luis Ibarra Date: Fri, 27 Mar 2026 20:31:41 -0500 Subject: [PATCH 3/3] fix: enhance tooltip for READ_ONLY mode in MSSQL plugin Updated the tooltip text for the READ_ONLY connection mode to provide clearer information on its behavior with Always On Availability Groups, emphasizing the potential for write access in certain configurations and the importance of using database roles for strict enforcement. --- .../appsmith-plugins/mssqlPlugin/src/main/resources/form.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9cf247f923d8..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,7 +9,7 @@ "configProperty": "datasourceConfiguration.connection.mode", "controlType": "SEGMENTED_CONTROL", "initialValue": "READ_WRITE", - "tooltipText": "Read-only mode uses ApplicationIntent=ReadOnly. Note: This only enforces read-only connections when using Always On Availability Groups.", + "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",