diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts index 424337038a..1cf4719163 100644 --- a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContext.ts @@ -30,9 +30,9 @@ export class ConnectionExecutionContext implements IConnectionExecutionContext { private currentTask: ITask | null; constructor( - private scheduler: TaskScheduler, - private connectionExecutionContextResource: ConnectionExecutionContextResource, - private contextId: string + private readonly scheduler: TaskScheduler, + private readonly connectionExecutionContextResource: ConnectionExecutionContextResource, + private readonly contextId: string ) { this.currentTask = null; makeObservable(this, { @@ -75,11 +75,14 @@ export class ConnectionExecutionContext implements IConnectionExecutionContext { await this.connectionExecutionContextResource.destroy(this.contextId); } - async update(defaultCatalog?: string, defaultSchema?: string): Promise { + 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); + return await this.connectionExecutionContextResource.update(this.contextId, defaultCatalog, defaultSchema); } } diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContext.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContext.ts index e717674fc8..2c0a6131c1 100644 --- a/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContext.ts +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/IConnectionExecutionContext.ts @@ -22,5 +22,5 @@ export interface IConnectionExecutionContext { ) => ITask; cancel: () => Promise; destroy: () => Promise; - update: (defaultCatalog?: string, defaultSchema?: string) => Promise; + update: (defaultCatalog?: string, defaultSchema?: string) => Promise; } diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/PluginBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/PluginBootstrap.ts index 4d08df8004..23d8a845db 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/PluginBootstrap.ts @@ -14,7 +14,7 @@ import { SessionExpireService } from '@cloudbeaver/core-root'; import { createPath } from '@cloudbeaver/core-utils'; import { ActionService, ACTION_SAVE, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; import { NavResourceNodeService, RESOURCE_NODE_TYPE, SaveScriptDialog, ResourceManagerService, ProjectsResource, RESOURCES_NODE_PATH } from '@cloudbeaver/plugin-resource-manager'; -import { DATA_CONTEXT_SQL_EDITOR_STATE, getSqlEditorName, SqlEditorService, SQL_EDITOR_ACTIONS_MENU } from '@cloudbeaver/plugin-sql-editor'; +import { DATA_CONTEXT_SQL_EDITOR_STATE, getSqlEditorName, SqlDataSourceService, SqlEditorService, SQL_EDITOR_ACTIONS_MENU } from '@cloudbeaver/plugin-sql-editor'; import { SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; import { isScript } from './isScript'; @@ -38,7 +38,8 @@ export class PluginBootstrap extends Bootstrap { private readonly commonDialogService: CommonDialogService, private readonly actionService: ActionService, private readonly menuService: MenuService, - private readonly sessionExpireService: SessionExpireService + private readonly sessionExpireService: SessionExpireService, + private readonly sqlDataSourceService: SqlDataSourceService ) { super(); } @@ -60,8 +61,13 @@ export class PluginBootstrap extends Bootstrap { }, handler: async (context, action) => { const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE); + const dataSource = this.sqlDataSourceService.get(state.editorId); const tab = this.navigationTabsService.currentTab; + if (!dataSource) { + return; + } + if (action === ACTION_SAVE) { const name = getSqlEditorName(state); const result = await this.commonDialogService.open(SaveScriptDialog, { @@ -73,7 +79,7 @@ export class PluginBootstrap extends Bootstrap { await this.projectsResource.load(); const scriptName = `${result.trim()}.${SCRIPT_EXTENSION}`; const folder = createPath([RESOURCES_NODE_PATH, this.projectsResource.userProject?.name]); - const nodeId = await this.navResourceNodeService.saveScript(folder, scriptName, state.query); + const nodeId = await this.navResourceNodeService.saveScript(folder, scriptName, dataSource.script); await this.navTreeResource.preloadNodeParents(NodeManagerUtils.parentsFromPath(nodeId), nodeId); const node = await this.navNodeInfoResource.load(nodeId); diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/SqlEditorTabResourceService.ts b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/SqlEditorTabResourceService.ts index 4ec17b4d12..77f7aa0677 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/SqlEditorTabResourceService.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/SqlEditorTabResourceService.ts @@ -19,7 +19,7 @@ import { ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; import { LocalStorageSaveService } from '@cloudbeaver/core-settings'; import { debounce, throttle } from '@cloudbeaver/core-utils'; import { NavResourceNodeService, ResourceManagerService } from '@cloudbeaver/plugin-resource-manager'; -import { IQueryChangeData, ISqlEditorTabState, SqlEditorService } from '@cloudbeaver/plugin-sql-editor'; +import { IQueryChangeData, ISqlEditorTabState, SqlDataSourceService, SqlEditorService } from '@cloudbeaver/plugin-sql-editor'; import { SqlEditorTabService } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; const SYNC_DELAY = 5 * 1000; @@ -49,6 +49,7 @@ export class SqlEditorTabResourceService { private readonly localStorageSaveService: LocalStorageSaveService, private readonly userInfoResource: UserInfoResource, private readonly commonDialogService: CommonDialogService, + private readonly sqlDataSourceService: SqlDataSourceService ) { this.state = new Map(); @@ -164,8 +165,14 @@ export class SqlEditorTabResourceService { tab: ITab, contexts: IExecutionContextProvider ) { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + + if (!dataSource) { + return; + } + try { - await this.updateResource(tab, tab.handlerState.query); + await this.updateResource(tab, dataSource.script); } catch { const result = await this.commonDialogService.open(ConfirmationDialog, { title: 'plugin_resource_manager_save_script_error_confirmation_title', diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorNavigatorService.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorNavigatorService.ts index 2f4ab40f48..f84b21e2f8 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorNavigatorService.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorNavigatorService.ts @@ -12,7 +12,7 @@ import { NotificationService } from '@cloudbeaver/core-events'; import { IExecutor, Executor, IExecutionContextProvider } from '@cloudbeaver/core-executor'; import { NavigationService } from '@cloudbeaver/core-ui'; import { uuid } from '@cloudbeaver/core-utils'; -import { ISqlEditorTabState, MemorySqlDataSource, SqlResultTabsService } from '@cloudbeaver/plugin-sql-editor'; +import { ISqlEditorTabState, MemorySqlDataSource, SqlDataSourceService, SqlResultTabsService } from '@cloudbeaver/plugin-sql-editor'; import { SQL_EDITOR_SOURCE_ACTION } from './SQL_EDITOR_SOURCE_ACTION'; import { SqlEditorTabService, isSQLEditorTab } from './SqlEditorTabService'; @@ -57,7 +57,8 @@ export class SqlEditorNavigatorService { private readonly notificationService: NotificationService, private readonly sqlEditorTabService: SqlEditorTabService, private readonly sqlResultTabsService: SqlResultTabsService, - navigationService: NavigationService + navigationService: NavigationService, + private readonly sqlDataSourceService: SqlDataSourceService ) { this.navigator = new Executor( null, @@ -101,10 +102,14 @@ export class SqlEditorNavigatorService { if (data.type === SQLEditorNavigationAction.create) { if (data.source === SQL_EDITOR_SOURCE_ACTION) { - tab = this.navigationTabsService.findTab(isSQLEditorTab(tab => ( - tab.handlerState.source === SQL_EDITOR_SOURCE_ACTION - && tab.handlerState.executionContext?.connectionId === data.connectionId - ))); + tab = this.navigationTabsService.findTab(isSQLEditorTab(tab => { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + + return ( + tab.handlerState.source === SQL_EDITOR_SOURCE_ACTION + && dataSource?.executionContext?.connectionId === data.connectionId + ); + })); } if (!tab) { diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTab.tsx b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTab.tsx index 93b7e3fb5b..9ae62ee0b6 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTab.tsx +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTab.tsx @@ -16,7 +16,7 @@ import { useService } from '@cloudbeaver/core-di'; import { useStyles } from '@cloudbeaver/core-theming'; import { TabIcon, Tab, TabTitle, ITabData } from '@cloudbeaver/core-ui'; import { CaptureViewContext, useDataContext } from '@cloudbeaver/core-view'; -import { DATA_CONTEXT_SQL_EDITOR_STATE, getSqlEditorName, ISqlEditorTabState } from '@cloudbeaver/plugin-sql-editor'; +import { DATA_CONTEXT_SQL_EDITOR_STATE, getSqlEditorName, ISqlEditorTabState, SqlDataSourceService } from '@cloudbeaver/plugin-sql-editor'; import { DATA_CONTEXT_SQL_EDITOR_TAB } from './DATA_CONTEXT_SQL_EDITOR_TAB'; @@ -29,9 +29,11 @@ export const SqlEditorTab: TabHandlerTabComponent = observer tabMenuContext.set(DATA_CONTEXT_SQL_EDITOR_TAB, true); tabMenuContext.set(DATA_CONTEXT_SQL_EDITOR_STATE, tab.handlerState); + const sqlDataSourceService = useService(SqlDataSourceService); const connectionInfo = useService(ConnectionInfoResource); - const connection = connectionInfo.get(tab.handlerState.executionContext?.connectionId || ''); + const dataSource = sqlDataSourceService.get(tab.handlerState.editorId); + const connection = connectionInfo.get(dataSource?.executionContext?.connectionId || ''); const name = getSqlEditorName(tab.handlerState, connection); const handleSelect = ({ tabId }: ITabData) => onSelect(tabId); diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts index 9412ae1667..db8bddfeb5 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts @@ -33,7 +33,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { Executor, ExecutorInterrupter } from '@cloudbeaver/core-executor'; import { CachedMapAllKey, ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; -import { SqlResultTabsService, ISqlEditorTabState, SqlEditorService, getSqlEditorName } from '@cloudbeaver/plugin-sql-editor'; +import { SqlResultTabsService, ISqlEditorTabState, SqlEditorService, getSqlEditorName, SqlDataSourceService } from '@cloudbeaver/plugin-sql-editor'; import { SqlEditorPanel } from './SqlEditorPanel'; import { SqlEditorTab } from './SqlEditorTab'; @@ -57,7 +57,8 @@ export class SqlEditorTabService extends Bootstrap { private readonly connectionExecutionContextResource: ConnectionExecutionContextResource, private readonly connectionInfoResource: ConnectionInfoResource, private readonly connectionSchemaManagerService: ConnectionSchemaManagerService, - private readonly navNodeManagerService: NavNodeManagerService + private readonly navNodeManagerService: NavNodeManagerService, + private readonly sqlDataSourceService: SqlDataSourceService ) { super(); @@ -96,7 +97,8 @@ export class SqlEditorTabService extends Bootstrap { load(): void { } getName(tabState: ISqlEditorTabState): string { - const connection = this.connectionInfoResource.get(tabState.executionContext?.connectionId || ''); + const dataSource = this.sqlDataSourceService.get(tabState.editorId); + const connection = this.connectionInfoResource.get(dataSource?.executionContext?.connectionId || ''); return getSqlEditorName(tabState, connection); } @@ -106,11 +108,13 @@ export class SqlEditorTabService extends Bootstrap { dataSourceKey: string, name?: string, source?: string, - query?: string, + script?: string, ): ITabOptions | null { const order = this.getFreeEditorId(); + this.sqlDataSourceService.create(editorId, dataSourceKey, script); + return { id: editorId, handlerId: sqlEditorTabHandlerKey, @@ -120,23 +124,28 @@ export class SqlEditorTabService extends Bootstrap { order, name, source, - query, - undefined ), }; } resetConnectionInfo(state: ISqlEditorTabState): void { - state.executionContext = undefined; + const dataSource = this.sqlDataSourceService.get(state.editorId); + + dataSource?.setExecutionContext(undefined); } private async handleConnectionDelete(key: ResourceKey) { const tabs = this.navigationTabsService.findTabs( - isSQLEditorTab(tab => !!tab.handlerState.executionContext) + isSQLEditorTab(tab => { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + + return !!dataSource?.executionContext; + }) ); for (const tab of tabs) { - if (ResourceKeyUtils.includes(key, tab.handlerState.executionContext?.connectionId)) { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + if (ResourceKeyUtils.includes(key, dataSource?.executionContext?.connectionId)) { this.resetConnectionInfo(tab.handlerState); } } @@ -168,31 +177,41 @@ export class SqlEditorTabService extends Bootstrap { private async handleExecutionContextUpdate(key: ResourceKey) { const tabs = this.navigationTabsService.findTabs( - isSQLEditorTab(tab => !!tab.handlerState.executionContext) + isSQLEditorTab(tab => { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + + return !!dataSource?.executionContext; + }) ); for (const tab of tabs) { - const executionContext = this.connectionExecutionContextService.get(tab.handlerState.executionContext!.id); + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId)!; + const executionContext = this.connectionExecutionContextService.get(dataSource.executionContext!.id); if (!executionContext?.context) { - if (!this.connectionInfoResource.has(tab.handlerState.executionContext?.connectionId || '')) { + if (!this.connectionInfoResource.has(dataSource.executionContext?.connectionId || '')) { this.resetConnectionInfo(tab.handlerState); } } else { - tab.handlerState.executionContext = { ...executionContext.context }; + dataSource.setExecutionContext({ ...executionContext.context }); } } } private async handleExecutionContextDelete(key: ResourceKey) { const tabs = this.navigationTabsService.findTabs( - isSQLEditorTab(tab => !!tab.handlerState.executionContext) + isSQLEditorTab(tab => { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + + return !!dataSource?.executionContext; + }) ); for (const tab of tabs) { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId)!; if ( - ResourceKeyUtils.includes(key, tab.handlerState.executionContext!.id) - && !this.connectionInfoResource.has(tab.handlerState.executionContext!.connectionId) + ResourceKeyUtils.includes(key, dataSource.executionContext!.id) + && !this.connectionInfoResource.has(dataSource.executionContext!.connectionId) ) { this.resetConnectionInfo(tab.handlerState); } @@ -205,32 +224,33 @@ export class SqlEditorTabService extends Bootstrap { } private async handleTabRestore(tab: ITab): Promise { - if (typeof tab.handlerState.query !== 'string' - || typeof tab.handlerState.editorId !== 'string' - || 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?.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) - || !['string', 'undefined', 'object'].includes(typeof tab.handlerState.name) - || !['string', 'undefined', 'object'].includes(typeof tab.handlerState.source) - || !['string', 'undefined', 'object'].includes(typeof tab.handlerState.currentModeId) - || !Array.isArray(tab.handlerState.modeState) - || !Array.isArray(tab.handlerState.tabs) - || !Array.isArray(tab.handlerState.executionPlanTabs) - || !Array.isArray(tab.handlerState.resultGroups) - || !Array.isArray(tab.handlerState.resultTabs) - || !Array.isArray(tab.handlerState.statisticsTabs) + if ( + typeof tab.handlerState.editorId !== 'string' + || typeof tab.handlerState.editorId !== 'string' + || typeof tab.handlerState.order !== 'number' + || !['string', 'undefined', 'object'].includes(typeof tab.handlerState.currentTabId) + || !['string', 'undefined', 'object'].includes(typeof tab.handlerState.name) + || !['string', 'undefined', 'object'].includes(typeof tab.handlerState.source) + || !['string', 'undefined', 'object'].includes(typeof tab.handlerState.currentModeId) + || !Array.isArray(tab.handlerState.modeState) + || !Array.isArray(tab.handlerState.tabs) + || !Array.isArray(tab.handlerState.executionPlanTabs) + || !Array.isArray(tab.handlerState.resultGroups) + || !Array.isArray(tab.handlerState.resultTabs) + || !Array.isArray(tab.handlerState.statisticsTabs) ) { return false; } - if (tab.handlerState.executionContext) { + const dataSource = this.sqlDataSourceService.create( + tab.handlerState.editorId, + tab.handlerState.datasourceKey + ); + + if (dataSource.executionContext) { await this.connectionInfoResource.load(CachedMapAllKey); - if (!this.connectionInfoResource.has(tab.handlerState.executionContext.connectionId)) { + if (!this.connectionInfoResource.has(dataSource.executionContext.connectionId)) { this.resetConnectionInfo(tab.handlerState); } } @@ -247,16 +267,19 @@ export class SqlEditorTabService extends Bootstrap { } private getConnectionId(tab: ITab) { - return tab.handlerState.executionContext?.connectionId; + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + return dataSource?.executionContext?.connectionId; } private getObjectCatalogId(tab: ITab) { - const context = this.connectionExecutionContextResource.get(tab.handlerState.executionContext?.id ?? ''); + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + const context = this.connectionExecutionContextResource.get(dataSource?.executionContext?.id ?? ''); return context?.defaultCatalog; } private getObjectSchemaId(tab: ITab) { - const context = this.connectionExecutionContextResource.get(tab.handlerState.executionContext?.id ?? ''); + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + const context = this.connectionExecutionContextResource.get(dataSource?.executionContext?.id ?? ''); return context?.defaultSchema; } @@ -270,22 +293,25 @@ export class SqlEditorTabService extends Bootstrap { } private async setObjectCatalogId(containerId: string, tab: ITab) { - if (!tab.handlerState.executionContext) { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + + if (!dataSource?.executionContext) { return false; } - const executionContext = this.connectionExecutionContextService.get(tab.handlerState.executionContext.id); + const executionContext = this.connectionExecutionContextService.get(dataSource.executionContext.id); if (!executionContext) { return false; } try { - await executionContext.update( + const context = await executionContext.update( containerId, - tab.handlerState.executionContext.defaultSchema, + dataSource.executionContext.defaultSchema, ); - tab.handlerState.executionContext.defaultCatalog = containerId; + + dataSource.setExecutionContext({ ...context }); return true; } catch (exception: any) { this.notificationService.logException(exception, 'Failed to change SQL-editor catalog'); @@ -294,22 +320,25 @@ export class SqlEditorTabService extends Bootstrap { } private async setObjectSchemaId(containerId: string, tab: ITab) { - if (!tab.handlerState.executionContext) { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); + + if (!dataSource?.executionContext) { return false; } - const executionContext = this.connectionExecutionContextService.get(tab.handlerState.executionContext.id); + const executionContext = this.connectionExecutionContextService.get(dataSource.executionContext.id); if (!executionContext) { return false; } try { - await executionContext.update( - tab.handlerState.executionContext.defaultCatalog, + const context = await executionContext.update( + dataSource.executionContext.defaultCatalog, containerId ); - tab.handlerState.executionContext.defaultSchema = containerId; + + dataSource.setExecutionContext({ ...context }); return true; } catch (exception: any) { this.notificationService.logException(exception, 'Failed to change SQL-editor schema'); @@ -327,8 +356,10 @@ export class SqlEditorTabService extends Bootstrap { } private async handleTabClose(editorTab: ITab) { - if (editorTab.handlerState.executionContext) { - await this.sqlEditorService.destroyContext(editorTab.handlerState.executionContext); + const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId); + + if (dataSource?.executionContext) { + await this.sqlEditorService.destroyContext(dataSource.executionContext); } this.sqlResultTabsService.removeResultTabs(editorTab.handlerState); diff --git a/webapp/packages/plugin-sql-editor-screen/src/PluginBootstrap.ts b/webapp/packages/plugin-sql-editor-screen/src/PluginBootstrap.ts index 291762f9da..ed3c1a70a1 100644 --- a/webapp/packages/plugin-sql-editor-screen/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-screen/src/PluginBootstrap.ts @@ -10,7 +10,7 @@ import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { MENU_TAB } from '@cloudbeaver/core-ui'; import { ActionService, ACTION_OPEN_IN_TAB, DATA_CONTEXT_MENU, IDataContextProvider, KeyBindingService, KEY_BINDING_OPEN_IN_TAB, MenuService, IAction } from '@cloudbeaver/core-view'; -import { DATA_CONTEXT_SQL_EDITOR_STATE } from '@cloudbeaver/plugin-sql-editor'; +import { DATA_CONTEXT_SQL_EDITOR_STATE, SqlDataSourceService } from '@cloudbeaver/plugin-sql-editor'; import { DATA_CONTEXT_SQL_EDITOR_TAB } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; import { SqlEditorScreenService } from './Screen/SqlEditorScreenService'; @@ -22,7 +22,8 @@ export class PluginBootstrap extends Bootstrap { private readonly keyBindingService: KeyBindingService, private readonly sqlEditorScreenService: SqlEditorScreenService, private readonly notificationService: NotificationService, - private readonly menuService: MenuService + private readonly menuService: MenuService, + private readonly sqlDataSourceService: SqlDataSourceService ) { super(); } @@ -34,7 +35,16 @@ export class PluginBootstrap extends Bootstrap { action === ACTION_OPEN_IN_TAB && contexts.has(DATA_CONTEXT_SQL_EDITOR_STATE) ), - isDisabled: (context, action) => context.tryGet(DATA_CONTEXT_SQL_EDITOR_STATE)?.executionContext === undefined, + isDisabled: (context, action) => { + const state = context.tryGet(DATA_CONTEXT_SQL_EDITOR_STATE); + + if (!state) { + return false; + } + + const dataSource = this.sqlDataSourceService.get(state.editorId); + return dataSource?.executionContext === undefined; + }, handler: this.openTab.bind(this), }); @@ -60,8 +70,9 @@ export class PluginBootstrap extends Bootstrap { private openTab(contexts: IDataContextProvider, action: IAction) { const context = contexts.get(DATA_CONTEXT_SQL_EDITOR_STATE); + const dataSource = this.sqlDataSourceService.get(context.editorId); - if (!context.executionContext) { + if (!dataSource?.executionContext) { this.notificationService.logError({ title: 'sql_editor_screen_no_context_title', message: 'sql_editor_screen_no_context_message', @@ -70,7 +81,7 @@ export class PluginBootstrap extends Bootstrap { } const url = this.sqlEditorScreenService.createURL({ - contextId: context.executionContext.id, + contextId: dataSource.executionContext.id, }); window.open(url, '_blank')?.focus(); diff --git a/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreen.tsx b/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreen.tsx index 76d6aff874..53eb8d542e 100644 --- a/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreen.tsx +++ b/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreen.tsx @@ -16,7 +16,7 @@ import { useTranslate } from '@cloudbeaver/core-localization'; import type { ScreenComponent } from '@cloudbeaver/core-routing'; import { CachedMapAllKey } from '@cloudbeaver/core-sdk'; import { uuid } from '@cloudbeaver/core-utils'; -import { ISqlEditorTabState, MemorySqlDataSource, SqlEditor, SqlEditorService } from '@cloudbeaver/plugin-sql-editor'; +import { ISqlEditorTabState, MemorySqlDataSource, SqlDataSourceService, SqlEditor, SqlEditorService } from '@cloudbeaver/plugin-sql-editor'; import type { ISqlEditorScreenParams } from './ISqlEditorScreenParams'; @@ -29,30 +29,41 @@ export const SqlEditorScreen: ScreenComponent = observer ConnectionExecutionContextResource, CachedMapAllKey ); + const sqlDataSourceService = useService(SqlDataSourceService); const sqlEditorService = useService(SqlEditorService); const connectionExecutionContextService = useService(ConnectionExecutionContextService); const context = connectionExecutionContextService.get(contextId); const state = useObservableRef(() => ({ + get dataSource() { + if (!this.state) { + return undefined; + } + + return sqlDataSourceService.get(this.state.editorId); + }, state: null as null | ISqlEditorTabState, setState(contextInfo: IConnectionExecutionContextInfo | undefined) { if (contextInfo) { + const editorId = uuid(); + + sqlDataSourceService.create(editorId, MemorySqlDataSource.key, '', contextInfo); + this.state = sqlEditorService.getState( - uuid(), + editorId, MemorySqlDataSource.key, 0, undefined, undefined, - undefined, - contextInfo ); + } else { this.state = null; } }, }), { state: observable }, false); - if (context?.context?.id !== state.state?.executionContext?.id) { + if (context?.context?.id !== state.dataSource?.executionContext?.id) { state.setState(context?.context); } diff --git a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts index bb1afd6aa9..ce942c875e 100644 --- a/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts +++ b/webapp/packages/plugin-sql-editor/src/ISqlEditorTabState.ts @@ -51,9 +51,6 @@ export interface ISqlEditorTabState { source?: string; order: number; - query: string; - executionContext?: IConnectionExecutionContextInfo; - currentTabId?: string; tabs: ISqlEditorResultTab[]; resultGroups: IResultGroup[]; diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor.tsx index de92fb66e8..f0a0a6eaa2 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor.tsx @@ -19,6 +19,7 @@ import { useStyles } from '@cloudbeaver/core-theming'; import { CaptureView } from '@cloudbeaver/core-view'; import type { ISqlEditorTabState } from './ISqlEditorTabState'; +import { SqlDataSourceService } from './SqlDataSource/SqlDataSourceService'; import { SqlEditorLoader } from './SqlEditor/SqlEditorLoader'; import { SqlEditorService } from './SqlEditorService'; import { SqlEditorView } from './SqlEditorView'; @@ -50,8 +51,14 @@ export const SqlEditor = observer(function SqlEditor({ state }) { const translate = useTranslate(); const sqlEditorView = useService(SqlEditorView); const sqlEditorService = useService(SqlEditorService); + const sqlDataSourceService = useService(SqlDataSourceService); const styles = useStyles(splitStyles, splitHorizontalStyles, viewerStyles); - const connection = useMapResource(SqlEditor, ConnectionInfoResource, state.executionContext?.connectionId ?? null); + const dataSource = sqlDataSourceService.get(state.editorId); + const connection = useMapResource( + SqlEditor, + ConnectionInfoResource, + dataSource?.executionContext?.connectionId ?? null + ); const driver = useMapResource(SqlEditor, DBDriverResource, connection.data?.driverId ?? null); const splitState = useSplitUserState('sql-editor'); @@ -60,11 +67,14 @@ export const SqlEditor = observer(function SqlEditor({ state }) { const context = useMapResource( SqlEditor, ConnectionExecutionContextResource, - connected ? (state.executionContext?.id ?? null) : null + connected ? (dataSource?.executionContext?.id ?? null) : null ); const initializingContext = getComputed(() => connection.isLoading() || context.isLoading()); - const initExecutionContext = getComputed(() => context.data === undefined && state.executionContext !== undefined); + const initExecutionContext = getComputed(() => ( + context.data === undefined + && dataSource?.executionContext !== undefined + )); async function cancelConnection() { await sqlEditorService.resetExecutionContext(state); @@ -75,8 +85,8 @@ export const SqlEditor = observer(function SqlEditor({ state }) { } const dataContainer = getComputed(() => NodeManagerUtils.concatSchemaAndCatalog( - state.executionContext?.defaultCatalog, - state.executionContext?.defaultSchema + dataSource?.executionContext?.defaultCatalog, + dataSource?.executionContext?.defaultSchema )); useEffect(() => { diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts b/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts index 30b44535e3..4820fe7ae6 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts @@ -18,6 +18,8 @@ import type { SqlDialectInfo } from '@cloudbeaver/core-sdk'; import { throttleAsync } from '@cloudbeaver/core-utils'; import type { ISqlEditorTabState } from '../ISqlEditorTabState'; +import type { ISqlDataSource } from '../SqlDataSource/ISqlDataSource'; +import { SqlDataSourceService } from '../SqlDataSource/SqlDataSourceService'; import { SqlDialectInfoService } from '../SqlDialectInfoService'; import { SqlEditorService } from '../SqlEditorService'; import { ISQLScriptSegment, SQLParser } from '../SQLParser'; @@ -35,6 +37,7 @@ interface ISQLEditorDataPrivate extends ISQLEditorData { readonly sqlExecutionPlanService: SqlExecutionPlanService; readonly commonDialogService: CommonDialogService; readonly sqlResultTabsService: SqlResultTabsService; + readonly dataSource: ISqlDataSource | undefined; cursor: ICursor; readonlyState: boolean; @@ -55,14 +58,18 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { const sqlExecutionPlanService = useService(SqlExecutionPlanService); const sqlResultTabsService = useService(SqlResultTabsService); const commonDialogService = useService(CommonDialogService); + const sqlDataSourceService = useService(SqlDataSourceService); const data = useObservableRef(() => ({ + get dataSource(): ISqlDataSource | undefined { + return sqlDataSourceService.get(this.state.editorId); + }, get dialect(): SqlDialectInfo | undefined { - if (!this.state.executionContext) { + if (!this.dataSource?.executionContext) { return undefined; } - return this.sqlDialectInfoService.getDialectInfo(this.state.executionContext.connectionId); + return this.sqlDialectInfoService.getDialectInfo(this.dataSource.executionContext.connectionId); }, get activeSegmentMode(): ISQLEditorMode { @@ -93,17 +100,17 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { }, get isDisabled(): boolean { - if (!this.state.executionContext) { + if (!this.dataSource?.executionContext) { return true; } - const context = this.connectionExecutionContextService.get(this.state.executionContext.id); + const context = this.connectionExecutionContextService.get(this.dataSource.executionContext.id); return context?.executing || false; }, get value(): string { - return this.state.query; + return this.dataSource?.script ?? ''; }, onMode: new SyncExecutor(), @@ -127,11 +134,11 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { this.parser.setScript(this.value); this.reactionDisposer = autorun(() => { - if (this.state.executionContext) { - const context = this.connectionExecutionContextService.get(this.state.executionContext.id); + if (this.dataSource?.executionContext) { + const context = this.connectionExecutionContextService.get(this.dataSource.executionContext.id); if (context) { - const connectionId = this.state.executionContext.connectionId; + const connectionId = this.dataSource.executionContext.connectionId; untracked(() => { this.sqlDialectInfoService @@ -159,14 +166,14 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { }, getHintProposals: throttleAsync(async function getHintProposals(this: ISQLEditorDataPrivate, position, simple) { - if (!this.state.executionContext) { + if (!this.dataSource?.executionContext) { return []; } const proposals = await this.sqlEditorService .getAutocomplete( - this.state.executionContext.connectionId, - this.state.executionContext.id, + this.dataSource.executionContext.connectionId, + this.dataSource.executionContext.id, this.value, position, undefined, @@ -177,7 +184,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { }, 1000 / 3), async formatScript(): Promise { - if (this.isDisabled || this.isScriptEmpty || !this.state.executionContext) { + if (this.isDisabled || this.isScriptEmpty || !this.dataSource?.executionContext) { return; } @@ -191,7 +198,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { this.onExecute.execute(true); try { this.readonlyState = true; - const formatted = await this.sqlDialectInfoService.formatScript(this.state.executionContext, script.query); + const formatted = await this.sqlDialectInfoService.formatScript(this.dataSource.executionContext, script.query); this.onFormat.execute([script, formatted]); this.setQuery( @@ -312,7 +319,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { }, 1000 / 2), async updateParserScripts() { - const connectionId = this.state.executionContext?.connectionId; + const connectionId = this.dataSource?.executionContext?.connectionId; const script = this.parser.actualScript; if (!connectionId || !script) { @@ -364,7 +371,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { }, async getResolvedSegment(): Promise { - const connectionId = this.state.executionContext?.connectionId; + const connectionId = this.dataSource?.executionContext?.connectionId; if (!connectionId || this.cursor.begin !== this.cursor.end) { return this.getSubQuery(); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx index acce6c1539..b79818a4f4 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx @@ -17,6 +17,7 @@ import { download, generateFileName, getTextFileReadingProcess } from '@cloudbea import { getSqlEditorName } from '../getSqlEditorName'; import type { ISqlEditorTabState } from '../ISqlEditorTabState'; +import { SqlDataSourceService } from '../SqlDataSource/SqlDataSourceService'; import { SqlEditorSettingsService } from '../SqlEditorSettingsService'; import { ScriptImportDialog } from './ScriptImportDialog'; @@ -32,6 +33,7 @@ export function useTools(state: ISqlEditorTabState): Readonly { const notificationService = useService(NotificationService); const connectionInfoResource = useService(ConnectionInfoResource); const sqlEditorSettingsService = useService(SqlEditorSettingsService); + const sqlDataSourceService = useService(SqlDataSourceService); return useObservableRef(() => ({ async tryReadScript(file: File, prevScript: string) { @@ -95,7 +97,8 @@ export function useTools(state: ISqlEditorTabState): Readonly { type: 'application/sql', }); - const connection = this.connectionInfoResource.get(this.state.executionContext?.connectionId ?? ''); + const dataSource = sqlDataSourceService.get(this.state.editorId); + const connection = this.connectionInfoResource.get(dataSource?.executionContext?.connectionId ?? ''); const name = getSqlEditorName(this.state, connection); download(blob, generateFileName(name, '.sql')); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts index c482c9d9c4..4763c80bae 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts @@ -11,7 +11,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { Executor } from '@cloudbeaver/core-executor'; import { GraphQLService, SqlCompletionProposal, SqlScriptInfoFragment } from '@cloudbeaver/core-sdk'; -import type { ISqlEditorTabState } from '@cloudbeaver/plugin-sql-editor'; +import { SqlDataSourceService, ISqlEditorTabState } from '@cloudbeaver/plugin-sql-editor'; export type SQLProposal = SqlCompletionProposal; @@ -30,7 +30,8 @@ export class SqlEditorService { private readonly connectionsManagerService: ConnectionsManagerService, private readonly notificationService: NotificationService, private readonly connectionExecutionContextService: ConnectionExecutionContextService, - private readonly connectionExecutionContextResource: ConnectionExecutionContextResource + private readonly connectionExecutionContextResource: ConnectionExecutionContextResource, + private readonly sqlDataSourceService: SqlDataSourceService ) { this.onQueryChange = new Executor(); } @@ -41,17 +42,13 @@ export class SqlEditorService { order: number, name?: string, source?: string, - query?: string, - contextInfo?: IConnectionExecutionContextInfo, ): ISqlEditorTabState { return { editorId, datasourceKey, name, source, - query: query ?? '', order, - executionContext: contextInfo ? { ...contextInfo } : undefined, tabs: [], resultGroups: [], resultTabs: [], @@ -113,18 +110,24 @@ export class SqlEditorService { } setQuery(query: string, state: ISqlEditorTabState) { - const prevQuery = state.query; + const dataSource = this.sqlDataSourceService.get(state.editorId); - state.query = query; - this.onQueryChange.execute({ prevQuery, query, state }); + if (dataSource) { + const prevQuery = dataSource.script; + + dataSource.setScript(query); + this.onQueryChange.execute({ prevQuery, query, state }); + } } async resetExecutionContext(state: ISqlEditorTabState) { - if (state.executionContext) { - await this.destroyContext(state.executionContext); - } + const dataSource = this.sqlDataSourceService.get(state.editorId); - state.executionContext = undefined; + if (dataSource?.executionContext) { + await this.destroyContext(dataSource.executionContext); + + dataSource.setExecutionContext(undefined); + } } async setConnection( @@ -135,13 +138,14 @@ export class SqlEditorService { ): Promise { try { const executionContext = await this.initContext(connectionId, catalogId, schemaId); + const dataSource = this.sqlDataSourceService.get(state.editorId); - if (!executionContext?.context) { + if (!executionContext?.context || !dataSource) { return false; } - const previousContext = state.executionContext; - state.executionContext = { ...executionContext.context }; + const previousContext = dataSource.executionContext; + dataSource.setExecutionContext({ ...executionContext.context }); if (previousContext) { await this.destroyContext(previousContext); @@ -154,15 +158,17 @@ export class SqlEditorService { } async initEditorConnection(state: ISqlEditorTabState): Promise { - if (!state.executionContext) { + const dataSource = this.sqlDataSourceService.get(state.editorId); + + if (!dataSource?.executionContext) { console.error('executeEditorQuery executionContext is not provided'); return; } const context = await this.initContext( - state.executionContext.connectionId, - state.executionContext.defaultCatalog, - state.executionContext.defaultSchema + dataSource.executionContext.connectionId, + dataSource.executionContext.defaultCatalog, + dataSource.executionContext.defaultSchema ); if (!context?.context) { @@ -170,7 +176,7 @@ export class SqlEditorService { return; } - state.executionContext = { ...context.context }; + dataSource.setExecutionContext({ ...context.context }); return context; } 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 4475895cd4..b8a749d21b 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.ts @@ -16,6 +16,7 @@ import { AsyncTaskInfoService, GraphQLService, SqlExecutionPlan } from '@cloudbe import { uuid } from '@cloudbeaver/core-utils'; import type { ISqlEditorTabState } from '../../ISqlEditorTabState'; +import { SqlDataSourceService } from '../../SqlDataSource/SqlDataSourceService'; interface IExecutionPlanData { task: ITask; @@ -30,7 +31,8 @@ export class SqlExecutionPlanService { private readonly graphQLService: GraphQLService, private readonly notificationService: NotificationService, private readonly asyncTaskInfoService: AsyncTaskInfoService, - private readonly connectionExecutionContextService: ConnectionExecutionContextService + private readonly connectionExecutionContextService: ConnectionExecutionContextService, + private readonly sqlDataSourceService: SqlDataSourceService ) { this.data = new Map(); @@ -43,7 +45,8 @@ export class SqlExecutionPlanService { editorState: ISqlEditorTabState, query: string, ): Promise { - const contextInfo = editorState.executionContext; + const dataSource = this.sqlDataSourceService.get(editorState.editorId); + const contextInfo = dataSource?.executionContext; const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id); @@ -138,8 +141,9 @@ export class SqlExecutionPlanService { } private createExecutionPlanTab(state: ISqlEditorTabState, query: string) { - if (!state.executionContext) { - throw new Error('ExecutionContext is not provided'); + const dataSource = this.sqlDataSourceService.get(state.editorId); + if (!dataSource) { + throw new Error('SQL Data Source is not provided'); } const id = uuid(); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts index 8410905c6a..ba51872e88 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts @@ -19,6 +19,7 @@ import { import type { IResultGroup, ISqlEditorTabState } from '../ISqlEditorTabState'; import { IDataQueryOptions, QueryDataSource } from '../QueryDataSource'; +import { SqlDataSourceService } from '../SqlDataSource/SqlDataSourceService'; import { SqlQueryResultService } from './SqlQueryResultService'; interface IQueryExecutionOptions { @@ -49,6 +50,7 @@ export class SqlQueryService { private readonly asyncTaskInfoService: AsyncTaskInfoService, private readonly dataViewerDataChangeConfirmationService: DataViewerDataChangeConfirmationService, private readonly dataViewerService: DataViewerService, + private readonly sqlDataSourceService: SqlDataSourceService ) { this.statisticsMap = new Map(); @@ -66,7 +68,8 @@ export class SqlQueryService { query: string, inNewTab: boolean ): Promise { - const contextInfo = editorState.executionContext; + const dataSource = this.sqlDataSourceService.get(editorState.editorId); + const contextInfo = dataSource?.executionContext; const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id); if (!contextInfo || !executionContext) { @@ -143,7 +146,8 @@ export class SqlQueryService { queries: string[], options?: IQueryExecutionOptions ): Promise { - const contextInfo = editorState.executionContext; + const dataSource = this.sqlDataSourceService.get(editorState.editorId); + const contextInfo = dataSource?.executionContext; const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id); if (!contextInfo || !executionContext) {