diff --git a/webapp/packages/core-bootstrap/src/manifest.ts b/webapp/packages/core-bootstrap/src/manifest.ts index bf9594ae88..c1f43db8c4 100644 --- a/webapp/packages/core-bootstrap/src/manifest.ts +++ b/webapp/packages/core-bootstrap/src/manifest.ts @@ -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, diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts new file mode 100644 index 0000000000..af82ec540f --- /dev/null +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts @@ -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 | null; + + constructor( + private scheduler: TaskScheduler, + private connectionExecutionContextResource: ConnectionExecutionContextResource, + private contextId: string + ) { + this.currentTask = null; + makeObservable(this, { + currentTask: observable.ref, + }); + } + + run(task: () => Promise, cancel?: () => Promise | void): ITask { + 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 { + await this.scheduler.cancel(this.contextId); + } + + async destroy(): Promise { + if (!this.context) { + return; + } + + await this.cancel(); + await this.connectionExecutionContextResource.destroy(this.contextId); + } + + async update(defaultCatalog?: string, defaultSchema?: string): Promise { + if (!this.context) { + throw new Error('Execution Context not found'); + } + + await this.connectionExecutionContextResource.update(this.contextId, defaultCatalog, defaultSchema); + } +} diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts new file mode 100644 index 0000000000..7bd4098af2 --- /dev/null +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts @@ -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 { + static keyAll = resourceKeyList(['all'], 'all'); + private loadedKeyMetadata: MetadataMap; + + 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): 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): 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 { + 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 { + 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 { + 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 { + this.resetIncludes(); + await this.load(ConnectionExecutionContextResource.keyAll); + + return this.values; + } + + async refreshAll(): Promise { + 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 + ): Promise> { + 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 { + 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; + } +} diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextService.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextService.ts new file mode 100644 index 0000000000..b221863ed5 --- /dev/null +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextService.ts @@ -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; + protected scheduler: TaskScheduler; + + 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 { + const context = await this.connectionExecutionContextResource.create(connectionId, defaultCatalog, defaultSchema); + + return this.contexts.get(context.id); + } +} diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContext.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContext.ts new file mode 100644 index 0000000000..05573da0dd --- /dev/null +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContext.ts @@ -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: (task: () => Promise, cancel?: () => Promise | void) => ITask; + cancel: () => Promise; + destroy: () => Promise; + update: (defaultCatalog?: string, defaultSchema?: string) => Promise; +} diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseExecutionContext.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContextInfo.ts similarity index 60% rename from webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseExecutionContext.ts rename to webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContextInfo.ts index 0e994952bb..e19ed14458 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseExecutionContext.ts +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContextInfo.ts @@ -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 { } diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index ca404cefe8..d6c3513473 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -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'; diff --git a/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextCreate.gql b/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextCreate.gql new file mode 100644 index 0000000000..203c1afeec --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextCreate.gql @@ -0,0 +1,16 @@ +mutation executionContextCreate( + $connectionId: ID! + $defaultCatalog: String + $defaultSchema: String +) { + context: sqlContextCreate( + connectionId: $connectionId + defaultCatalog: $defaultCatalog + defaultSchema: $defaultSchema + ) { + id + connectionId + defaultCatalog + defaultSchema + } +} diff --git a/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextDestroy.gql b/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextDestroy.gql new file mode 100644 index 0000000000..ddca512ee0 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextDestroy.gql @@ -0,0 +1,9 @@ +mutation executionContextDestroy( + $connectionId: ID! + $contextId: ID! +) { + sqlContextDestroy( + connectionId: $connectionId + contextId: $contextId + ) +} diff --git a/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextList.gql b/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextList.gql new file mode 100644 index 0000000000..45a30c5faa --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextList.gql @@ -0,0 +1,14 @@ +query executionContextList( + $connectionId: ID + $contextId: ID +) { + contexts: sqlListContexts( + connectionId: $connectionId + contextId: $contextId + ) { + id + connectionId + defaultCatalog + defaultSchema + } +} diff --git a/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextUpdate.gql b/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextUpdate.gql new file mode 100644 index 0000000000..1be4231f8b --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/connections/execution-context/executionContextUpdate.gql @@ -0,0 +1,13 @@ +mutation executionContextUpdate( + $connectionId: ID! + $contextId: ID! + $defaultCatalog: ID + $defaultSchema: ID +) { + context: sqlContextSetDefaults( + connectionId: $connectionId + contextId: $contextId + defaultCatalog: $defaultCatalog + defaultSchema: $defaultSchema + ) +} diff --git a/webapp/packages/core-sdk/src/queries/sqlContextCreate.gql b/webapp/packages/core-sdk/src/queries/sqlContextCreate.gql deleted file mode 100644 index f5caea2647..0000000000 --- a/webapp/packages/core-sdk/src/queries/sqlContextCreate.gql +++ /dev/null @@ -1,15 +0,0 @@ -mutation sqlContextCreate( - $connectionId: ID! - $defaultCatalog: String - $defaultSchema: String -) { - context: sqlContextCreate( - connectionId: $connectionId - defaultCatalog: $defaultCatalog - defaultSchema: $defaultSchema - ) { - id - defaultCatalog - defaultSchema - } -} diff --git a/webapp/packages/core-sdk/src/queries/sqlContextDestroy.gql b/webapp/packages/core-sdk/src/queries/sqlContextDestroy.gql deleted file mode 100644 index 0e68be30de..0000000000 --- a/webapp/packages/core-sdk/src/queries/sqlContextDestroy.gql +++ /dev/null @@ -1,9 +0,0 @@ -mutation sqlContextDestroy( - $connectionId: ID! - $contextId: ID! -) { - sqlContextDestroy( - connectionId: $connectionId - contextId: $contextId - ) -} diff --git a/webapp/packages/core-sdk/src/queries/sqlContextSetDefaults.gql b/webapp/packages/core-sdk/src/queries/sqlContextSetDefaults.gql deleted file mode 100644 index 7353633c38..0000000000 --- a/webapp/packages/core-sdk/src/queries/sqlContextSetDefaults.gql +++ /dev/null @@ -1,13 +0,0 @@ -mutation sqlContextSetDefaults( - $connectionId: ID! - $contextId: ID! - $defaultCatalog: ID - $defaultSchema: ID -) { - context: sqlContextSetDefaults( - connectionId: $connectionId - contextId: $contextId - defaultCatalog: $defaultCatalog - defaultSchema: $defaultSchema - ) -} diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index 93774e8255..df22204e55 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -12,8 +12,550 @@ export interface Scalars { Boolean: boolean; Int: number; Float: number; - Object: any; DateTime: any; + Object: any; +} + +export interface AdminConnectionGrantInfo { + connectionId: Scalars['ID']; + subjectId: Scalars['ID']; + subjectType: AdminSubjectType; +} + +export interface AdminConnectionSearchInfo { + displayName: Scalars['String']; + host: Scalars['String']; + port: Scalars['Int']; + possibleDrivers: Array; + defaultDriver: Scalars['ID']; +} + +export interface AdminPermissionInfo { + id: Scalars['ID']; + label?: Maybe; + description?: Maybe; + provider: Scalars['String']; + category?: Maybe; +} + +export interface AdminRoleInfo { + roleId: Scalars['ID']; + roleName?: Maybe; + description?: Maybe; + rolePermissions: Array>; +} + +export enum AdminSubjectType { + User = 'user', + Role = 'role' +} + +export interface AdminUserInfo { + userId: Scalars['ID']; + metaParameters: Scalars['Object']; + configurationParameters: Scalars['Object']; + grantedRoles: Array; + grantedConnections: AdminConnectionGrantInfo[]; + origins: ObjectOrigin[]; + linkedAuthProviders: Array; +} + +export interface AsyncTaskInfo { + id: Scalars['String']; + name?: Maybe; + running: Scalars['Boolean']; + status?: Maybe; + error?: Maybe; + /** @deprecated Field no longer supported */ + result?: Maybe; + taskResult?: Maybe; +} + +export enum AuthCredentialEncryption { + None = 'none', + Plain = 'plain', + Hash = 'hash' +} + +export interface AuthCredentialInfo { + id: Scalars['ID']; + displayName: Scalars['String']; + description?: Maybe; + admin?: Maybe; + user?: Maybe; + identifying?: Maybe; + possibleValues?: Maybe>>; + encryption?: Maybe; +} + +export interface AuthProviderInfo { + id: Scalars['ID']; + label: Scalars['String']; + icon?: Maybe; + description?: Maybe; + defaultProvider?: Maybe; + credentialParameters: AuthCredentialInfo[]; +} + +export interface ConnectionConfig { + connectionId?: Maybe; + name?: Maybe; + description?: Maybe; + templateId?: Maybe; + driverId?: Maybe; + host?: Maybe; + port?: Maybe; + databaseName?: Maybe; + url?: Maybe; + properties?: Maybe; + template?: Maybe; + readOnly?: Maybe; + saveCredentials?: Maybe; + authModelId?: Maybe; + credentials?: Maybe; + providerProperties?: Maybe; + networkHandlersConfig?: Maybe; + dataSourceId?: Maybe; + userName?: Maybe; + userPassword?: Maybe; +} + +export interface ConnectionInfo { + id: Scalars['ID']; + driverId: Scalars['ID']; + name: Scalars['String']; + description?: Maybe; + host?: Maybe; + port?: Maybe; + databaseName?: Maybe; + url?: Maybe; + properties?: Maybe; + template: Scalars['Boolean']; + connected: Scalars['Boolean']; + provided: Scalars['Boolean']; + readOnly: Scalars['Boolean']; + useUrl: Scalars['Boolean']; + saveCredentials: Scalars['Boolean']; + connectTime?: Maybe; + connectionError?: Maybe; + serverVersion?: Maybe; + clientVersion?: Maybe; + origin: ObjectOrigin; + authNeeded: Scalars['Boolean']; + authModel?: Maybe; + authProperties: ObjectPropertyInfo[]; + providerProperties: Scalars['Object']; + networkHandlersConfig: NetworkHandlerConfig[]; + features: Array; + navigatorSettings: NavigatorSettings; + supportedDataFormats: ResultDataFormat[]; +} + +export interface DataTransferParameters { + processorId: Scalars['ID']; + settings?: Maybe; + processorProperties: Scalars['Object']; + filter?: Maybe; +} + +export interface DataTransferProcessorInfo { + id: Scalars['ID']; + name?: Maybe; + description?: Maybe; + fileExtension?: Maybe; + appFileExtension?: Maybe; + appName?: Maybe; + order: Scalars['Int']; + icon?: Maybe; + properties?: Maybe>>; + isBinary?: Maybe; + isHTML?: Maybe; +} + +export interface DataTypeLogicalOperation { + id: Scalars['ID']; + expression: Scalars['String']; + argumentCount?: Maybe; +} + +export interface DatabaseAuthModel { + id: Scalars['ID']; + displayName: Scalars['String']; + description?: Maybe; + icon?: Maybe; + properties: ObjectPropertyInfo[]; +} + +export interface DatabaseDocument { + id?: Maybe; + contentType?: Maybe; + properties?: Maybe; + data?: Maybe; +} + +export interface DatabaseObjectInfo { + name?: Maybe; + description?: Maybe; + type?: Maybe; + properties?: Maybe>>; + ordinalPosition?: Maybe; + fullyQualifiedName?: Maybe; + overloadedName?: Maybe; + uniqueName?: Maybe; + state?: Maybe; + features?: Maybe>; + editors?: Maybe>; +} + +export interface DatabaseObjectInfoPropertiesArgs { + filter?: Maybe; +} + +export interface DatabaseStructContainers { + catalogList: DatabaseObjectInfo[]; + schemaList: DatabaseObjectInfo[]; +} + +export interface DriverInfo { + id: Scalars['ID']; + name?: Maybe; + description?: Maybe; + icon?: Maybe; + iconBig?: Maybe; + providerId?: Maybe; + driverClassName?: Maybe; + defaultPort?: Maybe; + defaultDatabase?: Maybe; + defaultServer?: Maybe; + defaultUser?: Maybe; + sampleURL?: Maybe; + driverInfoURL?: Maybe; + driverPropertiesURL?: Maybe; + embedded?: Maybe; + /** @deprecated Field no longer supported */ + allowsEmptyPassword?: Maybe; + licenseRequired?: Maybe; + license?: Maybe; + custom?: Maybe; + promotedScore?: Maybe; + driverProperties: ObjectPropertyInfo[]; + driverParameters: Scalars['Object']; + providerProperties: ObjectPropertyInfo[]; + anonymousAccess?: Maybe; + defaultAuthModel: Scalars['ID']; + applicableAuthModel: Array; + applicableNetworkHandlers: Array>; +} + +export interface LogEntry { + time?: Maybe; + type: Scalars['String']; + message?: Maybe; + stackTrace?: Maybe; +} + +export interface Mutation { + asyncReadDataFromContainer: AsyncTaskInfo; + asyncSqlExecuteQuery: AsyncTaskInfo; + asyncSqlExecuteResults: SqlExecuteInfo; + asyncSqlExplainExecutionPlan: AsyncTaskInfo; + asyncSqlExplainExecutionPlanResult: SqlExecutionPlan; + asyncTaskCancel?: Maybe; + asyncTaskInfo: AsyncTaskInfo; + /** @deprecated Field no longer supported */ + asyncTaskStatus: AsyncTaskInfo; + changeSessionLanguage?: Maybe; + closeConnection: ConnectionInfo; + closeSession?: Maybe; + copyConnectionFromNode: ConnectionInfo; + createConnection: ConnectionInfo; + createConnectionFromTemplate: ConnectionInfo; + deleteConnection: Scalars['Boolean']; + initConnection: ConnectionInfo; + /** @deprecated Field no longer supported */ + openConnection: ConnectionInfo; + openSession: SessionInfo; + refreshSessionConnections?: Maybe; + setConnectionNavigatorSettings: ConnectionInfo; + sqlContextCreate: SqlContextInfo; + sqlContextDestroy: Scalars['Boolean']; + sqlContextSetDefaults: Scalars['Boolean']; + sqlResultClose: Scalars['Boolean']; + testConnection: ConnectionInfo; + testNetworkHandler: NetworkEndpointInfo; + touchSession?: Maybe; + updateConnection: ConnectionInfo; + updateResultsData?: Maybe; + updateResultsDataBatch?: Maybe; +} + +export interface MutationAsyncReadDataFromContainerArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + containerNodePath: Scalars['ID']; + filter?: Maybe; + dataFormat?: Maybe; +} + +export interface MutationAsyncSqlExecuteQueryArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + sql: Scalars['String']; + filter?: Maybe; + dataFormat?: Maybe; +} + +export interface MutationAsyncSqlExecuteResultsArgs { + taskId: Scalars['ID']; +} + +export interface MutationAsyncSqlExplainExecutionPlanArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + query: Scalars['String']; + configuration: Scalars['Object']; +} + +export interface MutationAsyncSqlExplainExecutionPlanResultArgs { + taskId: Scalars['ID']; +} + +export interface MutationAsyncTaskCancelArgs { + id: Scalars['String']; +} + +export interface MutationAsyncTaskInfoArgs { + id: Scalars['String']; + removeOnFinish: Scalars['Boolean']; +} + +export interface MutationAsyncTaskStatusArgs { + id: Scalars['String']; +} + +export interface MutationChangeSessionLanguageArgs { + locale?: Maybe; +} + +export interface MutationCloseConnectionArgs { + id: Scalars['ID']; +} + +export interface MutationCopyConnectionFromNodeArgs { + nodePath: Scalars['String']; + config?: Maybe; +} + +export interface MutationCreateConnectionArgs { + config: ConnectionConfig; +} + +export interface MutationCreateConnectionFromTemplateArgs { + templateId: Scalars['ID']; + connectionName?: Maybe; +} + +export interface MutationDeleteConnectionArgs { + id: Scalars['ID']; +} + +export interface MutationInitConnectionArgs { + id: Scalars['ID']; + credentials?: Maybe; + networkCredentials?: Maybe; + saveCredentials?: Maybe; +} + +export interface MutationOpenConnectionArgs { + config: ConnectionConfig; +} + +export interface MutationSetConnectionNavigatorSettingsArgs { + id: Scalars['ID']; + settings: NavigatorSettingsInput; +} + +export interface MutationSqlContextCreateArgs { + connectionId: Scalars['ID']; + defaultCatalog?: Maybe; + defaultSchema?: Maybe; +} + +export interface MutationSqlContextDestroyArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; +} + +export interface MutationSqlContextSetDefaultsArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + defaultCatalog?: Maybe; + defaultSchema?: Maybe; +} + +export interface MutationSqlResultCloseArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + resultId: Scalars['ID']; +} + +export interface MutationTestConnectionArgs { + config: ConnectionConfig; +} + +export interface MutationTestNetworkHandlerArgs { + config: NetworkHandlerConfigInput; +} + +export interface MutationUpdateConnectionArgs { + config: ConnectionConfig; +} + +export interface MutationUpdateResultsDataArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + resultsId: Scalars['ID']; + updateRow: Array>; + updateValues?: Maybe; +} + +export interface MutationUpdateResultsDataBatchArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + resultsId: Scalars['ID']; + updatedRows?: Maybe; + deletedRows?: Maybe; + addedRows?: Maybe; +} + +export interface NavigatorNodeInfo { + id: Scalars['ID']; + name?: Maybe; + icon?: Maybe; + description?: Maybe; + nodeType?: Maybe; + hasChildren?: Maybe; + object?: Maybe; + features?: Maybe>; + nodeDetails?: Maybe; + folder?: Maybe; + inline?: Maybe; + navigable?: Maybe; +} + +export interface NavigatorSettings { + showSystemObjects: Scalars['Boolean']; + showUtilityObjects: Scalars['Boolean']; + showOnlyEntities: Scalars['Boolean']; + mergeEntities: Scalars['Boolean']; + hideFolders: Scalars['Boolean']; + hideSchemas: Scalars['Boolean']; + hideVirtualModel: Scalars['Boolean']; +} + +export interface NavigatorSettingsInput { + showSystemObjects: Scalars['Boolean']; + showUtilityObjects: Scalars['Boolean']; + showOnlyEntities: Scalars['Boolean']; + mergeEntities: Scalars['Boolean']; + hideFolders: Scalars['Boolean']; + hideSchemas: Scalars['Boolean']; + hideVirtualModel: Scalars['Boolean']; +} + +export interface NetworkEndpointInfo { + message?: Maybe; + clientVersion?: Maybe; + serverVersion?: Maybe; +} + +export interface NetworkHandlerConfig { + id: Scalars['ID']; + enabled: Scalars['Boolean']; + userName?: Maybe; + password?: Maybe; + savePassword: Scalars['Boolean']; + properties: Scalars['Object']; +} + +export interface NetworkHandlerConfigInput { + id: Scalars['ID']; + enabled?: Maybe; + userName?: Maybe; + password?: Maybe; + savePassword?: Maybe; + properties?: Maybe; +} + +export interface NetworkHandlerDescriptor { + id: Scalars['ID']; + codeName: Scalars['String']; + label: Scalars['String']; + description?: Maybe; + secured: Scalars['Boolean']; + type?: Maybe; + properties: ObjectPropertyInfo[]; +} + +export enum NetworkHandlerType { + Tunnel = 'TUNNEL', + Proxy = 'PROXY', + Config = 'CONFIG' +} + +export interface ObjectDescriptor { + id?: Maybe; + displayName?: Maybe; + fullName?: Maybe; + uniqueName?: Maybe; + description?: Maybe; + value?: Maybe; +} + +export interface ObjectDetails { + id?: Maybe; + displayName?: Maybe; + description?: Maybe; + value?: Maybe; +} + +export interface ObjectOrigin { + type: Scalars['ID']; + subType?: Maybe; + displayName: Scalars['String']; + icon?: Maybe; + configuration?: Maybe; + details?: Maybe; +} + +export interface ObjectPropertyFilter { + ids?: Maybe>; + features?: Maybe>; + categories?: Maybe>; + dataTypes?: Maybe>; +} + +export interface ObjectPropertyInfo { + id?: Maybe; + displayName?: Maybe; + description?: Maybe; + category?: Maybe; + dataType?: Maybe; + value?: Maybe; + validValues?: Maybe>>; + defaultValue?: Maybe; + features: Array; + order: Scalars['Int']; +} + +export interface ProductInfo { + id: Scalars['ID']; + version: Scalars['String']; + name: Scalars['String']; + description?: Maybe; + buildTime: Scalars['String']; + releaseTime: Scalars['String']; + licenseInfo?: Maybe; } export interface Query { @@ -65,7 +607,7 @@ export interface Query { setUserCredentials?: Maybe; sqlCompletionProposals?: Maybe>>; sqlDialectInfo?: Maybe; - sqlListContexts?: Maybe>>; + sqlListContexts: Array>; sqlSupportedOperations: DataTypeLogicalOperation[]; templateConnections: ConnectionInfo[]; updateConnectionConfiguration: ConnectionInfo; @@ -255,7 +797,8 @@ export interface QuerySqlDialectInfoArgs { } export interface QuerySqlListContextsArgs { - connectionId: Scalars['ID']; + connectionId?: Maybe; + contextId?: Maybe; } export interface QuerySqlSupportedOperationsArgs { @@ -280,237 +823,122 @@ export interface QueryUserConnectionsArgs { id?: Maybe; } -export interface Mutation { - asyncReadDataFromContainer: AsyncTaskInfo; - asyncSqlExecuteQuery: AsyncTaskInfo; - asyncSqlExecuteResults: SqlExecuteInfo; - asyncSqlExplainExecutionPlan: AsyncTaskInfo; - asyncSqlExplainExecutionPlanResult: SqlExecutionPlan; - asyncTaskCancel?: Maybe; - asyncTaskInfo: AsyncTaskInfo; - /** @deprecated Field no longer supported */ - asyncTaskStatus: AsyncTaskInfo; - changeSessionLanguage?: Maybe; - closeConnection: ConnectionInfo; - closeSession?: Maybe; - copyConnectionFromNode: ConnectionInfo; - createConnection: ConnectionInfo; - createConnectionFromTemplate: ConnectionInfo; - deleteConnection: Scalars['Boolean']; - initConnection: ConnectionInfo; - /** @deprecated Field no longer supported */ - openConnection: ConnectionInfo; - openSession: SessionInfo; - refreshSessionConnections?: Maybe; - setConnectionNavigatorSettings: ConnectionInfo; - sqlContextCreate: SqlContextInfo; - sqlContextDestroy: Scalars['Boolean']; - sqlContextSetDefaults: Scalars['Boolean']; - sqlResultClose: Scalars['Boolean']; - testConnection: ConnectionInfo; - testNetworkHandler: NetworkEndpointInfo; - touchSession?: Maybe; - updateConnection: ConnectionInfo; - updateResultsData?: Maybe; - updateResultsDataBatch?: Maybe; +export enum ResultDataFormat { + Resultset = 'resultset', + Document = 'document', + Graph = 'graph', + Timeseries = 'timeseries' } -export interface MutationAsyncReadDataFromContainerArgs { - connectionId: Scalars['ID']; - contextId: Scalars['ID']; - containerNodePath: Scalars['ID']; - filter?: Maybe; - dataFormat?: Maybe; +export interface SqlCompletionProposal { + displayString: Scalars['String']; + type: Scalars['String']; + score?: Maybe; + replacementString: Scalars['String']; + replacementOffset: Scalars['Int']; + replacementLength: Scalars['Int']; + cursorPosition?: Maybe; + icon?: Maybe; + nodePath?: Maybe; } -export interface MutationAsyncSqlExecuteQueryArgs { - connectionId: Scalars['ID']; - contextId: Scalars['ID']; - sql: Scalars['String']; - filter?: Maybe; - dataFormat?: Maybe; -} - -export interface MutationAsyncSqlExecuteResultsArgs { - taskId: Scalars['ID']; -} - -export interface MutationAsyncSqlExplainExecutionPlanArgs { - connectionId: Scalars['ID']; - contextId: Scalars['ID']; - query: Scalars['String']; - configuration: Scalars['Object']; -} - -export interface MutationAsyncSqlExplainExecutionPlanResultArgs { - taskId: Scalars['ID']; -} - -export interface MutationAsyncTaskCancelArgs { - id: Scalars['String']; -} - -export interface MutationAsyncTaskInfoArgs { - id: Scalars['String']; - removeOnFinish: Scalars['Boolean']; -} - -export interface MutationAsyncTaskStatusArgs { - id: Scalars['String']; -} - -export interface MutationChangeSessionLanguageArgs { - locale?: Maybe; -} - -export interface MutationCloseConnectionArgs { +export interface SqlContextInfo { id: Scalars['ID']; -} - -export interface MutationCopyConnectionFromNodeArgs { - nodePath: Scalars['String']; - config?: Maybe; -} - -export interface MutationCreateConnectionArgs { - config: ConnectionConfig; -} - -export interface MutationCreateConnectionFromTemplateArgs { - templateId: Scalars['ID']; - connectionName?: Maybe; -} - -export interface MutationDeleteConnectionArgs { - id: Scalars['ID']; -} - -export interface MutationInitConnectionArgs { - id: Scalars['ID']; - credentials?: Maybe; - networkCredentials?: Maybe; - saveCredentials?: Maybe; -} - -export interface MutationOpenConnectionArgs { - config: ConnectionConfig; -} - -export interface MutationSetConnectionNavigatorSettingsArgs { - id: Scalars['ID']; - settings: NavigatorSettingsInput; -} - -export interface MutationSqlContextCreateArgs { connectionId: Scalars['ID']; defaultCatalog?: Maybe; defaultSchema?: Maybe; } -export interface MutationSqlContextDestroyArgs { - connectionId: Scalars['ID']; - contextId: Scalars['ID']; +export interface SqlDataFilter { + offset?: Maybe; + limit?: Maybe; + constraints?: Maybe>>; + where?: Maybe; + orderBy?: Maybe; } -export interface MutationSqlContextSetDefaultsArgs { - connectionId: Scalars['ID']; - contextId: Scalars['ID']; - defaultCatalog?: Maybe; - defaultSchema?: Maybe; +export interface SqlDataFilterConstraint { + attribute: Scalars['String']; + orderPosition?: Maybe; + orderAsc?: Maybe; + criteria?: Maybe; + operator?: Maybe; + value?: Maybe; } -export interface MutationSqlResultCloseArgs { - connectionId: Scalars['ID']; - contextId: Scalars['ID']; - resultId: Scalars['ID']; +export interface SqlDialectInfo { + name: Scalars['String']; + dataTypes: Array>; + functions: Array>; + reservedWords: Array>; + quoteStrings: Array>>>; + singleLineComments: Array>; + multiLineComments: Array>>>; + catalogSeparator?: Maybe; + structSeparator?: Maybe; + scriptDelimiter?: Maybe; + supportsExplainExecutionPlan: Scalars['Boolean']; } -export interface MutationTestConnectionArgs { - config: ConnectionConfig; +export interface SqlExecuteInfo { + statusMessage?: Maybe; + duration: Scalars['Int']; + filterText?: Maybe; + results: SqlQueryResults[]; } -export interface MutationTestNetworkHandlerArgs { - config: NetworkHandlerConfigInput; +export interface SqlExecutionPlan { + query: Scalars['String']; + nodes: SqlExecutionPlanNode[]; } -export interface MutationUpdateConnectionArgs { - config: ConnectionConfig; +export interface SqlExecutionPlanNode { + id: Scalars['ID']; + parentId?: Maybe; + kind: Scalars['String']; + name?: Maybe; + type: Scalars['String']; + condition?: Maybe; + description?: Maybe; + properties: ObjectPropertyInfo[]; } -export interface MutationUpdateResultsDataArgs { - connectionId: Scalars['ID']; - contextId: Scalars['ID']; - resultsId: Scalars['ID']; - updateRow: Array>; +export interface SqlQueryResults { + title?: Maybe; + updateRowCount?: Maybe; + sourceQuery?: Maybe; + dataFormat?: Maybe; + resultSet?: Maybe; +} + +export interface SqlResultColumn { + position: Scalars['Int']; + name?: Maybe; + label?: Maybe; + icon?: Maybe; + entityName?: Maybe; + dataKind?: Maybe; + typeName?: Maybe; + fullTypeName?: Maybe; + maxLength?: Maybe; + scale?: Maybe; + precision?: Maybe; + required: Scalars['Boolean']; + readOnly: Scalars['Boolean']; + readOnlyStatus?: Maybe; + supportedOperations: DataTypeLogicalOperation[]; +} + +export interface SqlResultRow { + data: Array>; updateValues?: Maybe; } -export interface MutationUpdateResultsDataBatchArgs { - connectionId: Scalars['ID']; - contextId: Scalars['ID']; - resultsId: Scalars['ID']; - updatedRows?: Maybe; - deletedRows?: Maybe; - addedRows?: Maybe; -} - -export interface ObjectPropertyInfo { - id?: Maybe; - displayName?: Maybe; - description?: Maybe; - category?: Maybe; - dataType?: Maybe; - value?: Maybe; - validValues?: Maybe>>; - defaultValue?: Maybe; - features: Array; - order: Scalars['Int']; -} - -export interface AsyncTaskInfo { - id: Scalars['String']; - name?: Maybe; - running: Scalars['Boolean']; - status?: Maybe; - error?: Maybe; - /** @deprecated Field no longer supported */ - result?: Maybe; - taskResult?: Maybe; -} - -export interface ServerError { - message?: Maybe; - errorCode?: Maybe; - stackTrace?: Maybe; - causedBy?: Maybe; -} - -export interface ServerMessage { - time?: Maybe; - message?: Maybe; -} - -export interface ServerLanguage { - isoCode: Scalars['String']; - displayName?: Maybe; - nativeName?: Maybe; -} - -export interface WebServiceConfig { - id: Scalars['String']; - name: Scalars['String']; - description: Scalars['String']; - bundleVersion: Scalars['String']; -} - -export interface ProductInfo { +export interface SqlResultSet { id: Scalars['ID']; - version: Scalars['String']; - name: Scalars['String']; - description?: Maybe; - buildTime: Scalars['String']; - releaseTime: Scalars['String']; - licenseInfo?: Maybe; + columns?: Maybe>>; + rows?: Maybe>>>>; + hasMoreData?: Maybe; } export interface ServerConfig { @@ -541,419 +969,6 @@ export interface ServerConfig { defaultNavigatorSettings: NavigatorSettings; } -export interface SessionInfo { - createTime: Scalars['String']; - lastAccessTime: Scalars['String']; - locale: Scalars['String']; - cacheExpired: Scalars['Boolean']; - serverMessages?: Maybe>>; - connections: ConnectionInfo[]; -} - -export interface DatabaseAuthModel { - id: Scalars['ID']; - displayName: Scalars['String']; - description?: Maybe; - icon?: Maybe; - properties: ObjectPropertyInfo[]; -} - -export interface DriverInfo { - id: Scalars['ID']; - name?: Maybe; - description?: Maybe; - icon?: Maybe; - iconBig?: Maybe; - providerId?: Maybe; - driverClassName?: Maybe; - defaultPort?: Maybe; - defaultDatabase?: Maybe; - defaultServer?: Maybe; - defaultUser?: Maybe; - sampleURL?: Maybe; - driverInfoURL?: Maybe; - driverPropertiesURL?: Maybe; - embedded?: Maybe; - /** @deprecated Field no longer supported */ - allowsEmptyPassword?: Maybe; - licenseRequired?: Maybe; - license?: Maybe; - custom?: Maybe; - promotedScore?: Maybe; - driverProperties: ObjectPropertyInfo[]; - driverParameters: Scalars['Object']; - providerProperties: ObjectPropertyInfo[]; - anonymousAccess?: Maybe; - defaultAuthModel: Scalars['ID']; - applicableAuthModel: Array; - applicableNetworkHandlers: Array>; -} - -export enum ResultDataFormat { - Resultset = 'resultset', - Document = 'document', - Graph = 'graph', - Timeseries = 'timeseries' -} - -export enum NetworkHandlerType { - Tunnel = 'TUNNEL', - Proxy = 'PROXY', - Config = 'CONFIG' -} - -export interface NetworkHandlerDescriptor { - id: Scalars['ID']; - codeName: Scalars['String']; - label: Scalars['String']; - description?: Maybe; - secured: Scalars['Boolean']; - type?: Maybe; - properties: ObjectPropertyInfo[]; -} - -export interface NetworkHandlerConfig { - id: Scalars['ID']; - enabled: Scalars['Boolean']; - userName?: Maybe; - password?: Maybe; - savePassword: Scalars['Boolean']; - properties: Scalars['Object']; -} - -export interface ConnectionInfo { - id: Scalars['ID']; - driverId: Scalars['ID']; - name: Scalars['String']; - description?: Maybe; - host?: Maybe; - port?: Maybe; - databaseName?: Maybe; - url?: Maybe; - properties?: Maybe; - template: Scalars['Boolean']; - connected: Scalars['Boolean']; - provided: Scalars['Boolean']; - readOnly: Scalars['Boolean']; - useUrl: Scalars['Boolean']; - saveCredentials: Scalars['Boolean']; - connectTime?: Maybe; - connectionError?: Maybe; - serverVersion?: Maybe; - clientVersion?: Maybe; - origin: ObjectOrigin; - authNeeded: Scalars['Boolean']; - authModel?: Maybe; - authProperties: ObjectPropertyInfo[]; - providerProperties: Scalars['Object']; - networkHandlersConfig: NetworkHandlerConfig[]; - features: Array; - navigatorSettings: NavigatorSettings; - supportedDataFormats: ResultDataFormat[]; -} - -export interface NetworkEndpointInfo { - message?: Maybe; - clientVersion?: Maybe; - serverVersion?: Maybe; -} - -export interface ObjectOrigin { - type: Scalars['ID']; - subType?: Maybe; - displayName: Scalars['String']; - icon?: Maybe; - configuration?: Maybe; - details?: Maybe; -} - -export interface NavigatorSettings { - showSystemObjects: Scalars['Boolean']; - showUtilityObjects: Scalars['Boolean']; - showOnlyEntities: Scalars['Boolean']; - mergeEntities: Scalars['Boolean']; - hideFolders: Scalars['Boolean']; - hideSchemas: Scalars['Boolean']; - hideVirtualModel: Scalars['Boolean']; -} - -export interface LogEntry { - time?: Maybe; - type: Scalars['String']; - message?: Maybe; - stackTrace?: Maybe; -} - -export interface NavigatorSettingsInput { - showSystemObjects: Scalars['Boolean']; - showUtilityObjects: Scalars['Boolean']; - showOnlyEntities: Scalars['Boolean']; - mergeEntities: Scalars['Boolean']; - hideFolders: Scalars['Boolean']; - hideSchemas: Scalars['Boolean']; - hideVirtualModel: Scalars['Boolean']; -} - -export interface NetworkHandlerConfigInput { - id: Scalars['ID']; - enabled?: Maybe; - userName?: Maybe; - password?: Maybe; - savePassword?: Maybe; - properties?: Maybe; -} - -export interface ConnectionConfig { - connectionId?: Maybe; - name?: Maybe; - description?: Maybe; - templateId?: Maybe; - driverId?: Maybe; - host?: Maybe; - port?: Maybe; - databaseName?: Maybe; - url?: Maybe; - properties?: Maybe; - template?: Maybe; - readOnly?: Maybe; - saveCredentials?: Maybe; - authModelId?: Maybe; - credentials?: Maybe; - providerProperties?: Maybe; - networkHandlersConfig?: Maybe; - dataSourceId?: Maybe; - userName?: Maybe; - userPassword?: Maybe; -} - -export interface ObjectDescriptor { - id?: Maybe; - displayName?: Maybe; - fullName?: Maybe; - uniqueName?: Maybe; - description?: Maybe; - value?: Maybe; -} - -export interface ObjectPropertyFilter { - ids?: Maybe>; - features?: Maybe>; - categories?: Maybe>; - dataTypes?: Maybe>; -} - -export interface ObjectDetails { - id?: Maybe; - displayName?: Maybe; - description?: Maybe; - value?: Maybe; -} - -export interface DatabaseObjectInfo { - name?: Maybe; - description?: Maybe; - type?: Maybe; - properties?: Maybe>>; - ordinalPosition?: Maybe; - fullyQualifiedName?: Maybe; - overloadedName?: Maybe; - uniqueName?: Maybe; - state?: Maybe; - features?: Maybe>; - editors?: Maybe>; -} - -export interface DatabaseObjectInfoPropertiesArgs { - filter?: Maybe; -} - -export interface NavigatorNodeInfo { - id: Scalars['ID']; - name?: Maybe; - icon?: Maybe; - description?: Maybe; - nodeType?: Maybe; - hasChildren?: Maybe; - object?: Maybe; - features?: Maybe>; - nodeDetails?: Maybe; - folder?: Maybe; - inline?: Maybe; - navigable?: Maybe; -} - -export interface DatabaseStructContainers { - catalogList: DatabaseObjectInfo[]; - schemaList: DatabaseObjectInfo[]; -} - -export interface SqlDialectInfo { - name: Scalars['String']; - dataTypes: Array>; - functions: Array>; - reservedWords: Array>; - quoteStrings: Array>>>; - singleLineComments: Array>; - multiLineComments: Array>>>; - catalogSeparator?: Maybe; - structSeparator?: Maybe; - scriptDelimiter?: Maybe; - supportsExplainExecutionPlan: Scalars['Boolean']; -} - -export interface SqlCompletionProposal { - displayString: Scalars['String']; - type: Scalars['String']; - score?: Maybe; - replacementString: Scalars['String']; - replacementOffset: Scalars['Int']; - replacementLength: Scalars['Int']; - cursorPosition?: Maybe; - icon?: Maybe; - nodePath?: Maybe; -} - -export interface SqlContextInfo { - id: Scalars['ID']; - defaultCatalog?: Maybe; - defaultSchema?: Maybe; -} - -export interface SqlDataFilterConstraint { - attribute: Scalars['String']; - orderPosition?: Maybe; - orderAsc?: Maybe; - criteria?: Maybe; - operator?: Maybe; - value?: Maybe; -} - -export interface SqlDataFilter { - offset?: Maybe; - limit?: Maybe; - constraints?: Maybe>>; - where?: Maybe; - orderBy?: Maybe; -} - -export interface SqlResultColumn { - position: Scalars['Int']; - name?: Maybe; - label?: Maybe; - icon?: Maybe; - entityName?: Maybe; - dataKind?: Maybe; - typeName?: Maybe; - fullTypeName?: Maybe; - maxLength?: Maybe; - scale?: Maybe; - precision?: Maybe; - required: Scalars['Boolean']; - readOnly: Scalars['Boolean']; - readOnlyStatus?: Maybe; - supportedOperations: DataTypeLogicalOperation[]; -} - -export interface DatabaseDocument { - id?: Maybe; - contentType?: Maybe; - properties?: Maybe; - data?: Maybe; -} - -export interface SqlResultSet { - id: Scalars['ID']; - columns?: Maybe>>; - rows?: Maybe>>>>; - hasMoreData?: Maybe; -} - -export interface SqlQueryResults { - title?: Maybe; - updateRowCount?: Maybe; - sourceQuery?: Maybe; - dataFormat?: Maybe; - resultSet?: Maybe; -} - -export interface SqlExecuteInfo { - statusMessage?: Maybe; - duration: Scalars['Int']; - filterText?: Maybe; - results: SqlQueryResults[]; -} - -export interface SqlResultRow { - data: Array>; - updateValues?: Maybe; -} - -export interface DataTypeLogicalOperation { - id: Scalars['ID']; - expression: Scalars['String']; - argumentCount?: Maybe; -} - -export interface SqlExecutionPlan { - query: Scalars['String']; - nodes: SqlExecutionPlanNode[]; -} - -export interface SqlExecutionPlanNode { - id: Scalars['ID']; - parentId?: Maybe; - kind: Scalars['String']; - name?: Maybe; - type: Scalars['String']; - condition?: Maybe; - description?: Maybe; - properties: ObjectPropertyInfo[]; -} - -export enum AdminSubjectType { - User = 'user', - Role = 'role' -} - -export interface AdminConnectionGrantInfo { - connectionId: Scalars['ID']; - subjectId: Scalars['ID']; - subjectType: AdminSubjectType; -} - -export interface AdminConnectionSearchInfo { - displayName: Scalars['String']; - host: Scalars['String']; - port: Scalars['Int']; - possibleDrivers: Array; - defaultDriver: Scalars['ID']; -} - -export interface AdminUserInfo { - userId: Scalars['ID']; - metaParameters: Scalars['Object']; - configurationParameters: Scalars['Object']; - grantedRoles: Array; - grantedConnections: AdminConnectionGrantInfo[]; - origins: ObjectOrigin[]; - linkedAuthProviders: Array; -} - -export interface AdminRoleInfo { - roleId: Scalars['ID']; - roleName?: Maybe; - description?: Maybe; - rolePermissions: Array>; -} - -export interface AdminPermissionInfo { - id: Scalars['ID']; - label?: Maybe; - description?: Maybe; - provider: Scalars['String']; - category?: Maybe; -} - export interface ServerConfigInput { serverName?: Maybe; serverURL?: Maybe; @@ -968,30 +983,31 @@ export interface ServerConfigInput { sessionExpireTime?: Maybe; } -export enum AuthCredentialEncryption { - None = 'none', - Plain = 'plain', - Hash = 'hash' +export interface ServerError { + message?: Maybe; + errorCode?: Maybe; + stackTrace?: Maybe; + causedBy?: Maybe; } -export interface AuthCredentialInfo { - id: Scalars['ID']; - displayName: Scalars['String']; - description?: Maybe; - admin?: Maybe; - user?: Maybe; - identifying?: Maybe; - possibleValues?: Maybe>>; - encryption?: Maybe; +export interface ServerLanguage { + isoCode: Scalars['String']; + displayName?: Maybe; + nativeName?: Maybe; } -export interface AuthProviderInfo { - id: Scalars['ID']; - label: Scalars['String']; - icon?: Maybe; - description?: Maybe; - defaultProvider?: Maybe; - credentialParameters: AuthCredentialInfo[]; +export interface ServerMessage { + time?: Maybe; + message?: Maybe; +} + +export interface SessionInfo { + createTime: Scalars['String']; + lastAccessTime: Scalars['String']; + locale: Scalars['String']; + cacheExpired: Scalars['Boolean']; + serverMessages?: Maybe>>; + connections: ConnectionInfo[]; } export interface UserAuthToken { @@ -1010,25 +1026,11 @@ export interface UserInfo { linkedAuthProviders: Array; } -export interface DataTransferProcessorInfo { - id: Scalars['ID']; - name?: Maybe; - description?: Maybe; - fileExtension?: Maybe; - appFileExtension?: Maybe; - appName?: Maybe; - order: Scalars['Int']; - icon?: Maybe; - properties?: Maybe>>; - isBinary?: Maybe; - isHTML?: Maybe; -} - -export interface DataTransferParameters { - processorId: Scalars['ID']; - settings?: Maybe; - processorProperties: Scalars['Object']; - filter?: Maybe; +export interface WebServiceConfig { + id: Scalars['String']; + name: Scalars['String']; + description: Scalars['String']; + bundleVersion: Scalars['String']; } export type AsyncTaskCancelMutationVariables = Exact<{ @@ -1268,6 +1270,37 @@ export type DriverListQueryVariables = Exact<{ export interface DriverListQuery { drivers: DatabaseDriverFragment[] } +export type ExecutionContextCreateMutationVariables = Exact<{ + connectionId: Scalars['ID']; + defaultCatalog?: Maybe; + defaultSchema?: Maybe; +}>; + +export interface ExecutionContextCreateMutation { context: Pick } + +export type ExecutionContextDestroyMutationVariables = Exact<{ + connectionId: Scalars['ID']; + contextId: Scalars['ID']; +}>; + +export type ExecutionContextDestroyMutation = Pick; + +export type ExecutionContextListQueryVariables = Exact<{ + connectionId?: Maybe; + contextId?: Maybe; +}>; + +export interface ExecutionContextListQuery { contexts: Array>> } + +export type ExecutionContextUpdateMutationVariables = Exact<{ + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + defaultCatalog?: Maybe; + defaultSchema?: Maybe; +}>; + +export interface ExecutionContextUpdateMutation { context: Mutation['sqlContextSetDefaults'] } + export type GetAuthModelsQueryVariables = Exact<{ [key: string]: never }>; export interface GetAuthModelsQuery { @@ -1414,12 +1447,12 @@ export type AuthTokenFragment = ( export type DatabaseConnectionFragment = ( Pick - & { origin: ObjectOriginInfoFragment; authProperties: UserConnectionAuthPropertiesFragment[]; networkHandlersConfig: Array, 'userName' | 'password' | 'properties'>>; navigatorSettings: AllNavigatorSettingsFragment } + & { origin?: Maybe; authProperties?: Maybe; networkHandlersConfig: Array, 'userName' | 'password' | 'properties'>>; navigatorSettings: AllNavigatorSettingsFragment } ); export type DatabaseDriverFragment = ( MakeOptional, 'driverParameters'> - & { providerProperties: Array>; driverProperties: Array> } + & { providerProperties?: Maybe>>; driverProperties?: Maybe>> } ); export type NavNodeInfoFragment = ( @@ -1696,30 +1729,6 @@ export type TouchSessionMutationVariables = Exact<{ [key: string]: never }>; export type TouchSessionMutation = Pick; -export type SqlContextCreateMutationVariables = Exact<{ - connectionId: Scalars['ID']; - defaultCatalog?: Maybe; - defaultSchema?: Maybe; -}>; - -export interface SqlContextCreateMutation { context: Pick } - -export type SqlContextDestroyMutationVariables = Exact<{ - connectionId: Scalars['ID']; - contextId: Scalars['ID']; -}>; - -export type SqlContextDestroyMutation = Pick; - -export type SqlContextSetDefaultsMutationVariables = Exact<{ - connectionId: Scalars['ID']; - contextId: Scalars['ID']; - defaultCatalog?: Maybe; - defaultSchema?: Maybe; -}>; - -export interface SqlContextSetDefaultsMutation { context: Mutation['sqlContextSetDefaults'] } - export type SqlResultCloseMutationVariables = Exact<{ connectionId: Scalars['ID']; contextId: Scalars['ID']; @@ -2163,6 +2172,45 @@ export const DriverListDocument = ` } } ${DatabaseDriverFragmentDoc}`; +export const ExecutionContextCreateDocument = ` + mutation executionContextCreate($connectionId: ID!, $defaultCatalog: String, $defaultSchema: String) { + context: sqlContextCreate( + connectionId: $connectionId + defaultCatalog: $defaultCatalog + defaultSchema: $defaultSchema + ) { + id + connectionId + defaultCatalog + defaultSchema + } +} + `; +export const ExecutionContextDestroyDocument = ` + mutation executionContextDestroy($connectionId: ID!, $contextId: ID!) { + sqlContextDestroy(connectionId: $connectionId, contextId: $contextId) +} + `; +export const ExecutionContextListDocument = ` + query executionContextList($connectionId: ID, $contextId: ID) { + contexts: sqlListContexts(connectionId: $connectionId, contextId: $contextId) { + id + connectionId + defaultCatalog + defaultSchema + } +} + `; +export const ExecutionContextUpdateDocument = ` + mutation executionContextUpdate($connectionId: ID!, $contextId: ID!, $defaultCatalog: ID, $defaultSchema: ID) { + context: sqlContextSetDefaults( + connectionId: $connectionId + contextId: $contextId + defaultCatalog: $defaultCatalog + defaultSchema: $defaultSchema + ) +} + `; export const GetAuthModelsDocument = ` query getAuthModels { models: authModels { @@ -2728,34 +2776,6 @@ export const TouchSessionDocument = ` touchSession } `; -export const SqlContextCreateDocument = ` - mutation sqlContextCreate($connectionId: ID!, $defaultCatalog: String, $defaultSchema: String) { - context: sqlContextCreate( - connectionId: $connectionId - defaultCatalog: $defaultCatalog - defaultSchema: $defaultSchema - ) { - id - defaultCatalog - defaultSchema - } -} - `; -export const SqlContextDestroyDocument = ` - mutation sqlContextDestroy($connectionId: ID!, $contextId: ID!) { - sqlContextDestroy(connectionId: $connectionId, contextId: $contextId) -} - `; -export const SqlContextSetDefaultsDocument = ` - mutation sqlContextSetDefaults($connectionId: ID!, $contextId: ID!, $defaultCatalog: ID, $defaultSchema: ID) { - context: sqlContextSetDefaults( - connectionId: $connectionId - contextId: $contextId - defaultCatalog: $defaultCatalog - defaultSchema: $defaultSchema - ) -} - `; export const SqlResultCloseDocument = ` mutation sqlResultClose($connectionId: ID!, $contextId: ID!, $resultId: ID!) { result: sqlResultClose( @@ -2861,6 +2881,18 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = driverList(variables: DriverListQueryVariables): Promise { return withWrapper(() => client.request(DriverListDocument, variables)); }, + executionContextCreate(variables: ExecutionContextCreateMutationVariables): Promise { + return withWrapper(() => client.request(ExecutionContextCreateDocument, variables)); + }, + executionContextDestroy(variables: ExecutionContextDestroyMutationVariables): Promise { + return withWrapper(() => client.request(ExecutionContextDestroyDocument, variables)); + }, + executionContextList(variables?: ExecutionContextListQueryVariables): Promise { + return withWrapper(() => client.request(ExecutionContextListDocument, variables)); + }, + executionContextUpdate(variables: ExecutionContextUpdateMutationVariables): Promise { + return withWrapper(() => client.request(ExecutionContextUpdateDocument, variables)); + }, getAuthModels(variables?: GetAuthModelsQueryVariables): Promise { return withWrapper(() => client.request(GetAuthModelsDocument, variables)); }, @@ -2981,15 +3013,6 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = touchSession(variables?: TouchSessionMutationVariables): Promise { return withWrapper(() => client.request(TouchSessionDocument, variables)); }, - sqlContextCreate(variables: SqlContextCreateMutationVariables): Promise { - return withWrapper(() => client.request(SqlContextCreateDocument, variables)); - }, - sqlContextDestroy(variables: SqlContextDestroyMutationVariables): Promise { - return withWrapper(() => client.request(SqlContextDestroyDocument, variables)); - }, - sqlContextSetDefaults(variables: SqlContextSetDefaultsMutationVariables): Promise { - return withWrapper(() => client.request(SqlContextSetDefaultsDocument, variables)); - }, sqlResultClose(variables: SqlResultCloseMutationVariables): Promise { return withWrapper(() => client.request(SqlResultCloseDocument, variables)); }, diff --git a/webapp/packages/plugin-codemirror/src/theme/_base-code-editor.scss b/webapp/packages/plugin-codemirror/src/theme/_base-code-editor.scss index 0ddaafceb5..d225a6ddd6 100644 --- a/webapp/packages/plugin-codemirror/src/theme/_base-code-editor.scss +++ b/webapp/packages/plugin-codemirror/src/theme/_base-code-editor.scss @@ -192,5 +192,9 @@ text-decoration: underline; color: white !important; } + + .cm-s-material .active-query { + background-color: $active-query; + } } } diff --git a/webapp/packages/plugin-codemirror/src/theme/dark.module.scss b/webapp/packages/plugin-codemirror/src/theme/dark.module.scss index 0cbc82c2c6..456fa01e70 100644 --- a/webapp/packages/plugin-codemirror/src/theme/dark.module.scss +++ b/webapp/packages/plugin-codemirror/src/theme/dark.module.scss @@ -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); diff --git a/webapp/packages/plugin-codemirror/src/theme/light.module.scss b/webapp/packages/plugin-codemirror/src/theme/light.module.scss index 66c23caa8d..c6e29b3567 100644 --- a/webapp/packages/plugin-codemirror/src/theme/light.module.scss +++ b/webapp/packages/plugin-codemirror/src/theme/light.module.scss @@ -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); diff --git a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts index 8d33c878f8..66ffaebc3d 100644 --- a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts +++ b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts @@ -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, diff --git a/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts b/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts index e3bf15f546..8f428b42c7 100644 --- a/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts @@ -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 { - currentFetchTableProcess: FetchTableDataAsyncProcess | null; + currentTask: ITask | 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 { + if (this.currentTask) { + await this.currentTask.cancel(); } - return false; } async request( prevResults: IDatabaseResultSet[] ): Promise { - 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 { + 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 ({ data: diff.source, @@ -173,39 +171,16 @@ export class ContainerDataSource extends DatabaseDataSource { - if (this.executionContext) { - await this.graphQLService.sdk.sqlContextDestroy({ - connectionId: this.executionContext.connectionId, - contextId: this.executionContext.contextId, - }); - } + await this.executionContext?.destroy(); } - private async ensureContextCreated(): Promise { - if (!this.executionContext) { + private async ensureContextCreated(): Promise { + 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 { - const response = await this.graphQLService.sdk.sqlContextCreate({ - connectionId, - defaultCatalog, - defaultSchema, - }); - return { - contextId: response.context.id, - connectionId, - objectCatalogId: response.context.defaultCatalog, - objectSchemaId: response.context.defaultSchema, - }; - } } diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts index 824f5b9cbf..997f009d6c 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts @@ -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) { + 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(); } } diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts index 833d8870b3..adec298996 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerTableService.ts @@ -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 { 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 diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts index 81ff20378e..f13a14c279 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts @@ -118,7 +118,7 @@ implements IDatabaseDataModel { await this.source.requestData(); } - cancel(): Promise | boolean { + cancel(): Promise | void { return this.source.cancel(); } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts index 573bd8a131..8abc8d00d8 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts @@ -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 implements IDatabaseDataSource { @@ -31,7 +31,7 @@ implements IDatabaseDataSource { 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 { return this.actions.get(this.results[resultIndex], action); } - abstract cancel(): Promise | boolean; + abstract cancel(): Promise | void; hasResult(resultIndex: number): boolean { return resultIndex < this.results.length; @@ -171,7 +171,7 @@ implements IDatabaseDataSource { return this; } - setExecutionContext(context: IDatabaseExecutionContext | null): this { + setExecutionContext(context: IConnectionExecutionContext | null): this { this.executionContext = context; return this; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts index 4680e0c31e..7982ec58a4 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts @@ -37,6 +37,6 @@ export interface IDatabaseDataModel Promise; requestDataPortion: (offset: number, count: number) => Promise; requestData: () => Promise; - cancel: () => Promise | boolean; + cancel: () => Promise | void; dispose: () => Promise; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts index c93d9acd2c..55d20e9b91 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts @@ -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 boolean; @@ -63,12 +63,12 @@ export interface IDatabaseDataSource this; setDataFormat: (dataFormat: ResultDataFormat) => this; setSupportedDataFormats: (dataFormats: ResultDataFormat[]) => this; - setExecutionContext: (context: IDatabaseExecutionContext | null) => this; + setExecutionContext: (context: IConnectionExecutionContext | null) => this; retry: () => Promise; requestData: () => Promise | void; saveData: () => Promise | void; - cancel: () => Promise | boolean; + cancel: () => Promise | void; clearError: () => void; dispose: () => Promise; } diff --git a/webapp/packages/plugin-data-viewer/src/index.ts b/webapp/packages/plugin-data-viewer/src/index.ts index 2530a65909..14ef0d0fff 100644 --- a/webapp/packages/plugin-data-viewer/src/index.ts +++ b/webapp/packages/plugin-data-viewer/src/index.ts @@ -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'; diff --git a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts index b6ec06aa36..f48e269022 100644 --- a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts +++ b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts @@ -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; } export interface ISqlEditorTabState { order: number; query: string; - executionContext?: IDatabaseExecutionContext; + executionContext?: IConnectionExecutionContextInfo; currentTabId?: string; tabs: ISqlEditorResultTab[]; resultGroups: IResultGroup[]; diff --git a/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts b/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts index 4184450533..52240245c5 100644 --- a/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts @@ -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 { - get canCancel(): boolean { - return this.queryExecutionProcess?.getState() === EDeferredState.PENDING; - } + currentTask: ITask | 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 { + if (this.currentTask) { + await this.currentTask.cancel(); } - return false; } async save( prevResults: IDatabaseResultSet[] ): Promise { - if (!this.options || !this.executionContext) { + if (!this.options || !this.executionContext?.context) { return prevResults; } @@ -70,7 +67,7 @@ export class QueryDataSource extends DatabaseDataSource ({ data: diff.source, @@ -142,29 +139,35 @@ export class QueryDataSource extends DatabaseDataSource { - 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 { - // TODO: this.queryExecutionProcess maybe should be disposed somehow + await this.cancel(); } } diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/SQLCodeEditor/SQLCodeEditorController.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor/SQLCodeEditor/SQLCodeEditorController.tsx index ec56b6b7b8..e2c18cf000 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/SQLCodeEditor/SQLCodeEditorController.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/SQLCodeEditor/SQLCodeEditorController.tsx @@ -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'; diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorController.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorController.tsx index cbf8cd2c04..279b6329a6 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorController.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/SqlEditorController.tsx @@ -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; 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, { dialect: computed, isActionsDisabled: computed, value: computed, + cursor: observable, }); } init(tab: ITab): void { this.tab = tab; - this.loadDialect(); + autorun(() => { + if (this.tab.handlerState.executionContext) { + this.sqlDialectInfoService.loadSqlDialectInfo(this.tab.handlerState.executionContext.connectionId); + } + }); } handleExecute = async (): Promise => { @@ -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 { - if (!this.tab.handlerState.executionContext) { - return undefined; - } - - return await this.sqlDialectInfoService.loadSqlDialectInfo(this.tab.handlerState.executionContext.connectionId); - } - - private async getExecutingQuery(): Promise { + 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) { diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorNavigatorService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorNavigatorService.ts index 1decdb907d..8b2ca85600 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorNavigatorService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorNavigatorService.ts @@ -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) { diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts index 6c2c17b956..c3da54a9af 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts @@ -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 { + async initEditorConnection(state: ISqlEditorTabState): Promise { 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 { + ): Promise { 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 { - 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 { - 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 { - if (!contextId) { - throw new Error('updateSqlContext contextId not provided'); - } - await this.gql.sdk.sqlContextSetDefaults({ - connectionId, - contextId, - defaultCatalog, - defaultSchema, - }); - } } diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorTabService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorTabService.ts index 239119b0dd..f2305ed9d6 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorTabService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorTabService.ts @@ -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; readonly tabHandler: TabHandler; 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({ 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 | 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, 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) { - await ResourceKeyUtils.forEachAsync(key, async key => { - const tabs = this.navigationTabsService.findTabs( - isSQLEditorTab(tab => tab.handlerState.executionContext?.connectionId === key) - ); - const connection = this.connectionInfo.get(key); + private async handleExecutionContextUpdate() { + const tabs = this.navigationTabsService.findTabs( + isSQLEditorTab(tab => !!tab.handlerState.executionContext) + ); - if (!connection?.connected) { - for (const tab of tabs) { - this.resetConnectionInfo(tab.handlerState); - } - } - }); - } - - private async handleConnectionDelete(key: ResourceKey) { - await ResourceKeyUtils.forEachAsync(key, async key => { - const tabs = this.navigationTabsService.findTabs( - 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) { - return tab.handlerState.executionContext?.objectCatalogId; + return tab.handlerState.executionContext?.defaultCatalog; } private getObjectSchemaId(tab: ITab) { - return tab.handlerState.executionContext?.objectSchemaId; + return tab.handlerState.executionContext?.defaultSchema; } private async setConnectionId(connectionId: string, tab: ITab) { 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) { - 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); + } } } } diff --git a/webapp/packages/plugin-sql-editor/src/SqlExecutionState.ts b/webapp/packages/plugin-sql-editor/src/SqlExecutionState.ts deleted file mode 100644 index cf853881c3..0000000000 --- a/webapp/packages/plugin-sql-editor/src/SqlExecutionState.ts +++ /dev/null @@ -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(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 | null = null; - - setExecutionTask(executionTask: Deferred): void { - if (this.executionTask) { - throw new Error('Simultaneous execution of several queries is forbidden'); - } - this.executionTask = executionTask; - - executionTask.promise.finally(() => { - this.executionTask = null; - }); - } -} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SQLExecutionPlanProcess.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SQLExecutionPlanProcess.ts index 14f853053b..a28685a7de 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SQLExecutionPlanProcess.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SQLExecutionPlanProcess.ts @@ -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 { async start( query: string, - context: IDatabaseExecutionContext, + context: IConnectionExecutionContextInfo, ): Promise { // start async task try { @@ -105,11 +105,11 @@ export class SQLExecutionPlanProcess extends Deferred { private async explainExecutionPlanAsync( query: string, - context: IDatabaseExecutionContext, + context: IConnectionExecutionContextInfo, ): Promise { const { taskInfo } = await this.graphQLService.sdk.asyncSqlExplainExecutionPlan({ connectionId: context.connectionId, - contextId: context.contextId, + contextId: context.id, query, configuration: {}, }); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts index e601738eff..3b3763e8da 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts @@ -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; 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 { - 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 { - 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({ diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SQLQueryExecutionProcess.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SQLQueryExecutionProcess.ts index 1638784cf9..03addaff14 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SQLQueryExecutionProcess.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SQLQueryExecutionProcess.ts @@ -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 { async start( query: string, - context: IDatabaseExecutionContext, + context: IConnectionExecutionContextInfo, filter: SqlDataFilter, dataFormat: ResultDataFormat ): Promise { @@ -107,13 +107,13 @@ export class SQLQueryExecutionProcess extends Deferred { private async executeQueryAsync( query: string, - context: IDatabaseExecutionContext, + context: IConnectionExecutionContextInfo, filter: SqlDataFilter, dataFormat: ResultDataFormat ): Promise { const { taskInfo } = await this.graphQLService.sdk.asyncSqlExecuteQuery({ connectionId: context.connectionId, - contextId: context.contextId, + contextId: context.id, query, filter, dataFormat, diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts index ef2b5a7f4a..213e1de49f 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts @@ -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 { - 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 { - 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); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts index 20ddd5f00c..6b234ccdd8 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabsService.ts @@ -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; - 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 { + async removeResultTab(state: ISqlEditorTabState, tabId: string): Promise { const tab = state.tabs.find(tab => tab.id === tabId); if (tab) {