mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
* refactor: migrate to pure esm * chore: revert `useDefineForClassFields` tsconf * chore: remove patch.js
264 lines
8.4 KiB
TypeScript
264 lines
8.4 KiB
TypeScript
/*
|
|
* CloudBeaver - Cloud Database Manager
|
|
* Copyright (C) 2020-2024 DBeaver Corp and others
|
|
*
|
|
* Licensed under the Apache License, Version 2.0.
|
|
* you may not use this file except in compliance with the License.
|
|
*/
|
|
import { computed, makeObservable, observable } from 'mobx';
|
|
|
|
import type { ConnectionExecutionContextService, IConnectionExecutionContext, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
|
import type { IServiceProvider } from '@cloudbeaver/core-di';
|
|
import type { ITask } from '@cloudbeaver/core-executor';
|
|
import {
|
|
AsyncTask,
|
|
AsyncTaskInfoService,
|
|
GraphQLService,
|
|
ResultDataFormat,
|
|
type SqlExecuteInfo,
|
|
type SqlQueryResults,
|
|
type UpdateResultsDataBatchMutationVariables,
|
|
} from '@cloudbeaver/core-sdk';
|
|
import { uuid } from '@cloudbeaver/core-utils';
|
|
|
|
import { DocumentEditAction } from './DatabaseDataModel/Actions/Document/DocumentEditAction.js';
|
|
import type { IResultSetBlobValue } from './DatabaseDataModel/Actions/ResultSet/IResultSetBlobValue.js';
|
|
import { ResultSetEditAction } from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.js';
|
|
import type { IDatabaseDataOptions } from './DatabaseDataModel/IDatabaseDataOptions.js';
|
|
import type { IDatabaseResultSet } from './DatabaseDataModel/IDatabaseResultSet.js';
|
|
import { ResultSetDataSource } from './ResultSet/ResultSetDataSource.js';
|
|
|
|
export interface IDataContainerOptions extends IDatabaseDataOptions {
|
|
containerNodePath: string;
|
|
}
|
|
|
|
export class ContainerDataSource extends ResultSetDataSource<IDataContainerOptions> {
|
|
currentTask: ITask<SqlExecuteInfo> | null;
|
|
|
|
override get canCancel(): boolean {
|
|
return this.currentTask?.cancellable || false;
|
|
}
|
|
|
|
override get cancelled(): boolean {
|
|
return this.currentTask?.cancelled || false;
|
|
}
|
|
|
|
constructor(
|
|
serviceProvider: IServiceProvider,
|
|
graphQLService: GraphQLService,
|
|
asyncTaskInfoService: AsyncTaskInfoService,
|
|
protected connectionExecutionContextService: ConnectionExecutionContextService,
|
|
) {
|
|
super(serviceProvider, graphQLService, asyncTaskInfoService);
|
|
|
|
this.currentTask = null;
|
|
this.executionContext = null;
|
|
|
|
makeObservable(this, {
|
|
currentTask: observable.ref,
|
|
canCancel: computed,
|
|
});
|
|
}
|
|
|
|
override isOutdated(): boolean {
|
|
return super.isOutdated() || !this.executionContext?.context;
|
|
}
|
|
|
|
override async cancel(): Promise<void> {
|
|
await super.cancel();
|
|
await this.currentTask?.cancel();
|
|
}
|
|
|
|
async request(prevResults: IDatabaseResultSet[]): Promise<IDatabaseResultSet[]> {
|
|
const executionContext = await this.ensureContextCreated();
|
|
const context = executionContext.context!;
|
|
const limit = this.count;
|
|
const task = await this.getRequestTask(prevResults, context);
|
|
|
|
this.currentTask = executionContext.run(
|
|
async () => {
|
|
const info = await this.asyncTaskInfoService.run(task);
|
|
const { result } = await this.graphQLService.sdk.getSqlExecuteTaskResults({ taskId: info.id });
|
|
|
|
return result;
|
|
},
|
|
() => this.asyncTaskInfoService.cancel(task.id),
|
|
() => this.asyncTaskInfoService.remove(task.id),
|
|
);
|
|
|
|
try {
|
|
const response = await this.currentTask;
|
|
|
|
this.requestInfo = {
|
|
originalQuery: response.fullQuery || '',
|
|
requestDuration: response.duration || 0,
|
|
requestMessage: response.statusMessage || '',
|
|
requestFilter: response.filterText || '',
|
|
source: null,
|
|
};
|
|
|
|
this.clearError();
|
|
|
|
return this.transformResults(executionContext.context!, response.results, limit);
|
|
} catch (exception: any) {
|
|
this.error = exception;
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
async save(prevResults: IDatabaseResultSet[]): Promise<IDatabaseResultSet[]> {
|
|
const executionContext = await this.ensureContextCreated();
|
|
|
|
try {
|
|
for (const result of prevResults) {
|
|
if (result.id === null) {
|
|
continue;
|
|
}
|
|
const executionContextInfo = executionContext.context!;
|
|
const projectId = executionContextInfo.projectId;
|
|
const connectionId = executionContextInfo.connectionId;
|
|
const contextId = executionContextInfo.id;
|
|
const resultsId = result.id;
|
|
|
|
const updateVariables: UpdateResultsDataBatchMutationVariables = {
|
|
projectId,
|
|
connectionId,
|
|
contextId,
|
|
resultsId,
|
|
};
|
|
let editor: ResultSetEditAction | DocumentEditAction | undefined;
|
|
|
|
if (result.dataFormat === ResultDataFormat.Resultset) {
|
|
editor = this.actions.get(result, ResultSetEditAction);
|
|
} else if (result.dataFormat === ResultDataFormat.Document) {
|
|
editor = this.actions.get(result, DocumentEditAction);
|
|
}
|
|
|
|
let blobs: IResultSetBlobValue[] = [];
|
|
if (editor instanceof ResultSetEditAction) {
|
|
blobs = editor.getBlobsToUpload();
|
|
}
|
|
|
|
for (const blob of blobs) {
|
|
const fileId = uuid();
|
|
await this.graphQLService.sdk.uploadBlobResultSet(fileId, blob.blob);
|
|
blob.fileId = fileId;
|
|
}
|
|
|
|
if (editor) {
|
|
editor.fillBatch(updateVariables);
|
|
}
|
|
|
|
const response = await this.graphQLService.sdk.updateResultsDataBatch(updateVariables);
|
|
|
|
if (editor) {
|
|
const responseResult = this.transformResults(executionContextInfo, response.result.results, 0).find(
|
|
newResult => newResult.id === result.id,
|
|
);
|
|
|
|
if (responseResult) {
|
|
editor.applyUpdate(responseResult);
|
|
}
|
|
}
|
|
|
|
this.requestInfo = {
|
|
...this.requestInfo,
|
|
requestDuration: response.result.duration,
|
|
requestMessage: 'plugin_data_viewer_result_set_save_success',
|
|
source: null,
|
|
};
|
|
}
|
|
|
|
this.clearError();
|
|
} catch (exception: any) {
|
|
this.error = exception;
|
|
throw exception;
|
|
}
|
|
|
|
return prevResults;
|
|
}
|
|
|
|
protected getConfig(prevResults: IDatabaseResultSet[], context: IConnectionExecutionContextInfo) {
|
|
const options = this.options;
|
|
|
|
if (!options) {
|
|
throw new Error('Options must be provided');
|
|
}
|
|
|
|
const offset = this.offset;
|
|
const limit = this.count;
|
|
const resultId = this.getPreviousResultId(prevResults, context);
|
|
|
|
return {
|
|
projectId: context.projectId,
|
|
connectionId: context.connectionId,
|
|
contextId: context.id,
|
|
containerNodePath: options.containerNodePath,
|
|
resultId,
|
|
filter: {
|
|
offset,
|
|
limit,
|
|
constraints: options.constraints,
|
|
where: options.whereFilter || undefined,
|
|
},
|
|
dataFormat: this.dataFormat,
|
|
};
|
|
}
|
|
|
|
protected async getRequestTask(prevResults: IDatabaseResultSet[], context: IConnectionExecutionContextInfo): Promise<AsyncTask> {
|
|
const task = this.asyncTaskInfoService.create(async () => {
|
|
const config = this.getConfig(prevResults, context);
|
|
const { taskInfo } = await this.graphQLService.sdk.asyncReadDataFromContainer(config);
|
|
return taskInfo;
|
|
});
|
|
|
|
return task;
|
|
}
|
|
|
|
override setExecutionContext(context: IConnectionExecutionContext | null): this {
|
|
super.setExecutionContext(context);
|
|
|
|
for (const result of this.results) {
|
|
result.id = null;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
private transformResults(executionContextInfo: IConnectionExecutionContextInfo, results: SqlQueryResults[], limit: number): IDatabaseResultSet[] {
|
|
return results.map<IDatabaseResultSet>((result, index) => ({
|
|
id: result.resultSet?.id || '0',
|
|
uniqueResultId: `${executionContextInfo.connectionId}_${executionContextInfo.id}_${index}`,
|
|
projectId: executionContextInfo.projectId,
|
|
connectionId: executionContextInfo.connectionId,
|
|
contextId: executionContextInfo.id,
|
|
dataFormat: result.dataFormat!,
|
|
updateRowCount: result.updateRowCount || 0,
|
|
loadedFully: (result.resultSet?.rowsWithMetaData?.length || 0) < limit,
|
|
count: result.resultSet?.rowsWithMetaData?.length || 0,
|
|
totalCount: null,
|
|
data: result.resultSet,
|
|
}));
|
|
}
|
|
|
|
private async ensureContextCreated(): Promise<IConnectionExecutionContext> {
|
|
const currentContext = this.executionContext?.context;
|
|
|
|
if (!currentContext) {
|
|
if (!this.options) {
|
|
throw new Error('Options must be provided');
|
|
}
|
|
|
|
const executionContext = await this.connectionExecutionContextService.create(
|
|
this.options.connectionKey,
|
|
this.options.catalog,
|
|
this.options.schema,
|
|
);
|
|
|
|
this.setExecutionContext(executionContext);
|
|
}
|
|
|
|
return this.executionContext!;
|
|
}
|
|
}
|