Skip to content

Commit

Permalink
Merge branch 'devel' into CB-3773-form-controls
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-anton-t authored Sep 28, 2023
2 parents 46ca56b + c58e62c commit 77359aa
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
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.
Expand Down Expand Up @@ -82,6 +84,18 @@ public DBDRowIdentifier getDefaultRowIdentifier() {
return null;
}

@NotNull
public Set<DBDRowIdentifier> getRowIdentifiers() {
Set<DBDRowIdentifier> 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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
* Web SQL processor.
Expand Down Expand Up @@ -315,56 +316,67 @@ public WebSQLExecuteInfo updateResultsDataBatch(
@Nullable List<WebSQLResultsRow> addedRows,
@Nullable WebDataFormat dataFormat) throws DBException
{
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();

List<Object[]> 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<DBDRowIdentifier> 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<WebSQLQueryResults> queryResults = new ArrayList<>();
for (var rowIdentifier : rowIdentifierList) {
Map<DBSDataManipulator.ExecuteBatch, Object[]> 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<String, Object> options = Collections.emptyMap();
for (Map.Entry<DBSDataManipulator.ExecuteBatch, Object[]> 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<String, Object> options = Collections.emptyMap();
for (Map.Entry<DBSDataManipulator.ExecuteBatch, Object[]> 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);
}
}
}
}
Expand All @@ -376,7 +388,7 @@ public WebSQLExecuteInfo updateResultsDataBatch(
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);

Expand All @@ -396,26 +408,42 @@ public String generateResultsDataUpdateScript(
{
Map<DBSDataManipulator.ExecuteBatch, Object[]> resultBatches = new LinkedHashMap<>();

DBSDataManipulator dataManipulator = generateUpdateResultsDataBatch(
monitor, contextInfo, resultsId, updatedRows, deletedRows, addedRows, dataFormat, resultBatches, null);

List<DBEPersistAction> actions = new ArrayList<>();

DBCExecutionContext executionContext = getExecutionContext(dataManipulator);
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) {
Map<String, Object> options = Collections.emptyMap();
for (DBSDataManipulator.ExecuteBatch batch : resultBatches.keySet()) {
batch.generatePersistActions(session, actions, options);
WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId);
Set<DBDRowIdentifier> 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);

List<DBEPersistAction> actions = new ArrayList<>();

DBCExecutionContext executionContext = getExecutionContext(dataManipulator);
try (DBCSession session = executionContext.openSession(monitor, DBCExecutionPurpose.USER, "Update data in container")) {
Map<String, Object> options = Collections.emptyMap();
for (DBSDataManipulator.ExecuteBatch batch : resultBatches.keySet()) {
batch.generatePersistActions(session, actions, options);
}
}
}

