From dcf9daa4ba41eb8edf71604182165bb026b09459 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 25 Jun 2020 19:38:28 +0300 Subject: [PATCH] CB-153 Batch update implementation --- .../service/sql/DBWServiceSQL.java | 5 +- .../service/sql/WebSQLProcessor.java | 100 ++++++++++++++++++ .../service/sql/WebSQLResultsRow.java | 47 ++++++++ .../service/sql/WebServiceBindingSQL.java | 20 ++++ .../service/sql/impl/WebServiceSQL.java | 4 +- 5 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultsRow.java diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java index c8e5248d8e..33fb113e74 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java @@ -68,8 +68,9 @@ public interface DBWServiceSQL extends DBWService { WebSQLExecuteInfo updateResultsDataBatch( @NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, - @NotNull List updateRow, - @NotNull Map updateValues) throws DBWebException; + @Nullable List updatedRows, + @Nullable List deletedRows, + @Nullable List addedRows) throws DBWebException; @WebAction WebAsyncTaskInfo asyncExecuteQuery(@NotNull WebSQLContextInfo contextInfo, @NotNull String sql, @Nullable WebSQLDataFilter filter) throws DBException; 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 f9f400bfe6..3300ac9d2e 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 @@ -294,6 +294,106 @@ public class WebSQLProcessor { return result; } + @WebAction + public WebSQLExecuteInfo updateResultsDataBatch( + @NotNull WebSQLContextInfo contextInfo, + @NotNull String resultsId, + @Nullable List updatedRows, + @Nullable List deletedRows, + @Nullable List addedRows) throws DBWebException + { + WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId); + + DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier(); + if (rowIdentifier == null) { + throw new DBWebException("Can't detect row identifier for results '" + resultsId + "'"); + } + DBSEntity dataContainer = rowIdentifier.getEntity(); + if (!(dataContainer instanceof DBSDataManipulator)) { + throw new DBWebException("Data container '" + dataContainer.getName() + "' is not editable"); + } + DBSDataManipulator dataManipulator = (DBSDataManipulator) dataContainer; + + DBRProgressMonitor monitor = webSession.getProgressMonitor(); + WebSQLExecuteInfo result = new WebSQLExecuteInfo(); + List queryResults = new ArrayList<>(); + + try { + DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> { + try { + DBCExecutionContext executionContext = getExecutionContext(dataManipulator); + try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update rows in container")) { + WebExecutionSource executionSource = new WebExecutionSource(dataManipulator, executionContext, this); + + DBDAttributeBinding[] allAttributes = resultsInfo.getAttributes(); + DBDAttributeBinding[] keyAttributes = rowIdentifier.getAttributes().toArray(new DBDAttributeBinding[0]); + + if (!CommonUtils.isEmpty(updatedRows)) { + for (WebSQLResultsRow row : updatedRows) { + Map updateValues = row.getUpdateValues(); + if (CommonUtils.isEmpty(row.getData()) || CommonUtils.isEmpty(updateValues)) { + continue; + } + DBDAttributeBinding[] updateAttributes = new DBDAttributeBinding[updateValues.size()]; + // Final row is what we return back + Object[] finalRow = row.getData().toArray(); + + int index = 0; + for (String indexStr : updateValues.keySet()) { + int attrIndex = CommonUtils.toInt(indexStr, -1); + updateAttributes[index++] = allAttributes[attrIndex]; + } + + Object[] rowValues = new Object[updateAttributes.length + keyAttributes.length]; + for (int i = 0; i < updateAttributes.length; i++) { + DBDAttributeBinding updateAttribute = updateAttributes[i]; + Object cellRawValue = updateValues.get(String.valueOf(updateAttribute.getOrdinalPosition())); + Object realCellValue = updateAttribute.getValueHandler().getValueFromObject(session, updateAttribute, cellRawValue, false, true); + rowValues[i] = realCellValue; + finalRow[updateAttribute.getOrdinalPosition()] = WebSQLUtils.makeWebCellValue(monitor, null, realCellValue); + } + for (int i = 0; i < keyAttributes.length; i++) { + DBDAttributeBinding keyAttribute = keyAttributes[i]; + Object cellValueRaw = finalRow[keyAttribute.getOrdinalPosition()]; + rowValues[updateAttributes.length + i] = keyAttribute.getValueHandler().getValueFromObject(session, keyAttribute, cellValueRaw, false, true); + } + + DBSDataManipulator.ExecuteBatch updateBatch = dataManipulator.updateData(session, updateAttributes, keyAttributes, null, executionSource); + updateBatch.add(rowValues); + DBCStatistics statistics = updateBatch.execute(session); + + WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet(); + updatedResultSet.setResultsInfo(resultsInfo); + updatedResultSet.setColumns(resultsInfo.getAttributes()); + updatedResultSet.setRows(new Object[][]{finalRow}); + + WebSQLQueryResults updateResults = new WebSQLQueryResults(); + updateResults.setUpdateRowCount((int) statistics.getRowsUpdated()); + updateResults.setResultSet(updatedResultSet); + queryResults.add(updateResults); + result.setDuration(result.getDuration() + statistics.getExecuteTime()); + } + } + + if (!CommonUtils.isEmpty(updatedRows)) { + throw new DBCException("New row add is not supported"); + } + + if (!CommonUtils.isEmpty(deletedRows)) { + throw new DBCException("Row delete is not supported"); + } + } + } catch (Exception e) { + throw new InvocationTargetException(e); + } + }); + } catch (DBException e) { + throw new DBWebException("Error updating data", e); + } + result.setResults(queryResults.toArray(new WebSQLQueryResults[0])); + return result; + } + @NotNull public T getDataContainerByNodePath(DBRProgressMonitor monitor, @NotNull String containerPath, Class type) throws DBException { DBNNode node = webSession.getNavigatorModel().getNodeByPath(monitor, containerPath); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultsRow.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultsRow.java new file mode 100644 index 0000000000..362c5ce192 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLResultsRow.java @@ -0,0 +1,47 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cloudbeaver.service.sql; + +import org.jkiss.dbeaver.model.data.json.JSONUtils; + +import java.util.List; +import java.util.Map; + +/** + * Web query results row. + */ +public class WebSQLResultsRow { + + private List data; + private Map updateValues; + + public WebSQLResultsRow() { + } + + public WebSQLResultsRow(Map map) { + data = JSONUtils.getObjectList(map, "data"); + updateValues = JSONUtils.getObject(map, "updateValues"); + } + + public List getData() { + return data; + } + + public Map getUpdateValues() { + return updateValues; + } +} diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java index 19d801deef..4874e35b65 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebServiceBindingSQL.java @@ -24,9 +24,12 @@ import io.cloudbeaver.service.DBWBindingContext; import io.cloudbeaver.service.WebServiceBindingBase; import io.cloudbeaver.service.sql.impl.WebServiceSQL; import org.jkiss.dbeaver.DBException; +import org.jkiss.utils.CommonUtils; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Web service implementation @@ -90,6 +93,14 @@ public class WebServiceBindingSQL extends WebServiceBindingBase { env.getArgument("updateRow"), env.getArgument("updateValues") )) + .dataFetcher("updateResultsDataBatch", env -> + getService(env).updateResultsDataBatch( + getSQLContext(env), + env.getArgument("resultsId"), + getResultsRow(env, "updatedRows"), + getResultsRow(env, "deletedRows"), + getResultsRow(env, "addedRows") + )) .dataFetcher("asyncSqlExecuteQuery", env -> getService(env).asyncExecuteQuery( getSQLContext(env), @@ -154,4 +165,13 @@ public class WebServiceBindingSQL extends WebServiceBindingBase { Map filterProps = env.getArgument("filter"); return filterProps == null ? null : new WebSQLDataFilter(filterProps); } + + private static List getResultsRow(DataFetchingEnvironment env, String param) { + List> mapList = env.getArgument("param"); + if (CommonUtils.isEmpty(mapList)) { + return null; + } + return mapList.stream().map(WebSQLResultsRow::new).collect(Collectors.toList()); + } + } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java index 1e2bbb3f2b..252a9d59b6 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/impl/WebServiceSQL.java @@ -144,8 +144,8 @@ public class WebServiceSQL implements DBWServiceSQL { } @Override - public WebSQLExecuteInfo updateResultsDataBatch(@NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, @NotNull List updateRow, @NotNull Map updateValues) throws DBWebException { - throw new DBWebException("Not supported"); + public WebSQLExecuteInfo updateResultsDataBatch(@NotNull WebSQLContextInfo contextInfo, @NotNull String resultsId, @Nullable List updatedRows, @Nullable List deletedRows, @Nullable List addedRows) throws DBWebException { + return contextInfo.getProcessor().updateResultsDataBatch(contextInfo, resultsId, updatedRows, deletedRows, addedRows); } @NotNull