mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
refactor: CB-1093execution contexts
This commit is contained in:
@@ -50,6 +50,8 @@ import {
|
||||
} from '@cloudbeaver/core-authentication';
|
||||
import { BlocksLocaleService } from '@cloudbeaver/core-blocks';
|
||||
import {
|
||||
ConnectionExecutionContextResource,
|
||||
ConnectionExecutionContextService,
|
||||
ConnectionsManagerService,
|
||||
ConnectionInfoResource,
|
||||
ContainerResource,
|
||||
@@ -156,6 +158,8 @@ export const coreManifest: PluginManifest = {
|
||||
ContainerResource,
|
||||
DBDriverResource,
|
||||
NetworkHandlerResource,
|
||||
ConnectionExecutionContextResource,
|
||||
ConnectionExecutionContextService,
|
||||
ConnectionsManagerService,
|
||||
ScreenService,
|
||||
AppScreenService,
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { ITask, TaskScheduler } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { ConnectionExecutionContextResource } from './ConnectionExecutionContextResource';
|
||||
import type { IConnectionExecutionContext } from './IConnectionExecutionContext';
|
||||
import type { IConnectionExecutionContextInfo } from './IConnectionExecutionContextInfo';
|
||||
|
||||
export class ConnectionExecutionContext implements IConnectionExecutionContext {
|
||||
get context(): IConnectionExecutionContextInfo | undefined {
|
||||
return this.connectionExecutionContextResource.get(this.contextId);
|
||||
}
|
||||
|
||||
get executing(): boolean {
|
||||
return this.scheduler.isExecuting(this.contextId);
|
||||
}
|
||||
|
||||
get cancellable(): boolean {
|
||||
return this.currentTask?.cancellable || false;
|
||||
}
|
||||
|
||||
private currentTask: ITask<any> | null;
|
||||
|
||||
constructor(
|
||||
private scheduler: TaskScheduler<string>,
|
||||
private connectionExecutionContextResource: ConnectionExecutionContextResource,
|
||||
private contextId: string
|
||||
) {
|
||||
this.currentTask = null;
|
||||
makeObservable<this, 'currentTask'>(this, {
|
||||
currentTask: observable.ref,
|
||||
});
|
||||
}
|
||||
|
||||
run<T>(task: () => Promise<T>, cancel?: () => Promise<any> | void): ITask<T> {
|
||||
if (!this.context) {
|
||||
throw new Error('Execution Context not found');
|
||||
}
|
||||
|
||||
this.currentTask = this.scheduler
|
||||
.schedule(this.contextId, task, { cancel })
|
||||
.finally(() => {
|
||||
this.currentTask = null;
|
||||
});
|
||||
|
||||
return this.currentTask;
|
||||
}
|
||||
|
||||
async cancel(): Promise<void> {
|
||||
await this.scheduler.cancel(this.contextId);
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.cancel();
|
||||
await this.connectionExecutionContextResource.destroy(this.contextId);
|
||||
}
|
||||
|
||||
async update(defaultCatalog?: string, defaultSchema?: string): Promise<void> {
|
||||
if (!this.context) {
|
||||
throw new Error('Execution Context not found');
|
||||
}
|
||||
|
||||
await this.connectionExecutionContextResource.update(this.contextId, defaultCatalog, defaultSchema);
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { injectable } from '@cloudbeaver/core-di';
|
||||
import { SessionDataResource } from '@cloudbeaver/core-root';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedMapResource,
|
||||
resourceKeyList,
|
||||
ResourceKey,
|
||||
ResourceKeyUtils,
|
||||
ResourceKeyList,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
import { flat, MetadataMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ConnectionInfoResource } from '../ConnectionInfoResource';
|
||||
import type { IConnectionExecutionContextInfo } from './IConnectionExecutionContextInfo';
|
||||
|
||||
@injectable()
|
||||
export class ConnectionExecutionContextResource extends CachedMapResource<string, IConnectionExecutionContextInfo> {
|
||||
static keyAll = resourceKeyList(['all'], 'all');
|
||||
private loadedKeyMetadata: MetadataMap<string, boolean>;
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private connectionInfoResource: ConnectionInfoResource,
|
||||
sessionDataResource: SessionDataResource
|
||||
) {
|
||||
super();
|
||||
this.loadedKeyMetadata = new MetadataMap(() => false);
|
||||
|
||||
sessionDataResource.onDataOutdated.addHandler(() => this.markOutdated());
|
||||
sessionDataResource.onDataUpdate.addHandler(async () => {
|
||||
await this.load(ConnectionExecutionContextResource.keyAll);
|
||||
});
|
||||
connectionInfoResource.onItemAdd.addHandler(this.updateConnectionContexts.bind(this));
|
||||
connectionInfoResource.onItemDelete.addHandler(this.deleteConnectionContexts.bind(this));
|
||||
|
||||
this.addAlias(ConnectionExecutionContextResource.keyAll, key => {
|
||||
if (this.keys.length > 0) {
|
||||
return resourceKeyList(this.keys, ConnectionExecutionContextResource.keyAll.mark);
|
||||
}
|
||||
return ConnectionExecutionContextResource.keyAll;
|
||||
});
|
||||
}
|
||||
|
||||
has(id: string): boolean {
|
||||
if (this.loadedKeyMetadata.has(id)) {
|
||||
return this.loadedKeyMetadata.get(id);
|
||||
}
|
||||
|
||||
return this.data.has(id);
|
||||
}
|
||||
|
||||
updateConnectionContexts(key: ResourceKey<string>): void {
|
||||
this.delete(
|
||||
resourceKeyList(
|
||||
flat(ResourceKeyUtils.map(
|
||||
key,
|
||||
connectionId => this.values.filter(context => {
|
||||
const connection = this.connectionInfoResource.get(connectionId);
|
||||
return context.connectionId === connectionId && !connection?.connected;
|
||||
})
|
||||
)).map(context => context.id)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
deleteConnectionContexts(key: ResourceKey<string>): void {
|
||||
this.delete(
|
||||
resourceKeyList(
|
||||
flat(ResourceKeyUtils.map(
|
||||
key,
|
||||
connectionId => this.values.filter(context => context.connectionId === connectionId)
|
||||
)).map(context => context.id)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async create(
|
||||
connectionId: string,
|
||||
defaultCatalog?: string,
|
||||
defaultSchema?: string
|
||||
): Promise<IConnectionExecutionContextInfo> {
|
||||
return await this.performUpdate('', [], async () => {
|
||||
const { context } = await this.graphQLService.sdk.executionContextCreate({
|
||||
connectionId,
|
||||
defaultCatalog,
|
||||
defaultSchema,
|
||||
});
|
||||
|
||||
this.updateContexts(context);
|
||||
|
||||
return this.get(context.id)!;
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
contextId: string,
|
||||
defaultCatalog?: string,
|
||||
defaultSchema?: string
|
||||
): Promise<IConnectionExecutionContextInfo> {
|
||||
const context = this.get(contextId);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Execution context not found');
|
||||
}
|
||||
|
||||
await this.performUpdate(contextId, [], async () => {
|
||||
await this.graphQLService.sdk.executionContextUpdate({
|
||||
contextId,
|
||||
connectionId: context.connectionId,
|
||||
defaultCatalog,
|
||||
defaultSchema,
|
||||
});
|
||||
|
||||
context.defaultCatalog = defaultCatalog;
|
||||
context.defaultSchema = defaultSchema;
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async destroy(contextId: string): Promise<void> {
|
||||
const context = this.get(contextId);
|
||||
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.performUpdate(contextId, [], async () => {
|
||||
await this.graphQLService.sdk.executionContextDestroy({
|
||||
contextId,
|
||||
connectionId: context.connectionId,
|
||||
});
|
||||
});
|
||||
|
||||
this.delete(contextId);
|
||||
}
|
||||
|
||||
async loadAll(): Promise<IConnectionExecutionContextInfo[]> {
|
||||
this.resetIncludes();
|
||||
await this.load(ConnectionExecutionContextResource.keyAll);
|
||||
|
||||
return this.values;
|
||||
}
|
||||
|
||||
async refreshAll(): Promise<IConnectionExecutionContextInfo[]> {
|
||||
this.resetIncludes();
|
||||
await this.refresh(ConnectionExecutionContextResource.keyAll);
|
||||
return this.values;
|
||||
}
|
||||
|
||||
refreshAllLazy(): void {
|
||||
this.resetIncludes();
|
||||
this.markOutdated(ConnectionExecutionContextResource.keyAll);
|
||||
this.loadedKeyMetadata.set(ConnectionExecutionContextResource.keyAll.list[0], false);
|
||||
}
|
||||
|
||||
protected async loader(
|
||||
key: ResourceKey<string>
|
||||
): Promise<Map<string, IConnectionExecutionContextInfo>> {
|
||||
const all = ResourceKeyUtils.hasMark(key, ConnectionExecutionContextResource.keyAll.mark);
|
||||
|
||||
await ResourceKeyUtils.forEachAsync(key, async contextId => {
|
||||
const { contexts } = await this.graphQLService.sdk.executionContextList({
|
||||
contextId: all ? undefined : contextId,
|
||||
// connectionId
|
||||
});
|
||||
|
||||
const key = this.updateContexts(...contexts);
|
||||
|
||||
for (const contextId of this.keys) {
|
||||
if (!ResourceKeyUtils.includes(key, contextId)) {
|
||||
this.delete(contextId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (all) {
|
||||
this.loadedKeyMetadata.set(ConnectionExecutionContextResource.keyAll.list[0], true);
|
||||
}
|
||||
return this.data;
|
||||
}
|
||||
|
||||
private updateContexts(...contexts: IConnectionExecutionContextInfo[]): ResourceKeyList<string> {
|
||||
const key = resourceKeyList(contexts.map(context => context.id));
|
||||
|
||||
const oldContexts = this.get(key);
|
||||
this.set(key, oldContexts.map((context, i) => ({ ...context, ...contexts[i] })));
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { injectable } from '@cloudbeaver/core-di';
|
||||
import { TaskScheduler } from '@cloudbeaver/core-executor';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ResourceKeyUtils } from '../../../core-sdk/src';
|
||||
import { ConnectionExecutionContext } from './ConnectionExecutionContext';
|
||||
import { ConnectionExecutionContextResource } from './ConnectionExecutionContextResource';
|
||||
|
||||
@injectable()
|
||||
export class ConnectionExecutionContextService {
|
||||
private contexts: MetadataMap<string, ConnectionExecutionContext>;
|
||||
protected scheduler: TaskScheduler<string>;
|
||||
|
||||
constructor(
|
||||
private connectionExecutionContextResource: ConnectionExecutionContextResource
|
||||
) {
|
||||
this.contexts = new MetadataMap(contextId => new ConnectionExecutionContext(
|
||||
this.scheduler,
|
||||
this.connectionExecutionContextResource,
|
||||
contextId
|
||||
));
|
||||
this.scheduler = new TaskScheduler((a, b) => a === b);
|
||||
this.connectionExecutionContextResource.onItemDelete.addHandler(
|
||||
key => ResourceKeyUtils.forEach(key, contextId => this.contexts.delete(contextId))
|
||||
);
|
||||
}
|
||||
|
||||
get(contextId: string): ConnectionExecutionContext | undefined {
|
||||
if (!this.connectionExecutionContextResource.has(contextId)) {
|
||||
return undefined;
|
||||
}
|
||||
return this.contexts.get(contextId);
|
||||
}
|
||||
|
||||
async create(
|
||||
connectionId: string,
|
||||
defaultCatalog?: string,
|
||||
defaultSchema?: string
|
||||
): Promise<ConnectionExecutionContext> {
|
||||
const context = await this.connectionExecutionContextResource.create(connectionId, defaultCatalog, defaultSchema);
|
||||
|
||||
return this.contexts.get(context.id);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 { ITask } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { IConnectionExecutionContextInfo } from './IConnectionExecutionContextInfo';
|
||||
|
||||
export interface IConnectionExecutionContext {
|
||||
readonly context: IConnectionExecutionContextInfo | undefined;
|
||||
readonly executing: boolean;
|
||||
readonly cancellable: boolean;
|
||||
|
||||
run: <T>(task: () => Promise<T>, cancel?: () => Promise<any> | void) => ITask<T>;
|
||||
cancel: () => Promise<void>;
|
||||
destroy: () => Promise<void>;
|
||||
update: (defaultCatalog?: string, defaultSchema?: string) => Promise<void>;
|
||||
}
|
||||
+3
-6
@@ -6,9 +6,6 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export interface IDatabaseExecutionContext {
|
||||
connectionId: string;
|
||||
contextId: string;
|
||||
objectCatalogId?: string;
|
||||
objectSchemaId?: string;
|
||||
}
|
||||
import type { SqlContextInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
export interface IConnectionExecutionContextInfo extends SqlContextInfo { }
|
||||
@@ -1,3 +1,16 @@
|
||||
export * from './Administration/Connections/ConnectionsAdministration';
|
||||
export * from './Administration/Connections/ConnectionsAdministrationNavService';
|
||||
export * from './Administration/Connections/ConnectionsAdministrationService';
|
||||
export * from './Administration/Connections/CreateConnection/Manual/ConnectionManualService';
|
||||
export * from './Administration/Connections/CreateConnection/Search/ConnectionSearchService';
|
||||
export * from './Administration/Connections/CreateConnection/CreateConnectionBaseBootstrap';
|
||||
export * from './Administration/Connections/CreateConnectionService';
|
||||
export * from './Administration/ConnectionsResource';
|
||||
export * from './ConnectionExecutionContext/ConnectionExecutionContext';
|
||||
export * from './ConnectionExecutionContext/ConnectionExecutionContextResource';
|
||||
export * from './ConnectionExecutionContext/ConnectionExecutionContextService';
|
||||
export * from './ConnectionExecutionContext/IConnectionExecutionContext';
|
||||
export * from './ConnectionExecutionContext/IConnectionExecutionContextInfo';
|
||||
export * from './ConnectionForm/Options/ConnectionOptionsTabService';
|
||||
export * from './ConnectionForm/DriverProperties/ConnectionDriverPropertiesTabService';
|
||||
export * from './ConnectionForm/SSH/ConnectionSSHTabService';
|
||||
@@ -12,14 +25,6 @@ export * from './ConnectionForm/ConnectionFormState';
|
||||
export * from './ConnectionForm/connectionFormStateContext';
|
||||
export * from './ConnectionForm/IConnectionFormProps';
|
||||
export * from './ConnectionForm/useConnectionFormState';
|
||||
export * from './Administration/Connections/ConnectionsAdministration';
|
||||
export * from './Administration/Connections/ConnectionsAdministrationNavService';
|
||||
export * from './Administration/Connections/ConnectionsAdministrationService';
|
||||
export * from './Administration/Connections/CreateConnection/Manual/ConnectionManualService';
|
||||
export * from './Administration/Connections/CreateConnection/Search/ConnectionSearchService';
|
||||
export * from './Administration/Connections/CreateConnection/CreateConnectionBaseBootstrap';
|
||||
export * from './Administration/Connections/CreateConnectionService';
|
||||
export * from './Administration/ConnectionsResource';
|
||||
|
||||
export * from './extensions/IConnectionProvider';
|
||||
export * from './extensions/IConnectionSetter';
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
mutation executionContextCreate(
|
||||
$connectionId: ID!
|
||||
$defaultCatalog: String
|
||||
$defaultSchema: String
|
||||
) {
|
||||
context: sqlContextCreate(
|
||||
connectionId: $connectionId
|
||||
defaultCatalog: $defaultCatalog
|
||||
defaultSchema: $defaultSchema
|
||||
) {
|
||||
id
|
||||
connectionId
|
||||
defaultCatalog
|
||||
defaultSchema
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
mutation executionContextDestroy(
|
||||
$connectionId: ID!
|
||||
$contextId: ID!
|
||||
) {
|
||||
sqlContextDestroy(
|
||||
connectionId: $connectionId
|
||||
contextId: $contextId
|
||||
)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
query executionContextList(
|
||||
$connectionId: ID
|
||||
$contextId: ID
|
||||
) {
|
||||
contexts: sqlListContexts(
|
||||
connectionId: $connectionId
|
||||
contextId: $contextId
|
||||
) {
|
||||
id
|
||||
connectionId
|
||||
defaultCatalog
|
||||
defaultSchema
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
mutation executionContextUpdate(
|
||||
$connectionId: ID!
|
||||
$contextId: ID!
|
||||
$defaultCatalog: ID
|
||||
$defaultSchema: ID
|
||||
) {
|
||||
context: sqlContextSetDefaults(
|
||||
connectionId: $connectionId
|
||||
contextId: $contextId
|
||||
defaultCatalog: $defaultCatalog
|
||||
defaultSchema: $defaultSchema
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
mutation sqlContextCreate(
|
||||
$connectionId: ID!
|
||||
$defaultCatalog: String
|
||||
$defaultSchema: String
|
||||
) {
|
||||
context: sqlContextCreate(
|
||||
connectionId: $connectionId
|
||||
defaultCatalog: $defaultCatalog
|
||||
defaultSchema: $defaultSchema
|
||||
) {
|
||||
id
|
||||
defaultCatalog
|
||||
defaultSchema
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
mutation sqlContextDestroy(
|
||||
$connectionId: ID!
|
||||
$contextId: ID!
|
||||
) {
|
||||
sqlContextDestroy(
|
||||
connectionId: $connectionId
|
||||
contextId: $contextId
|
||||
)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
mutation sqlContextSetDefaults(
|
||||
$connectionId: ID!
|
||||
$contextId: ID!
|
||||
$defaultCatalog: ID
|
||||
$defaultSchema: ID
|
||||
) {
|
||||
context: sqlContextSetDefaults(
|
||||
connectionId: $connectionId
|
||||
contextId: $contextId
|
||||
defaultCatalog: $defaultCatalog
|
||||
defaultSchema: $defaultSchema
|
||||
)
|
||||
}
|
||||
+746
-723
File diff suppressed because it is too large
Load Diff
@@ -192,5 +192,9 @@
|
||||
text-decoration: underline;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.cm-s-material .active-query {
|
||||
background-color: $active-query;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ $link: #219;
|
||||
$error: #f00;
|
||||
$delimiter: rgb(238, 204, 100);
|
||||
$cursor: #fff;
|
||||
$active-query: $mdc-theme-secondary;
|
||||
|
||||
:global .CodeMirror-hints {
|
||||
@include mdc-elevation(3);
|
||||
|
||||
@@ -31,6 +31,7 @@ $link: #905;
|
||||
$error: #f00;
|
||||
$delimiter: rgb(238, 204, 100);
|
||||
$cursor: #000;
|
||||
$active-query: $mdc-theme-secondary;
|
||||
|
||||
:global .CodeMirror-hints {
|
||||
@include mdc-elevation(3);
|
||||
|
||||
@@ -81,7 +81,7 @@ export class DataExportMenuService {
|
||||
|
||||
this.commonDialogService.open(DataExportDialog, {
|
||||
connectionId: source.options.connectionId,
|
||||
contextId: context.data.model.source.executionContext?.contextId,
|
||||
contextId: context.data.model.source.executionContext?.context?.id,
|
||||
containerNodePath: source.options.containerNodePath,
|
||||
resultId: result.id,
|
||||
sourceName: source.options.query,
|
||||
|
||||
@@ -6,16 +6,14 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
|
||||
import type { ConnectionExecutionContextService, IConnectionExecutionContext } from '@cloudbeaver/core-connections';
|
||||
import type { NotificationService } from '@cloudbeaver/core-events';
|
||||
import type { GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import { EDeferredState } from '@cloudbeaver/core-utils';
|
||||
import type { ITask } from '@cloudbeaver/core-executor';
|
||||
import type { GraphQLService, SqlExecuteInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DatabaseDataEditor } from './DatabaseDataModel/DatabaseDataEditor';
|
||||
import { DatabaseDataSource } from './DatabaseDataModel/DatabaseDataSource';
|
||||
import type { IDatabaseDataOptions } from './DatabaseDataModel/IDatabaseDataOptions';
|
||||
import type { IDatabaseExecutionContext } from './DatabaseDataModel/IDatabaseExecutionContext';
|
||||
import type { IDatabaseResultSet } from './DatabaseDataModel/IDatabaseResultSet';
|
||||
import { FetchTableDataAsyncProcess } from './FetchTableDataAsyncProcess';
|
||||
|
||||
@@ -24,23 +22,20 @@ export interface IDataContainerOptions extends IDatabaseDataOptions {
|
||||
}
|
||||
|
||||
export class ContainerDataSource extends DatabaseDataSource<IDataContainerOptions, IDatabaseResultSet> {
|
||||
currentFetchTableProcess: FetchTableDataAsyncProcess | null;
|
||||
currentTask: ITask<SqlExecuteInfo> | null;
|
||||
|
||||
get canCancel(): boolean {
|
||||
return this.currentFetchTableProcess?.getState() === EDeferredState.PENDING;
|
||||
return this.currentTask?.cancellable || false;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private notificationService: NotificationService,
|
||||
private connectionExecutionContextService: ConnectionExecutionContextService
|
||||
) {
|
||||
super();
|
||||
|
||||
makeObservable(this, {
|
||||
currentFetchTableProcess: observable,
|
||||
});
|
||||
|
||||
this.currentFetchTableProcess = null;
|
||||
this.currentTask = null;
|
||||
this.executionContext = null;
|
||||
this.editor = new DatabaseDataEditor();
|
||||
}
|
||||
@@ -49,17 +44,18 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
return !this.getResult(resultIndex)?.data && this.error === null;
|
||||
}
|
||||
|
||||
cancel(): boolean {
|
||||
if (this.currentFetchTableProcess) {
|
||||
return this.currentFetchTableProcess.cancel();
|
||||
async cancel(): Promise<void> {
|
||||
if (this.currentTask) {
|
||||
await this.currentTask.cancel();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async request(
|
||||
prevResults: IDatabaseResultSet[]
|
||||
): Promise<IDatabaseResultSet[]> {
|
||||
if (!this.options?.containerNodePath) {
|
||||
const options = this.options;
|
||||
|
||||
if (!options) {
|
||||
throw new Error('containerNodePath must be provided for table');
|
||||
}
|
||||
|
||||
@@ -69,25 +65,27 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
|
||||
const fetchTableProcess = new FetchTableDataAsyncProcess(this.graphQLService, this.notificationService);
|
||||
|
||||
fetchTableProcess.start(
|
||||
{
|
||||
connectionId: executionContext.connectionId,
|
||||
contextId: executionContext.contextId,
|
||||
containerNodePath: this.options.containerNodePath,
|
||||
},
|
||||
{
|
||||
offset,
|
||||
limit,
|
||||
constraints: this.options.constraints,
|
||||
where: this.options.whereFilter || undefined,
|
||||
},
|
||||
this.dataFormat,
|
||||
);
|
||||
this.currentTask = executionContext.run(async () => {
|
||||
await fetchTableProcess.start(
|
||||
{
|
||||
connectionId: executionContext.context!.connectionId,
|
||||
contextId: executionContext.context!.id,
|
||||
containerNodePath: options.containerNodePath,
|
||||
},
|
||||
{
|
||||
offset,
|
||||
limit,
|
||||
constraints: options.constraints,
|
||||
where: options.whereFilter || undefined,
|
||||
},
|
||||
this.dataFormat,
|
||||
);
|
||||
|
||||
this.currentFetchTableProcess = fetchTableProcess;
|
||||
return fetchTableProcess.promise;
|
||||
}, () => { fetchTableProcess.cancel(); });
|
||||
|
||||
try {
|
||||
const response = await fetchTableProcess.promise;
|
||||
const response = await this.currentTask;
|
||||
|
||||
this.requestInfo = {
|
||||
requestDuration: response?.duration || 0,
|
||||
@@ -129,8 +127,8 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
try {
|
||||
for (const update of changes) {
|
||||
const response = await this.graphQLService.sdk.updateResultsDataBatch({
|
||||
connectionId: executionContext.connectionId,
|
||||
contextId: executionContext.contextId,
|
||||
connectionId: executionContext.context!.connectionId,
|
||||
contextId: executionContext.context!.id,
|
||||
resultsId: update.resultId,
|
||||
updatedRows: Array.from(update.diff.values()).map(diff => ({
|
||||
data: diff.source,
|
||||
@@ -173,39 +171,16 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
if (this.executionContext) {
|
||||
await this.graphQLService.sdk.sqlContextDestroy({
|
||||
connectionId: this.executionContext.connectionId,
|
||||
contextId: this.executionContext.contextId,
|
||||
});
|
||||
}
|
||||
await this.executionContext?.destroy();
|
||||
}
|
||||
|
||||
private async ensureContextCreated(): Promise<IDatabaseExecutionContext> {
|
||||
if (!this.executionContext) {
|
||||
private async ensureContextCreated(): Promise<IConnectionExecutionContext> {
|
||||
if (!this.executionContext?.context) {
|
||||
if (!this.options) {
|
||||
throw new Error('Options must be provided');
|
||||
}
|
||||
this.executionContext = await this.createExecutionContext(this.options.connectionId);
|
||||
this.executionContext = await this.connectionExecutionContextService.create(this.options.connectionId);
|
||||
}
|
||||
return this.executionContext;
|
||||
}
|
||||
|
||||
private async createExecutionContext(
|
||||
connectionId: string,
|
||||
defaultCatalog?: string,
|
||||
defaultSchema?: string
|
||||
): Promise<IDatabaseExecutionContext> {
|
||||
const response = await this.graphQLService.sdk.sqlContextCreate({
|
||||
connectionId,
|
||||
defaultCatalog,
|
||||
defaultSchema,
|
||||
});
|
||||
return {
|
||||
contextId: response.context.id,
|
||||
connectionId,
|
||||
objectCatalogId: response.context.defaultCatalog,
|
||||
objectSchemaId: response.context.defaultSchema,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,11 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
NavNodeManagerService,
|
||||
INodeNavigationData,
|
||||
ITab,
|
||||
NodeManagerUtils
|
||||
} from '@cloudbeaver/core-app';
|
||||
import { NavNodeManagerService, INodeNavigationData, ITab } from '@cloudbeaver/core-app';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import {
|
||||
DBObjectPageService, ObjectPage, ObjectViewerTabService, IObjectViewerTabState
|
||||
} from '@cloudbeaver/plugin-object-viewer';
|
||||
import { DBObjectPageService, ObjectPage, ObjectViewerTabService, IObjectViewerTabState } from '@cloudbeaver/plugin-object-viewer';
|
||||
|
||||
import { DataPresentationService } from './DataPresentationService';
|
||||
import { DataViewerPanel } from './DataViewerPage/DataViewerPanel';
|
||||
@@ -35,7 +28,7 @@ export class DataViewerTabService {
|
||||
private objectViewerTabService: ObjectViewerTabService,
|
||||
private dbObjectPageService: DBObjectPageService,
|
||||
private notificationService: NotificationService,
|
||||
private dataPresentationService: DataPresentationService
|
||||
private dataPresentationService: DataPresentationService,
|
||||
) {
|
||||
this.page = this.dbObjectPageService.register({
|
||||
key: 'data_viewer_data',
|
||||
@@ -70,51 +63,48 @@ export class DataViewerTabService {
|
||||
if (tabInfo.isNewlyCreated) {
|
||||
trySwitchPage(this.page);
|
||||
}
|
||||
// if (nodeInfo.childrenId === '') {
|
||||
// tabInfo.trySwitchHandler(this.tabHandler);
|
||||
// }
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Data Viewer Error', 'Error in Data Viewer while processing action with database node');
|
||||
}
|
||||
}
|
||||
|
||||
private async handleTabSelect(tab: ITab<IObjectViewerTabState>) {
|
||||
if (tab.handlerState.pageId !== this.page.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tab.handlerState.connectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = await this.navNodeManagerService.loadNode({
|
||||
nodeId: tab.handlerState.objectId,
|
||||
parentId: tab.handlerState.parentId,
|
||||
});
|
||||
|
||||
if (tab.handlerState.pageId !== this.page.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.navNodeManagerService.isNodeHasData(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dataViewerTableService.has(tab.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeInfo = this.navNodeManagerService
|
||||
.getNodeContainerInfo(tab.handlerState.objectId);
|
||||
|
||||
if (!nodeInfo.connectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let model = this.dataViewerTableService.get(tab.handlerState.tableId || '');
|
||||
|
||||
if (tab.handlerState.tableId && model && !model.source.executionContext?.context) {
|
||||
await this.dataViewerTableService.removeTableModel(tab.handlerState.tableId);
|
||||
model = undefined;
|
||||
}
|
||||
|
||||
if (!model) {
|
||||
model = await this.dataViewerTableService.create(
|
||||
NodeManagerUtils.connectionNodeIdToConnectionId(nodeInfo.connectionId),
|
||||
tab.handlerState.connectionId,
|
||||
tab.handlerState.objectId
|
||||
);
|
||||
tab.handlerState.tableId = model.id;
|
||||
|
||||
const pageState = this.page.getState(tab);
|
||||
|
||||
if (pageState) {
|
||||
const presentation = this.dataPresentationService.get(pageState?.presentationId);
|
||||
|
||||
if (presentation?.dataFormat !== undefined) {
|
||||
model.setDataFormat(presentation.dataFormat);
|
||||
}
|
||||
@@ -123,7 +113,7 @@ export class DataViewerTabService {
|
||||
|
||||
// TODO: used for initial data fetch, but can repeat request each time data tab is selected,
|
||||
// so probably should be refactored and managed by presentation
|
||||
if (model.source.results.length === 0) {
|
||||
if (model.source.error === null && model.source.results.length === 0) {
|
||||
model.requestData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { ConnectionInfoResource, ConnectionExecutionContextService } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
@@ -23,7 +23,8 @@ export class DataViewerTableService {
|
||||
private tableViewerStorageService: TableViewerStorageService,
|
||||
private connectionInfoResource: ConnectionInfoResource,
|
||||
private graphQLService: GraphQLService,
|
||||
private notificationService: NotificationService
|
||||
private notificationService: NotificationService,
|
||||
private connectionExecutionContextService: ConnectionExecutionContextService,
|
||||
) { }
|
||||
|
||||
has(tableId: string): boolean {
|
||||
@@ -47,7 +48,11 @@ export class DataViewerTableService {
|
||||
containerNodePath = ''
|
||||
): Promise<DataModelWrapper> {
|
||||
const connectionInfo = await this.connectionInfoResource.load(connectionId);
|
||||
const source = new ContainerDataSource(this.graphQLService, this.notificationService);
|
||||
const source = new ContainerDataSource(
|
||||
this.graphQLService,
|
||||
this.notificationService,
|
||||
this.connectionExecutionContextService
|
||||
);
|
||||
|
||||
const dataModel = this.tableViewerStorageService.create(
|
||||
source
|
||||
|
||||
@@ -118,7 +118,7 @@ implements IDatabaseDataModel<TOptions, TResult> {
|
||||
await this.source.requestData();
|
||||
}
|
||||
|
||||
cancel(): Promise<boolean> | boolean {
|
||||
cancel(): Promise<void> | void {
|
||||
return this.source.cancel();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
|
||||
import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections';
|
||||
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DatabaseDataActions } from './DatabaseDataActions';
|
||||
@@ -16,7 +17,6 @@ import type { IDatabaseDataActions } from './IDatabaseDataActions';
|
||||
import type { IDatabaseDataEditor, IDatabaseDataResultEditor } from './IDatabaseDataEditor';
|
||||
import type { IDatabaseDataResult } from './IDatabaseDataResult';
|
||||
import { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource';
|
||||
import type { IDatabaseExecutionContext } from './IDatabaseExecutionContext';
|
||||
|
||||
export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseDataResult>
|
||||
implements IDatabaseDataSource<TOptions, TResult> {
|
||||
@@ -31,7 +31,7 @@ implements IDatabaseDataSource<TOptions, TResult> {
|
||||
options: TOptions | null;
|
||||
requestInfo: IRequestInfo;
|
||||
error: Error | null;
|
||||
executionContext: IDatabaseExecutionContext | null;
|
||||
executionContext: IConnectionExecutionContext | null;
|
||||
abstract get canCancel(): boolean;
|
||||
|
||||
protected disabled: boolean;
|
||||
@@ -91,7 +91,7 @@ implements IDatabaseDataSource<TOptions, TResult> {
|
||||
return this.actions.get(this.results[resultIndex], action);
|
||||
}
|
||||
|
||||
abstract cancel(): Promise<boolean> | boolean;
|
||||
abstract cancel(): Promise<void> | void;
|
||||
|
||||
hasResult(resultIndex: number): boolean {
|
||||
return resultIndex < this.results.length;
|
||||
@@ -171,7 +171,7 @@ implements IDatabaseDataSource<TOptions, TResult> {
|
||||
return this;
|
||||
}
|
||||
|
||||
setExecutionContext(context: IDatabaseExecutionContext | null): this {
|
||||
setExecutionContext(context: IConnectionExecutionContext | null): this {
|
||||
this.executionContext = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,6 @@ export interface IDatabaseDataModel<TOptions = any, TResult extends IDatabaseDat
|
||||
reload: () => Promise<void>;
|
||||
requestDataPortion: (offset: number, count: number) => Promise<void>;
|
||||
requestData: () => Promise<void>;
|
||||
cancel: () => Promise<boolean> | boolean;
|
||||
cancel: () => Promise<void> | void;
|
||||
dispose: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections';
|
||||
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IDatabaseDataAction, IDatabaseDataActionClass } from './IDatabaseDataAction';
|
||||
import type { IDatabaseDataActions } from './IDatabaseDataActions';
|
||||
import type { IDatabaseDataEditor, IDatabaseDataResultEditor } from './IDatabaseDataEditor';
|
||||
import type { IDatabaseDataResult } from './IDatabaseDataResult';
|
||||
import type { IDatabaseExecutionContext } from './IDatabaseExecutionContext';
|
||||
|
||||
export interface IRequestInfo {
|
||||
readonly requestDuration: number;
|
||||
@@ -38,7 +38,7 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
|
||||
readonly options: TOptions | null;
|
||||
readonly requestInfo: IRequestInfo;
|
||||
readonly error: Error | null;
|
||||
readonly executionContext: IDatabaseExecutionContext | null;
|
||||
readonly executionContext: IConnectionExecutionContext | null;
|
||||
readonly canCancel: boolean;
|
||||
|
||||
isReadonly: () => boolean;
|
||||
@@ -63,12 +63,12 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
|
||||
setOptions: (options: TOptions) => this;
|
||||
setDataFormat: (dataFormat: ResultDataFormat) => this;
|
||||
setSupportedDataFormats: (dataFormats: ResultDataFormat[]) => this;
|
||||
setExecutionContext: (context: IDatabaseExecutionContext | null) => this;
|
||||
setExecutionContext: (context: IConnectionExecutionContext | null) => this;
|
||||
|
||||
retry: () => Promise<void>;
|
||||
requestData: () => Promise<void> | void;
|
||||
saveData: () => Promise<void> | void;
|
||||
cancel: () => Promise<boolean> | boolean;
|
||||
cancel: () => Promise<void> | void;
|
||||
clearError: () => void;
|
||||
dispose: () => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ export * from './DatabaseDataModel/IDatabaseDataModel';
|
||||
export * from './DatabaseDataModel/IDatabaseDataOptions';
|
||||
export * from './DatabaseDataModel/IDatabaseDataResult';
|
||||
export * from './DatabaseDataModel/IDatabaseDataSource';
|
||||
export * from './DatabaseDataModel/IDatabaseExecutionContext';
|
||||
export * from './DatabaseDataModel/IDatabaseResultSet';
|
||||
export * from './DatabaseDataModel/Order';
|
||||
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IDatabaseExecutionContext } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
export interface ISqlQueryParams {
|
||||
query: string;
|
||||
executionContext: IDatabaseExecutionContext;
|
||||
}
|
||||
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
|
||||
export interface IResultTab {
|
||||
tabId: string;
|
||||
@@ -24,9 +19,9 @@ export interface IResultTab {
|
||||
export interface IResultGroup {
|
||||
groupId: string;
|
||||
modelId: string;
|
||||
sqlQueryParams: ISqlQueryParams;
|
||||
order: number;
|
||||
nameOrder: number;
|
||||
query: string;
|
||||
}
|
||||
|
||||
export interface ISqlEditorResultTab {
|
||||
@@ -38,15 +33,15 @@ export interface ISqlEditorResultTab {
|
||||
|
||||
export interface IExecutionPlanTab {
|
||||
tabId: string;
|
||||
executionContext: IDatabaseExecutionContext;
|
||||
order: number;
|
||||
query: string;
|
||||
options?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ISqlEditorTabState {
|
||||
order: number;
|
||||
query: string;
|
||||
executionContext?: IDatabaseExecutionContext;
|
||||
executionContext?: IConnectionExecutionContextInfo;
|
||||
currentTabId?: string;
|
||||
tabs: ISqlEditorResultTab[];
|
||||
resultGroups: IResultGroup[];
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
|
||||
import type { NotificationService } from '@cloudbeaver/core-events';
|
||||
import type { ITask } from '@cloudbeaver/core-executor';
|
||||
import type { GraphQLService, SqlExecuteInfo } from '@cloudbeaver/core-sdk';
|
||||
import { EDeferredState } from '@cloudbeaver/core-utils';
|
||||
import { DatabaseDataEditor, DatabaseDataSource, IDatabaseDataOptions, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import type { SqlExecutionState } from './SqlExecutionState';
|
||||
import { SQLQueryExecutionProcess } from './SqlResultTabs/SQLQueryExecutionProcess';
|
||||
|
||||
export interface IDataQueryOptions extends IDatabaseDataOptions {
|
||||
@@ -21,42 +20,40 @@ export interface IDataQueryOptions extends IDatabaseDataOptions {
|
||||
}
|
||||
|
||||
export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IDatabaseResultSet> {
|
||||
get canCancel(): boolean {
|
||||
return this.queryExecutionProcess?.getState() === EDeferredState.PENDING;
|
||||
}
|
||||
currentTask: ITask<SqlExecuteInfo> | null;
|
||||
|
||||
queryExecutionProcess: SQLQueryExecutionProcess | null;
|
||||
get canCancel(): boolean {
|
||||
return this.currentTask?.cancellable || false;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private notificationService: NotificationService,
|
||||
private sqlExecutionState: SqlExecutionState
|
||||
private notificationService: NotificationService
|
||||
) {
|
||||
super();
|
||||
|
||||
makeObservable(this, {
|
||||
queryExecutionProcess: observable,
|
||||
currentTask: observable.ref,
|
||||
});
|
||||
|
||||
this.queryExecutionProcess = null;
|
||||
this.currentTask = null;
|
||||
this.editor = new DatabaseDataEditor();
|
||||
}
|
||||
|
||||
isDisabled(resultIndex: number): boolean {
|
||||
return !this.getResult(resultIndex)?.data && this.error === null;
|
||||
return (!this.getResult(resultIndex)?.data && this.error === null) || !this.executionContext?.context;
|
||||
}
|
||||
|
||||
cancel(): boolean {
|
||||
if (this.queryExecutionProcess) {
|
||||
this.queryExecutionProcess.cancel();
|
||||
async cancel(): Promise<void> {
|
||||
if (this.currentTask) {
|
||||
await this.currentTask.cancel();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async save(
|
||||
prevResults: IDatabaseResultSet[]
|
||||
): Promise<IDatabaseResultSet[]> {
|
||||
if (!this.options || !this.executionContext) {
|
||||
if (!this.options || !this.executionContext?.context) {
|
||||
return prevResults;
|
||||
}
|
||||
|
||||
@@ -70,7 +67,7 @@ export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IData
|
||||
for (const update of changes) {
|
||||
const response = await this.graphQLService.sdk.updateResultsDataBatch({
|
||||
connectionId: this.options.connectionId,
|
||||
contextId: this.executionContext.contextId,
|
||||
contextId: this.executionContext.context.id,
|
||||
resultsId: update.resultId,
|
||||
updatedRows: Array.from(update.diff.values()).map(diff => ({
|
||||
data: diff.source,
|
||||
@@ -142,29 +139,35 @@ export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IData
|
||||
async request(
|
||||
prevResults: IDatabaseResultSet[]
|
||||
): Promise<IDatabaseResultSet[]> {
|
||||
if (!this.options || !this.executionContext) {
|
||||
const options = this.options;
|
||||
const executionContext = this.executionContext;
|
||||
const executionContextInfo = this.executionContext?.context;
|
||||
|
||||
if (!options || !executionContext || !executionContextInfo) {
|
||||
return prevResults;
|
||||
}
|
||||
const limit = this.count;
|
||||
|
||||
this.queryExecutionProcess = new SQLQueryExecutionProcess(this.graphQLService, this.notificationService);
|
||||
const queryExecutionProcess = new SQLQueryExecutionProcess(this.graphQLService, this.notificationService);
|
||||
|
||||
this.sqlExecutionState.setExecutionTask(this.queryExecutionProcess);
|
||||
|
||||
try {
|
||||
await this.queryExecutionProcess.start(
|
||||
this.options.query,
|
||||
this.executionContext,
|
||||
this.currentTask = executionContext.run(async () => {
|
||||
await queryExecutionProcess.start(
|
||||
options.query,
|
||||
executionContextInfo,
|
||||
{
|
||||
offset: this.offset,
|
||||
limit,
|
||||
constraints: this.options.constraints,
|
||||
where: this.options.whereFilter || undefined,
|
||||
constraints: options.constraints,
|
||||
where: options.whereFilter || undefined,
|
||||
},
|
||||
this.dataFormat
|
||||
);
|
||||
|
||||
const response = await this.queryExecutionProcess.promise;
|
||||
return await queryExecutionProcess.promise;
|
||||
}, () => { queryExecutionProcess.cancel(); });
|
||||
|
||||
try {
|
||||
const response = await this.currentTask;
|
||||
|
||||
const results = this.getResults(response, limit);
|
||||
this.clearError();
|
||||
@@ -181,6 +184,6 @@ export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IData
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
// TODO: this.queryExecutionProcess maybe should be disposed somehow
|
||||
await this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -11,6 +11,7 @@
|
||||
import { Editor, EditorConfiguration, findModeByName } from 'codemirror';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
import 'codemirror/addon/hint/sql-hint';
|
||||
import 'codemirror/addon/search/searchcursor';
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
import type { IControlledCodeMirror } from 'react-codemirror2';
|
||||
|
||||
|
||||
@@ -15,10 +15,11 @@ import type {
|
||||
ShowHintOptions,
|
||||
HintFunction,
|
||||
} from 'codemirror';
|
||||
import { computed, makeObservable } from 'mobx';
|
||||
import { observable, computed, makeObservable, autorun } from 'mobx';
|
||||
import type { IControlledCodeMirror } from 'react-codemirror2';
|
||||
|
||||
import type { ITab } from '@cloudbeaver/core-app';
|
||||
import { ConnectionExecutionContextService } from '@cloudbeaver/core-connections';
|
||||
import { IInitializableController, injectable } from '@cloudbeaver/core-di';
|
||||
import type { SqlDialectInfo } from '@cloudbeaver/core-sdk';
|
||||
import { throttleAsync } from '@cloudbeaver/core-utils';
|
||||
@@ -28,7 +29,6 @@ import { SqlDialectInfoService } from '../SqlDialectInfoService';
|
||||
import { SqlEditorService } from '../SqlEditorService';
|
||||
import { SqlExecutionPlanService } from '../SqlResultTabs/ExecutionPlan/SqlExecutionPlanService';
|
||||
import { SqlQueryService } from '../SqlResultTabs/SqlQueryService';
|
||||
import { SqlResultTabsService } from '../SqlResultTabs/SqlResultTabsService';
|
||||
|
||||
const closeCharacters = /[\s()[\]{};:>,=]/;
|
||||
|
||||
@@ -43,7 +43,17 @@ export class SqlEditorController implements IInitializableController {
|
||||
}
|
||||
|
||||
get isActionsDisabled(): boolean {
|
||||
return this.sqlResultTabsService.getTabExecutionContext(this.tab.id).isExecuting;
|
||||
if (!this.tab.handlerState.executionContext) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.cursor && !this.getSubQuery()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const context = this.connectionExecutionContextService.get(this.tab.handlerState.executionContext.id);
|
||||
|
||||
return context?.executing || false;
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
@@ -83,11 +93,12 @@ export class SqlEditorController implements IInitializableController {
|
||||
editorDidMount: this.handleEditorConfigure.bind(this),
|
||||
};
|
||||
|
||||
private cursor: Position | null;
|
||||
private tab!: ITab<ISqlEditorTabState>;
|
||||
private editor?: Editor;
|
||||
|
||||
constructor(
|
||||
private sqlResultTabsService: SqlResultTabsService,
|
||||
private connectionExecutionContextService: ConnectionExecutionContextService,
|
||||
private sqlQueryService: SqlQueryService,
|
||||
private sqlDialectInfoService: SqlDialectInfoService,
|
||||
private sqlEditorService: SqlEditorService,
|
||||
@@ -95,16 +106,23 @@ export class SqlEditorController implements IInitializableController {
|
||||
) {
|
||||
this.getHandleAutocomplete = this.getHandleAutocomplete.bind(this);
|
||||
this.getHandleAutocomplete = throttleAsync(this.getHandleAutocomplete, 1000 / 3);
|
||||
makeObservable(this, {
|
||||
this.cursor = null;
|
||||
|
||||
makeObservable<this, 'cursor'>(this, {
|
||||
dialect: computed,
|
||||
isActionsDisabled: computed,
|
||||
value: computed,
|
||||
cursor: observable,
|
||||
});
|
||||
}
|
||||
|
||||
init(tab: ITab<ISqlEditorTabState>): void {
|
||||
this.tab = tab;
|
||||
this.loadDialect();
|
||||
autorun(() => {
|
||||
if (this.tab.handlerState.executionContext) {
|
||||
this.sqlDialectInfoService.loadSqlDialectInfo(this.tab.handlerState.executionContext.connectionId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleExecute = async (): Promise<void> => {
|
||||
@@ -112,9 +130,8 @@ export class SqlEditorController implements IInitializableController {
|
||||
return;
|
||||
}
|
||||
this.sqlQueryService.executeEditorQuery(
|
||||
this.sqlResultTabsService.getTabExecutionContext(this.tab.id),
|
||||
this.tab.handlerState,
|
||||
await this.getExecutingQuery(),
|
||||
this.getSubQuery(),
|
||||
false
|
||||
);
|
||||
};
|
||||
@@ -124,9 +141,8 @@ export class SqlEditorController implements IInitializableController {
|
||||
return;
|
||||
}
|
||||
this.sqlQueryService.executeEditorQuery(
|
||||
this.sqlResultTabsService.getTabExecutionContext(this.tab.id),
|
||||
this.tab.handlerState,
|
||||
await this.getExecutingQuery(),
|
||||
this.getSubQuery(),
|
||||
true
|
||||
);
|
||||
};
|
||||
@@ -136,9 +152,8 @@ export class SqlEditorController implements IInitializableController {
|
||||
return;
|
||||
}
|
||||
this.sqlExecutionPlanService.executeExecutionPlan(
|
||||
this.sqlResultTabsService.getTabExecutionContext(this.tab.id),
|
||||
this.tab.handlerState,
|
||||
await this.getExecutingQuery(),
|
||||
this.getSubQuery(),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -169,15 +184,7 @@ export class SqlEditorController implements IInitializableController {
|
||||
});
|
||||
}
|
||||
|
||||
private async loadDialect(): Promise<SqlDialectInfo | undefined> {
|
||||
if (!this.tab.handlerState.executionContext) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return await this.sqlDialectInfoService.loadSqlDialectInfo(this.tab.handlerState.executionContext.connectionId);
|
||||
}
|
||||
|
||||
private async getExecutingQuery(): Promise<string> {
|
||||
private getExecutingQuery(): string {
|
||||
if (!this.editor) {
|
||||
return this.tab.handlerState.query;
|
||||
}
|
||||
@@ -187,10 +194,9 @@ export class SqlEditorController implements IInitializableController {
|
||||
}
|
||||
|
||||
const delimiters = [];
|
||||
const dialect = await this.loadDialect();
|
||||
|
||||
if (dialect?.scriptDelimiter) {
|
||||
delimiters.push(dialect.scriptDelimiter);
|
||||
if (this.dialect?.scriptDelimiter) {
|
||||
delimiters.push(this.dialect.scriptDelimiter);
|
||||
}
|
||||
|
||||
const cursor = this.editor.getCursor();
|
||||
@@ -221,7 +227,7 @@ export class SqlEditorController implements IInitializableController {
|
||||
let proposals = await this.sqlEditorService
|
||||
.getAutocomplete(
|
||||
this.tab.handlerState.executionContext.connectionId,
|
||||
this.tab.handlerState.executionContext.contextId,
|
||||
this.tab.handlerState.executionContext.id,
|
||||
this.tab.handlerState.query,
|
||||
cursorPosition,
|
||||
undefined,
|
||||
@@ -251,7 +257,8 @@ export class SqlEditorController implements IInitializableController {
|
||||
private handleEditorConfigure(editor: Editor) {
|
||||
this.editor = editor;
|
||||
|
||||
let cursor: Position | null = null;
|
||||
let cursor: Position = editor.getCursor('from');
|
||||
this.cursor = { ...cursor };
|
||||
|
||||
const ignoredChanges = ['+delete', 'undo', 'complete'];
|
||||
|
||||
@@ -275,15 +282,51 @@ export class SqlEditorController implements IInitializableController {
|
||||
});
|
||||
|
||||
editor.on('cursorActivity', () => {
|
||||
if (editor.state.completionActive) {
|
||||
const newCursor = editor.getCursor('from');
|
||||
const newCursor = editor.getCursor('from');
|
||||
this.cursor = { ...newCursor };
|
||||
|
||||
if (editor.state.completionActive) {
|
||||
if (newCursor.ch !== cursor?.ch || newCursor.line !== cursor.line) {
|
||||
cursor = newCursor;
|
||||
editor.state.completionActive.update();
|
||||
}
|
||||
}
|
||||
this.highlightActiveQuery();
|
||||
});
|
||||
|
||||
this.highlightActiveQuery();
|
||||
}
|
||||
|
||||
private highlightActiveQuery() {
|
||||
this.editor?.eachLine(line => {
|
||||
this.editor?.removeLineClass(line, 'background', 'active-query');
|
||||
});
|
||||
|
||||
if (this.isActionsDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = this.getSubQuery();
|
||||
const search = this.editor?.getSearchCursor(query, this.editor.getCursor('from'));
|
||||
|
||||
if (search?.find(false) || search?.find(true)) {
|
||||
const from = search.from();
|
||||
const to = search.to();
|
||||
|
||||
for (let line = from.line; line <= to.line; line++) {
|
||||
this.editor?.addLineClass(line, 'background', 'active-query');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getSubQuery(): string {
|
||||
const query = this.getExecutingQuery();
|
||||
|
||||
if (this.dialect?.scriptDelimiter && query.endsWith(this.dialect?.scriptDelimiter)) {
|
||||
return query.slice(0, query.length - this.dialect.scriptDelimiter.length);
|
||||
}
|
||||
|
||||
return query.trim();
|
||||
}
|
||||
|
||||
private findQueryBegin(editor: Editor, delimiters: string[], position: number) {
|
||||
|
||||
@@ -127,9 +127,9 @@ export class SqlEditorNavigatorService {
|
||||
}
|
||||
|
||||
if (data.type === SQLEditorNavigationAction.select) {
|
||||
this.sqlEditorTabService.selectResultTab(tab, data.resultId);
|
||||
this.sqlEditorTabService.selectResultTab(tab.handlerState, data.resultId);
|
||||
} else if (data.type === SQLEditorNavigationAction.close) {
|
||||
await this.sqlResultTabsService.removeResultTab(tab.handlerState, data.resultId, data.editorId);
|
||||
await this.sqlResultTabsService.removeResultTab(tab.handlerState, data.resultId);
|
||||
}
|
||||
this.navigationTabsService.selectTab(tab.id);
|
||||
} catch (exception) {
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { ConnectionInfoResource, ConnectionsManagerService } from '@cloudbeaver/core-connections';
|
||||
import { ConnectionExecutionContextService, ConnectionsManagerService, IConnectionExecutionContext } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { GraphQLService, QuerySqlCompletionProposalsQuery } from '@cloudbeaver/core-sdk';
|
||||
import type { IDatabaseExecutionContext } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import type { ISqlEditorTabState } from './ISqlEditorTabState';
|
||||
import { SqlDialectInfoService } from './SqlDialectInfoService';
|
||||
@@ -19,10 +18,10 @@ import { SqlDialectInfoService } from './SqlDialectInfoService';
|
||||
export class SqlEditorService {
|
||||
constructor(
|
||||
private gql: GraphQLService,
|
||||
private connectionInfoResource: ConnectionInfoResource,
|
||||
private sqlDialectInfoService: SqlDialectInfoService,
|
||||
private connectionsManagerService: ConnectionsManagerService,
|
||||
private notificationService: NotificationService,
|
||||
private connectionExecutionContextService: ConnectionExecutionContextService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -46,7 +45,7 @@ export class SqlEditorService {
|
||||
return result.sqlCompletionProposals;
|
||||
}
|
||||
|
||||
async initEditorConnection(state: ISqlEditorTabState): Promise<IDatabaseExecutionContext | undefined> {
|
||||
async initEditorConnection(state: ISqlEditorTabState): Promise<IConnectionExecutionContext | undefined> {
|
||||
if (!state.executionContext) {
|
||||
console.error('executeEditorQuery executionContext is not provided');
|
||||
return;
|
||||
@@ -54,8 +53,8 @@ export class SqlEditorService {
|
||||
|
||||
const context = await this.initContext(
|
||||
state.executionContext.connectionId,
|
||||
state.executionContext.objectCatalogId,
|
||||
state.executionContext.objectSchemaId
|
||||
state.executionContext.defaultCatalog,
|
||||
state.executionContext.defaultSchema
|
||||
);
|
||||
|
||||
if (!context) {
|
||||
@@ -69,7 +68,7 @@ export class SqlEditorService {
|
||||
connectionId?: string,
|
||||
catalogId?: string,
|
||||
schemaId?: string
|
||||
): Promise<IDatabaseExecutionContext | null> {
|
||||
): Promise<IConnectionExecutionContext | null> {
|
||||
const connection = await this.connectionsManagerService.requireConnection(connectionId);
|
||||
if (!connection) {
|
||||
return null;
|
||||
@@ -78,7 +77,7 @@ export class SqlEditorService {
|
||||
try {
|
||||
await this.sqlDialectInfoService.loadSqlDialectInfo(connection.id);
|
||||
|
||||
return await this.createSqlContext(connection.id, catalogId, schemaId);
|
||||
return await this.connectionExecutionContextService.create(connection.id, catalogId, schemaId);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(
|
||||
exception,
|
||||
@@ -87,60 +86,4 @@ export class SqlEditorService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async destroySqlContext(context: IDatabaseExecutionContext): Promise<void> {
|
||||
const connection = this.connectionInfoResource.get(context.connectionId);
|
||||
if (!connection?.connected) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.gql.sdk.sqlContextDestroy(context);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, `Failed to destroy SQL-context ${context.contextId}`, '', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns context id, context catalog and schema
|
||||
* When try create context without catalog or schema the context is created with default catalog and schema
|
||||
* and response contains its ids.
|
||||
* If in the response there are no catalog or schema it means that database has no catalogs or schemas at all.
|
||||
*/
|
||||
async createSqlContext(
|
||||
connectionId: string,
|
||||
defaultCatalog?: string,
|
||||
defaultSchema?: string
|
||||
): Promise<IDatabaseExecutionContext> {
|
||||
const response = await this.gql.sdk.sqlContextCreate({
|
||||
connectionId,
|
||||
defaultCatalog,
|
||||
defaultSchema,
|
||||
});
|
||||
return {
|
||||
contextId: response.context.id,
|
||||
connectionId,
|
||||
objectCatalogId: response.context.defaultCatalog,
|
||||
objectSchemaId: response.context.defaultSchema,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update catalog and schema for the exiting sql context in the certain connection
|
||||
*/
|
||||
async updateSqlContext(
|
||||
connectionId: string,
|
||||
contextId?: string,
|
||||
defaultCatalog?: string,
|
||||
defaultSchema?: string
|
||||
): Promise<void> {
|
||||
if (!contextId) {
|
||||
throw new Error('updateSqlContext contextId not provided');
|
||||
}
|
||||
await this.gql.sdk.sqlContextSetDefaults({
|
||||
connectionId,
|
||||
contextId,
|
||||
defaultCatalog,
|
||||
defaultSchema,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,39 +17,36 @@ import {
|
||||
ITabOptions
|
||||
} from '@cloudbeaver/core-app';
|
||||
import {
|
||||
ConnectionInfoResource,
|
||||
ConnectionExecutionContextResource,
|
||||
ConnectionExecutionContextService,
|
||||
connectionProvider,
|
||||
connectionSetter,
|
||||
IConnectionExecutionContextInfo,
|
||||
} from '@cloudbeaver/core-connections';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { ISqlEditorTabState } from './ISqlEditorTabState';
|
||||
import { SqlEditorPanel } from './SqlEditorPanel';
|
||||
import { SqlEditorService } from './SqlEditorService';
|
||||
import { SqlEditorTab } from './SqlEditorTab';
|
||||
import { sqlEditorTabHandlerKey } from './sqlEditorTabHandlerKey';
|
||||
import { SqlExecutionState } from './SqlExecutionState';
|
||||
import { SqlResultTabsService } from './SqlResultTabs/SqlResultTabsService';
|
||||
|
||||
@injectable()
|
||||
export class SqlEditorTabService extends Bootstrap {
|
||||
readonly tabExecutionState: MetadataMap<string, SqlExecutionState>;
|
||||
readonly tabHandler: TabHandler<ISqlEditorTabState>;
|
||||
|
||||
constructor(
|
||||
private navigationTabsService: NavigationTabsService,
|
||||
private notificationService: NotificationService,
|
||||
private sqlEditorService: SqlEditorService,
|
||||
private connectionInfo: ConnectionInfoResource,
|
||||
private readonly sqlResultTabsService: SqlResultTabsService
|
||||
private readonly sqlResultTabsService: SqlResultTabsService,
|
||||
private connectionExecutionContextService: ConnectionExecutionContextService,
|
||||
private connectionExecutionContextResource: ConnectionExecutionContextResource
|
||||
) {
|
||||
super();
|
||||
|
||||
this.tabExecutionState = new MetadataMap(() => new SqlExecutionState());
|
||||
|
||||
this.tabHandler = this.navigationTabsService
|
||||
.registerTabHandler<ISqlEditorTabState>({
|
||||
key: sqlEditorTabHandlerKey,
|
||||
@@ -69,8 +66,8 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.connectionInfo.onItemDelete.addHandler(this.handleConnectionDelete.bind(this));
|
||||
this.connectionInfo.onItemAdd.addHandler(this.handleConnectionUpdate.bind(this));
|
||||
this.connectionExecutionContextResource.onItemAdd.addHandler(this.handleExecutionContextUpdate.bind(this));
|
||||
this.connectionExecutionContextResource.onItemDelete.addHandler(this.handleExecutionContextUpdate.bind(this));
|
||||
}
|
||||
|
||||
load(): void {}
|
||||
@@ -82,7 +79,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
): Promise<ITabOptions<ISqlEditorTabState> | null> {
|
||||
const executionContext = await this.sqlEditorService.initContext(connectionId, catalogId, schemaId);
|
||||
|
||||
if (!executionContext) {
|
||||
if (!executionContext?.context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -93,7 +90,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
handlerState: {
|
||||
query: '',
|
||||
order,
|
||||
executionContext,
|
||||
executionContext: { ...executionContext.context },
|
||||
tabs: [],
|
||||
resultGroups: [],
|
||||
resultTabs: [],
|
||||
@@ -102,39 +99,25 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
};
|
||||
}
|
||||
|
||||
selectResultTab(tab: ITab<ISqlEditorTabState>, resultId: string): void {
|
||||
tab.handlerState.currentTabId = resultId;
|
||||
selectResultTab(state: ISqlEditorTabState, resultId: string): void {
|
||||
state.currentTabId = resultId;
|
||||
}
|
||||
|
||||
resetConnectionInfo(state: ISqlEditorTabState): void {
|
||||
state.executionContext = undefined;
|
||||
}
|
||||
|
||||
private async handleConnectionUpdate(key: ResourceKey<string>) {
|
||||
await ResourceKeyUtils.forEachAsync(key, async key => {
|
||||
const tabs = this.navigationTabsService.findTabs<ISqlEditorTabState>(
|
||||
isSQLEditorTab(tab => tab.handlerState.executionContext?.connectionId === key)
|
||||
);
|
||||
const connection = this.connectionInfo.get(key);
|
||||
private async handleExecutionContextUpdate() {
|
||||
const tabs = this.navigationTabsService.findTabs<ISqlEditorTabState>(
|
||||
isSQLEditorTab(tab => !!tab.handlerState.executionContext)
|
||||
);
|
||||
|
||||
if (!connection?.connected) {
|
||||
for (const tab of tabs) {
|
||||
this.resetConnectionInfo(tab.handlerState);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async handleConnectionDelete(key: ResourceKey<string>) {
|
||||
await ResourceKeyUtils.forEachAsync(key, async key => {
|
||||
const tabs = this.navigationTabsService.findTabs<ISqlEditorTabState>(
|
||||
isSQLEditorTab(tab => tab.handlerState.executionContext?.connectionId === key)
|
||||
);
|
||||
|
||||
for (const tab of tabs) {
|
||||
for (const tab of tabs) {
|
||||
const executionContext = this.connectionExecutionContextService.get(tab.handlerState.executionContext!.id);
|
||||
if (!executionContext) {
|
||||
this.resetConnectionInfo(tab.handlerState);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getFreeEditorId() {
|
||||
@@ -148,8 +131,9 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
|| typeof tab.handlerState.order !== 'number'
|
||||
|| !['undefined', 'object'].includes(typeof tab.handlerState.executionContext)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.executionContext?.connectionId)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.executionContext?.contextId)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.executionContext?.objectCatalogId)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.executionContext?.id)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.executionContext?.defaultCatalog)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.executionContext?.defaultSchema)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.currentTabId)
|
||||
|| !Array.isArray(tab.handlerState.tabs)
|
||||
|| !Array.isArray(tab.handlerState.executionPlanTabs)
|
||||
@@ -160,9 +144,9 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
}
|
||||
|
||||
if (tab.handlerState.executionContext) {
|
||||
const connection = this.connectionInfo.get(tab.handlerState.executionContext.connectionId);
|
||||
const executionContext = this.connectionExecutionContextService.get(tab.handlerState.executionContext.id);
|
||||
|
||||
if (!connection?.connected) {
|
||||
if (!executionContext) {
|
||||
this.resetConnectionInfo(tab.handlerState);
|
||||
}
|
||||
}
|
||||
@@ -181,27 +165,28 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
}
|
||||
|
||||
private getObjectCatalogId(tab: ITab<ISqlEditorTabState>) {
|
||||
return tab.handlerState.executionContext?.objectCatalogId;
|
||||
return tab.handlerState.executionContext?.defaultCatalog;
|
||||
}
|
||||
|
||||
private getObjectSchemaId(tab: ITab<ISqlEditorTabState>) {
|
||||
return tab.handlerState.executionContext?.objectSchemaId;
|
||||
return tab.handlerState.executionContext?.defaultSchema;
|
||||
}
|
||||
|
||||
private async setConnectionId(connectionId: string, tab: ITab<ISqlEditorTabState>) {
|
||||
try {
|
||||
const context = await this.sqlEditorService.initContext(connectionId);
|
||||
const executionContext = await this.sqlEditorService.initContext(connectionId);
|
||||
|
||||
if (!context) {
|
||||
if (!executionContext?.context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tab.handlerState.executionContext) {
|
||||
// when new context created - destroy old one silently
|
||||
await this.sqlEditorService.destroySqlContext(tab.handlerState.executionContext);
|
||||
const previousContext = tab.handlerState.executionContext;
|
||||
tab.handlerState.executionContext = { ...executionContext.context };
|
||||
|
||||
if (previousContext) {
|
||||
await this.destroyContext(previousContext);
|
||||
}
|
||||
|
||||
tab.handlerState.executionContext = context;
|
||||
return true;
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Failed to change SQL-editor connection');
|
||||
@@ -213,13 +198,19 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
if (!tab.handlerState.executionContext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const executionContext = this.connectionExecutionContextService.get(tab.handlerState.executionContext.id);
|
||||
|
||||
if (!executionContext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sqlEditorService.updateSqlContext(
|
||||
tab.handlerState.executionContext?.connectionId,
|
||||
tab.handlerState.executionContext?.contextId,
|
||||
containerId
|
||||
await executionContext.update(
|
||||
tab.handlerState.executionContext?.id,
|
||||
containerId,
|
||||
);
|
||||
tab.handlerState.executionContext.objectCatalogId = containerId;
|
||||
tab.handlerState.executionContext.defaultCatalog = containerId;
|
||||
return true;
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Failed to change SQL-editor catalog');
|
||||
@@ -231,14 +222,19 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
if (!tab.handlerState.executionContext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const executionContext = this.connectionExecutionContextService.get(tab.handlerState.executionContext.id);
|
||||
|
||||
if (!executionContext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sqlEditorService.updateSqlContext(
|
||||
tab.handlerState.executionContext.connectionId,
|
||||
tab.handlerState.executionContext.contextId,
|
||||
tab.handlerState.executionContext.objectCatalogId,
|
||||
await executionContext.update(
|
||||
tab.handlerState.executionContext.defaultCatalog,
|
||||
containerId
|
||||
);
|
||||
tab.handlerState.executionContext.objectSchemaId = containerId;
|
||||
tab.handlerState.executionContext.defaultSchema = containerId;
|
||||
return true;
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Failed to change SQL-editor schema');
|
||||
@@ -247,15 +243,23 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async handleTabClose(editorTab: ITab<ISqlEditorTabState>) {
|
||||
this.tabExecutionState.delete(editorTab.id);
|
||||
if (editorTab.handlerState.executionContext) {
|
||||
const context = this.sqlResultTabsService.getTabExecutionContext(editorTab.id);
|
||||
context.cancelTask();
|
||||
this.sqlResultTabsService.removeTabExecutionContext(editorTab.id);
|
||||
await this.sqlEditorService.destroySqlContext(editorTab.handlerState.executionContext);
|
||||
await this.destroyContext(editorTab.handlerState.executionContext);
|
||||
}
|
||||
for (const tab of editorTab.handlerState.tabs) {
|
||||
await this.sqlResultTabsService.removeResultTab(editorTab.handlerState, tab.id, editorTab.id);
|
||||
await this.sqlResultTabsService.removeResultTab(editorTab.handlerState, tab.id);
|
||||
}
|
||||
}
|
||||
|
||||
private async destroyContext(contextInfo: IConnectionExecutionContextInfo) {
|
||||
const executionContext = this.connectionExecutionContextService.get(contextInfo.id);
|
||||
|
||||
if (executionContext) {
|
||||
try {
|
||||
await executionContext.destroy();
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, `Failed to destroy SQL-context ${executionContext.context?.id}`, '', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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, observable, makeObservable } from 'mobx';
|
||||
|
||||
import { Deferred, EDeferredState } from '@cloudbeaver/core-utils';
|
||||
|
||||
export class SqlExecutionState {
|
||||
constructor() {
|
||||
makeObservable<SqlExecutionState, 'executionTask'>(this, {
|
||||
isExecuting: computed,
|
||||
canCancel: computed,
|
||||
executionTask: observable.ref,
|
||||
});
|
||||
}
|
||||
|
||||
get isExecuting(): boolean {
|
||||
return this.executionTask ? this.executionTask.isInProgress : false;
|
||||
}
|
||||
|
||||
get isCanceling(): boolean {
|
||||
return this.executionTask ? this.executionTask.getState() === EDeferredState.CANCELLING : false;
|
||||
}
|
||||
|
||||
get canCancel(): boolean {
|
||||
return this.executionTask ? this.executionTask.getState() === EDeferredState.PENDING : false;
|
||||
}
|
||||
|
||||
cancelTask = () => {
|
||||
if (this.executionTask) {
|
||||
this.executionTask.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
private executionTask: Deferred<any> | null = null;
|
||||
|
||||
setExecutionTask(executionTask: Deferred<any>): void {
|
||||
if (this.executionTask) {
|
||||
throw new Error('Simultaneous execution of several queries is forbidden');
|
||||
}
|
||||
this.executionTask = executionTask;
|
||||
|
||||
executionTask.promise.finally(() => {
|
||||
this.executionTask = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -6,6 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import type { NotificationService } from '@cloudbeaver/core-events';
|
||||
import {
|
||||
AsyncTaskInfo, GraphQLService, ServerInternalError, SqlExecutionPlan
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
import {
|
||||
CancellablePromise, cancellableTimeout, Deferred, EDeferredState
|
||||
} from '@cloudbeaver/core-utils';
|
||||
import type { IDatabaseExecutionContext } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
const DELAY_BETWEEN_TRIES = 1000;
|
||||
|
||||
@@ -31,7 +31,7 @@ export class SQLExecutionPlanProcess extends Deferred<SqlExecutionPlan> {
|
||||
|
||||
async start(
|
||||
query: string,
|
||||
context: IDatabaseExecutionContext,
|
||||
context: IConnectionExecutionContextInfo,
|
||||
): Promise<void> {
|
||||
// start async task
|
||||
try {
|
||||
@@ -105,11 +105,11 @@ export class SQLExecutionPlanProcess extends Deferred<SqlExecutionPlan> {
|
||||
|
||||
private async explainExecutionPlanAsync(
|
||||
query: string,
|
||||
context: IDatabaseExecutionContext,
|
||||
context: IConnectionExecutionContextInfo,
|
||||
): Promise<AsyncTaskInfo> {
|
||||
const { taskInfo } = await this.graphQLService.sdk.asyncSqlExplainExecutionPlan({
|
||||
connectionId: context.connectionId,
|
||||
contextId: context.contextId,
|
||||
contextId: context.id,
|
||||
query,
|
||||
configuration: {},
|
||||
});
|
||||
|
||||
+14
-34
@@ -8,14 +8,13 @@
|
||||
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { ConnectionExecutionContextService } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { GraphQLService, SqlExecutionPlan } from '@cloudbeaver/core-sdk';
|
||||
import { EDeferredState, uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { ISqlEditorTabState } from '../../ISqlEditorTabState';
|
||||
import { SqlDialectInfoService } from '../../SqlDialectInfoService';
|
||||
import type { SqlExecutionState } from '../../SqlExecutionState';
|
||||
import { SQLExecutionPlanProcess } from './SQLExecutionPlanProcess';
|
||||
|
||||
interface IExecutionPlanData {
|
||||
@@ -28,9 +27,9 @@ export class SqlExecutionPlanService {
|
||||
data: Map<string, IExecutionPlanData>;
|
||||
|
||||
constructor(
|
||||
private sqlDialectInfoService: SqlDialectInfoService,
|
||||
private graphQLService: GraphQLService,
|
||||
private notificationService: NotificationService,
|
||||
private connectionExecutionContextService: ConnectionExecutionContextService
|
||||
) {
|
||||
makeObservable(this, {
|
||||
data: observable,
|
||||
@@ -39,40 +38,34 @@ export class SqlExecutionPlanService {
|
||||
}
|
||||
|
||||
async executeExecutionPlan(
|
||||
executionState: SqlExecutionState,
|
||||
editorState: ISqlEditorTabState,
|
||||
query: string,
|
||||
): Promise<void> {
|
||||
if (!query.trim()) {
|
||||
return;
|
||||
}
|
||||
const contextInfo = editorState.executionContext;
|
||||
|
||||
if (!editorState.executionContext) {
|
||||
const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id);
|
||||
|
||||
if (!contextInfo || !executionContext) {
|
||||
console.error('executeExecutionPlan executionContext is not provided');
|
||||
return;
|
||||
}
|
||||
|
||||
const tabId = this.createExecutionPlanTab(editorState);
|
||||
const subQuery = await this.getSubQuery(editorState.executionContext.connectionId, query);
|
||||
const tabId = this.createExecutionPlanTab(editorState, query);
|
||||
const task = new SQLExecutionPlanProcess(this.graphQLService, this.notificationService);
|
||||
|
||||
this.data.set(tabId, {
|
||||
process: task,
|
||||
executionPlan: null,
|
||||
});
|
||||
|
||||
try {
|
||||
executionState.setExecutionTask(task);
|
||||
editorState.currentTabId = tabId;
|
||||
|
||||
await task.start(
|
||||
subQuery,
|
||||
{
|
||||
connectionId: editorState.executionContext.connectionId,
|
||||
contextId: editorState.executionContext.contextId,
|
||||
}
|
||||
);
|
||||
const executionPlan = await executionContext.run(async () => {
|
||||
await task.start(query, contextInfo);
|
||||
|
||||
const executionPlan = await task.promise;
|
||||
return task.promise;
|
||||
}, () => { task.cancel(); });
|
||||
|
||||
const tab = editorState.tabs.find(tab => tab.id === tabId);
|
||||
|
||||
@@ -91,16 +84,6 @@ export class SqlExecutionPlanService {
|
||||
}
|
||||
}
|
||||
|
||||
async getSubQuery(connectionId: string, query: string): Promise<string> {
|
||||
const dialectInfo = await this.sqlDialectInfoService.loadSqlDialectInfo(connectionId);
|
||||
|
||||
if (dialectInfo?.scriptDelimiter && query.endsWith(dialectInfo?.scriptDelimiter)) {
|
||||
return query.slice(0, query.length - dialectInfo.scriptDelimiter.length);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private removeTab(state: ISqlEditorTabState, tabId: string) {
|
||||
const tab = state.tabs.find(tab => tab.id === tabId);
|
||||
if (tab) {
|
||||
@@ -131,7 +114,7 @@ export class SqlExecutionPlanService {
|
||||
this.data.delete(tabId);
|
||||
}
|
||||
|
||||
private createExecutionPlanTab(state: ISqlEditorTabState) {
|
||||
private createExecutionPlanTab(state: ISqlEditorTabState, query: string) {
|
||||
if (!state.executionContext) {
|
||||
throw new Error('ExecutionContext is not provided');
|
||||
}
|
||||
@@ -142,11 +125,8 @@ export class SqlExecutionPlanService {
|
||||
|
||||
state.executionPlanTabs.push({
|
||||
tabId: id,
|
||||
executionContext: {
|
||||
connectionId: state.executionContext.connectionId,
|
||||
contextId: state.executionContext.contextId,
|
||||
},
|
||||
order: nameOrder,
|
||||
query,
|
||||
});
|
||||
|
||||
state.tabs.push({
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import type { NotificationService } from '@cloudbeaver/core-events';
|
||||
import {
|
||||
AsyncTaskInfo, GraphQLService, ServerInternalError, SqlExecuteInfo, SqlDataFilter, ResultDataFormat
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
import {
|
||||
CancellablePromise, cancellableTimeout, Deferred, EDeferredState
|
||||
} from '@cloudbeaver/core-utils';
|
||||
import type { IDatabaseExecutionContext } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
const DELAY_BETWEEN_TRIES = 1000;
|
||||
|
||||
@@ -31,7 +31,7 @@ export class SQLQueryExecutionProcess extends Deferred<SqlExecuteInfo> {
|
||||
|
||||
async start(
|
||||
query: string,
|
||||
context: IDatabaseExecutionContext,
|
||||
context: IConnectionExecutionContextInfo,
|
||||
filter: SqlDataFilter,
|
||||
dataFormat: ResultDataFormat
|
||||
): Promise<void> {
|
||||
@@ -107,13 +107,13 @@ export class SQLQueryExecutionProcess extends Deferred<SqlExecuteInfo> {
|
||||
|
||||
private async executeQueryAsync(
|
||||
query: string,
|
||||
context: IDatabaseExecutionContext,
|
||||
context: IConnectionExecutionContextInfo,
|
||||
filter: SqlDataFilter,
|
||||
dataFormat: ResultDataFormat
|
||||
): Promise<AsyncTaskInfo> {
|
||||
const { taskInfo } = await this.graphQLService.sdk.asyncSqlExecuteQuery({
|
||||
connectionId: context.connectionId,
|
||||
contextId: context.contextId,
|
||||
contextId: context.id,
|
||||
query,
|
||||
filter,
|
||||
dataFormat,
|
||||
|
||||
@@ -6,19 +6,16 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { ConnectionExecutionContextService, ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import { uuid, EDeferredState } from '@cloudbeaver/core-utils';
|
||||
import { uuid } from '@cloudbeaver/core-utils';
|
||||
import { DatabaseDataAccessMode, DataModelWrapper, TableViewerStorageService } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import type {
|
||||
IResultGroup, ISqlEditorTabState, ISqlQueryParams
|
||||
} from '../ISqlEditorTabState';
|
||||
import type { IResultGroup, ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { QueryDataSource } from '../QueryDataSource';
|
||||
import { SqlDialectInfoService } from '../SqlDialectInfoService';
|
||||
import type { SqlExecutionState } from '../SqlExecutionState';
|
||||
|
||||
@injectable()
|
||||
export class SqlQueryService {
|
||||
@@ -28,19 +25,18 @@ export class SqlQueryService {
|
||||
private graphQLService: GraphQLService,
|
||||
private notificationService: NotificationService,
|
||||
private connectionInfoResource: ConnectionInfoResource,
|
||||
private connectionExecutionContextService: ConnectionExecutionContextService
|
||||
) { }
|
||||
|
||||
async executeEditorQuery(
|
||||
executionState: SqlExecutionState,
|
||||
editorState: ISqlEditorTabState,
|
||||
query: string,
|
||||
inNewTab: boolean
|
||||
): Promise<void> {
|
||||
if (!query.trim()) {
|
||||
return;
|
||||
}
|
||||
const contextInfo = editorState.executionContext;
|
||||
const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id);
|
||||
|
||||
if (!editorState.executionContext) {
|
||||
if (!contextInfo || !executionContext) {
|
||||
console.error('executeEditorQuery executionContext is not provided');
|
||||
return;
|
||||
}
|
||||
@@ -50,27 +46,23 @@ export class SqlQueryService {
|
||||
let tabGroup: IResultGroup;
|
||||
let isNewTabCreated = false;
|
||||
|
||||
const connectionInfo = await this.connectionInfoResource.load(editorState.executionContext.connectionId);
|
||||
const connectionInfo = await this.connectionInfoResource.load(contextInfo.connectionId);
|
||||
const currentTab = editorState.tabs.find(tab => tab.id === editorState.currentTabId);
|
||||
const resultTab = editorState.resultTabs.find(tab => tab.tabId === currentTab?.id);
|
||||
|
||||
const sqlQueryParams: ISqlQueryParams = {
|
||||
executionContext: editorState.executionContext,
|
||||
query: await this.getSubQuery(editorState.executionContext.connectionId, query),
|
||||
};
|
||||
|
||||
if (inNewTab || !resultTab) {
|
||||
source = new QueryDataSource(this.graphQLService, this.notificationService, executionState);
|
||||
source = new QueryDataSource(this.graphQLService, this.notificationService);
|
||||
|
||||
model = this.tableViewerStorageService.create(source)
|
||||
.setCountGain()
|
||||
.setSlice(0);
|
||||
|
||||
tabGroup = this.createGroup(sqlQueryParams, editorState, model.id);
|
||||
tabGroup = this.createGroup(editorState, model.id, query);
|
||||
|
||||
isNewTabCreated = true;
|
||||
} else {
|
||||
tabGroup = editorState.resultGroups.find(group => group.groupId === resultTab.groupId)!;
|
||||
tabGroup.sqlQueryParams = sqlQueryParams;
|
||||
tabGroup.query = query;
|
||||
model = this.tableViewerStorageService.get(tabGroup.modelId)!;
|
||||
source = model.source as any as QueryDataSource;
|
||||
}
|
||||
@@ -79,12 +71,12 @@ export class SqlQueryService {
|
||||
.setAccess(connectionInfo.readOnly ? DatabaseDataAccessMode.Readonly : DatabaseDataAccessMode.Default);
|
||||
|
||||
source.setOptions({
|
||||
query: sqlQueryParams.query,
|
||||
connectionId: sqlQueryParams.executionContext.connectionId,
|
||||
query: query,
|
||||
connectionId: contextInfo.connectionId,
|
||||
constraints: [],
|
||||
whereFilter: '',
|
||||
})
|
||||
.setExecutionContext(sqlQueryParams.executionContext)
|
||||
.setExecutionContext(executionContext)
|
||||
.setSupportedDataFormats(connectionInfo.supportedDataFormats);
|
||||
|
||||
this.createTabsForGroup(editorState, tabGroup, model);
|
||||
@@ -119,7 +111,7 @@ export class SqlQueryService {
|
||||
this.selectFirstResult(editorState, tabGroup.groupId);
|
||||
} catch (exception) {
|
||||
// remove first panel if execution was cancelled
|
||||
if (source.queryExecutionProcess?.getState() === EDeferredState.CANCELLED && isNewTabCreated) {
|
||||
if (source.currentTask?.cancelled && isNewTabCreated) {
|
||||
this.removeGroup(editorState, tabGroup.groupId);
|
||||
const message = `Query execution has been canceled${status ? `: ${status}` : ''}`;
|
||||
this.notificationService.logException(exception, 'Query execution Error', message);
|
||||
@@ -128,9 +120,9 @@ export class SqlQueryService {
|
||||
}
|
||||
|
||||
createGroup(
|
||||
params: ISqlQueryParams,
|
||||
tabState: ISqlEditorTabState,
|
||||
modelId: string
|
||||
modelId: string,
|
||||
query: string,
|
||||
): IResultGroup {
|
||||
const nameOrder = Math.max(1, ...tabState.resultGroups.map(group => group.nameOrder + 1));
|
||||
const order = Math.max(0, ...tabState.tabs.map(tab => tab.order + 1));
|
||||
@@ -141,22 +133,12 @@ export class SqlQueryService {
|
||||
modelId,
|
||||
order,
|
||||
nameOrder,
|
||||
sqlQueryParams: params,
|
||||
query,
|
||||
});
|
||||
|
||||
return tabState.resultGroups.find(group => group.groupId === groupId)!;
|
||||
}
|
||||
|
||||
async getSubQuery(connectionId: string, query: string): Promise<string> {
|
||||
const dialectInfo = await this.sqlDialectInfoService.loadSqlDialectInfo(connectionId);
|
||||
|
||||
if (dialectInfo?.scriptDelimiter && query.endsWith(dialectInfo?.scriptDelimiter)) {
|
||||
return query.slice(0, query.length - dialectInfo.scriptDelimiter.length);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
removeGroup(tabState: ISqlEditorTabState, groupId: string): void {
|
||||
const tabsToRemove = tabState.resultTabs.filter(tab => tab.groupId === groupId).map(tab => tab.tabId);
|
||||
|
||||
|
||||
@@ -7,33 +7,19 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { SqlExecutionState } from '../SqlExecutionState';
|
||||
import { SqlExecutionPlanService } from './ExecutionPlan/SqlExecutionPlanService';
|
||||
import { SqlQueryService } from './SqlQueryService';
|
||||
|
||||
@injectable()
|
||||
export class SqlResultTabsService {
|
||||
private tabExecutionContext: MetadataMap<string, SqlExecutionState>;
|
||||
|
||||
constructor(
|
||||
private sqlQueryService: SqlQueryService,
|
||||
private sqlExecutionPlanService: SqlExecutionPlanService,
|
||||
) {
|
||||
this.tabExecutionContext = new MetadataMap(() => new SqlExecutionState());
|
||||
}
|
||||
) { }
|
||||
|
||||
getTabExecutionContext(tabId: string): SqlExecutionState {
|
||||
return this.tabExecutionContext.get(tabId);
|
||||
}
|
||||
|
||||
removeTabExecutionContext(tabId: string): void {
|
||||
this.tabExecutionContext.delete(tabId);
|
||||
}
|
||||
|
||||
async removeResultTab(state: ISqlEditorTabState, tabId: string, editorTabId: string): Promise<void> {
|
||||
async removeResultTab(state: ISqlEditorTabState, tabId: string): Promise<void> {
|
||||
const tab = state.tabs.find(tab => tab.id === tabId);
|
||||
|
||||
if (tab) {
|
||||
|
||||
Reference in New Issue
Block a user