return SQLUtils.generateScript(executionContext.getDataSource(), actions.toArray(new DBEPersistAction[0]), false);
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<WebSQLResultsRow> updatedRows,
@Nullable List<WebSQLResultsRow> deletedRows,
@Nullable List<WebSQLResultsRow> addedRows,
Expand All @@ -424,10 +452,7 @@ private DBSDataManipulator generateUpdateResultsDataBatch(
@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;
Expand All @@ -448,7 +473,9 @@ private DBSDataManipulator generateUpdateResultsDataBatch(
if (!CommonUtils.isEmpty(updatedRows)) {

for (WebSQLResultsRow row : updatedRows) {
Map<String, Object> updateValues = row.getUpdateValues();
Map<String, Object> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface ITreeNodeState {
disabled?: boolean;
loading?: boolean;
selected?: boolean;
/** It is true when the node is neither selected nor unselected, used to indicate a mixed or partial selection in a group of sub-options */
indeterminateSelected?: boolean;
externalExpanded?: boolean;
expanded?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ module.exports = (env, argv) => {
proxy: {
'/api': {
target: envServer,
secure: false,
},
'/api/ws': {
target: `${urlObject.protocol === 'https:' ? 'wss:' : 'ws:'}//${urlObject.hostname}:${urlObject.port}/api/ws`,
ws: true,
secure: false,
},
},
onListening: function (devServer, ...args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import { CaptureViewContext, useDataContext } from '@cloudbeaver/core-view';
import { DataPresentationComponent, IDatabaseResultSet, TableViewerLoader } from '@cloudbeaver/plugin-data-viewer';

import { DATA_CONTEXT_DV_DDM_RS_GROUPING } from './DataContext/DATA_CONTEXT_DV_DDM_RS_GROUPING';
import { DEFAULT_GROUPING_QUERY_OPERATION } from './DEFAULT_GROUPING_QUERY_OPERATION';
import type { IGroupingQueryState } from './IGroupingQueryState';
import { useGroupingData } from './useGroupingData';
import { useGroupingDataModel } from './useGroupingDataModel';
import { useGroupingDnDColumns } from './useGroupingDnDColumns';
import { DEFAULT_GROUPING_QUERY_OPERATION } from './DEFAULT_GROUPING_QUERY_OPERATION';

const styles = css`
drop-area {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ export interface IRequestEventData<TOptions = any, TResult extends IDatabaseData
model: IDatabaseDataModel<TOptions, TResult>;
}

/** Represents an interface for interacting with a database. It is used for managing and requesting data. */
export interface IDatabaseDataModel<TOptions = any, TResult extends IDatabaseDataResult = IDatabaseDataResult> {
readonly id: string;
readonly name: string | null;
readonly source: IDatabaseDataSource<TOptions, TResult>;
/** Holds metadata about a data request. */
readonly requestInfo: IRequestInfo;
readonly supportedDataFormats: ResultDataFormat[];
/** Represents the value by which the number of loaded rows will be increased when loading the next data portion */
readonly countGain: number;

readonly onOptionsChange: IExecutor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export interface IDatabaseDataOptions {
connectionKey: IConnectionInfoParams;
schema?: string;
catalog?: string;
/** A raw string representation of the query filter conditions ("id=4") */
whereFilter: string;
/** A complex object that can represent filters and sorting options of the result set */
constraints: SqlDataFilterConstraint[];
readLogs?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface IRequestInfo {
readonly originalQuery: string;
readonly requestDuration: number;
readonly requestMessage: string;
/** A string representation of the filters constraints applied to the data request. Also returns as it is in case of whereFilter */
readonly requestFilter: string;
readonly source: string | null;
}
Expand All @@ -30,11 +31,13 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
readonly access: DatabaseDataAccessMode;
readonly dataFormat: ResultDataFormat;
readonly supportedDataFormats: ResultDataFormat[];
/** Indicates whether database supports filtering and sorting via constraints */
readonly constraintsAvailable: boolean;
readonly actions: IDatabaseDataActions<TOptions, TResult>;
readonly results: TResult[];
readonly offset: number;
readonly count: number;
/** Options of the previous request */
readonly prevOptions: Readonly<TOptions> | null;
readonly options: TOptions | null;
readonly requestInfo: IRequestInfo;
Expand Down Expand Up @@ -80,6 +83,8 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
setExecutionContext: (context: IConnectionExecutionContext | null) => this;

retry: () => Promise<void>;
/** Allows to perform an asynchronous action on the data source, this action will wait previous action to finish and save or load requests.
* The data source will have a loading and disabled state while performing an action */
runTask: <T>(task: () => Promise<T>) => Promise<T>;
requestData: () => Promise<void> | void;
refreshData: () => Promise<void> | void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ interface Props {
resultIndex: number | undefined;
presentationId: string | undefined;
valuePresentationId: string | null | undefined;
/** Display data in simple mode, some features will be hidden or disabled */
simple?: boolean;
context?: IDataContext;
className?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ import { IElementsTreeOptions, useElementsTree } from './useElementsTree';
import { useElementsTreeFolderExplorer } from './useElementsTreeFolderExplorer';

export interface ElementsTreeProps extends IElementsTreeOptions, React.PropsWithChildren {
/** Specifies the root path for the tree. ROOT_NODE_PATH will be used if not defined */
root?: string;
selectionTree?: boolean;
/** Specifies a custom control component for navigation tree */
control?: NavTreeControlComponent;
/** A placeholder component to be displayed when the elements tree is empty */
emptyPlaceholder?: React.FC;
className?: string;
settingsElements?: PlaceholderElement<IElementsTreeSettingsProps>[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface IElementsTreeOptions {
renderers?: IElementsTreeCustomRenderer[];
nodeInfoTransformers?: IElementsTreeCustomNodeInfo[];
expandStateGetters?: IElementsTreeNodeExpandInfoGetter[];
/** Allows to pass external state. It can be used to manipulate a tree state from the outside or to store it in an external state */
localState?: MetadataMap<string, ITreeNodeState>;
getChildren: (id: string) => string[] | undefined;
loadChildren: (id: string, manual: boolean) => Promise<boolean>;
Expand Down

0 comments on commit 77359aa

Please sign in to comment.