diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLResultsInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLResultsInfo.java index ca2bc074e5..3fd68da3c2 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLResultsInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/service/sql/WebSQLResultsInfo.java @@ -26,7 +26,9 @@ import org.jkiss.dbeaver.model.struct.DBSAttributeBase; import org.jkiss.dbeaver.model.struct.DBSDataContainer; import org.jkiss.dbeaver.model.struct.DBSTypedObject; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Web query results info. @@ -82,6 +84,18 @@ public class WebSQLResultsInfo { return null; } + @NotNull + public Set getRowIdentifiers() { + Set rowIdentifiers = new HashSet<>(); + for (DBDAttributeBinding column : attributes) { + DBDRowIdentifier rowIdentifier = column.getRowIdentifier(); + if (rowIdentifier != null) { + rowIdentifiers.add(rowIdentifier); + } + } + return rowIdentifiers; + } + public DBSAttributeBase getAttribute(String attributeName) { DBPDataSource dataSource = dataContainer.getDataSource(); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java index 4ea954fae9..28b3b20261 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java @@ -55,6 +55,7 @@ import org.jkiss.utils.CommonUtils; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** * Web SQL processor. @@ -315,56 +316,67 @@ public class WebSQLProcessor implements WebSessionProvider { @Nullable List addedRows, @Nullable WebDataFormat dataFormat) throws DBException { - Map resultBatches = new LinkedHashMap<>(); - + List newResultSetRows = new ArrayList<>(); KeyDataReceiver keyReceiver = new KeyDataReceiver(contextInfo.getResults(resultsId)); - - DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch( - monitor, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, keyReceiver); - WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId); + Set rowIdentifierList = new HashSet<>(); + // several row identifiers could be if we update result set table with join + // we can't add or delete rows from result set table with join + if (!CommonUtils.isEmpty(deletedRows) || !CommonUtils.isEmpty(addedRows)) { + rowIdentifierList.add(resultsInfo.getDefaultRowIdentifier()); + } else if (!CommonUtils.isEmpty(updatedRows)) { + rowIdentifierList = resultsInfo.getRowIdentifiers(); + } + long totalUpdateCount = 0; WebSQLExecuteInfo result = new WebSQLExecuteInfo(); List queryResults = new ArrayList<>(); + for (var rowIdentifier : rowIdentifierList) { + Map resultBatches = new LinkedHashMap<>(); + DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch( + monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, keyReceiver); - DBCExecutionContext executionContext = getExecutionContext(dataManipulator); - try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) { - DBCTransactionManager txnManager = DBUtils.getTransactionManager(executionContext); - boolean revertToAutoCommit = false; - if (txnManager != null && txnManager.isSupportsTransactions() && txnManager.isAutoCommit()) { - txnManager.setAutoCommit(monitor, false); - revertToAutoCommit = true; - } - try { - Map options = Collections.emptyMap(); - for (Map.Entry rb : resultBatches.entrySet()) { - DBSDataManipulator.ExecuteBatch batch = rb.getKey(); - Object[] rowValues = rb.getValue(); - keyReceiver.setRow(rowValues); - DBCStatistics statistics = batch.execute(session, options); - // Patch result rows (adapt to web format) - for (int i = 0; i < rowValues.length; i++) { - rowValues[i] = WebSQLUtils.makeWebCellValue(webSession, resultsInfo.getAttributeByPosition(i), rowValues[i], dataFormat); + DBCExecutionContext executionContext = getExecutionContext(dataManipulator); + try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) { + DBCTransactionManager txnManager = DBUtils.getTransactionManager(executionContext); + boolean revertToAutoCommit = false; + if (txnManager != null && txnManager.isSupportsTransactions() && txnManager.isAutoCommit()) { + txnManager.setAutoCommit(monitor, false); + revertToAutoCommit = true; + } + try { + Map options = Collections.emptyMap(); + for (Map.Entry rb : resultBatches.entrySet()) { + DBSDataManipulator.ExecuteBatch batch = rb.getKey(); + Object[] rowValues = rb.getValue(); + keyReceiver.setRow(rowValues); + DBCStatistics statistics = batch.execute(session, options); + + // Patch result rows (adapt to web format) + for (int i = 0; i < rowValues.length; i++) { + rowValues[i] = WebSQLUtils.makeWebCellValue(webSession, resultsInfo.getAttributeByPosition(i), rowValues[i], dataFormat); + } + + totalUpdateCount += statistics.getRowsUpdated(); + result.setDuration(result.getDuration() + statistics.getExecuteTime()); + newResultSetRows.add(rowValues); } - totalUpdateCount += statistics.getRowsUpdated(); - result.setDuration(result.getDuration() + statistics.getExecuteTime()); - } - - if (txnManager != null && txnManager.isSupportsTransactions()) { - txnManager.commit(session); - } - } catch (Exception e) { - if (txnManager != null && txnManager.isSupportsTransactions()) { - txnManager.rollback(session, null); - } - throw new DBCException("Error persisting data changes", e); - } finally { - if (revertToAutoCommit) { - txnManager.setAutoCommit(monitor, true); + if (txnManager != null && txnManager.isSupportsTransactions()) { + txnManager.commit(session); + } + } catch (Exception e) { + if (txnManager != null && txnManager.isSupportsTransactions()) { + txnManager.rollback(session, null); + } + throw new DBCException("Error persisting data changes", e); + } finally { + if (revertToAutoCommit) { + txnManager.setAutoCommit(monitor, true); + } } } } @@ -376,7 +388,7 @@ public class WebSQLProcessor implements WebSessionProvider { WebSQLQueryResults updateResults = new WebSQLQueryResults(webSession, dataFormat); updateResults.setUpdateRowCount(totalUpdateCount); updateResults.setResultSet(updatedResultSet); - updatedResultSet.setRows(resultBatches.values().toArray(new Object[0][])); + updatedResultSet.setRows(newResultSetRows.toArray(new Object[0][])); queryResults.add(updateResults); @@ -396,26 +408,42 @@ public class WebSQLProcessor implements WebSessionProvider { { Map resultBatches = new LinkedHashMap<>(); - DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch( - monitor, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, null); - List actions = new ArrayList<>(); - - DBCExecutionContext executionContext = getExecutionContext(dataManipulator); - try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) { - Map options = Collections.emptyMap(); - for (DBSDataManipulator.ExecuteBatch batch : resultBatches.keySet()) { - batch.generatePersistActions(session, actions, options); - } + WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId); + Set rowIdentifierList = new HashSet<>(); + // several row identifiers could be if we update result set table with join + // we can't add or delete rows from result set table with join + if (!CommonUtils.isEmpty(deletedRows) || !CommonUtils.isEmpty(addedRows)) { + rowIdentifierList.add(resultsInfo.getDefaultRowIdentifier()); + } else if (!CommonUtils.isEmpty(updatedRows)) { + rowIdentifierList = resultsInfo.getRowIdentifiers(); } + StringBuilder sqlBuilder = new StringBuilder(); + for (var rowIdentifier : rowIdentifierList) { + DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch( + monitor, resultsInfo, rowIdentifier, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, null); - return SQLUtils.generateScript(executionContext.getDataSource(), actions.toArray(new DBEPersistAction[0]), false); + List actions = new ArrayList<>(); + + DBCExecutionContext executionContext = getExecutionContext(dataManipulator); + try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) { + Map options = Collections.emptyMap(); + for (DBSDataManipulator.ExecuteBatch batch : resultBatches.keySet()) { + batch.generatePersistActions(session, actions, options); + } + } + + sqlBuilder.append( + SQLUtils.generateScript(executionContext.getDataSource(), actions.toArray(new DBEPersistAction[0]), false) + ); + } + return sqlBuilder.toString(); } private DBSDataManipulator generateUpdateResultsDataBatch( @NotNull DBRProgressMonitor monitor, - @NotNull WebSQLContextInfo contextInfo, - @NotNull String resultsId, + @NotNull WebSQLResultsInfo resultsInfo, + @NotNull DBDRowIdentifier rowIdentifier, @Nullable List updatedRows, @Nullable List deletedRows, @Nullable List addedRows, @@ -424,10 +452,7 @@ public class WebSQLProcessor implements WebSessionProvider { @Nullable DBDDataReceiver keyReceiver) throws DBException { - WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId); - DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier(); - checkRowIdentifier(resultsInfo, rowIdentifier); DBSEntity dataContainer = rowIdentifier.getEntity(); checkDataEditAllowed(dataContainer); DBSDataManipulator dataManipulator = (DBSDataManipulator) dataContainer; @@ -448,7 +473,9 @@ public class WebSQLProcessor implements WebSessionProvider { if (!CommonUtils.isEmpty(updatedRows)) { for (WebSQLResultsRow row : updatedRows) { - Map updateValues = row.getUpdateValues(); + Map updateValues = row.getUpdateValues().entrySet().stream() + .filter(x -> CommonUtils.equalObjects(allAttributes[CommonUtils.toInt(x.getKey())].getRowIdentifier(), rowIdentifier)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (CommonUtils.isEmpty(row.getData()) || CommonUtils.isEmpty(updateValues)) { continue; }