Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mutation getSqlExecuteTaskResults(
rows
singleEntity
hasMoreData
hasRowIdentifier
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mutation updateResultsDataBatch(
rows
singleEntity
hasMoreData
hasRowIdentifier
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions webapp/packages/core-sdk/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -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'];
Expand All @@ -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'];
Expand Down Expand Up @@ -3383,6 +3384,7 @@ export const GetSqlExecuteTaskResultsDocument = `
rows
singleEntity
hasMoreData
hasRowIdentifier
}
}
}
Expand Down Expand Up @@ -3434,6 +3436,7 @@ export const UpdateResultsDataBatchDocument = `
rows
singleEntity
hasMoreData
hasRowIdentifier
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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
)
);
},
}
);
}
Expand Down
6 changes: 0 additions & 6 deletions webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@

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';
import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo, SqlQueryResults, UpdateResultsDataBatchMutationVariables } from '@cloudbeaver/core-sdk';

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';

Expand All @@ -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;
Expand All @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -58,7 +57,6 @@ export class DataViewerTableService {
this.graphQLService,
this.asyncTaskInfoService,
this.connectionExecutionContextService,
this.quotasService
);

source
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading