From 5ff97155d624bbe3a3844ae8e5d16fca2b4057eb Mon Sep 17 00:00:00 2001 From: Tony Germano Date: Wed, 22 Oct 2025 14:59:07 -0400 Subject: [PATCH] Fix potential data loss saving plugin properties This addresses an upsert race condition that occurred when saving plugin properties (e.g., Data Pruner settings, third-party plugins) in environments with a read/write split database configuration where the read-only connection points to a replica. The Problem: The prior code attempted to determine whether to INSERT or UPDATE by first checking for the property's existence using the read-only database connection. Since updating all properties for a plugin involves deleting them all first, if this DELETE operation had not yet propagated to the replica, the read-only check would incorrectly indicate the property still existed. The Result: An UPDATE statement would be attempted, which would fail to match any rows (since the data had already been deleted from the primary) and silently return zero rows updated. This failure was not being checked, leading to data loss for the affected property. The Solution: This change eliminates the preliminary read check. It now attempts an UPDATE first. If the update affects zero rows, a guaranteed INSERT is performed. This pattern ensures atomicity and correctness regardless of replication latency. See https://sqlperformance.com/2020/09/locking/upsert-anti-pattern Issue: https://github.com/Innovar-Healthcare/BridgeLink/issues/66 Signed-off-by: Tony Germano --- .../controllers/DefaultConfigurationController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/com/mirth/connect/server/controllers/DefaultConfigurationController.java b/server/src/com/mirth/connect/server/controllers/DefaultConfigurationController.java index 49e113d23..bfee2eddb 100644 --- a/server/src/com/mirth/connect/server/controllers/DefaultConfigurationController.java +++ b/server/src/com/mirth/connect/server/controllers/DefaultConfigurationController.java @@ -1047,10 +1047,10 @@ public void saveProperty(String category, String name, String value) { parameterMap.put("name", name); parameterMap.put("value", value); - if (getProperty(category, name) == null) { - SqlConfig.getInstance().getSqlSessionManager().insert("Configuration.insertProperty", parameterMap); - } else { - SqlConfig.getInstance().getSqlSessionManager().insert("Configuration.updateProperty", parameterMap); + SqlSessionManager sqlSessionManager = SqlConfig.getInstance().getSqlSessionManager(); + int updatedRows = sqlSessionManager.update("Configuration.updateProperty", parameterMap); + if (updatedRows == 0) { + sqlSessionManager.insert("Configuration.insertProperty", parameterMap); } if (DatabaseUtil.statementExists("Configuration.vacuumConfigurationTable")) {