mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
dbeaver/pro#6327 refactor: sql editor & vqb (#3742)
* dbeaver/pro#6327 refactor: sql editor & vqb * fix: state sync * fix: segment resolution * chore: add comment * refactor: remove old code, add todo about problem * fix: yarn.lock --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
mr-anton-t
Evgenia
parent
0fe432f45c
commit
4f53ece164
@@ -32,6 +32,9 @@
|
||||
"vitest": "^3"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "^0"
|
||||
"async-mutex": "^0",
|
||||
"p-debounce": "^4.0.0",
|
||||
"p-memoize": "^8.0.0",
|
||||
"quick-lru": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export { default as debouncePromise } from 'p-debounce';
|
||||
@@ -1,3 +1,13 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export * from './debouncePromise.js';
|
||||
export * from './isDefined.js';
|
||||
export * from './isNotNullDefined.js';
|
||||
export * from './memoizeLast.js';
|
||||
export * from './mutex.js';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 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 pMemoize from 'p-memoize';
|
||||
import QuickLRU from 'quick-lru';
|
||||
|
||||
const cacheKey = (args: unknown[]) => JSON.stringify(args);
|
||||
|
||||
export function memoizeLast<T extends (...a: any[]) => Promise<any>>(fn: T) {
|
||||
return pMemoize(fn, {
|
||||
cache: new QuickLRU({ maxSize: 5 }),
|
||||
cacheKey,
|
||||
});
|
||||
}
|
||||
@@ -34,6 +34,7 @@ export * from './DisplayError.js';
|
||||
export * from './ErrorBoundary.js';
|
||||
export * from './Icon.js';
|
||||
export * from './useHotkeys.js';
|
||||
export * from './useSync.js';
|
||||
|
||||
export * from './ItemList/ItemList.js';
|
||||
export * from './ItemList/ItemListSearch.js';
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 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 { mutex } from '@dbeaver/js-helpers';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useObjectRef } from './useObjectRef.js';
|
||||
|
||||
interface ISyncHook {
|
||||
markOutdated(): void;
|
||||
}
|
||||
|
||||
export function useSync(callback: () => void | Promise<void>, canSync = true): ISyncHook {
|
||||
const [syncMutex] = useState(() => new mutex.Mutex());
|
||||
const [outdated, setOutdated] = useState(false);
|
||||
const [delayedOutdated, setDelayedOutdated] = useState(false);
|
||||
|
||||
function markOutdated() {
|
||||
if (syncMutex.isLocked()) {
|
||||
setDelayedOutdated(true);
|
||||
} else {
|
||||
setOutdated(true);
|
||||
}
|
||||
}
|
||||
|
||||
function markUpdated() {
|
||||
if (delayedOutdated) {
|
||||
setDelayedOutdated(false);
|
||||
setOutdated(true);
|
||||
} else {
|
||||
setOutdated(false);
|
||||
}
|
||||
}
|
||||
|
||||
const data = useObjectRef(() => ({
|
||||
markOutdated,
|
||||
markUpdated,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (outdated && !syncMutex.isLocked() && canSync) {
|
||||
syncMutex
|
||||
.runExclusive(async () => {
|
||||
try {
|
||||
await callback();
|
||||
} finally {
|
||||
data.markUpdated();
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
type ISqlEditorTabState,
|
||||
SQL_EDITOR_TAB_STATE_SCHEMA,
|
||||
SqlDataSourceService,
|
||||
SqlEditorModelService,
|
||||
SqlEditorService,
|
||||
SqlResultTabsService,
|
||||
} from '@cloudbeaver/plugin-sql-editor';
|
||||
@@ -67,6 +68,7 @@ const SqlEditorTab = importLazyComponent(() => import('./SqlEditorTab.js').then(
|
||||
ConnectionsManagerService,
|
||||
ContainerResource,
|
||||
CommonDialogService,
|
||||
SqlEditorModelService,
|
||||
])
|
||||
export class SqlEditorTabService extends Bootstrap {
|
||||
get sqlEditorTabs(): ITab<ISqlEditorTabState>[] {
|
||||
@@ -89,6 +91,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
private readonly connectionsManagerService: ConnectionsManagerService,
|
||||
private readonly containerResource: ContainerResource,
|
||||
private readonly commonDialogService: CommonDialogService,
|
||||
private readonly sqlEditorModelService: SqlEditorModelService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -305,6 +308,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
|
||||
private async handleTabRestore(tab: ITab<ISqlEditorTabState>): Promise<boolean> {
|
||||
if (!SQL_EDITOR_TAB_STATE_SCHEMA.safeParse(tab.handlerState).success) {
|
||||
await this.sqlEditorModelService.destroy(tab.handlerState.editorId);
|
||||
await this.sqlDataSourceService.destroy(tab.handlerState.editorId);
|
||||
return false;
|
||||
}
|
||||
@@ -531,12 +535,14 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async handleTabUnload(editorTab: ITab<ISqlEditorTabState>) {
|
||||
await this.sqlEditorModelService.unload(editorTab.handlerState.editorId);
|
||||
await this.sqlDataSourceService.unload(editorTab.handlerState.editorId);
|
||||
|
||||
this.sqlResultTabsService.removeResultTabs(editorTab.handlerState);
|
||||
}
|
||||
|
||||
private async handleTabCloseSilent(editorTab: ITab<ISqlEditorTabState>) {
|
||||
await this.sqlEditorModelService.destroySilent(editorTab.handlerState.editorId);
|
||||
const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId);
|
||||
|
||||
if (dataSource?.executionContext) {
|
||||
@@ -548,6 +554,8 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async handleTabClose(editorTab: ITab<ISqlEditorTabState>) {
|
||||
await this.sqlEditorModelService.destroy(editorTab.handlerState.editorId);
|
||||
|
||||
const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId);
|
||||
|
||||
if (dataSource?.executionContext) {
|
||||
|
||||
+3
-16
@@ -1,23 +1,10 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 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 { observer } from 'mobx-react-lite';
|
||||
import { forwardRef } from 'react';
|
||||
import { importLazyComponent } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { ComplexLoader, createComplexLoader } from '@cloudbeaver/core-blocks';
|
||||
import type { IDefaultExtensions, IEditorProps, IEditorRef } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
const loader = createComplexLoader(async function loader() {
|
||||
const { SQLCodeEditor } = await import('./SQLCodeEditor.js');
|
||||
return { SQLCodeEditor };
|
||||
});
|
||||
|
||||
export const SQLCodeEditorLoader = observer<IEditorProps & IDefaultExtensions, IEditorRef>(
|
||||
forwardRef(function SQLCodeEditorLoader(props, ref) {
|
||||
return <ComplexLoader loader={loader}>{({ SQLCodeEditor }) => <SQLCodeEditor {...props} ref={ref} />}</ComplexLoader>;
|
||||
}),
|
||||
);
|
||||
export const SQLCodeEditorLoader = importLazyComponent(() => import('./SQLCodeEditor.js').then(m => m.SQLCodeEditor));
|
||||
|
||||
+4
-4
@@ -86,11 +86,11 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
|
||||
});
|
||||
|
||||
function applyIncoming() {
|
||||
data.dataSource?.applyIncoming();
|
||||
data.model.dataSource?.applyIncoming();
|
||||
}
|
||||
|
||||
function keepCurrent() {
|
||||
data.dataSource?.keepCurrent();
|
||||
data.model.dataSource?.keepCurrent();
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -99,8 +99,8 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
|
||||
ref={setEditorRef}
|
||||
getValue={() => data.value}
|
||||
cursor={{
|
||||
anchor: data.cursor.anchor,
|
||||
head: data.cursor.head,
|
||||
anchor: data.model.cursor.anchor,
|
||||
head: data.model.cursor.head,
|
||||
}}
|
||||
incomingValue={data.incomingValue}
|
||||
extensions={extensions}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,7 @@ export class SQLCodeEditorPanelService {
|
||||
key: 'sql-editor',
|
||||
icon: '/icons/sql_script_sm.svg',
|
||||
name: 'sql_editor_script_editor',
|
||||
isHidden: (_, props) => props?.data.dataSource?.hasFeature(ESqlDataSourceFeatures.script) !== true,
|
||||
isHidden: (_, props) => props?.data.model.dataSource?.hasFeature(ESqlDataSourceFeatures.script) !== true,
|
||||
panel: () => SQLCodeEditorPanel,
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ export function useSQLCodeEditorPanel(data: ISQLEditorData, editor: IEditor) {
|
||||
highlightActiveQuery() {
|
||||
this.editor.clearActiveQueryHighlight();
|
||||
|
||||
const segment = this.data.activeSegment;
|
||||
const segment = this.data.model.cursorSegment;
|
||||
|
||||
if (segment) {
|
||||
this.editor.highlightActiveQuery(segment.begin, segment.end);
|
||||
@@ -52,7 +52,7 @@ export function useSQLCodeEditorPanel(data: ISQLEditorData, editor: IEditor) {
|
||||
);
|
||||
|
||||
useExecutor({
|
||||
executor: data.onUpdate,
|
||||
executor: data.model.onUpdate,
|
||||
handlers: [updateHighlight],
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ const codemirrorComplexLoader = createComplexLoader(() => import('@cloudbeaver/p
|
||||
const SQL_EDITOR_COMPARTMENT = new Compartment();
|
||||
|
||||
export function useSqlDialectExtension(dialectInfo: SqlDialectInfo | undefined): [Compartment, Extension] {
|
||||
//TODO: probably we need to refactor it to lazy approach without triggering suspense
|
||||
const { SQLDialect, SQL_EDITOR } = useComplexLoader(codemirrorComplexLoader);
|
||||
const loader = getDialectLoader(dialectInfo?.name);
|
||||
const dialect = useComplexLoader(loader);
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"@cloudbeaver/plugin-codemirror6": "workspace:*",
|
||||
"@cloudbeaver/plugin-data-viewer": "workspace:*",
|
||||
"@cloudbeaver/plugin-navigation-tabs": "workspace:*",
|
||||
"@dbeaver/js-helpers": "workspace:^",
|
||||
"mobx": "^6",
|
||||
"mobx-react-lite": "^4",
|
||||
"react": "^19",
|
||||
|
||||
@@ -161,11 +161,6 @@ export class MenuBootstrap extends Bootstrap {
|
||||
id: 'sql-editor-actions-more',
|
||||
actions: [ACTION_DOWNLOAD, ACTION_UPLOAD],
|
||||
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA, DATA_CONTEXT_SQL_EDITOR_STATE],
|
||||
isHidden: context => {
|
||||
const data = context.get(DATA_CONTEXT_SQL_EDITOR_DATA)!;
|
||||
|
||||
return data.activeSegmentMode.activeSegmentMode;
|
||||
},
|
||||
isDisabled: (context, action) => {
|
||||
const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE)!;
|
||||
|
||||
@@ -217,10 +212,7 @@ export class MenuBootstrap extends Bootstrap {
|
||||
this.menuService.addCreator({
|
||||
menus: [SQL_EDITOR_ACTIONS_MENU],
|
||||
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA, DATA_CONTEXT_SQL_EDITOR_STATE],
|
||||
getItems: (context, items) => [
|
||||
...items,
|
||||
...EXECUTIONS_ACTIONS,
|
||||
],
|
||||
getItems: (context, items) => [...items, ...EXECUTIONS_ACTIONS],
|
||||
});
|
||||
|
||||
this.keyBindingService.addKeyBindingHandler({
|
||||
@@ -242,12 +234,7 @@ export class MenuBootstrap extends Bootstrap {
|
||||
|
||||
this.actionService.addHandler({
|
||||
id: 'sql-editor-actions',
|
||||
actions: [
|
||||
...EXECUTIONS_ACTIONS,
|
||||
ACTION_SQL_EDITOR_FORMAT,
|
||||
ACTION_REDO,
|
||||
ACTION_UNDO,
|
||||
],
|
||||
actions: [...EXECUTIONS_ACTIONS, ACTION_SQL_EDITOR_FORMAT, ACTION_REDO, ACTION_UNDO],
|
||||
contexts: [DATA_CONTEXT_SQL_EDITOR_DATA],
|
||||
isActionApplicable: (contexts, action): boolean => {
|
||||
const sqlEditorData = contexts.get(DATA_CONTEXT_SQL_EDITOR_DATA)!;
|
||||
@@ -256,25 +243,21 @@ export class MenuBootstrap extends Bootstrap {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!sqlEditorData.isExecutionAllowed &&
|
||||
EXECUTIONS_ACTIONS.includes(action)
|
||||
) {
|
||||
if (!sqlEditorData.isExecutionAllowed && EXECUTIONS_ACTIONS.includes(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action === ACTION_SQL_EDITOR_FORMAT) {
|
||||
return !!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.script) && !sqlEditorData.activeSegmentMode.activeSegmentMode;
|
||||
return !!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.script);
|
||||
}
|
||||
|
||||
if (action === ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN) {
|
||||
return !!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
|
||||
!!sqlEditorData.dialect?.supportsExplainExecutionPlan;
|
||||
return !!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query) && !!sqlEditorData.dialect?.supportsExplainExecutionPlan;
|
||||
}
|
||||
|
||||
// TODO we have to add check for output action ?
|
||||
if (
|
||||
!sqlEditorData.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
|
||||
!sqlEditorData.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query) &&
|
||||
[ACTION_SQL_EDITOR_EXECUTE, ACTION_SQL_EDITOR_EXECUTE_NEW, ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN].includes(action)
|
||||
) {
|
||||
return false;
|
||||
@@ -402,24 +385,16 @@ export class MenuBootstrap extends Bootstrap {
|
||||
data.executeQueryNewTab();
|
||||
break;
|
||||
case ACTION_SQL_EDITOR_EXECUTE_SCRIPT:
|
||||
if (data.activeSegmentMode.activeSegmentMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.executeScript();
|
||||
break;
|
||||
case ACTION_SQL_EDITOR_FORMAT:
|
||||
if (data.activeSegmentMode.activeSegmentMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.formatScript();
|
||||
break;
|
||||
case ACTION_UNDO:
|
||||
data.dataSource?.history.undo();
|
||||
data.model.dataSource?.history.undo();
|
||||
break;
|
||||
case ACTION_REDO:
|
||||
data.dataSource?.history.redo();
|
||||
data.model.dataSource?.history.redo();
|
||||
break;
|
||||
case ACTION_SQL_EDITOR_SHOW_EXECUTION_PLAN:
|
||||
data.showExecutionPlan();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 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 { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
|
||||
export interface IQueryInfo {
|
||||
@@ -39,9 +40,12 @@ export class SQLParser {
|
||||
private _scripts: ISQLScriptSegment[];
|
||||
private script: string;
|
||||
|
||||
private lastParsingArgs: any[];
|
||||
|
||||
constructor() {
|
||||
this._scripts = [];
|
||||
this.script = '';
|
||||
this.lastParsingArgs = [];
|
||||
|
||||
makeObservable<this, '_scripts' | 'script'>(this, {
|
||||
actualScript: computed,
|
||||
@@ -55,8 +59,36 @@ export class SQLParser {
|
||||
});
|
||||
}
|
||||
|
||||
parse<Args extends any[], TResult extends Promise<IQueryInfo[]> | IQueryInfo[]>(
|
||||
parser: (script: string, ...args: Args) => TResult,
|
||||
...args: Args
|
||||
): TResult extends Promise<any> ? Promise<void> : void {
|
||||
const parsingScript = this.actualScript;
|
||||
const parsingArgs = [parsingScript, parser, ...args];
|
||||
|
||||
if (isArraysEqual(this.lastParsingArgs, parsingArgs)) {
|
||||
return undefined as any;
|
||||
}
|
||||
const result = parser(parsingScript, ...args);
|
||||
|
||||
const applyResult = (queries: IQueryInfo[]) => {
|
||||
if (this.actualScript === parsingScript) {
|
||||
this.setQueries(queries);
|
||||
this.lastParsingArgs = parsingArgs;
|
||||
}
|
||||
};
|
||||
|
||||
if (result instanceof Promise) {
|
||||
return result.then(applyResult) as any;
|
||||
}
|
||||
|
||||
applyResult(result);
|
||||
|
||||
return undefined as any;
|
||||
}
|
||||
|
||||
getScriptSegment(): ISQLScriptSegment {
|
||||
const script = this.script || '';
|
||||
const script = this.actualScript || '';
|
||||
|
||||
return {
|
||||
query: script,
|
||||
@@ -70,12 +102,8 @@ export class SQLParser {
|
||||
return this.getQueryAtPos(begin);
|
||||
}
|
||||
|
||||
if (end === -1) {
|
||||
end = begin;
|
||||
}
|
||||
|
||||
return {
|
||||
query: (this.script || '').substring(begin, end),
|
||||
query: this.actualScript.substring(begin, end),
|
||||
begin,
|
||||
end,
|
||||
};
|
||||
@@ -103,7 +131,7 @@ export class SQLParser {
|
||||
|
||||
setQueries(queries: IQueryInfo[]): this {
|
||||
this._scripts = queries.map<ISQLScriptSegment>(query => ({
|
||||
query: this.script.substring(query.start, query.end),
|
||||
query: this.actualScript.substring(query.start, query.end),
|
||||
begin: query.start,
|
||||
end: query.end,
|
||||
}));
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 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 { ConnectionDialectResource, type IConnectionExecutionContextInfo, type IConnectionInfoParams } from '@cloudbeaver/core-connections';
|
||||
import { ConnectionDialectResource, type IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import type { SqlDialectInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable(() => [ConnectionDialectResource, NotificationService])
|
||||
export class SqlDialectInfoService {
|
||||
@@ -25,20 +24,4 @@ export class SqlDialectInfoService {
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
getDialectInfo(key: IConnectionInfoParams): SqlDialectInfo | undefined {
|
||||
return this.connectionDialectResource.get(key);
|
||||
}
|
||||
|
||||
async loadSqlDialectInfo(key: IConnectionInfoParams): Promise<SqlDialectInfo | undefined> {
|
||||
if (!this.connectionDialectResource.has(key)) {
|
||||
try {
|
||||
return this.connectionDialectResource.load(key);
|
||||
} catch (error: any) {
|
||||
this.notificationService.logException(error, 'Failed to load SqlDialectInfo');
|
||||
}
|
||||
}
|
||||
|
||||
return this.getDialectInfo(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
import type { ISyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { SqlDialectInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { ISqlDataSource, ISqlEditorCursor } from '../SqlDataSource/ISqlDataSource.js';
|
||||
import type { ISqlEditorCursor } from '../SqlDataSource/ISqlDataSource.js';
|
||||
import type { SQLProposal } from '../SqlEditorService.js';
|
||||
import type { ISQLScriptSegment, SQLParser } from '../SQLParser.js';
|
||||
import type { ISQLEditorMode } from './SQLEditorModeContext.js';
|
||||
import type { ISQLScriptSegment } from '../SQLParser.js';
|
||||
import type { ISqlEditorModel } from '../SqlEditorModel/ISqlEditorModel.js';
|
||||
|
||||
export interface ISegmentExecutionData {
|
||||
segment: ISQLScriptSegment;
|
||||
@@ -19,12 +19,8 @@ export interface ISegmentExecutionData {
|
||||
}
|
||||
|
||||
export interface ISQLEditorData {
|
||||
readonly cursor: ISqlEditorCursor;
|
||||
activeSegmentMode: ISQLEditorMode;
|
||||
readonly parser: SQLParser;
|
||||
readonly model: ISqlEditorModel;
|
||||
readonly dialect: SqlDialectInfo | undefined;
|
||||
readonly activeSegment: ISQLScriptSegment | undefined;
|
||||
readonly cursorSegment: ISQLScriptSegment | undefined;
|
||||
readonly readonly: boolean;
|
||||
readonly editing: boolean;
|
||||
readonly isScriptEmpty: boolean;
|
||||
@@ -33,28 +29,21 @@ export interface ISQLEditorData {
|
||||
readonly value: string;
|
||||
readonly incomingValue?: string;
|
||||
readonly isExecutionAllowed: boolean;
|
||||
readonly dataSource: ISqlDataSource | undefined;
|
||||
readonly onExecute: ISyncExecutor<boolean>;
|
||||
readonly onSegmentExecute: ISyncExecutor<ISegmentExecutionData>;
|
||||
readonly onFormat: ISyncExecutor<[ISQLScriptSegment, string]>;
|
||||
readonly onUpdate: ISyncExecutor;
|
||||
readonly onMode: ISyncExecutor<ISQLEditorData>;
|
||||
/** displays if last getHintProposals call ended with limit */
|
||||
readonly hintsLimitIsMet: boolean;
|
||||
|
||||
updateParserScriptsDebounced(): Promise<void>;
|
||||
setScript(query: string, source?: string, cursor?: ISqlEditorCursor): void;
|
||||
init(): void;
|
||||
destruct(): void;
|
||||
setCursor(begin: number, end?: number): void;
|
||||
formatScript(): Promise<void>;
|
||||
executeQuery(): Promise<void>;
|
||||
executeQueryNewTab(): Promise<void>;
|
||||
showExecutionPlan(): Promise<void>;
|
||||
executeScript(): Promise<void>;
|
||||
switchEditing(): void;
|
||||
getHintProposals(position: number, simple: boolean): Promise<SQLProposal[]>;
|
||||
getResolvedSegment(): Promise<ISQLScriptSegment | undefined>;
|
||||
executeQueryAction<T>(
|
||||
segment: ISQLScriptSegment | undefined,
|
||||
action: (query: ISQLScriptSegment) => T | Promise<T>,
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 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 { ISyncContextLoader } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { ISQLScriptSegment } from '../SQLParser.js';
|
||||
import type { ISQLEditorData } from './ISQLEditorData.js';
|
||||
|
||||
export interface ISQLEditorMode {
|
||||
activeSegment: ISQLScriptSegment | undefined;
|
||||
activeSegmentMode: boolean;
|
||||
}
|
||||
|
||||
export const SQLEditorModeContext: ISyncContextLoader<ISQLEditorMode, ISQLEditorData> = function SQLEditorModeContext(context, data) {
|
||||
const from = Math.min(data.cursor.anchor, data.cursor.head);
|
||||
const to = Math.max(data.cursor.anchor, data.cursor.head);
|
||||
|
||||
return {
|
||||
activeSegment: data.parser.getSegment(from, to),
|
||||
activeSegmentMode: false,
|
||||
};
|
||||
};
|
||||
@@ -1,21 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { ComplexLoader, createComplexLoader } from '@cloudbeaver/core-blocks';
|
||||
import { importLazyComponent } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { ISqlEditorProps } from './ISqlEditorProps.js';
|
||||
|
||||
const loader = createComplexLoader(async function loader() {
|
||||
const { SqlEditor } = await import('./SqlEditor.js');
|
||||
return { SqlEditor };
|
||||
});
|
||||
|
||||
export const SqlEditorLoader = observer<ISqlEditorProps>(function SqlEditorLoader(props) {
|
||||
return <ComplexLoader loader={loader}>{({ SqlEditor }) => <SqlEditor {...props} />}</ComplexLoader>;
|
||||
});
|
||||
export const SqlEditorLoader = importLazyComponent(() => import('./SqlEditor.js').then(m => m.SqlEditor));
|
||||
|
||||
@@ -40,7 +40,7 @@ export function useActiveQuery(state: ISQLEditorData): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const segment = await sqlEditorData.getResolvedSegment();
|
||||
const segment = await sqlEditorData.model.getResolvedSegment();
|
||||
let query = data.update.query.trim();
|
||||
|
||||
if (segment) {
|
||||
@@ -65,7 +65,7 @@ export function useActiveQuery(state: ISQLEditorData): void {
|
||||
head: firstQueryPart.length,
|
||||
});
|
||||
} else {
|
||||
sqlEditorData.setScript(query, undefined, sqlEditorData.cursor);
|
||||
sqlEditorData.setScript(query, undefined, sqlEditorData.model.cursor);
|
||||
}
|
||||
break;
|
||||
case 'append':
|
||||
|
||||
@@ -5,32 +5,30 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { action, autorun, computed, type IReactionDisposer, observable, runInAction, untracked } from 'mobx';
|
||||
import { useEffect } from 'react';
|
||||
import { action, computed, type IReactionDisposer, observable } from 'mobx';
|
||||
|
||||
import { ConfirmationDialog, useExecutor, useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { ConnectionExecutionContextService, createConnectionParam } from '@cloudbeaver/core-connections';
|
||||
import { ConfirmationDialog, getComputed, useExecutor, useObservableRef, useResource } from '@cloudbeaver/core-blocks';
|
||||
import { ConnectionDialectResource, ConnectionExecutionContextService, createConnectionParam } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { SqlCompletionProposal, SqlDialectInfo, SqlScriptInfoFragment } from '@cloudbeaver/core-sdk';
|
||||
import { createLastPromiseGetter, debounceAsync, type LastPromiseGetter, throttleAsync } from '@cloudbeaver/core-utils';
|
||||
import type { SqlCompletionProposal, SqlScriptInfoFragment } from '@cloudbeaver/core-sdk';
|
||||
import { createLastPromiseGetter, type LastPromiseGetter, throttleAsync } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState.js';
|
||||
import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures.js';
|
||||
import type { ISqlDataSource, ISqlEditorCursor } from '../SqlDataSource/ISqlDataSource.js';
|
||||
import { SqlDataSourceService } from '../SqlDataSource/SqlDataSourceService.js';
|
||||
import type { ISqlEditorCursor } from '../SqlDataSource/ISqlDataSource.js';
|
||||
import { SqlDialectInfoService } from '../SqlDialectInfoService.js';
|
||||
import { SqlEditorService } from '../SqlEditorService.js';
|
||||
import { type ISQLScriptSegment, SQLParser } from '../SQLParser.js';
|
||||
import { type ISQLScriptSegment } from '../SQLParser.js';
|
||||
import { SqlExecutionPlanService } from '../SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.js';
|
||||
import { OUTPUT_LOGS_TAB_ID } from '../SqlResultTabs/OutputLogs/OUTPUT_LOGS_TAB_ID.js';
|
||||
import { SqlQueryService } from '../SqlResultTabs/SqlQueryService.js';
|
||||
import { SqlResultTabsService } from '../SqlResultTabs/SqlResultTabsService.js';
|
||||
import type { ISQLEditorData } from './ISQLEditorData.js';
|
||||
import { SQLEditorModeContext } from './SQLEditorModeContext.js';
|
||||
import { SqlEditorSettingsService } from '../SqlEditorSettingsService.js';
|
||||
import { SqlEditorModelService } from '../SqlEditorModel/SqlEditorModelService.js';
|
||||
|
||||
interface ISQLEditorDataPrivate extends ISQLEditorData {
|
||||
readonly sqlDialectInfoService: SqlDialectInfoService;
|
||||
@@ -42,20 +40,15 @@ interface ISQLEditorDataPrivate extends ISQLEditorData {
|
||||
readonly sqlEditorSettingsService: SqlEditorSettingsService;
|
||||
readonly commonDialogService: CommonDialogService;
|
||||
readonly sqlResultTabsService: SqlResultTabsService;
|
||||
readonly dataSource: ISqlDataSource | undefined;
|
||||
readonly getLastAutocomplete: LastPromiseGetter<SqlCompletionProposal[]>;
|
||||
readonly parseScript: LastPromiseGetter<SqlScriptInfoFragment>;
|
||||
|
||||
cursor: ISqlEditorCursor;
|
||||
readonlyState: boolean;
|
||||
executingScript: boolean;
|
||||
state: ISqlEditorTabState;
|
||||
reactionDisposer: IReactionDisposer | null;
|
||||
hintsLimitIsMet: boolean;
|
||||
updateParserScripts(): Promise<void>;
|
||||
loadDatabaseDataModels(): Promise<void>;
|
||||
getExecutingQuery(script: boolean): ISQLScriptSegment | undefined;
|
||||
getSubQuery(): ISQLScriptSegment | undefined;
|
||||
}
|
||||
|
||||
const MAX_HINTS_LIMIT = 200;
|
||||
@@ -69,42 +62,31 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
const sqlExecutionPlanService = useService(SqlExecutionPlanService);
|
||||
const sqlResultTabsService = useService(SqlResultTabsService);
|
||||
const commonDialogService = useService(CommonDialogService);
|
||||
const sqlDataSourceService = useService(SqlDataSourceService);
|
||||
const sqlEditorSettingsService = useService(SqlEditorSettingsService);
|
||||
const sqlEditorModelService = useService(SqlEditorModelService);
|
||||
|
||||
const model = sqlEditorModelService.getOrCreate(state);
|
||||
|
||||
const key = getComputed(() => {
|
||||
const executionContext = model.dataSource?.executionContext;
|
||||
if (executionContext) {
|
||||
const context = connectionExecutionContextService.get(executionContext.id)?.context;
|
||||
if (context) {
|
||||
return createConnectionParam(context.projectId, context.connectionId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const connectionDialectLoader = useResource(useSqlEditor, ConnectionDialectResource, key);
|
||||
|
||||
const data = useObservableRef<ISQLEditorDataPrivate>(
|
||||
() => ({
|
||||
get dataSource(): ISqlDataSource | undefined {
|
||||
return sqlDataSourceService.get(this.state.editorId);
|
||||
},
|
||||
get dialect(): SqlDialectInfo | undefined {
|
||||
const executionContext = this.dataSource?.executionContext;
|
||||
if (!executionContext) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.sqlDialectInfoService.getDialectInfo(createConnectionParam(executionContext.projectId, executionContext.connectionId));
|
||||
},
|
||||
|
||||
activeSegmentMode: {
|
||||
activeSegment: undefined,
|
||||
activeSegmentMode: false,
|
||||
},
|
||||
|
||||
get activeSegment(): ISQLScriptSegment | undefined {
|
||||
return this.activeSegmentMode.activeSegment;
|
||||
},
|
||||
|
||||
get cursorSegment(): ISQLScriptSegment | undefined {
|
||||
return this.parser.getSegment(this.cursor.anchor, -1);
|
||||
},
|
||||
|
||||
get readonly(): boolean {
|
||||
return this.executingScript || this.readonlyState || !!this.dataSource?.isReadonly() || !this.editing;
|
||||
return this.executingScript || this.readonlyState || !!this.model.dataSource?.isReadonly() || !this.editing;
|
||||
},
|
||||
|
||||
get editing(): boolean {
|
||||
return this.dataSource?.isEditing() ?? false;
|
||||
return this.model.dataSource?.isEditing() ?? false;
|
||||
},
|
||||
|
||||
get isScriptEmpty(): boolean {
|
||||
@@ -112,85 +94,47 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
|
||||
get isDisabled(): boolean {
|
||||
if (!this.dataSource?.executionContext || !this.dataSource.isLoaded()) {
|
||||
if (!this.model.dataSource?.executionContext || !this.model.dataSource.isLoaded()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const context = this.connectionExecutionContextService.get(this.dataSource.executionContext.id);
|
||||
const context = this.connectionExecutionContextService.get(this.model.dataSource.executionContext.id);
|
||||
|
||||
return context?.executing || false;
|
||||
},
|
||||
|
||||
get isIncomingChanges(): boolean {
|
||||
return this.dataSource?.isIncomingChanges ?? false;
|
||||
},
|
||||
|
||||
get cursor(): ISqlEditorCursor {
|
||||
return this.dataSource?.cursor ?? { anchor: 0, head: 0 };
|
||||
return this.model.dataSource?.isIncomingChanges ?? false;
|
||||
},
|
||||
|
||||
get value(): string {
|
||||
return this.dataSource?.script ?? '';
|
||||
return this.model.dataSource?.script ?? '';
|
||||
},
|
||||
|
||||
get incomingValue(): string | undefined {
|
||||
return this.dataSource?.incomingScript;
|
||||
return this.model.dataSource?.incomingScript;
|
||||
},
|
||||
|
||||
get isExecutionAllowed(): boolean {
|
||||
return !!this.dataSource?.hasFeature(ESqlDataSourceFeatures.executable) && this.sqlEditorSettingsService.scriptExecutionEnabled;
|
||||
return !!this.model.dataSource?.hasFeature(ESqlDataSourceFeatures.executable) && this.sqlEditorSettingsService.scriptExecutionEnabled;
|
||||
},
|
||||
|
||||
onMode: new SyncExecutor(),
|
||||
onExecute: new SyncExecutor(),
|
||||
onSegmentExecute: new SyncExecutor(),
|
||||
onUpdate: new SyncExecutor(),
|
||||
parser: new SQLParser(),
|
||||
|
||||
readonlyState: false,
|
||||
executingScript: false,
|
||||
reactionDisposer: null,
|
||||
hintsLimitIsMet: false,
|
||||
|
||||
init(): void {
|
||||
if (this.reactionDisposer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.parser.setScript(this.value);
|
||||
|
||||
this.reactionDisposer = autorun(() => {
|
||||
const executionContext = this.dataSource?.executionContext;
|
||||
if (executionContext) {
|
||||
const context = this.connectionExecutionContextService.get(executionContext.id)?.context;
|
||||
if (context) {
|
||||
const key = createConnectionParam(context.projectId, context.connectionId);
|
||||
|
||||
untracked(() => {
|
||||
this.sqlDialectInfoService.loadSqlDialectInfo(key).then(async () => {
|
||||
try {
|
||||
await this.updateParserScriptsDebounced();
|
||||
} catch {}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
destruct(): void {
|
||||
this.reactionDisposer?.();
|
||||
},
|
||||
|
||||
setCursor(begin: number, end = begin): void {
|
||||
this.dataSource?.setCursor(begin, end);
|
||||
this.model.dataSource?.setCursor(begin, end);
|
||||
},
|
||||
|
||||
getLastAutocomplete: createLastPromiseGetter(),
|
||||
parseScript: createLastPromiseGetter(),
|
||||
|
||||
getHintProposals: throttleAsync(async function getHintProposals(this: ISQLEditorDataPrivate, position, simple) {
|
||||
const executionContext = this.dataSource?.executionContext;
|
||||
const executionContext = this.model.dataSource?.executionContext;
|
||||
if (!executionContext) {
|
||||
return [];
|
||||
}
|
||||
@@ -211,13 +155,11 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
}, 300),
|
||||
|
||||
async formatScript(): Promise<void> {
|
||||
if (this.isDisabled || this.isScriptEmpty || !this.dataSource?.executionContext) {
|
||||
if (this.isDisabled || this.isScriptEmpty || !this.model.dataSource?.executionContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateParserScripts();
|
||||
const query = this.value;
|
||||
const script = this.getExecutingQuery(false);
|
||||
const script = await this.model.getResolvedSegment();
|
||||
|
||||
if (!script) {
|
||||
return;
|
||||
@@ -226,39 +168,38 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
this.onExecute.execute(true);
|
||||
try {
|
||||
this.readonlyState = true;
|
||||
const formatted = await this.sqlDialectInfoService.formatScript(this.dataSource.executionContext, script.query);
|
||||
const formatted = await this.sqlDialectInfoService.formatScript(this.model.dataSource.executionContext, script.query);
|
||||
|
||||
this.setScript(query.substring(0, script.begin) + formatted + query.substring(script.end));
|
||||
this.setCursor(script.begin + formatted.length);
|
||||
const cursorAnchor = this.model.cursor.anchor;
|
||||
this.setScript(this.value.substring(0, script.begin) + formatted + this.value.substring(script.end), 'format', {
|
||||
anchor: cursorAnchor,
|
||||
head: cursorAnchor,
|
||||
});
|
||||
} finally {
|
||||
this.readonlyState = false;
|
||||
}
|
||||
},
|
||||
|
||||
async executeQuery(): Promise<void> {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isQuery = this.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
|
||||
if (!isQuery || !this.isExecutionAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateParserScripts();
|
||||
const query = this.getSubQuery();
|
||||
|
||||
try {
|
||||
await this.executeQueryAction(await this.executeQueryAction(query, () => this.getResolvedSegment()), query =>
|
||||
this.sqlQueryService.executeEditorQuery(this.state, query.query, false),
|
||||
);
|
||||
const segment = await this.model.getResolvedSegment();
|
||||
await this.executeQueryAction(segment, query => this.sqlQueryService.executeEditorQuery(this.state, query.query, false));
|
||||
} catch {}
|
||||
},
|
||||
|
||||
async loadDatabaseDataModels(): Promise<void> {
|
||||
const query = this.getExecutingQuery(true);
|
||||
|
||||
// force script parsing, cursorSegment depends on it
|
||||
await this.model.getResolvedSegment();
|
||||
await this.executeQueryAction(
|
||||
query,
|
||||
this.model.cursorSegment,
|
||||
() => {
|
||||
if (this.dataSource?.databaseModels.length) {
|
||||
if (this.model.dataSource?.databaseModels.length) {
|
||||
this.sqlQueryService.initDatabaseDataModels(this.state);
|
||||
}
|
||||
},
|
||||
@@ -268,43 +209,32 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
|
||||
async executeQueryNewTab(): Promise<void> {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isQuery = this.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
|
||||
if (!isQuery || !this.isExecutionAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateParserScripts();
|
||||
const query = this.getSubQuery();
|
||||
|
||||
try {
|
||||
await this.executeQueryAction(await this.executeQueryAction(query, () => this.getResolvedSegment()), query =>
|
||||
await this.executeQueryAction(await this.executeQueryAction(this.model.cursorSegment, () => this.model.getResolvedSegment()), query =>
|
||||
this.sqlQueryService.executeEditorQuery(this.state, query.query, true),
|
||||
);
|
||||
} catch {}
|
||||
},
|
||||
|
||||
async showExecutionPlan(): Promise<void> {
|
||||
const isQuery = this.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isQuery = this.model.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
|
||||
if (!isQuery || !this.isExecutionAllowed || !this.dialect?.supportsExplainExecutionPlan) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateParserScripts();
|
||||
const query = this.getSubQuery();
|
||||
|
||||
try {
|
||||
await this.executeQueryAction(await this.executeQueryAction(query, () => this.getResolvedSegment()), query =>
|
||||
this.sqlExecutionPlanService.executeExecutionPlan(this.state, query.query),
|
||||
);
|
||||
const segment = await this.model.getResolvedSegment();
|
||||
await this.executeQueryAction(segment, query => this.sqlExecutionPlanService.executeExecutionPlan(this.state, query.query));
|
||||
} catch {}
|
||||
},
|
||||
|
||||
switchEditing(): void {
|
||||
this.dataSource?.setEditing(!this.dataSource.isEditing());
|
||||
},
|
||||
|
||||
async executeScript(): Promise<void> {
|
||||
if (!this.isExecutionAllowed || this.isDisabled || this.isScriptEmpty) {
|
||||
return;
|
||||
@@ -336,8 +266,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
this.onExecute.execute(true);
|
||||
try {
|
||||
this.executingScript = true;
|
||||
await this.updateParserScripts();
|
||||
const queries = this.parser.scripts;
|
||||
await this.model.getResolvedSegment();
|
||||
const queries = this.model.parser.scripts;
|
||||
|
||||
await this.sqlQueryService.executeQueries(
|
||||
this.state,
|
||||
@@ -363,41 +293,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
|
||||
setScript(query: string, source?: string, cursor?: ISqlEditorCursor): void {
|
||||
this.dataSource?.setScript(query, source, cursor);
|
||||
},
|
||||
|
||||
updateParserScriptsDebounced: debounceAsync(async function updateParserScriptsThrottle() {
|
||||
await data.updateParserScripts();
|
||||
}, 2000),
|
||||
|
||||
async updateParserScripts() {
|
||||
if (!this.dataSource?.hasFeature(ESqlDataSourceFeatures.script)) {
|
||||
return;
|
||||
}
|
||||
const projectId = this.dataSource.executionContext?.projectId;
|
||||
const connectionId = this.dataSource.executionContext?.connectionId;
|
||||
const script = this.parser.actualScript;
|
||||
|
||||
if (!projectId || !connectionId || !script) {
|
||||
this.parser.setQueries([]);
|
||||
this.onUpdate.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
const { queries } = await this.parseScript([connectionId, script], async () => {
|
||||
try {
|
||||
return await this.sqlEditorService.parseSQLScript(projectId, connectionId, script);
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'Failed to parse SQL script');
|
||||
throw exception;
|
||||
}
|
||||
});
|
||||
|
||||
// check if script was changed while we were waiting for response
|
||||
if (this.parser.actualScript === script) {
|
||||
this.parser.setQueries(queries);
|
||||
this.onUpdate.execute();
|
||||
}
|
||||
this.model.dataSource?.setScript(query, source, cursor);
|
||||
},
|
||||
|
||||
async executeQueryAction<T>(
|
||||
@@ -425,64 +321,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
throw exception;
|
||||
}
|
||||
},
|
||||
|
||||
getExecutingQuery(script: boolean): ISQLScriptSegment | undefined {
|
||||
if (script) {
|
||||
return this.parser.getScriptSegment();
|
||||
}
|
||||
|
||||
return this.activeSegment;
|
||||
},
|
||||
|
||||
async getResolvedSegment(): Promise<ISQLScriptSegment | undefined> {
|
||||
const projectId = this.dataSource?.executionContext?.projectId;
|
||||
const connectionId = this.dataSource?.executionContext?.connectionId;
|
||||
|
||||
while (true) {
|
||||
const currentScript = this.parser.actualScript;
|
||||
// TODO: we updating parser scripts
|
||||
// script may be changed this will lead to temporary wrong segments offsets
|
||||
await data.updateParserScripts();
|
||||
if (currentScript !== this.parser.actualScript) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!projectId || !connectionId || this.cursor.anchor !== this.cursor.head) {
|
||||
return this.getSubQuery();
|
||||
}
|
||||
|
||||
if (this.activeSegmentMode.activeSegmentMode) {
|
||||
return this.activeSegment;
|
||||
}
|
||||
|
||||
const result = await this.sqlEditorService.parseSQLQuery(projectId, connectionId, currentScript, this.cursor.anchor);
|
||||
if (currentScript !== this.parser.actualScript) {
|
||||
continue;
|
||||
}
|
||||
if (result.end === 0 && result.start === 0) {
|
||||
return this.cursorSegment;
|
||||
}
|
||||
|
||||
// TODO: here we use parser that may be outdated and segment will return wrong value
|
||||
const segment = this.parser.getSegment(result.start, result.end);
|
||||
return segment;
|
||||
}
|
||||
},
|
||||
|
||||
getSubQuery(): ISQLScriptSegment | undefined {
|
||||
const query = this.getExecutingQuery(false);
|
||||
|
||||
if (!query) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
query.query = query.query.trim();
|
||||
|
||||
return query;
|
||||
},
|
||||
setModeId(tabId: string): void {
|
||||
this.state.currentModeId = tabId;
|
||||
this.onUpdate.execute();
|
||||
},
|
||||
}),
|
||||
{
|
||||
@@ -492,13 +332,12 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
executeQueryNewTab: action.bound,
|
||||
showExecutionPlan: action.bound,
|
||||
executeScript: action.bound,
|
||||
switchEditing: action.bound,
|
||||
dialect: computed,
|
||||
isDisabled: computed,
|
||||
value: computed,
|
||||
readonly: computed,
|
||||
cursor: computed,
|
||||
activeSegmentMode: observable.ref,
|
||||
state: observable.ref,
|
||||
model: observable.ref,
|
||||
dialect: observable.ref,
|
||||
hintsLimitIsMet: observable.ref,
|
||||
readonlyState: observable,
|
||||
executingScript: observable,
|
||||
@@ -506,6 +345,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
{
|
||||
state,
|
||||
model,
|
||||
dialect: connectionDialectLoader.tryGetData,
|
||||
connectionExecutionContextService,
|
||||
sqlQueryService,
|
||||
sqlDialectInfoService,
|
||||
@@ -518,21 +359,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
},
|
||||
);
|
||||
|
||||
untracked(() => data.init());
|
||||
|
||||
useExecutor({
|
||||
executor: data.dataSource?.onSetScript,
|
||||
handlers: [
|
||||
function setScript({ script }) {
|
||||
data.parser.setScript(script);
|
||||
data.updateParserScriptsDebounced().catch(() => {});
|
||||
data.onUpdate.execute();
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
useExecutor({
|
||||
executor: data.dataSource?.onDatabaseModelUpdate,
|
||||
executor: model.dataSource?.onDatabaseModelUpdate,
|
||||
handlers: [
|
||||
function updateDatabaseModels() {
|
||||
data.loadDatabaseDataModels();
|
||||
@@ -540,34 +368,5 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
],
|
||||
});
|
||||
|
||||
useExecutor({
|
||||
executor: data.onUpdate,
|
||||
handlers: [
|
||||
function updateActiveSegmentMode() {
|
||||
// Probably we need to rework this logic
|
||||
// we want to track active segment mode with mobx
|
||||
// right now it's leads to bag when script changed from empty to not empty
|
||||
// data.isLineScriptEmpty skips this change
|
||||
const contexts = data.onMode.execute(data);
|
||||
data.activeSegmentMode = contexts.getContext(SQLEditorModeContext);
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = autorun(() => {
|
||||
const contexts = data.onMode.execute(data);
|
||||
const activeSegmentMode = contexts.getContext(SQLEditorModeContext);
|
||||
|
||||
runInAction(() => {
|
||||
data.activeSegmentMode = activeSegmentMode;
|
||||
});
|
||||
});
|
||||
|
||||
return subscription;
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => () => data.destruct(), []);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 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 { Disposable } from '@cloudbeaver/core-di';
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState.js';
|
||||
import type { IDataQueryOptions, QueryDataSource } from '../QueryDataSource.js';
|
||||
import type { ISqlDataSource, ISqlEditorCursor } from '../SqlDataSource/ISqlDataSource.js';
|
||||
import type { ISQLScriptSegment, SQLParser } from '../SQLParser.js';
|
||||
import type { SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
export interface ISqlEditorModel<TDataSource extends QueryDataSource = QueryDataSource<IDataQueryOptions>> extends Disposable {
|
||||
readonly cursor: ISqlEditorCursor;
|
||||
readonly cursorSegment: ISQLScriptSegment | undefined;
|
||||
readonly parser: SQLParser;
|
||||
readonly state: ISqlEditorTabState | null;
|
||||
readonly dataSource: ISqlDataSource<TDataSource> | undefined;
|
||||
|
||||
readonly onUpdate: SyncExecutor;
|
||||
|
||||
setState(state: ISqlEditorTabState): void;
|
||||
getResolvedSegment(): Promise<ISQLScriptSegment | undefined>;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 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 { Disposable, injectable } from '@cloudbeaver/core-di';
|
||||
import type { ISqlEditorModel } from './ISqlEditorModel.js';
|
||||
import { SqlDataSourceService } from '../SqlDataSource/SqlDataSourceService.js';
|
||||
import type { QueryDataSource } from '../QueryDataSource.js';
|
||||
import type { ISetScriptData, ISqlDataSource, ISqlEditorCursor } from '../SqlDataSource/ISqlDataSource.js';
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState.js';
|
||||
import { SQLParser, type IQueryInfo, type ISQLScriptSegment } from '../SQLParser.js';
|
||||
import { SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures.js';
|
||||
import { SqlEditorService } from '../SqlEditorService.js';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { computed, makeObservable, observable, reaction } from 'mobx';
|
||||
import { debouncePromise, memoizeLast } from '@dbeaver/js-helpers';
|
||||
|
||||
const ZERO_CURSOR: ISqlEditorCursor = { anchor: 0, head: 0 };
|
||||
|
||||
@injectable(() => [SqlDataSourceService, SqlEditorService, NotificationService])
|
||||
export class SqlEditorModel<TDataSource extends QueryDataSource> extends Disposable implements ISqlEditorModel<TDataSource> {
|
||||
state: ISqlEditorTabState | null;
|
||||
get cursor(): ISqlEditorCursor {
|
||||
return this.dataSource?.cursor ?? ZERO_CURSOR;
|
||||
}
|
||||
get cursorSegment(): ISQLScriptSegment | undefined {
|
||||
const from = Math.min(this.cursor.anchor, this.cursor.head);
|
||||
const to = Math.max(this.cursor.anchor, this.cursor.head);
|
||||
|
||||
return this.parser.getSegment(from, to);
|
||||
}
|
||||
get dataSource(): ISqlDataSource<TDataSource> | undefined {
|
||||
if (this.state) {
|
||||
return this.sqlDataSourceService.get(this.state.editorId);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
readonly parser: SQLParser;
|
||||
|
||||
readonly onUpdate: SyncExecutor;
|
||||
|
||||
private parserFn: (script: string, projectId: string, connectionId: string) => Promise<IQueryInfo[]>;
|
||||
private getSqlQueryAtPos: typeof this.sqlEditorService.parseSQLQuery;
|
||||
private dataSourceListener;
|
||||
private updateParserScriptsDebounced: typeof this.updateParserScripts;
|
||||
|
||||
constructor(
|
||||
private readonly sqlDataSourceService: SqlDataSourceService,
|
||||
private readonly sqlEditorService: SqlEditorService,
|
||||
private readonly notificationService: NotificationService,
|
||||
) {
|
||||
super();
|
||||
this.state = null;
|
||||
this.parser = new SQLParser();
|
||||
this.onUpdate = new SyncExecutor();
|
||||
this.parserFn = (script, projectId, connectionId) =>
|
||||
this.sqlEditorService.parseSQLScript(projectId, connectionId, script).then(({ queries }) => queries);
|
||||
|
||||
this.getSqlQueryAtPos = memoizeLast(this.sqlEditorService.parseSQLQuery.bind(this.sqlEditorService));
|
||||
this.syncScriptWithDataSource = this.syncScriptWithDataSource.bind(this);
|
||||
|
||||
this.updateParserScriptsDebounced = debouncePromise(() => this.updateParserScripts(), 2000);
|
||||
|
||||
this.dataSourceListener = reaction(
|
||||
() => this.dataSource,
|
||||
(dataSource, prev) => {
|
||||
prev?.onSetScript.removeHandler(this.syncScriptWithDataSource);
|
||||
dataSource?.onSetScript.addHandler(this.syncScriptWithDataSource);
|
||||
this.syncScriptWithDataSource({ script: dataSource?.script || '', cursor: dataSource?.cursor });
|
||||
},
|
||||
{ fireImmediately: true },
|
||||
);
|
||||
|
||||
makeObservable(this, {
|
||||
state: observable.shallow,
|
||||
dataSource: computed,
|
||||
cursor: computed,
|
||||
});
|
||||
}
|
||||
|
||||
setState(state: ISqlEditorTabState | null): void {
|
||||
this.state = state;
|
||||
this.onUpdate.execute();
|
||||
}
|
||||
|
||||
async getResolvedSegment(): Promise<ISQLScriptSegment | undefined> {
|
||||
const projectId = this.dataSource?.executionContext?.projectId;
|
||||
const connectionId = this.dataSource?.executionContext?.connectionId;
|
||||
|
||||
for (let attempts = 0; attempts < 5; attempts++) {
|
||||
const currentScript = this.parser.actualScript;
|
||||
|
||||
// TODO: we updating parser scripts
|
||||
// script may be changed this will lead to temporary wrong segments offsets
|
||||
await this.updateParserScripts();
|
||||
if (currentScript !== this.parser.actualScript) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!projectId || !connectionId || this.cursor.anchor !== this.cursor.head) {
|
||||
return this.getQuery();
|
||||
}
|
||||
|
||||
const result = await this.getSqlQueryAtPos(projectId, connectionId, currentScript, this.cursor.anchor);
|
||||
|
||||
if (currentScript !== this.parser.actualScript) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.end === 0 && result.start === 0) {
|
||||
return this.cursorSegment;
|
||||
}
|
||||
|
||||
return this.parser.getSegment(result.start, result.end);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async updateParserScripts() {
|
||||
const projectId = this.dataSource?.executionContext?.projectId;
|
||||
const connectionId = this.dataSource?.executionContext?.connectionId;
|
||||
const script = this.parser.actualScript;
|
||||
|
||||
if (!projectId || !connectionId || !script || !this.dataSource?.hasFeature(ESqlDataSourceFeatures.script)) {
|
||||
this.parser.parse(() => []);
|
||||
this.onUpdate.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.parser.parse(this.parserFn, projectId, connectionId);
|
||||
this.onUpdate.execute();
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'Failed to parse SQL script');
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private getQuery(): ISQLScriptSegment | undefined {
|
||||
const query = this.cursorSegment;
|
||||
|
||||
if (!query) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
query.query = query.query.trim();
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private syncScriptWithDataSource({ script }: ISetScriptData) {
|
||||
this.parser.setScript(script);
|
||||
this.updateParserScriptsDebounced().catch(() => {});
|
||||
this.onUpdate.execute();
|
||||
}
|
||||
|
||||
protected override dispose(): Promise<void> | void {
|
||||
this.dataSourceListener();
|
||||
this.dataSource?.onSetScript.removeHandler(this.syncScriptWithDataSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 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 { Disposable, injectable, IServiceProvider } from '@cloudbeaver/core-di';
|
||||
import type { ISqlEditorModel } from './ISqlEditorModel.js';
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState.js';
|
||||
import { SqlEditorModel } from './SqlEditorModel.js';
|
||||
import type { QueryDataSource } from '../QueryDataSource.js';
|
||||
|
||||
@injectable(() => [IServiceProvider])
|
||||
export class SqlEditorModelService extends Disposable {
|
||||
private readonly models: Map<string, ISqlEditorModel<any>>;
|
||||
|
||||
constructor(private readonly serviceProvider: IServiceProvider) {
|
||||
super();
|
||||
this.models = new Map();
|
||||
|
||||
makeObservable<this, 'models'>(this, {
|
||||
models: observable.shallow,
|
||||
});
|
||||
}
|
||||
|
||||
getOrCreate<TDataSource extends QueryDataSource>(state: ISqlEditorTabState): ISqlEditorModel<TDataSource> {
|
||||
let model = this.models.get(state.editorId);
|
||||
if (!model) {
|
||||
model = this.serviceProvider.getService(SqlEditorModel);
|
||||
|
||||
model.setState(state);
|
||||
this.models.set(state.editorId, model);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
async destroy(editorId: string): Promise<void> {
|
||||
const model = this.models.get(editorId);
|
||||
|
||||
if (model) {
|
||||
await model[Symbol.asyncDispose]?.().catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
this.models.delete(editorId);
|
||||
}
|
||||
|
||||
async destroySilent(editorId: string): Promise<void> {
|
||||
await this.destroy(editorId);
|
||||
}
|
||||
|
||||
async unload(editorId: string): Promise<void> {
|
||||
await this.destroy(editorId);
|
||||
}
|
||||
|
||||
protected override async dispose(): Promise<void> {
|
||||
for (const model of this.models.values()) {
|
||||
await model[Symbol.asyncDispose]?.().catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
this.models.clear();
|
||||
}
|
||||
}
|
||||
@@ -33,13 +33,13 @@ export * from './SqlEditor/ISQLEditorData.js';
|
||||
export * from './SqlEditor/DATA_CONTEXT_SQL_EDITOR_DATA.js';
|
||||
export * from './SqlEditor/SQL_EDITOR_ACTIONS_MENU.js';
|
||||
export * from './SqlEditor/SQL_EDITOR_TOOLS_MENU.js';
|
||||
export * from './SqlEditor/SQLEditorModeContext.js';
|
||||
export * from './SqlEditor/SqlEditorStateContext.js';
|
||||
export * from './SqlResultTabs/DATA_CONTEXT_SQL_EDITOR_RESULT_ID.js';
|
||||
export * from './SqlResultTabs/SqlResultTabsService.js';
|
||||
export * from './SqlResultTabs/OutputLogs/OutputLogsEventHandler.js';
|
||||
export * from './SqlResultTabs/OutputLogs/OutputLogsResource.js';
|
||||
export * from './SqlResultTabs/OutputLogs/OutputLogsService.js';
|
||||
export * from './SqlEditorModel/SqlEditorModelService.js';
|
||||
export * from './DATA_CONTEXT_SQL_EDITOR_STATE.js';
|
||||
export * from './getSqlEditorName.js';
|
||||
export * from './QueryDataSource.js';
|
||||
|
||||
@@ -25,12 +25,15 @@ import { SqlDataSourceService } from './SqlDataSource/SqlDataSourceService.js';
|
||||
import { LocalStorageSqlDataSourceBootstrap } from './SqlDataSource/LocalStorage/LocalStorageSqlDataSourceBootstrap.js';
|
||||
import { MenuBootstrap } from './MenuBootstrap.js';
|
||||
import { LocaleService } from './LocaleService.js';
|
||||
import { SqlEditorModel } from './SqlEditorModel/SqlEditorModel.js';
|
||||
import { SqlEditorModelService } from './SqlEditorModel/SqlEditorModelService.js';
|
||||
|
||||
export default ModuleRegistry.add({
|
||||
name: '@cloudbeaver/plugin-sql-editor',
|
||||
|
||||
configure: serviceCollection => {
|
||||
serviceCollection
|
||||
.addTransient(SqlEditorModel)
|
||||
.addSingleton(Bootstrap, LocaleService)
|
||||
.addSingleton(Bootstrap, proxy(LocalStorageSqlDataSourceBootstrap))
|
||||
.addSingleton(Bootstrap, proxy(MenuBootstrap))
|
||||
@@ -54,6 +57,7 @@ export default ModuleRegistry.add({
|
||||
.addSingleton(SqlDialectInfoService)
|
||||
.addSingleton(SqlDataSourceService)
|
||||
.addSingleton(LocalStorageSqlDataSourceBootstrap)
|
||||
.addSingleton(SqlEditorModelService)
|
||||
.addSingleton(MenuBootstrap);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
{
|
||||
"path": "../../common-typescript/@dbeaver/cli"
|
||||
},
|
||||
{
|
||||
"path": "../../common-typescript/@dbeaver/js-helpers"
|
||||
},
|
||||
{
|
||||
"path": "../core-authentication"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { computed, observable } from 'mobx';
|
||||
import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
@@ -21,15 +21,15 @@ import {
|
||||
useClipboard,
|
||||
useErrorDetails,
|
||||
useObservableRef,
|
||||
useResource,
|
||||
useS,
|
||||
useTranslate,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
|
||||
import { ConnectionDialectResource, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import type { DialogComponentProps } from '@cloudbeaver/core-dialogs';
|
||||
import { GQLErrorCatcher, type SqlDialectInfo } from '@cloudbeaver/core-sdk';
|
||||
import { GQLErrorCatcher } from '@cloudbeaver/core-sdk';
|
||||
import { useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6';
|
||||
import { SqlDialectInfoService } from '@cloudbeaver/plugin-sql-editor';
|
||||
import { SQLCodeEditorLoader, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-new';
|
||||
|
||||
import style from './GeneratedSqlDialog.module.css';
|
||||
@@ -45,7 +45,6 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
|
||||
const copy = useClipboard();
|
||||
const styles = useS(style);
|
||||
|
||||
const sqlDialectInfoService = useService(SqlDialectInfoService);
|
||||
const sqlGeneratorsResource = useService(SqlGeneratorsResource);
|
||||
const connectionInfoResource = useService(ConnectionInfoResource);
|
||||
const connection = connectionInfoResource.getConnectionForNode(payload.pathId);
|
||||
@@ -55,13 +54,6 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
|
||||
query: '',
|
||||
loading: true,
|
||||
error: new GQLErrorCatcher(),
|
||||
get dialect(): SqlDialectInfo | undefined {
|
||||
if (!this.connection?.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.sqlDialectInfoService.getDialectInfo(createConnectionParam(this.connection));
|
||||
},
|
||||
async load() {
|
||||
this.error.clear();
|
||||
|
||||
@@ -78,12 +70,12 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
|
||||
query: observable.ref,
|
||||
loading: observable.ref,
|
||||
connection: observable.ref,
|
||||
dialect: computed,
|
||||
},
|
||||
{ connection, sqlDialectInfoService },
|
||||
{ connection },
|
||||
);
|
||||
const connectionDialectResource = useResource(GeneratedSqlDialog, ConnectionDialectResource, connection ? createConnectionParam(connection) : null);
|
||||
const sqlDialect = useSqlDialectExtension(connectionDialectResource.data);
|
||||
|
||||
const sqlDialect = useSqlDialectExtension(state.dialect);
|
||||
const extensions = useCodemirrorExtensions();
|
||||
extensions.set(...sqlDialect);
|
||||
const error = useErrorDetails(state.error.exception);
|
||||
@@ -92,17 +84,6 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
|
||||
state.load();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
sqlDialectInfoService.loadSqlDialectInfo(createConnectionParam(connection)).catch(exception => {
|
||||
console.error(exception);
|
||||
console.warn(`Can't get dialect for connection: '${connection.id}'. Default dialect will be used`);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<CommonDialogWrapper size="large">
|
||||
<CommonDialogHeader title="app_shared_sql_generators_dialog_title" icon="sql-script" onReject={rejectDialog} />
|
||||
|
||||
+37
-2
@@ -4012,6 +4012,7 @@ __metadata:
|
||||
"@cloudbeaver/plugin-navigation-tabs": "workspace:*"
|
||||
"@cloudbeaver/tsconfig": "workspace:*"
|
||||
"@dbeaver/cli": "workspace:*"
|
||||
"@dbeaver/js-helpers": "workspace:^"
|
||||
"@dbeaver/react-tests": "workspace:*"
|
||||
"@types/react": "npm:^19"
|
||||
mobx: "npm:^6"
|
||||
@@ -4629,13 +4630,16 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@dbeaver/js-helpers@workspace:*, @dbeaver/js-helpers@workspace:common-typescript/@dbeaver/js-helpers":
|
||||
"@dbeaver/js-helpers@workspace:*, @dbeaver/js-helpers@workspace:^, @dbeaver/js-helpers@workspace:common-typescript/@dbeaver/js-helpers":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@dbeaver/js-helpers@workspace:common-typescript/@dbeaver/js-helpers"
|
||||
dependencies:
|
||||
"@dbeaver/cli": "workspace:^"
|
||||
"@dbeaver/tsconfig": "workspace:^"
|
||||
async-mutex: "npm:^0"
|
||||
p-debounce: "npm:^4.0.0"
|
||||
p-memoize: "npm:^8.0.0"
|
||||
quick-lru: "npm:^7.1.0"
|
||||
rimraf: "npm:^6"
|
||||
typescript: "npm:^5"
|
||||
vitest: "npm:^3"
|
||||
@@ -14635,6 +14639,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-function@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "mimic-function@npm:5.0.1"
|
||||
checksum: 10c0/f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"min-indent@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "min-indent@npm:1.0.1"
|
||||
@@ -15213,6 +15224,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-debounce@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "p-debounce@npm:4.0.0"
|
||||
checksum: 10c0/2d50bcb53659ad4345ef59b220581ccb40d581528949d8c63547c22b418e9a92f5138617eb2f41cd03bed293ee78a3dbb9b2795f572a4044434b89f058ccdfa2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-limit@npm:3.1.0, p-limit@npm:^3.0.2":
|
||||
version: 3.1.0
|
||||
resolution: "p-limit@npm:3.1.0"
|
||||
@@ -15265,6 +15283,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-memoize@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "p-memoize@npm:8.0.0"
|
||||
dependencies:
|
||||
mimic-function: "npm:^5.0.1"
|
||||
type-fest: "npm:^4.41.0"
|
||||
checksum: 10c0/cd4333c42f67dee273bff855e18cb974fac61136605ae06c30e779f71fb0e390fbab5c87160dfd8b5f905154b41c3f41bcc2e9870b2ce9b5e5c2c912012261ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-try@npm:^2.0.0":
|
||||
version: 2.2.0
|
||||
resolution: "p-try@npm:2.2.0"
|
||||
@@ -15820,6 +15848,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"quick-lru@npm:^7.1.0":
|
||||
version: 7.1.0
|
||||
resolution: "quick-lru@npm:7.1.0"
|
||||
checksum: 10c0/ef2df33368a66e3d3b32a42c523fcd0939c03f6192ec7f29db36179e22f015d2c75790aa9ab530c719a0dd3fb510f972bc66acca4e92bc9f01f92f0e6c2084f2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"randombytes@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "randombytes@npm:2.1.0"
|
||||
@@ -18078,7 +18113,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-fest@npm:^4.21.0, type-fest@npm:^4.26.1":
|
||||
"type-fest@npm:^4.21.0, type-fest@npm:^4.26.1, type-fest@npm:^4.41.0":
|
||||
version: 4.41.0
|
||||
resolution: "type-fest@npm:4.41.0"
|
||||
checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
|
||||
|
||||
Reference in New Issue
Block a user