mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
refactor: CB-491 Database Data API
This commit is contained in:
@@ -11,9 +11,9 @@ import {
|
||||
} from '@cloudbeaver/core-app';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
IContextMenuItem, IMenuContext, CommonDialogService, ContextMenuService
|
||||
IMenuContext, CommonDialogService, ContextMenuService
|
||||
} from '@cloudbeaver/core-dialogs';
|
||||
import { TableFooterMenuService, DataModelWrapper } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { TableFooterMenuService, ITableFooterMenuContext } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataExportDialog } from './Dialog/DataExportDialog';
|
||||
|
||||
@@ -25,8 +25,8 @@ export class DataExportMenuService {
|
||||
private contextMenuService: ContextMenuService
|
||||
) { }
|
||||
|
||||
register() {
|
||||
const exportData: IContextMenuItem<DataModelWrapper> = {
|
||||
register(): void {
|
||||
this.tableFooterMenuService.registerMenuItem({
|
||||
id: 'export ',
|
||||
isPresent(context) {
|
||||
return context.contextType === TableFooterMenuService.nodeContextType;
|
||||
@@ -35,8 +35,7 @@ export class DataExportMenuService {
|
||||
title: 'data_transfer_dialog_export',
|
||||
icon: 'table-export',
|
||||
onClick: this.exportData.bind(this),
|
||||
};
|
||||
this.tableFooterMenuService.registerMenuItem(exportData);
|
||||
});
|
||||
|
||||
this.contextMenuService.addMenuItem<NavNode>(
|
||||
this.contextMenuService.getRootMenuToken(),
|
||||
@@ -60,13 +59,18 @@ export class DataExportMenuService {
|
||||
);
|
||||
}
|
||||
|
||||
private exportData(context: IMenuContext<DataModelWrapper>) {
|
||||
private exportData(context: IMenuContext<ITableFooterMenuContext>) {
|
||||
const result = context.data.model.getResult(context.data.resultIndex);
|
||||
if (!result) {
|
||||
throw new Error('Result must be provided');
|
||||
}
|
||||
|
||||
this.commonDialogService.open(DataExportDialog, {
|
||||
connectionId: context.data.deprecatedModel.connectionId,
|
||||
contextId: context.data.deprecatedModel.executionContext?.contextId,
|
||||
containerNodePath: context.data.deprecatedModel.containerNodePath,
|
||||
resultId: context.data.deprecatedModel.resultId,
|
||||
sourceName: context.data.deprecatedModel.sourceName,
|
||||
connectionId: context.data.model.deprecatedModel.connectionId,
|
||||
contextId: context.data.model.source.executionContext?.contextId,
|
||||
containerNodePath: context.data.model.deprecatedModel.containerNodePath,
|
||||
resultId: result.id,
|
||||
sourceName: context.data.model.deprecatedModel.sourceName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import { IDatabaseDataResult } from './DatabaseDataModel/IDatabaseDataResult';
|
||||
import { DataUpdate } from './DatabaseDataModel/IDatabaseDataSource';
|
||||
import { FetchTableDataAsyncProcess } from './FetchTableDataAsyncProcess';
|
||||
import { IExecutionContext } from './IExecutionContext';
|
||||
import { RowDiff } from './TableViewer/TableDataModel/EditedRow';
|
||||
import { IRequestDataResult } from './TableViewer/TableViewerModel';
|
||||
|
||||
export interface IDataContainerOptions {
|
||||
containerNodePath: string;
|
||||
@@ -31,7 +33,6 @@ export interface IDataContainerResult extends IDatabaseDataResult {
|
||||
|
||||
export class ContainerDataSource extends DatabaseDataSource<IDataContainerOptions, IDataContainerResult> {
|
||||
@observable currentFetchTableProcess: FetchTableDataAsyncProcess | null;
|
||||
private executionContext: IExecutionContext | null;
|
||||
|
||||
get canCancel(): boolean {
|
||||
return this.currentFetchTableProcess ? this.currentFetchTableProcess.getState() === EDeferredState.PENDING : false;
|
||||
@@ -122,6 +123,30 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated will be refactored
|
||||
*/
|
||||
async saveDataDeprecated(resultId: string, rows: RowDiff[]): Promise<IRequestDataResult> {
|
||||
const executionContext = await this.ensureContextCreated();
|
||||
|
||||
const response = await this.graphQLService.sdk.updateResultsDataBatch({
|
||||
connectionId: executionContext.connectionId,
|
||||
contextId: executionContext.contextId,
|
||||
resultsId: resultId,
|
||||
updatedRows: rows.map(row => ({ data: row.source, updateValues: row.values })),
|
||||
});
|
||||
|
||||
const dataSet = response.result!.results[0].resultSet!; // we expect only one dataset for a table
|
||||
|
||||
return {
|
||||
rows: dataSet.rows!,
|
||||
columns: [], // not in use while saving data
|
||||
duration: response.result!.duration,
|
||||
isFullyLoaded: false, // not in use while saving data
|
||||
statusMessage: 'Saved successfully',
|
||||
};
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
if (this.executionContext) {
|
||||
await this.graphQLService.sdk.sqlContextDestroy({
|
||||
|
||||
@@ -13,8 +13,6 @@ import { GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { ContainerDataSource } from './ContainerDataSource';
|
||||
import { DatabaseDataAccessMode } from './DatabaseDataModel/IDatabaseDataModel';
|
||||
import { FetchTableDataAsyncProcess } from './FetchTableDataAsyncProcess';
|
||||
import { IExecutionContext } from './IExecutionContext';
|
||||
import { RowDiff } from './TableViewer/TableDataModel/EditedRow';
|
||||
import { IRequestDataResult, TableViewerModel } from './TableViewer/TableViewerModel';
|
||||
import { TableViewerStorageService } from './TableViewer/TableViewerStorageService';
|
||||
@@ -35,15 +33,6 @@ export class DataViewerTableService {
|
||||
const model = this.tableViewerStorageService.get(tableId);
|
||||
if (model) {
|
||||
await model.dispose();
|
||||
|
||||
if (model.deprecatedModel.executionContext) {
|
||||
try {
|
||||
await this.graphQLService.sdk.sqlContextDestroy({
|
||||
connectionId: model.deprecatedModel.executionContext.connectionId,
|
||||
contextId: model.deprecatedModel.executionContext.contextId,
|
||||
});
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
this.tableViewerStorageService.remove(tableId);
|
||||
}
|
||||
@@ -56,14 +45,44 @@ export class DataViewerTableService {
|
||||
const connectionInfo = await this.connectionInfoResource.load(connectionId);
|
||||
const source = new ContainerDataSource(this.graphQLService, this.notificationService);
|
||||
|
||||
return this.tableViewerStorageService.create(
|
||||
const dataModel = this.tableViewerStorageService.create(
|
||||
{
|
||||
tableId: tabId,
|
||||
connectionId,
|
||||
containerNodePath,
|
||||
access: connectionInfo.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default,
|
||||
requestDataAsync: this.requestDataAsync.bind(this, source),
|
||||
saveChanges: this.saveChanges.bind(this),
|
||||
requestDataAsync: async (
|
||||
model: TableViewerModel,
|
||||
offset: number,
|
||||
count: number,
|
||||
): Promise<IRequestDataResult> => {
|
||||
await dataModel
|
||||
.setSlice(0, dataModel.countGain + dataModel.source.count)
|
||||
.requestData();
|
||||
|
||||
const result = dataModel.getResult(0);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Result not exists');
|
||||
}
|
||||
|
||||
return {
|
||||
rows: result.data.rows!,
|
||||
columns: result.data.columns!,
|
||||
duration: dataModel.source.requestInfo.requestDuration,
|
||||
statusMessage: dataModel.source.requestInfo.requestMessage,
|
||||
isFullyLoaded: result.loadedFully,
|
||||
};
|
||||
},
|
||||
saveChanges: async (data: TableViewerModel, rows: RowDiff[]): Promise<IRequestDataResult> => {
|
||||
const result = dataModel.getResult(0);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('It is expected that result was set after first fetch');
|
||||
}
|
||||
|
||||
return await source.saveDataDeprecated(result.id, rows);
|
||||
},
|
||||
},
|
||||
source
|
||||
.setOptions({
|
||||
@@ -74,100 +93,8 @@ export class DataViewerTableService {
|
||||
})
|
||||
.setSupportedDataFormats(connectionInfo.supportedDataFormats)
|
||||
)
|
||||
.setAccess(connectionInfo.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default)
|
||||
.deprecatedModel;
|
||||
}
|
||||
.setAccess(connectionInfo.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default);
|
||||
|
||||
private async createExecutionContext(
|
||||
connectionId: string,
|
||||
defaultCatalog?: string,
|
||||
defaultSchema?: string
|
||||
): Promise<IExecutionContext> {
|
||||
const response = await this.graphQLService.sdk.sqlContextCreate({
|
||||
connectionId,
|
||||
defaultCatalog,
|
||||
defaultSchema,
|
||||
});
|
||||
return {
|
||||
contextId: response.context.id,
|
||||
connectionId,
|
||||
objectCatalogId: response.context.defaultCatalog,
|
||||
objectSchemaId: response.context.defaultSchema,
|
||||
};
|
||||
}
|
||||
|
||||
private async saveChanges(data: TableViewerModel, rows: RowDiff[]): Promise<IRequestDataResult> {
|
||||
if (!data.resultId) {
|
||||
throw new Error('It is expected that resultId was set after first fetch');
|
||||
}
|
||||
|
||||
if (!data.executionContext) {
|
||||
throw new Error('It is expected that data.sqlContextParams was set after first fetch');
|
||||
}
|
||||
|
||||
const response = await this.graphQLService.sdk.updateResultsDataBatch({
|
||||
connectionId: data.executionContext.connectionId,
|
||||
contextId: data.executionContext.contextId,
|
||||
resultsId: data.resultId,
|
||||
updatedRows: rows.map(row => ({ data: row.source, updateValues: row.values })),
|
||||
});
|
||||
|
||||
const dataSet = response.result!.results[0].resultSet!; // we expect only one dataset for a table
|
||||
|
||||
const result: IRequestDataResult = {
|
||||
rows: dataSet.rows!,
|
||||
columns: [], // not in use while saving data
|
||||
duration: response.result!.duration,
|
||||
isFullyLoaded: false, // not in use while saving data
|
||||
statusMessage: 'Saved successfully',
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
private async requestDataAsync(
|
||||
source: ContainerDataSource,
|
||||
model: TableViewerModel,
|
||||
offset: number,
|
||||
count: number,
|
||||
): Promise<IRequestDataResult> {
|
||||
if (!model.containerNodePath) {
|
||||
throw new Error('containerNodePath must be provided for table');
|
||||
}
|
||||
if (!model.executionContext) {
|
||||
// it is first data request
|
||||
const executionContext: IExecutionContext = await this.createExecutionContext(model.connectionId);
|
||||
model.executionContext = executionContext;
|
||||
}
|
||||
|
||||
const fetchTableProcess = new FetchTableDataAsyncProcess(this.graphQLService, this.notificationService);
|
||||
|
||||
fetchTableProcess.start(
|
||||
{
|
||||
connectionId: model.executionContext.connectionId,
|
||||
contextId: model.executionContext.contextId,
|
||||
containerNodePath: model.containerNodePath,
|
||||
},
|
||||
{
|
||||
offset,
|
||||
limit: count,
|
||||
constraints: Array.from(model.getSortedColumns()),
|
||||
where: model.getQueryWhereFilter() || undefined,
|
||||
},
|
||||
);
|
||||
|
||||
source.currentFetchTableProcess = fetchTableProcess;
|
||||
const response = await fetchTableProcess.promise;
|
||||
|
||||
const dataSet = response!.results[0].resultSet!; // we expect only one dataset for a table
|
||||
model.resultId = dataSet.id; // server generates new resultId on each fetch
|
||||
|
||||
const result: IRequestDataResult = {
|
||||
rows: dataSet.rows!,
|
||||
columns: dataSet.columns!,
|
||||
duration: response!.duration,
|
||||
statusMessage: response!.statusMessage || '',
|
||||
isFullyLoaded: (dataSet.rows?.length || 0) < count,
|
||||
};
|
||||
return result;
|
||||
return dataModel.deprecatedModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,18 @@ import { observable } from 'mobx';
|
||||
|
||||
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { IExecutionContext } from '../IExecutionContext';
|
||||
import { IDatabaseDataResult } from './IDatabaseDataResult';
|
||||
import { DataUpdate, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource';
|
||||
|
||||
export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseDataResult>
|
||||
implements IDatabaseDataSource<TOptions, TResult> {
|
||||
implements IDatabaseDataSource<TOptions, TResult> {
|
||||
@observable offset: number;
|
||||
@observable count: number;
|
||||
@observable dataFormat: ResultDataFormat;
|
||||
@observable options: TOptions | null;
|
||||
@observable requestInfo: IRequestInfo;
|
||||
@observable executionContext: IExecutionContext | null;
|
||||
@observable supportedDataFormats: ResultDataFormat[];
|
||||
abstract get canCancel(): boolean;
|
||||
|
||||
@@ -32,6 +34,7 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
|
||||
this.options = null;
|
||||
this.activeRequest = null;
|
||||
this.activeSave = null;
|
||||
this.executionContext = null;
|
||||
this.dataFormat = ResultDataFormat.Resultset;
|
||||
this.supportedDataFormats = [];
|
||||
this.requestInfo = {
|
||||
@@ -67,6 +70,11 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
|
||||
return this;
|
||||
}
|
||||
|
||||
setExecutionContext(context: IExecutionContext | null): this {
|
||||
this.executionContext = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
async requestData(prevResults: TResult[]): Promise<TResult[]> {
|
||||
if (this.activeSave) {
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { ResultDataFormat, SqlResultSet } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { IExecutionContext } from '../IExecutionContext';
|
||||
import { IDatabaseDataResult } from './IDatabaseDataResult';
|
||||
|
||||
export enum DataUpdateType {
|
||||
@@ -33,6 +34,7 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
|
||||
readonly options: TOptions | null;
|
||||
readonly requestInfo: IRequestInfo;
|
||||
readonly dataFormat: ResultDataFormat;
|
||||
readonly executionContext: IExecutionContext | null;
|
||||
readonly supportedDataFormats: ResultDataFormat[];
|
||||
readonly canCancel: boolean;
|
||||
|
||||
@@ -41,6 +43,7 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
|
||||
setOptions: (options: TOptions) => this;
|
||||
setDataFormat: (dataFormat: ResultDataFormat) => this;
|
||||
setSupportedDataFormats: (dataFormats: ResultDataFormat[]) => this;
|
||||
setExecutionContext: (context: IExecutionContext | null) => this;
|
||||
requestData: (
|
||||
prevResults: TResult[]
|
||||
) => Promise<TResult[]> | TResult[];
|
||||
|
||||
@@ -68,10 +68,12 @@ const tableFooterStyles = composes(
|
||||
);
|
||||
|
||||
interface TableFooterProps {
|
||||
resultIndex: number;
|
||||
model: DataModelWrapper;
|
||||
}
|
||||
|
||||
export const TableFooter = observer(function TableFooter({
|
||||
resultIndex,
|
||||
model,
|
||||
}: TableFooterProps) {
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
@@ -100,7 +102,7 @@ export const TableFooter = observer(function TableFooter({
|
||||
<input ref={ref} type="number" value={model.countGain} onBlur={handleChange} {...use({ mod: 'surface' })} />
|
||||
</SubmittingForm>
|
||||
</count>
|
||||
<TableFooterMenu model={model} />
|
||||
<TableFooterMenu model={model} resultIndex={resultIndex} />
|
||||
{(model.source.dataFormat === ResultDataFormat.Resultset
|
||||
&& model.deprecatedModel.requestStatusMessage.length > 0) && (
|
||||
<time>
|
||||
|
||||
+7
-2
@@ -27,16 +27,21 @@ const styles = css`
|
||||
`;
|
||||
|
||||
interface TableFooterMenuProps {
|
||||
resultIndex: number;
|
||||
model: DataModelWrapper;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TableFooterMenu = observer(function TableFooterMenu({ model, className }: TableFooterMenuProps) {
|
||||
export const TableFooterMenu = observer(function TableFooterMenu({
|
||||
resultIndex,
|
||||
model,
|
||||
className,
|
||||
}: TableFooterMenuProps) {
|
||||
const mainMenuService = useService(TableFooterMenuService);
|
||||
|
||||
return styled(styles)(
|
||||
<menu-wrapper as="div" className={className}>
|
||||
{mainMenuService.constructMenuWithContext(model).map((topItem, i) => (
|
||||
{mainMenuService.constructMenuWithContext(model, resultIndex).map((topItem, i) => (
|
||||
<TableFooterMenuItem key={i} menuItem={topItem} />
|
||||
))}
|
||||
</menu-wrapper>
|
||||
|
||||
+14
-9
@@ -13,6 +13,11 @@ import {
|
||||
|
||||
import { DataModelWrapper } from '../../DataModelWrapper';
|
||||
|
||||
export interface ITableFooterMenuContext {
|
||||
model: DataModelWrapper;
|
||||
resultIndex: number;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class TableFooterMenuService {
|
||||
static nodeContextType = 'NodeWithParent';
|
||||
@@ -27,12 +32,12 @@ export class TableFooterMenuService {
|
||||
return context.contextType === TableFooterMenuService.nodeContextType;
|
||||
},
|
||||
isDisabled(context) {
|
||||
return !context.data.deprecatedModel.isEdited();
|
||||
return !context.data.model.deprecatedModel.isEdited();
|
||||
},
|
||||
order: 1,
|
||||
title: 'ui_processing_save',
|
||||
icon: 'table-save',
|
||||
onClick: context => context.data.deprecatedModel.saveChanges(),
|
||||
onClick: context => context.data.model.deprecatedModel.saveChanges(),
|
||||
});
|
||||
this.registerMenuItem({
|
||||
id: 'cancel ',
|
||||
@@ -40,26 +45,26 @@ export class TableFooterMenuService {
|
||||
return context.contextType === TableFooterMenuService.nodeContextType;
|
||||
},
|
||||
isDisabled(context) {
|
||||
return !context.data.deprecatedModel.isEdited();
|
||||
return !context.data.model.deprecatedModel.isEdited();
|
||||
},
|
||||
order: 2,
|
||||
title: 'ui_processing_cancel',
|
||||
icon: 'table-cancel',
|
||||
onClick: context => context.data.deprecatedModel.cancelChanges(),
|
||||
onClick: context => context.data.model.deprecatedModel.cancelChanges(),
|
||||
});
|
||||
}
|
||||
|
||||
constructMenuWithContext(model: DataModelWrapper): IMenuItem[] {
|
||||
const context: IMenuContext<DataModelWrapper> = {
|
||||
constructMenuWithContext(model: DataModelWrapper, resultIndex: number): IMenuItem[] {
|
||||
const context: IMenuContext<ITableFooterMenuContext> = {
|
||||
menuId: this.tableFooterMenuToken,
|
||||
contextId: model.id,
|
||||
contextType: TableFooterMenuService.nodeContextType,
|
||||
data: model,
|
||||
data: { model, resultIndex },
|
||||
};
|
||||
return this.contextMenuService.createContextMenu(context, this.tableFooterMenuToken).menuItems;
|
||||
}
|
||||
|
||||
registerMenuItem(options: IContextMenuItem<DataModelWrapper>): void {
|
||||
this.contextMenuService.addMenuItem<DataModelWrapper>(this.tableFooterMenuToken, options);
|
||||
registerMenuItem(options: IContextMenuItem<ITableFooterMenuContext>): void {
|
||||
this.contextMenuService.addMenuItem<ITableFooterMenuContext>(this.tableFooterMenuToken, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ type TableViewerProps = PropsWithChildren<{
|
||||
|
||||
export const TableViewer = observer(function TableViewer({
|
||||
tableId,
|
||||
resultIndex,
|
||||
resultIndex = 0,
|
||||
presentationId,
|
||||
className,
|
||||
onPresentationChange,
|
||||
@@ -60,13 +60,15 @@ export const TableViewer = observer(function TableViewer({
|
||||
const dataPresentationService = useService(DataPresentationService);
|
||||
const tableViewerStorageService = useService(TableViewerStorageService);
|
||||
const dataModel = tableViewerStorageService.get(tableId);
|
||||
const result = dataModel?.getResult(resultIndex || 0);
|
||||
const result = dataModel?.getResult(resultIndex);
|
||||
|
||||
const handlePresentationChange = useCallback((id: string) => {
|
||||
const presentation = dataPresentationService.get(id);
|
||||
if (presentation && presentation.dataFormat !== dataModel?.source.dataFormat) {
|
||||
dataModel?.setDataFormat(presentation.dataFormat)
|
||||
.reload();
|
||||
if (presentation) {
|
||||
if (presentation.dataFormat !== dataModel?.source.dataFormat) {
|
||||
dataModel?.setDataFormat(presentation.dataFormat)
|
||||
.reload();
|
||||
}
|
||||
onPresentationChange(id);
|
||||
}
|
||||
}, [onPresentationChange, dataModel]);
|
||||
@@ -97,7 +99,7 @@ export const TableViewer = observer(function TableViewer({
|
||||
/>
|
||||
<table-data as='div'>
|
||||
<TableGrid model={dataModel} presentation={presentation} />
|
||||
<TableFooter model={dataModel} />
|
||||
<TableFooter model={dataModel} resultIndex={resultIndex} />
|
||||
</table-data>
|
||||
</table-content>
|
||||
<Loader
|
||||
|
||||
@@ -15,7 +15,6 @@ import { GQLError, SqlDataFilterConstraint } from '@cloudbeaver/core-sdk';
|
||||
import { uuid, MetadataMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { DatabaseDataAccessMode } from '../DatabaseDataModel/IDatabaseDataModel';
|
||||
import { IExecutionContext } from '../IExecutionContext';
|
||||
import { ErrorDialog } from './ErrorDialog';
|
||||
import { RowDiff } from './TableDataModel/EditedRow';
|
||||
import { TableColumn } from './TableDataModel/TableColumn';
|
||||
@@ -65,8 +64,6 @@ export interface ITableViewerModelOptions {
|
||||
tableId?: string;
|
||||
connectionId: string;
|
||||
containerNodePath?: string;
|
||||
resultId?: string | null; // will be filled after fist data fetch
|
||||
executionContext?: IExecutionContext | null; // will be filled before fist data fetch
|
||||
sourceName?: string; // TODO: refactor it, used for showing sql query for export
|
||||
noLoaderWhileRequestingDataAsync?: boolean;
|
||||
access?: DatabaseDataAccessMode;
|
||||
@@ -90,8 +87,6 @@ export class TableViewerModel {
|
||||
tableId: string;
|
||||
connectionId: string;
|
||||
containerNodePath?: string;
|
||||
resultId: string | null;
|
||||
executionContext: IExecutionContext | null;
|
||||
sourceName?: string;
|
||||
noLoaderWhileRequestingDataAsync?: boolean;
|
||||
|
||||
@@ -153,8 +148,6 @@ export class TableViewerModel {
|
||||
this.tableId = options.tableId || uuid();
|
||||
this.connectionId = options.connectionId;
|
||||
this.containerNodePath = options.containerNodePath;
|
||||
this.resultId = options.resultId || null;
|
||||
this.executionContext = options.executionContext || null;
|
||||
this.sourceName = options.sourceName;
|
||||
this.noLoaderWhileRequestingDataAsync = options.noLoaderWhileRequestingDataAsync;
|
||||
this.access = options.access || DatabaseDataAccessMode.Default;
|
||||
|
||||
+22
-14
@@ -23,7 +23,8 @@ import {
|
||||
RowDiff,
|
||||
TableViewerStorageService,
|
||||
TableViewerModel,
|
||||
DatabaseDataAccessMode
|
||||
DatabaseDataAccessMode,
|
||||
DataModelWrapper
|
||||
} from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { IResultDataTab, IQueryTabGroup } from '../../ISqlEditorTabState';
|
||||
@@ -53,6 +54,7 @@ implements IInitializableController, IDestructibleController {
|
||||
private group!: IQueryTabGroup;
|
||||
private tabId!: string;
|
||||
private source: QueryDataSource;
|
||||
private model: DataModelWrapper | null;
|
||||
|
||||
constructor(
|
||||
private sqlResultService: SqlResultService,
|
||||
@@ -64,6 +66,7 @@ implements IInitializableController, IDestructibleController {
|
||||
private sqlResultTabsService: SqlResultTabsService
|
||||
) {
|
||||
this.source = new QueryDataSource(this.sqlEditorGroupMetadataService, this.sqlResultTabsService);
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
async init(tabId: string, panelInit: IResultDataTab, group: IQueryTabGroup) {
|
||||
@@ -112,15 +115,19 @@ implements IInitializableController, IDestructibleController {
|
||||
constraints: [],
|
||||
whereFilter: '',
|
||||
})
|
||||
.setExecutionContext({
|
||||
connectionId: this.group.sqlQueryParams.connectionId,
|
||||
contextId: this.group.sqlQueryParams.contextId,
|
||||
objectCatalogId: this.group.sqlQueryParams.objectCatalogId,
|
||||
objectSchemaId: this.group.sqlQueryParams.objectSchemaId,
|
||||
})
|
||||
.setDataFormat(dataSet.dataFormat || ResultDataFormat.Resultset)
|
||||
.setSupportedDataFormats(connectionInfo.supportedDataFormats);
|
||||
|
||||
const tableModel = this.tableViewerStorageService.create(
|
||||
this.model = this.tableViewerStorageService.create(
|
||||
{
|
||||
tableId: this.getTableId(),
|
||||
connectionId: this.group.sqlQueryParams.connectionId,
|
||||
executionContext: this.group.sqlQueryParams,
|
||||
resultId: dataSet.resultSet.id,
|
||||
sourceName: this.group.sqlQueryParams.query,
|
||||
access: connectionInfo.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default,
|
||||
requestDataAsync: this.requestDataAsync.bind(this, sqlExecutionContext),
|
||||
@@ -130,8 +137,9 @@ implements IInitializableController, IDestructibleController {
|
||||
this.source
|
||||
)
|
||||
.setAccess(connectionInfo.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default)
|
||||
.setResults(this.source.getResults(response, fetchingSettings.fetchDefault) || [])
|
||||
.deprecatedModel;
|
||||
.setResults(this.source.getResults(response, fetchingSettings.fetchDefault) || []);
|
||||
|
||||
const tableModel = this.model.deprecatedModel;
|
||||
|
||||
tableModel.insertRows(0, initialState.rows, !initialState.isFullyLoaded);
|
||||
tableModel.setColumns(initialState.columns);
|
||||
@@ -214,27 +222,27 @@ implements IInitializableController, IDestructibleController {
|
||||
// /**
|
||||
// * Note that each data fetching overwrites resultId
|
||||
// */
|
||||
const dataSet = response.results![this.panelInit.indexInResultSet]!.resultSet!;
|
||||
await this.updateTableInfo(model, dataSet.id);
|
||||
this.model?.setResults(this.source.getResults(response, fetchingSettings.fetchDefault) || []);
|
||||
await this.updateTableInfo(model);
|
||||
return dataResults;
|
||||
}
|
||||
|
||||
private async updateTableInfo(model: TableViewerModel, resultId: string) {
|
||||
private async updateTableInfo(model: TableViewerModel) {
|
||||
const connectionInfo = await this.connectionInfoResource.load(this.group.sqlQueryParams.connectionId);
|
||||
|
||||
model.resultId = resultId;
|
||||
model.access = connectionInfo.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default;
|
||||
model.sourceName = this.group.sqlQueryParams.query;
|
||||
model.executionContext = this.group.sqlQueryParams;
|
||||
model.connectionId = this.group.sqlQueryParams.connectionId;
|
||||
}
|
||||
|
||||
async saveChanges(model: TableViewerModel, diffs: RowDiff[]): Promise<IRequestDataResult> {
|
||||
if (!model.resultId) {
|
||||
throw new Error('resultId must be provided before saving changes');
|
||||
const result = this.model?.getResult(this.panelInit.indexInResultSet);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('result must be provided before saving changes');
|
||||
}
|
||||
|
||||
const response = await this.sqlResultService.saveChanges(this.group.sqlQueryParams, model.resultId, diffs);
|
||||
const response = await this.sqlResultService.saveChanges(this.group.sqlQueryParams, result.id, diffs);
|
||||
|
||||
return this.sqlResultService.sqlExecuteInfoToData(response, this.panelInit.indexInResultSet);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user