mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-24 11:35:54 +08:00
Merge branch 'devel' of https://github.com/dbeaver/cloudbeaver into devel
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
source.. = src/
|
||||
output.. = target/classes/
|
||||
bin.includes = .,\
|
||||
META-INF/,\
|
||||
plugin.xml
|
||||
META-INF/,\
|
||||
plugin.xml,\
|
||||
OSGI-INF/
|
||||
|
||||
@@ -38,6 +38,7 @@ mutation getSqlExecuteTaskResults(
|
||||
rows
|
||||
singleEntity
|
||||
hasMoreData
|
||||
hasRowIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ mutation updateResultsDataBatch(
|
||||
rows
|
||||
singleEntity
|
||||
hasMoreData
|
||||
hasRowIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1308,6 +1308,7 @@ export interface SqlResultRow {
|
||||
export interface SqlResultSet {
|
||||
columns?: Maybe<Array<Maybe<SqlResultColumn>>>;
|
||||
hasMoreData: Scalars['Boolean'];
|
||||
hasRowIdentifier: Scalars['Boolean'];
|
||||
id: Scalars['ID'];
|
||||
rows?: Maybe<Array<Maybe<Array<Maybe<Scalars['Object']>>>>>;
|
||||
singleEntity: Scalars['Boolean'];
|
||||
@@ -2095,7 +2096,7 @@ export type GetSqlExecuteTaskResultsMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetSqlExecuteTaskResultsMutation = { result: { duration: number; statusMessage?: string; filterText?: string; results: Array<{ title?: string; updateRowCount?: number; sourceQuery?: string; dataFormat?: ResultDataFormat; resultSet?: { id: string; rows?: Array<Array<any>>; singleEntity: boolean; hasMoreData: boolean; columns?: Array<{ dataKind?: string; entityName?: string; fullTypeName?: string; icon?: string; label?: string; maxLength?: number; name?: string; position: number; precision?: number; required: boolean; readOnly: boolean; readOnlyStatus?: string; scale?: number; typeName?: string; supportedOperations: Array<{ id: string; expression: string; argumentCount?: number }> }> } }> } };
|
||||
export type GetSqlExecuteTaskResultsMutation = { result: { duration: number; statusMessage?: string; filterText?: string; results: Array<{ title?: string; updateRowCount?: number; sourceQuery?: string; dataFormat?: ResultDataFormat; resultSet?: { id: string; rows?: Array<Array<any>>; singleEntity: boolean; hasMoreData: boolean; hasRowIdentifier: boolean; columns?: Array<{ dataKind?: string; entityName?: string; fullTypeName?: string; icon?: string; label?: string; maxLength?: number; name?: string; position: number; precision?: number; required: boolean; readOnly: boolean; readOnlyStatus?: string; scale?: number; typeName?: string; supportedOperations: Array<{ id: string; expression: string; argumentCount?: number }> }> } }> } };
|
||||
|
||||
export type GetSqlExecutionPlanResultMutationVariables = Exact<{
|
||||
taskId: Scalars['ID'];
|
||||
@@ -2114,7 +2115,7 @@ export type UpdateResultsDataBatchMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateResultsDataBatchMutation = { result: { duration: number; filterText?: string; results: Array<{ updateRowCount?: number; resultSet?: { id: string; rows?: Array<Array<any>>; singleEntity: boolean; hasMoreData: boolean } }> } };
|
||||
export type UpdateResultsDataBatchMutation = { result: { duration: number; filterText?: string; results: Array<{ updateRowCount?: number; resultSet?: { id: string; rows?: Array<Array<any>>; singleEntity: boolean; hasMoreData: boolean; hasRowIdentifier: boolean } }> } };
|
||||
|
||||
export type UpdateResultsDataBatchScriptMutationVariables = Exact<{
|
||||
connectionId: Scalars['ID'];
|
||||
@@ -3383,6 +3384,7 @@ export const GetSqlExecuteTaskResultsDocument = `
|
||||
rows
|
||||
singleEntity
|
||||
hasMoreData
|
||||
hasRowIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3434,6 +3436,7 @@ export const UpdateResultsDataBatchDocument = `
|
||||
rows
|
||||
singleEntity
|
||||
hasMoreData
|
||||
hasRowIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-11
@@ -8,7 +8,7 @@
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { ResultSetDataContentAction, ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataGridContextMenuService } from './DataGridContextMenuService';
|
||||
|
||||
@@ -37,21 +37,26 @@ export class DataGridContextMenuSaveContentService {
|
||||
return context.contextType === DataGridContextMenuService.cellContext;
|
||||
},
|
||||
onClick: async context => {
|
||||
const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction);
|
||||
try {
|
||||
await context.data.model.source.dataManager.downloadFileData(context.data.key, context.data.resultIndex);
|
||||
await content.downloadFileData(context.data.key);
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'data_grid_table_context_menu_save_value_error');
|
||||
}
|
||||
},
|
||||
isHidden: context => !context.data.model.source.dataManager.isContent(
|
||||
context.data.key,
|
||||
context.data.resultIndex
|
||||
),
|
||||
isDisabled: context => context.data.model.isLoading() || (
|
||||
!!context.data.model.source.dataManager.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual(
|
||||
context.data.key, context.data.model.source.dataManager.activeElement
|
||||
)
|
||||
),
|
||||
isHidden: context => {
|
||||
const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction);
|
||||
return !content.isDownloadable(context.data.key);
|
||||
},
|
||||
isDisabled: context => {
|
||||
const content = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataContentAction);
|
||||
|
||||
return context.data.model.isLoading() || (
|
||||
!!content.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual(
|
||||
context.data.key, content.activeElement
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import { computed, makeObservable, observable } from 'mobx';
|
||||
|
||||
import type { QuotasService } from '@cloudbeaver/core-app';
|
||||
import type { ConnectionExecutionContextService, IConnectionExecutionContext, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import type { IServiceInjector } from '@cloudbeaver/core-di';
|
||||
import type { ITask } from '@cloudbeaver/core-executor';
|
||||
@@ -16,9 +15,7 @@ import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo,
|
||||
|
||||
import { DocumentEditAction } from './DatabaseDataModel/Actions/Document/DocumentEditAction';
|
||||
import { ResultSetEditAction } from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
import { DatabaseDataManager } from './DatabaseDataModel/DatabaseDataManager';
|
||||
import { DatabaseDataSource } from './DatabaseDataModel/DatabaseDataSource';
|
||||
import type { IDatabaseDataManager } from './DatabaseDataModel/IDatabaseDataManager';
|
||||
import type { IDatabaseDataOptions } from './DatabaseDataModel/IDatabaseDataOptions';
|
||||
import type { IDatabaseResultSet } from './DatabaseDataModel/IDatabaseResultSet';
|
||||
|
||||
@@ -28,7 +25,6 @@ export interface IDataContainerOptions extends IDatabaseDataOptions {
|
||||
|
||||
export class ContainerDataSource extends DatabaseDataSource<IDataContainerOptions, IDatabaseResultSet> {
|
||||
currentTask: ITask<SqlExecuteInfo> | null;
|
||||
dataManager: IDatabaseDataManager;
|
||||
|
||||
get canCancel(): boolean {
|
||||
return this.currentTask?.cancellable || false;
|
||||
@@ -39,11 +35,9 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly connectionExecutionContextService: ConnectionExecutionContextService,
|
||||
private readonly quotasService: QuotasService,
|
||||
) {
|
||||
super(serviceInjector);
|
||||
|
||||
this.dataManager = new DatabaseDataManager(this.graphQLService, this.quotasService, this);
|
||||
this.currentTask = null;
|
||||
this.executionContext = null;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { EObjectFeature, NavNode, QuotasService } from '@cloudbeaver/core-app';
|
||||
import { EObjectFeature, NavNode } from '@cloudbeaver/core-app';
|
||||
import { ConnectionExecutionContextService, Connection } from '@cloudbeaver/core-connections';
|
||||
import { App, injectable } from '@cloudbeaver/core-di';
|
||||
import { AsyncTaskInfoService, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
@@ -29,7 +29,6 @@ export class DataViewerTableService {
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly connectionExecutionContextService: ConnectionExecutionContextService,
|
||||
private readonly dataViewerService: DataViewerService,
|
||||
private readonly quotasService: QuotasService
|
||||
) { }
|
||||
|
||||
has(tableId: string): boolean {
|
||||
@@ -58,7 +57,6 @@ export class DataViewerTableService {
|
||||
this.graphQLService,
|
||||
this.asyncTaskInfoService,
|
||||
this.connectionExecutionContextService,
|
||||
this.quotasService
|
||||
);
|
||||
|
||||
source
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 type { IResultSetContentValue } from './IResultSetContentValue';
|
||||
import type { IResultSetElementKey } from './IResultSetDataKey';
|
||||
|
||||
export interface IResultSetDataContentAction {
|
||||
activeElement: IResultSetElementKey | null;
|
||||
isContentTruncated: (content: IResultSetContentValue) => boolean;
|
||||
isDownloadable: (element: IResultSetElementKey) => boolean;
|
||||
getFileDataUrl: (element: IResultSetElementKey) => Promise<string>;
|
||||
resolveFileDataUrl: (element: IResultSetElementKey) => Promise<string>;
|
||||
retrieveFileDataUrlFromCache: (element: IResultSetElementKey) => string | undefined;
|
||||
downloadFileData: (element: IResultSetElementKey) => Promise<void>;
|
||||
clearCache: () => void;
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { QuotasService } from '@cloudbeaver/core-app';
|
||||
import { GraphQLService, ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
import { download, GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { DatabaseDataAction } from '../../DatabaseDataAction';
|
||||
import type { IDatabaseDataSource } from '../../IDatabaseDataSource';
|
||||
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
|
||||
import { databaseDataAction } from '../DatabaseDataActionDecorator';
|
||||
import type { IResultSetContentValue } from './IResultSetContentValue';
|
||||
import type { IResultSetDataContentAction } from './IResultSetDataContentAction';
|
||||
import type { IResultSetElementKey } from './IResultSetDataKey';
|
||||
import { isResultSetContentValue } from './isResultSetContentValue';
|
||||
import { ResultSetDataAction } from './ResultSetDataAction';
|
||||
import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils';
|
||||
import type { IResultSetValue } from './ResultSetFormatAction';
|
||||
import { ResultSetViewAction } from './ResultSetViewAction';
|
||||
|
||||
const RESULT_VALUE_PATH = 'sql-result-value';
|
||||
|
||||
@databaseDataAction()
|
||||
export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabaseResultSet>
|
||||
implements IResultSetDataContentAction {
|
||||
static dataFormat = [ResultDataFormat.Resultset];
|
||||
|
||||
private readonly view: ResultSetViewAction;
|
||||
private readonly data: ResultSetDataAction;
|
||||
|
||||
private readonly graphQLService: GraphQLService;
|
||||
private readonly quotasService: QuotasService;
|
||||
|
||||
private readonly cache: Map<string, string>;
|
||||
activeElement: IResultSetElementKey | null;
|
||||
|
||||
constructor(
|
||||
source: IDatabaseDataSource<any, IDatabaseResultSet>,
|
||||
result: IDatabaseResultSet,
|
||||
view: ResultSetViewAction,
|
||||
data: ResultSetDataAction,
|
||||
graphQLService: GraphQLService,
|
||||
quotasService: QuotasService,
|
||||
) {
|
||||
super(source, result);
|
||||
|
||||
this.view = view;
|
||||
this.data = data;
|
||||
|
||||
this.graphQLService = graphQLService;
|
||||
this.quotasService = quotasService;
|
||||
|
||||
this.cache = new Map();
|
||||
this.activeElement = null;
|
||||
|
||||
makeObservable<this, 'cache'>(this, {
|
||||
cache: observable,
|
||||
activeElement: observable.ref,
|
||||
});
|
||||
}
|
||||
|
||||
isContentTruncated(content: IResultSetContentValue) {
|
||||
return (content.contentLength ?? 0) > this.quotasService.getQuota('sqlBinaryPreviewMaxLength');
|
||||
}
|
||||
|
||||
isDownloadable(element: IResultSetElementKey) {
|
||||
const cellValue = this.view.getCellValue(element);
|
||||
return !!this.result.data?.hasRowIdentifier && isResultSetContentValue(cellValue);
|
||||
}
|
||||
|
||||
async getFileDataUrl(element: IResultSetElementKey) {
|
||||
const column = this.data.getColumn(element.column);
|
||||
const row = this.data.getRowValue(element.row);
|
||||
|
||||
if (!row || !column) {
|
||||
throw new Error('Failed to get value metadata information');
|
||||
}
|
||||
|
||||
const url = await this.source.runTask(
|
||||
async () => {
|
||||
try {
|
||||
this.activeElement = element;
|
||||
const fileName = await this.loadFileName(this.result, column.position, row);
|
||||
return this.generateFileDataUrl(fileName);
|
||||
} finally {
|
||||
this.activeElement = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
async resolveFileDataUrl(element: IResultSetElementKey) {
|
||||
const cache = this.retrieveFileDataUrlFromCache(element);
|
||||
|
||||
if (cache) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
const url = await this.getFileDataUrl(element);
|
||||
this.cache.set(this.getHash(element), url);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
retrieveFileDataUrlFromCache(element: IResultSetElementKey) {
|
||||
const hash = this.getHash(element);
|
||||
return this.cache.get(hash);
|
||||
}
|
||||
|
||||
async downloadFileData(element: IResultSetElementKey) {
|
||||
const url = await this.getFileDataUrl(element);
|
||||
download(url);
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
private generateFileDataUrl(fileName: string) {
|
||||
return `${GlobalConstants.serviceURI}/${RESULT_VALUE_PATH}/${fileName}`;
|
||||
}
|
||||
|
||||
private getHash(element: IResultSetElementKey) {
|
||||
return ResultSetDataKeysUtils.serializeElementKey(element);
|
||||
}
|
||||
|
||||
private async loadFileName(result: IDatabaseResultSet, columnIndex: number, row: IResultSetValue[]) {
|
||||
if (!result.id) {
|
||||
throw new Error("Result's id must be provided");
|
||||
}
|
||||
|
||||
const response = await this.graphQLService.sdk.getResultsetDataURL({
|
||||
resultsId: result.id,
|
||||
connectionId: result.connectionId,
|
||||
contextId: result.contextId,
|
||||
lobColumnIndex: columnIndex,
|
||||
row: {
|
||||
data: row,
|
||||
},
|
||||
});
|
||||
|
||||
return response.url;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 { makeObservable, observable } from 'mobx';
|
||||
|
||||
import type { QuotasService } from '@cloudbeaver/core-app';
|
||||
import type { GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import { download, GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { IResultSetContentValue } from './Actions/ResultSet/IResultSetContentValue';
|
||||
import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey';
|
||||
import { isResultSetContentValue } from './Actions/ResultSet/isResultSetContentValue';
|
||||
import { ResultSetDataAction } from './Actions/ResultSet/ResultSetDataAction';
|
||||
import { ResultSetDataKeysUtils } from './Actions/ResultSet/ResultSetDataKeysUtils';
|
||||
import type { IResultSetValue } from './Actions/ResultSet/ResultSetFormatAction';
|
||||
import { ResultSetViewAction } from './Actions/ResultSet/ResultSetViewAction';
|
||||
import type { IDatabaseDataManager } from './IDatabaseDataManager';
|
||||
import type { IDatabaseDataSource } from './IDatabaseDataSource';
|
||||
import type { IDatabaseResultSet } from './IDatabaseResultSet';
|
||||
|
||||
const RESULT_VALUE_PATH = 'sql-result-value';
|
||||
|
||||
export class DatabaseDataManager implements IDatabaseDataManager {
|
||||
private readonly cache: Map<string, string>;
|
||||
activeElement: IResultSetElementKey | null;
|
||||
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly quotasService: QuotasService,
|
||||
private readonly source: IDatabaseDataSource<any, any>
|
||||
) {
|
||||
this.cache = new Map();
|
||||
this.activeElement = null;
|
||||
|
||||
makeObservable<this, 'cache'>(this, {
|
||||
cache: observable,
|
||||
activeElement: observable.ref,
|
||||
});
|
||||
}
|
||||
|
||||
isContent(element: IResultSetElementKey, resultIndex: number) {
|
||||
const view = this.source.getAction(resultIndex, ResultSetViewAction);
|
||||
const cellValue = view.getCellValue(element);
|
||||
|
||||
return isResultSetContentValue(cellValue);
|
||||
}
|
||||
|
||||
isContentTruncated(content: IResultSetContentValue) {
|
||||
return (content.contentLength ?? 0) > this.quotasService.getQuota('sqlBinaryPreviewMaxLength');
|
||||
}
|
||||
|
||||
async getFileDataUrl(element: IResultSetElementKey, resultIndex: number) {
|
||||
const result = this.source.getResult(resultIndex);
|
||||
const data = this.source.getAction(resultIndex, ResultSetDataAction);
|
||||
const column = data.getColumn(element.column);
|
||||
const row = data.getRowValue(element.row);
|
||||
|
||||
if (!result || !row || !column) {
|
||||
throw new Error('Failed to get value metadata information');
|
||||
}
|
||||
|
||||
const url = await this.source.runTask(
|
||||
async () => {
|
||||
try {
|
||||
this.activeElement = element;
|
||||
const fileName = await this.loadFileName(result, column.position, row);
|
||||
return this.generateFileDataUrl(fileName);
|
||||
} finally {
|
||||
this.activeElement = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
async resolveFileDataUrl(element: IResultSetElementKey, resultIndex: number) {
|
||||
const cache = this.retrieveFileDataUrlFromCache(element, resultIndex);
|
||||
|
||||
if (cache) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
const url = await this.getFileDataUrl(element, resultIndex);
|
||||
this.cache.set(this.getHash(element, resultIndex), url);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
retrieveFileDataUrlFromCache(element: IResultSetElementKey, resultIndex: number) {
|
||||
const hash = this.getHash(element, resultIndex);
|
||||
return this.cache.get(hash);
|
||||
}
|
||||
|
||||
async downloadFileData(element: IResultSetElementKey, resultIndex: number) {
|
||||
const url = await this.getFileDataUrl(element, resultIndex);
|
||||
download(url);
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
private generateFileDataUrl(fileName: string) {
|
||||
return `${GlobalConstants.serviceURI}/${RESULT_VALUE_PATH}/${fileName}`;
|
||||
}
|
||||
|
||||
private getHash(element: IResultSetElementKey, resultIndex: number) {
|
||||
return `${ResultSetDataKeysUtils.serializeElementKey(element)}_${resultIndex}`;
|
||||
}
|
||||
|
||||
private async loadFileName(result: IDatabaseResultSet, columnIndex: number, row: IResultSetValue[]) {
|
||||
if (!result.id) {
|
||||
throw new Error("Result's id must be provided");
|
||||
}
|
||||
|
||||
const response = await this.graphQLService.sdk.getResultsetDataURL({
|
||||
resultsId: result.id,
|
||||
connectionId: result.connectionId,
|
||||
contextId: result.contextId,
|
||||
lobColumnIndex: columnIndex,
|
||||
row: {
|
||||
data: row,
|
||||
},
|
||||
});
|
||||
|
||||
return response.url;
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,11 @@ import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
import { DatabaseDataActions } from './DatabaseDataActions';
|
||||
import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction';
|
||||
import type { IDatabaseDataActions } from './IDatabaseDataActions';
|
||||
import type { IDatabaseDataManager } from './IDatabaseDataManager';
|
||||
import type { IDatabaseDataResult } from './IDatabaseDataResult';
|
||||
import { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource';
|
||||
|
||||
export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseDataResult>
|
||||
implements IDatabaseDataSource<TOptions, TResult> {
|
||||
abstract readonly dataManager: IDatabaseDataManager;
|
||||
access: DatabaseDataAccessMode;
|
||||
dataFormat: ResultDataFormat;
|
||||
supportedDataFormats: ResultDataFormat[];
|
||||
@@ -176,7 +174,6 @@ implements IDatabaseDataSource<TOptions, TResult> {
|
||||
setResults(results: TResult[]): this {
|
||||
this.actions.updateResults(results);
|
||||
this.results = results;
|
||||
this.dataManager.clearCache();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 type { IResultSetContentValue } from './Actions/ResultSet/IResultSetContentValue';
|
||||
import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey';
|
||||
|
||||
export interface IDatabaseDataManager {
|
||||
activeElement: IResultSetElementKey | null;
|
||||
isContent: (element: IResultSetElementKey, resultIndex: number) => boolean;
|
||||
isContentTruncated: (content: IResultSetContentValue) => boolean;
|
||||
getFileDataUrl: (element: IResultSetElementKey, resultIndex: number) => Promise<string>;
|
||||
resolveFileDataUrl: (element: IResultSetElementKey, resultIndex: number) => Promise<string>;
|
||||
retrieveFileDataUrlFromCache: (element: IResultSetElementKey, resultIndex: number) => string | undefined;
|
||||
downloadFileData: (element: IResultSetElementKey, resultIndex: number) => Promise<void>;
|
||||
clearCache: () => void;
|
||||
}
|
||||
@@ -10,10 +10,8 @@ import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections'
|
||||
import type { IServiceInjector } from '@cloudbeaver/core-di';
|
||||
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { DatabaseDataManager } from './DatabaseDataManager';
|
||||
import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction';
|
||||
import type { IDatabaseDataActions } from './IDatabaseDataActions';
|
||||
import type { IDatabaseDataManager } from './IDatabaseDataManager';
|
||||
import type { IDatabaseDataResult } from './IDatabaseDataResult';
|
||||
|
||||
export interface IRequestInfo {
|
||||
@@ -29,7 +27,6 @@ export enum DatabaseDataAccessMode {
|
||||
}
|
||||
|
||||
export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResult = IDatabaseDataResult> {
|
||||
readonly dataManager: IDatabaseDataManager;
|
||||
readonly access: DatabaseDataAccessMode;
|
||||
readonly dataFormat: ResultDataFormat;
|
||||
readonly supportedDataFormats: ResultDataFormat[];
|
||||
|
||||
+24
-19
@@ -21,6 +21,7 @@ import { bytesToSize, download, getMIME, isImageFormat, isValidUrl } from '@clou
|
||||
|
||||
import type { IResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetContentValue';
|
||||
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
|
||||
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import { ResultSetDataKeysUtils } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils';
|
||||
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
|
||||
import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction';
|
||||
@@ -112,6 +113,8 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
const quotasService = useService(QuotasService);
|
||||
const style = useStyles(styles);
|
||||
|
||||
const content = model.source.getAction(resultIndex, ResultSetDataContentAction);
|
||||
|
||||
const state = useObservableRef(() => ({
|
||||
get selectedCell() {
|
||||
const selection = this.model.source.getAction(this.resultIndex, ResultSetSelectAction);
|
||||
@@ -139,17 +142,17 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
return '';
|
||||
},
|
||||
get savedSrc() {
|
||||
return this.model.source.dataManager.retrieveFileDataUrlFromCache(this.selectedCell, this.resultIndex);
|
||||
},
|
||||
get content() {
|
||||
return this.model.source.dataManager.isContent(this.selectedCell, this.resultIndex);
|
||||
return content.retrieveFileDataUrlFromCache(this.selectedCell);
|
||||
},
|
||||
get canSave() {
|
||||
return this.content || !!this.src;
|
||||
if (this.truncated) {
|
||||
return content.isDownloadable(this.selectedCell);
|
||||
}
|
||||
|
||||
return !!this.src;
|
||||
},
|
||||
get truncated() {
|
||||
return isResultSetContentValue(this.cellValue)
|
||||
&& this.model.source.dataManager.isContentTruncated(this.cellValue);
|
||||
return isResultSetContentValue(this.cellValue) && content.isContentTruncated(this.cellValue);
|
||||
},
|
||||
stretch: false,
|
||||
toggleStretch() {
|
||||
@@ -157,10 +160,10 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
},
|
||||
async save() {
|
||||
try {
|
||||
if (!this.content) {
|
||||
download(this.src, '', true);
|
||||
if (this.truncated) {
|
||||
await content.downloadFileData(this.selectedCell);
|
||||
} else {
|
||||
await this.model.source.dataManager.downloadFileData(this.selectedCell, this.resultIndex);
|
||||
download(this.src, '', true);
|
||||
}
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
|
||||
@@ -187,7 +190,7 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
await model.source.dataManager.resolveFileDataUrl(state.selectedCell, resultIndex);
|
||||
await content.resolveFileDataUrl(state.selectedCell);
|
||||
} catch (exception: any) {
|
||||
notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
|
||||
}
|
||||
@@ -196,14 +199,16 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
return styled(style)(
|
||||
<container>
|
||||
<QuotaPlaceholder limit={limit} size={valueSize}>
|
||||
<Button
|
||||
disabled={loading}
|
||||
loading={!!model.source.dataManager.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual(
|
||||
model.source.dataManager.activeElement, state.selectedCell)}
|
||||
onClick={load}
|
||||
>
|
||||
{translate('ui_download')}
|
||||
</Button>
|
||||
{content.isDownloadable(state.selectedCell) && (
|
||||
<Button
|
||||
disabled={loading}
|
||||
loading={!!content.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual(
|
||||
content.activeElement, state.selectedCell)}
|
||||
onClick={load}
|
||||
>
|
||||
{translate('ui_download')}
|
||||
</Button>
|
||||
)}
|
||||
</QuotaPlaceholder>
|
||||
<Tools loading={loading} onSave={save} />
|
||||
</container>
|
||||
|
||||
+6
-3
@@ -22,6 +22,7 @@ import { CodeEditorLoader } from '@cloudbeaver/plugin-codemirror';
|
||||
|
||||
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
|
||||
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
|
||||
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
|
||||
@@ -81,6 +82,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
const quotasService = useService(QuotasService);
|
||||
const textValuePresentationService = useService(TextValuePresentationService);
|
||||
const style = useStyles(styles, BASE_CONTAINERS_STYLES, UNDERLINE_TAB_STYLES, VALUE_PANEL_TOOLS_STYLES);
|
||||
|
||||
const state = useObservableRef(() => ({
|
||||
currentContentType: 'text/plain',
|
||||
lastContentType: 'text/plain',
|
||||
@@ -99,6 +101,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
|
||||
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const editor = model.source.getAction(resultIndex, ResultSetEditAction);
|
||||
const content = model.source.getAction(resultIndex, ResultSetDataContentAction);
|
||||
|
||||
const focusCell = selection.getFocusedElement();
|
||||
|
||||
@@ -123,7 +126,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
|
||||
|
||||
if (isResultSetContentValue(value)) {
|
||||
valueTruncated = model.source.dataManager.isContentTruncated(value);
|
||||
valueTruncated = content.isContentTruncated(value);
|
||||
|
||||
if (valueTruncated) {
|
||||
limit = bytesToSize(quotasService.getQuota('sqlBinaryPreviewMaxLength'));
|
||||
@@ -162,7 +165,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
}
|
||||
|
||||
try {
|
||||
await model.source.dataManager.downloadFileData(firstSelectedCell, resultIndex);
|
||||
await content.downloadFileData(firstSelectedCell);
|
||||
} catch (exception) {
|
||||
notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error');
|
||||
}
|
||||
@@ -170,7 +173,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
|
||||
const useCodeEditor = state.currentContentType !== 'text/plain';
|
||||
const autoFormat = firstSelectedCell && !editor.isElementEdited(firstSelectedCell);
|
||||
const canSave = firstSelectedCell && model.source.dataManager.isContent(firstSelectedCell, resultIndex);
|
||||
const canSave = !!firstSelectedCell && content.isDownloadable(firstSelectedCell);
|
||||
|
||||
return styled(style)(
|
||||
<container>
|
||||
|
||||
@@ -16,6 +16,7 @@ export * from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
|
||||
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetViewAction';
|
||||
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
export * from './DatabaseDataModel/Actions/DatabaseDataActionDecorator';
|
||||
export * from './DatabaseDataModel/Actions/DatabaseEditAction';
|
||||
export * from './DatabaseDataModel/Actions/DatabaseSelectAction';
|
||||
@@ -38,8 +39,6 @@ export * from './DatabaseDataModel/IDatabaseDataSource';
|
||||
export * from './DatabaseDataModel/IDatabaseResultSet';
|
||||
export * from './DatabaseDataModel/Order';
|
||||
export * from './DataViewerService';
|
||||
export * from './DatabaseDataModel/DatabaseDataManager';
|
||||
export * from './DatabaseDataModel/IDatabaseDataManager';
|
||||
|
||||
// All Services and Components that is provided by this plugin should be exported here
|
||||
export * from './TableViewer/TableViewerStorageService';
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
|
||||
import type { QuotasService } from '@cloudbeaver/core-app';
|
||||
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import type { IServiceInjector } from '@cloudbeaver/core-di';
|
||||
import type { ITask } from '@cloudbeaver/core-executor';
|
||||
import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo, SqlQueryResults, UpdateResultsDataBatchMutationVariables } from '@cloudbeaver/core-sdk';
|
||||
import { DatabaseDataManager, DatabaseDataSource, DocumentEditAction, IDatabaseDataManager, IDatabaseDataOptions, IDatabaseResultSet, IRequestInfo, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { DatabaseDataSource, DocumentEditAction, IDatabaseDataOptions, IDatabaseResultSet, IRequestInfo, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
export interface IDataQueryOptions extends IDatabaseDataOptions {
|
||||
query: string;
|
||||
@@ -24,7 +23,6 @@ export interface IQueryRequestInfo extends IRequestInfo {
|
||||
}
|
||||
|
||||
export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IDatabaseResultSet> {
|
||||
readonly dataManager: IDatabaseDataManager;
|
||||
currentTask: ITask<SqlExecuteInfo> | null;
|
||||
requestInfo: IQueryRequestInfo;
|
||||
|
||||
@@ -36,11 +34,9 @@ export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IData
|
||||
readonly serviceInjector: IServiceInjector,
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly quotasService: QuotasService
|
||||
) {
|
||||
super(serviceInjector);
|
||||
|
||||
this.dataManager = new DatabaseDataManager(this.graphQLService, this.quotasService, this);
|
||||
this.currentTask = null;
|
||||
this.requestInfo = {
|
||||
requestDuration: 0,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { QuotasService } from '@cloudbeaver/core-app';
|
||||
import { ConnectionExecutionContextService, ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { App, injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
@@ -50,7 +49,6 @@ export class SqlQueryService {
|
||||
private readonly asyncTaskInfoService: AsyncTaskInfoService,
|
||||
private readonly dataViewerDataChangeConfirmationService: DataViewerDataChangeConfirmationService,
|
||||
private readonly dataViewerService: DataViewerService,
|
||||
private readonly quotasService: QuotasService
|
||||
) {
|
||||
this.statisticsMap = new Map();
|
||||
|
||||
@@ -88,7 +86,6 @@ export class SqlQueryService {
|
||||
this.app.getServiceInjector(),
|
||||
this.graphQLService,
|
||||
this.asyncTaskInfoService,
|
||||
this.quotasService
|
||||
);
|
||||
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
|
||||
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
|
||||
@@ -183,7 +180,6 @@ export class SqlQueryService {
|
||||
this.app.getServiceInjector(),
|
||||
this.graphQLService,
|
||||
this.asyncTaskInfoService,
|
||||
this.quotasService
|
||||
);
|
||||
model = this.tableViewerStorageService.add(new DatabaseDataModel(source));
|
||||
this.dataViewerDataChangeConfirmationService.trackTableDataUpdate(model.id);
|
||||
|
||||
Reference in New Issue
Block a user