mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-25 09:34:18 +08:00
dbeaver/pro#6201 feat: ai chat query insertion (#3578)
* dbeaver/pro#6201 feat: ai chat query insertion * chore: remove confusing comment * chore: query execution and replacement --------- Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
Evgenia
parent
dcc13811e8
commit
36e8d1fac9
+11
-1
@@ -334,6 +334,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@dbeaver/cli": "workspace:^"
|
||||
"@dbeaver/tsconfig": "workspace:^"
|
||||
async-mutex: "npm:^0"
|
||||
rimraf: "npm:^6"
|
||||
typescript: "npm:^5"
|
||||
vitest: "npm:^3"
|
||||
@@ -2197,6 +2198,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async-mutex@npm:^0":
|
||||
version: 0.5.0
|
||||
resolution: "async-mutex@npm:0.5.0"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10c0/9096e6ad6b674c894d8ddd5aa4c512b09bb05931b8746ebd634952b05685608b2b0820ed5c406e6569919ff5fe237ab3c491e6f2887d6da6b6ba906db3ee9c32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axe-core@npm:^4.10.2":
|
||||
version: 4.10.3
|
||||
resolution: "axe-core@npm:4.10.3"
|
||||
@@ -6910,7 +6920,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2, tslib@npm:^2.0.3, tslib@npm:^2.3.0":
|
||||
"tslib@npm:^2, tslib@npm:^2.0.3, tslib@npm:^2.3.0, tslib@npm:^2.4.0":
|
||||
version: 2.8.1
|
||||
resolution: "tslib@npm:2.8.1"
|
||||
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
|
||||
|
||||
@@ -30,5 +30,8 @@
|
||||
"rimraf": "^6",
|
||||
"typescript": "^5",
|
||||
"vitest": "^3"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "^0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './isDefined.js';
|
||||
export * from './isNotNullDefined.js';
|
||||
export * from './mutex.js';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * as mutex from 'async-mutex';
|
||||
@@ -144,6 +144,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@dbeaver/cli": "workspace:^"
|
||||
"@dbeaver/tsconfig": "workspace:^"
|
||||
async-mutex: "npm:^0"
|
||||
rimraf: "npm:^6"
|
||||
typescript: "npm:^5"
|
||||
vitest: "npm:^3"
|
||||
@@ -989,6 +990,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async-mutex@npm:^0":
|
||||
version: 0.5.0
|
||||
resolution: "async-mutex@npm:0.5.0"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10c0/9096e6ad6b674c894d8ddd5aa4c512b09bb05931b8746ebd634952b05685608b2b0820ed5c406e6569919ff5fe237ab3c491e6f2887d6da6b6ba906db3ee9c32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"balanced-match@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "balanced-match@npm:1.0.2"
|
||||
@@ -2801,7 +2811,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2, tslib@npm:^2.3.0":
|
||||
"tslib@npm:^2, tslib@npm:^2.3.0, tslib@npm:^2.4.0":
|
||||
version: 2.8.1
|
||||
resolution: "tslib@npm:2.8.1"
|
||||
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
|
||||
|
||||
@@ -34,6 +34,7 @@ export default [
|
||||
['ui_custom', 'Custom'],
|
||||
['ui_configure', 'Configure'],
|
||||
['ui_apply', 'Apply'],
|
||||
['ui_replace', 'Replace'],
|
||||
['ui_cancel', 'Cancel'],
|
||||
['ui_reject', 'Reject'],
|
||||
['ui_ignore', 'Ignore'],
|
||||
|
||||
@@ -32,6 +32,7 @@ export default [
|
||||
['ui_custom', 'Personnalisé'],
|
||||
['ui_configure', 'Configurer'],
|
||||
['ui_apply', 'Appliquer'],
|
||||
['ui_replace', 'Remplacer'],
|
||||
['ui_cancel', 'Annuler'],
|
||||
['ui_reject', 'Reject'],
|
||||
['ui_ignore', 'Ignorer'],
|
||||
|
||||
@@ -32,6 +32,7 @@ export default [
|
||||
['ui_custom', 'Custom'],
|
||||
['ui_configure', 'Configure'],
|
||||
['ui_apply', 'Applica'],
|
||||
['ui_replace', 'Sostituisci'],
|
||||
['ui_cancel', 'Annulla'],
|
||||
['ui_reject', 'Reject'],
|
||||
['ui_ignore', 'Ignore'],
|
||||
|
||||
@@ -31,6 +31,7 @@ export default [
|
||||
['ui_custom', 'Настроить'],
|
||||
['ui_configure', 'Настроить'],
|
||||
['ui_apply', 'Применить'],
|
||||
['ui_replace', 'Заменить'],
|
||||
['ui_cancel', 'Отменить'],
|
||||
['ui_reject', 'Отклонить'],
|
||||
['ui_ignore', 'Пропустить'],
|
||||
|
||||
@@ -34,6 +34,7 @@ export default [
|
||||
['ui_custom', 'Tùy chỉnh'],
|
||||
['ui_configure', 'Cấu hình'],
|
||||
['ui_apply', 'Áp dụng'],
|
||||
['ui_replace', 'Thay thế'],
|
||||
['ui_cancel', 'Hủy'],
|
||||
['ui_reject', 'Từ chối'],
|
||||
['ui_ignore', 'Bỏ qua'],
|
||||
|
||||
@@ -32,8 +32,9 @@ export default [
|
||||
['ui_custom', '个性化'],
|
||||
['ui_configure', '配置'],
|
||||
['ui_apply', '应用'],
|
||||
['ui_cancel', 'Cancel'],
|
||||
['ui_reject', 'Reject'],
|
||||
['ui_replace', '替换'],
|
||||
['ui_cancel', '取消'],
|
||||
['ui_reject', '拒绝'],
|
||||
['ui_ignore', '忽略'],
|
||||
['ui_error', '错误'],
|
||||
['ui_error_message', '错误:'],
|
||||
|
||||
@@ -42,6 +42,8 @@ export interface ISQLEditorOptions {
|
||||
schemaId?: string;
|
||||
source?: string;
|
||||
query?: string;
|
||||
dataSourceState?: Record<string, any>;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface SQLCreateAction extends SQLEditorActionContext, ISQLEditorOptions {
|
||||
@@ -142,6 +144,8 @@ export class SqlEditorNavigatorService {
|
||||
data.name,
|
||||
data.source,
|
||||
data.query,
|
||||
data.dataSourceState,
|
||||
data.metadata,
|
||||
);
|
||||
|
||||
if (tabOptions) {
|
||||
|
||||
@@ -121,12 +121,20 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
this.connectionExecutionContextResource.onItemDelete.addHandler(this.handleExecutionContextDelete.bind(this));
|
||||
}
|
||||
|
||||
createNewEditor(editorId: string, dataSourceKey: string, name?: string, source?: string, script?: string): ITabOptions<ISqlEditorTabState> | null {
|
||||
createNewEditor(
|
||||
editorId: string,
|
||||
dataSourceKey: string,
|
||||
name?: string,
|
||||
source?: string,
|
||||
script?: string,
|
||||
dataSourceState?: Record<string, any>,
|
||||
metadata?: Record<string, any>,
|
||||
): ITabOptions<ISqlEditorTabState> | null {
|
||||
const order = this.getFreeEditorId();
|
||||
|
||||
const handlerState = this.sqlEditorService.getState(editorId, dataSourceKey, order, source);
|
||||
const handlerState = this.sqlEditorService.getState(editorId, dataSourceKey, order, source, metadata);
|
||||
|
||||
const datasource = this.sqlDataSourceService.create(handlerState, dataSourceKey, { name, script });
|
||||
const datasource = this.sqlDataSourceService.create(handlerState, dataSourceKey, { name, script, dataSourceState });
|
||||
|
||||
return {
|
||||
id: editorId,
|
||||
@@ -160,7 +168,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
return createConnectionParam(context.projectId, context.connectionId);
|
||||
}
|
||||
|
||||
private async handleConnectionDelete(key: ResourceKeySimple<IConnectionInfoParams>) {
|
||||
private handleConnectionDelete(key: ResourceKeySimple<IConnectionInfoParams>) {
|
||||
const tabs = this.navigationTabsService.findTabs<ISqlEditorTabState>(
|
||||
isSQLEditorTab(tab => {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
@@ -225,7 +233,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
};
|
||||
}
|
||||
|
||||
private async handleExecutionContextUpdate(key: ResourceKeySimple<string>) {
|
||||
private handleExecutionContextUpdate(key: ResourceKeySimple<string>) {
|
||||
const tabs = this.navigationTabsService.findTabs<ISqlEditorTabState>(
|
||||
isSQLEditorTab(tab => {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
@@ -254,7 +262,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleExecutionContextDelete(key: ResourceKeySimple<string>) {
|
||||
private handleExecutionContextDelete(key: ResourceKeySimple<string>) {
|
||||
const tabs = this.navigationTabsService.findTabs<ISqlEditorTabState>(
|
||||
isSQLEditorTab(tab => {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
@@ -437,7 +445,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
private async syncDatasourceUpdate(data: ISQLDatasourceUpdateData) {
|
||||
private syncDatasourceUpdate(data: ISQLDatasourceUpdateData) {
|
||||
const tab = this.sqlEditorTabs.find(tab => tab.handlerState.editorId === data.editorId);
|
||||
|
||||
if (tab) {
|
||||
|
||||
@@ -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.
|
||||
@@ -74,6 +74,7 @@ export const SQL_EDITOR_TAB_STATE_SCHEMA = schema.object({
|
||||
outputLogsTab: OUTPUT_LOGS_TAB_SCHEMA.optional(),
|
||||
currentModeId: schema.string().optional(),
|
||||
modeState: schema.array(schema.tuple([schema.string(), schema.any()])),
|
||||
metadata: schema.record(schema.any()).optional(),
|
||||
});
|
||||
|
||||
export type ISqlEditorTabState = schema.infer<typeof SQL_EDITOR_TAB_STATE_SCHEMA>;
|
||||
|
||||
@@ -153,6 +153,9 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
}
|
||||
|
||||
setScript(script: string, source?: string, cursor?: ISqlEditorCursor): void {
|
||||
if (cursor) {
|
||||
this.setInnerCursorState(cursor);
|
||||
}
|
||||
this.onSetScript.execute({ script, source, cursor });
|
||||
}
|
||||
|
||||
@@ -243,13 +246,10 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
}
|
||||
|
||||
setCursor(anchor: number, head = anchor): void {
|
||||
const scriptLength = this.script.length;
|
||||
|
||||
this.innerCursorState = Object.freeze({
|
||||
anchor: Math.min(anchor, scriptLength),
|
||||
head: Math.min(head, scriptLength),
|
||||
this.setInnerCursorState({
|
||||
anchor,
|
||||
head,
|
||||
});
|
||||
|
||||
this.onUpdate.execute();
|
||||
}
|
||||
|
||||
@@ -305,4 +305,12 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
|
||||
protected abstract setBaseScript(script: string): void;
|
||||
protected abstract setBaseExecutionContext(executionContext: IConnectionExecutionContextInfo | undefined): void;
|
||||
protected setInnerCursorState(cursor: ISqlEditorCursor): void {
|
||||
const scriptLength = this.script.length;
|
||||
|
||||
this.innerCursorState = Object.freeze({
|
||||
anchor: Math.min(cursor.anchor, scriptLength),
|
||||
head: Math.min(cursor.head, scriptLength),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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.
|
||||
@@ -14,4 +14,5 @@ export interface ILocalStorageSqlDataSourceState {
|
||||
name?: string;
|
||||
executionContext?: IConnectionExecutionContextInfo;
|
||||
history: ISqlDataSourceHistoryState;
|
||||
showOnlyResults?: boolean;
|
||||
}
|
||||
|
||||
+8
@@ -38,6 +38,9 @@ export class LocalStorageSqlDataSource extends BaseSqlDataSource {
|
||||
}
|
||||
|
||||
override get features(): ESqlDataSourceFeatures[] {
|
||||
if (this.state.showOnlyResults) {
|
||||
return [];
|
||||
}
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.query, ESqlDataSourceFeatures.executable, ESqlDataSourceFeatures.setName];
|
||||
}
|
||||
|
||||
@@ -76,6 +79,11 @@ export class LocalStorageSqlDataSource extends BaseSqlDataSource {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: should we move it to the BaseSqlDataSource?
|
||||
setShowOnlyResults(showOnlyResults: boolean): void {
|
||||
this.state.showOnlyResults = showOnlyResults;
|
||||
}
|
||||
|
||||
override setScript(script: string, source?: string, cursor?: ISqlEditorCursor): void {
|
||||
this.state.script = script;
|
||||
super.setScript(script, source, cursor);
|
||||
|
||||
+2
-1
@@ -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.
|
||||
@@ -82,6 +82,7 @@ export class LocalStorageSqlDataSourceBootstrap extends Bootstrap {
|
||||
script: options?.script ?? '',
|
||||
executionContext: options?.executionContext,
|
||||
history: createSqlDataSourceHistoryInitialState(options?.script),
|
||||
...options?.dataSourceState,
|
||||
});
|
||||
|
||||
this.dataSourceStateState.set(editorId, state);
|
||||
|
||||
@@ -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.
|
||||
@@ -19,6 +19,7 @@ export interface ISqlDataSourceOptions {
|
||||
name?: string;
|
||||
script?: string;
|
||||
executionContext?: IConnectionExecutionContextInfo;
|
||||
dataSourceState?: Record<string, any>;
|
||||
}
|
||||
|
||||
type ISqlDataSourceFactory = (editorId: string, options?: ISqlDataSourceOptions) => ISqlDataSource;
|
||||
|
||||
@@ -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.
|
||||
@@ -22,6 +22,7 @@ import SqlEditorTab from './shared/SqlEditorTab.module.css';
|
||||
import SqlEditorTabList from './shared/SqlEditorTabList.module.css';
|
||||
import { SQLEditorActions } from './SQLEditorActions.js';
|
||||
import { useSqlEditor } from './useSqlEditor.js';
|
||||
import { useActiveQuery } from './useActiveQuery.js';
|
||||
|
||||
const sqlEditorRegistry: StyleRegistry = [
|
||||
[TabListStyles, { mode: 'append', styles: [SqlEditorTabList] }],
|
||||
@@ -33,6 +34,7 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
|
||||
const style = useS(styles, SqlEditorTab);
|
||||
const sqlEditorModeService = useService(SqlEditorModeService);
|
||||
const data = useSqlEditor(state);
|
||||
useActiveQuery(data);
|
||||
const [modesState] = useState(() => new MetadataMap<string, any>());
|
||||
|
||||
useMemo(() => {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 { ISQLEditorData } from './ISQLEditorData.js';
|
||||
|
||||
interface ISqlEditorStateContext {
|
||||
state: ISQLEditorData | null;
|
||||
setState: (state: ISQLEditorData | null) => void;
|
||||
}
|
||||
|
||||
export function SqlEditorStateContext(): ISqlEditorStateContext {
|
||||
return {
|
||||
state: null,
|
||||
setState(state) {
|
||||
this.state = state;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 { useService } from '@cloudbeaver/core-di';
|
||||
import type { ISQLEditorData } from './ISQLEditorData.js';
|
||||
import { SqlEditorService, type ISqlEditorActiveQueryUpdateData } from '../SqlEditorService.js';
|
||||
import { useExecutor } from '@cloudbeaver/core-blocks';
|
||||
import { useState } from 'react';
|
||||
import { ExecutorHandlersCollection } from '@cloudbeaver/core-executor';
|
||||
import { SqlEditorStateContext } from './SqlEditorStateContext.js';
|
||||
|
||||
export function useActiveQuery(state: ISQLEditorData): void {
|
||||
const [collection] = useState(() => new ExecutorHandlersCollection<ISqlEditorActiveQueryUpdateData>());
|
||||
const sqlEditorService = useService(SqlEditorService);
|
||||
|
||||
useExecutor({
|
||||
executor: collection,
|
||||
handlers: [
|
||||
function fillEditorState(data, context) {
|
||||
const sqlEditorStateContext = context.getContext(SqlEditorStateContext);
|
||||
sqlEditorStateContext.setState(state);
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
useExecutor({
|
||||
executor: sqlEditorService.updateActiveQuery,
|
||||
before: collection,
|
||||
handlers: [
|
||||
async function updateActiveQuery(data, context) {
|
||||
const sqlEditorStateContext = context.getContext(SqlEditorStateContext);
|
||||
const sqlEditorData = sqlEditorStateContext.state;
|
||||
|
||||
if (!sqlEditorData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const segment = await sqlEditorData.getResolvedSegment();
|
||||
let query = data.update.query.trim();
|
||||
|
||||
if (segment) {
|
||||
query = query.trim();
|
||||
// TODO: getResolvedSegment will return segment without end delimiter
|
||||
// we need this to avoid duplicated end delimiter
|
||||
const dialectDelimiter = sqlEditorData.dialect?.scriptDelimiter ?? '';
|
||||
const endDelimiterLength = dialectDelimiter.length;
|
||||
const endDelimiter = sqlEditorData.value.slice(segment.end, segment.end + endDelimiterLength);
|
||||
|
||||
if (endDelimiter === dialectDelimiter && query.endsWith(endDelimiter)) {
|
||||
query = query.slice(0, -endDelimiterLength);
|
||||
}
|
||||
}
|
||||
|
||||
switch (data.update.type) {
|
||||
case 'replace':
|
||||
if (segment) {
|
||||
const firstQueryPart = sqlEditorData.value.slice(0, segment.begin) + query;
|
||||
sqlEditorData.setScript(firstQueryPart + sqlEditorData.value.slice(segment.end), undefined, {
|
||||
anchor: firstQueryPart.length,
|
||||
head: firstQueryPart.length,
|
||||
});
|
||||
} else {
|
||||
sqlEditorData.setScript(query, undefined, sqlEditorData.cursor);
|
||||
}
|
||||
break;
|
||||
case 'append':
|
||||
{
|
||||
const newScript = sqlEditorData.value + query;
|
||||
sqlEditorData.setScript(newScript, undefined, { anchor: newScript.length, head: newScript.length });
|
||||
}
|
||||
break;
|
||||
case 'prepend':
|
||||
{
|
||||
const newScript = query + sqlEditorData.value;
|
||||
sqlEditorData.setScript(newScript, undefined, { anchor: 0, head: 0 });
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -404,7 +404,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
passEmpty?: boolean,
|
||||
passDisabled?: boolean,
|
||||
): Promise<T | undefined> {
|
||||
if (!segment || (this.isDisabled && !passDisabled) || (!passEmpty && this.isScriptEmpty)) {
|
||||
if (!segment || segment.end === segment.begin || (this.isDisabled && !passDisabled) || (!passEmpty && this.isScriptEmpty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -435,24 +435,35 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
const projectId = this.dataSource?.executionContext?.projectId;
|
||||
const connectionId = this.dataSource?.executionContext?.connectionId;
|
||||
|
||||
await data.updateParserScripts();
|
||||
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 (!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;
|
||||
}
|
||||
|
||||
if (this.activeSegmentMode.activeSegmentMode) {
|
||||
return this.activeSegment;
|
||||
}
|
||||
|
||||
const result = await this.sqlEditorService.parseSQLQuery(projectId, connectionId, this.value, this.cursor.anchor);
|
||||
|
||||
if (result.end === 0 && result.start === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const segment = this.parser.getSegment(result.start, result.end);
|
||||
return segment;
|
||||
},
|
||||
|
||||
getSubQuery(): ISQLScriptSegment | undefined {
|
||||
@@ -508,8 +519,6 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
executor: data.dataSource?.onSetScript,
|
||||
handlers: [
|
||||
function setScript({ script }) {
|
||||
// ensure that cursor is in script boundaries
|
||||
data.setCursor(data.cursor.anchor, data.cursor.head);
|
||||
data.parser.setScript(script);
|
||||
data.updateParserScriptsDebounced().catch(() => {});
|
||||
data.onUpdate.execute();
|
||||
|
||||
@@ -30,12 +30,22 @@ import type { ISqlEditorTabState } from './ISqlEditorTabState.js';
|
||||
import { ESqlDataSourceFeatures } from './SqlDataSource/ESqlDataSourceFeatures.js';
|
||||
import { SqlDataSourceService } from './SqlDataSource/SqlDataSourceService.js';
|
||||
import { SqlEditorSettingsService } from './SqlEditorSettingsService.js';
|
||||
import { Executor, type IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { SqlQueryService } from './SqlResultTabs/SqlQueryService.js';
|
||||
|
||||
export type SQLProposal = SqlCompletionProposal;
|
||||
|
||||
export interface ISqlEditorActiveQueryUpdateData {
|
||||
editorId: string;
|
||||
update: {
|
||||
query: string;
|
||||
type: 'replace' | 'append' | 'prepend';
|
||||
};
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SqlEditorService {
|
||||
get autoSave() {
|
||||
get autoSave(): boolean {
|
||||
return this.sqlEditorSettingsService.autoSave && !this.serverConfigResource.isFeatureEnabled(FEATURE_GIT_ID, true);
|
||||
}
|
||||
|
||||
@@ -43,6 +53,12 @@ export class SqlEditorService {
|
||||
return this.sqlEditorSettingsService.insertTableAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* This executor implemented in the useActiveQuery hook.
|
||||
* It will work only when editor is mounted in the dom.
|
||||
*/
|
||||
readonly updateActiveQuery: IExecutor<ISqlEditorActiveQueryUpdateData>;
|
||||
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly connectionsManagerService: ConnectionsManagerService,
|
||||
@@ -53,14 +69,20 @@ export class SqlEditorService {
|
||||
private readonly sqlDataSourceService: SqlDataSourceService,
|
||||
private readonly sqlEditorSettingsService: SqlEditorSettingsService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
) {}
|
||||
private readonly sqlQueryService: SqlQueryService,
|
||||
) {
|
||||
this.updateActiveQuery = new Executor();
|
||||
|
||||
getState(editorId: string, datasourceKey: string, order: number, source?: string): ISqlEditorTabState {
|
||||
this.sqlQueryService.onQueryExecution.addHandler(editorState => this.initEditorConnection(editorState));
|
||||
}
|
||||
|
||||
getState(editorId: string, datasourceKey: string, order: number, source?: string, metadata?: Record<string, any>): ISqlEditorTabState {
|
||||
return observable({
|
||||
editorId,
|
||||
datasourceKey,
|
||||
source,
|
||||
order,
|
||||
metadata,
|
||||
tabs: observable([]),
|
||||
resultGroups: observable([]),
|
||||
resultTabs: observable([]),
|
||||
|
||||
@@ -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.
|
||||
@@ -27,6 +27,7 @@ import type { IResultGroup, ISqlEditorTabState } from '../ISqlEditorTabState.js'
|
||||
import { QueryDataSource } from '../QueryDataSource.js';
|
||||
import { SqlDataSourceService } from '../SqlDataSource/SqlDataSourceService.js';
|
||||
import { SqlQueryResultService } from './SqlQueryResultService.js';
|
||||
import { Executor, type IExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
interface IQueryExecutionOptions {
|
||||
onQueryExecutionStart?: (query: string, index: number) => void;
|
||||
@@ -44,6 +45,7 @@ export interface IQueryExecutionStatistics {
|
||||
@injectable()
|
||||
export class SqlQueryService {
|
||||
private readonly statisticsMap: Map<string, IQueryExecutionStatistics>;
|
||||
readonly onQueryExecution: IExecutor<ISqlEditorTabState>;
|
||||
|
||||
constructor(
|
||||
private readonly serviceProvider: IServiceProvider,
|
||||
@@ -60,6 +62,7 @@ export class SqlQueryService {
|
||||
private readonly dataViewerSettingsService: DataViewerSettingsService,
|
||||
) {
|
||||
this.statisticsMap = new Map();
|
||||
this.onQueryExecution = new Executor();
|
||||
|
||||
makeObservable<this, 'statisticsMap'>(this, {
|
||||
statisticsMap: observable,
|
||||
@@ -111,6 +114,7 @@ export class SqlQueryService {
|
||||
}
|
||||
|
||||
async executeEditorQuery(editorState: ISqlEditorTabState, query: string, inNewTab: boolean): Promise<void> {
|
||||
await this.onQueryExecution.execute(editorState);
|
||||
const dataSource = this.sqlDataSourceService.get(editorState.editorId);
|
||||
const contextInfo = dataSource?.executionContext;
|
||||
const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id);
|
||||
@@ -179,6 +183,7 @@ export class SqlQueryService {
|
||||
}
|
||||
|
||||
async executeQueries(editorState: ISqlEditorTabState, queries: string[], options?: IQueryExecutionOptions): Promise<void> {
|
||||
await this.onQueryExecution.execute(editorState);
|
||||
const dataSource = this.sqlDataSourceService.get(editorState.editorId);
|
||||
const contextInfo = dataSource?.executionContext;
|
||||
const executionContext = contextInfo && this.connectionExecutionContextService.get(contextInfo.id);
|
||||
|
||||
@@ -33,6 +33,7 @@ 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';
|
||||
|
||||
@@ -4506,6 +4506,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@dbeaver/cli": "workspace:^"
|
||||
"@dbeaver/tsconfig": "workspace:^"
|
||||
async-mutex: "npm:^0"
|
||||
rimraf: "npm:^6"
|
||||
typescript: "npm:^5"
|
||||
vitest: "npm:^3"
|
||||
@@ -8644,6 +8645,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async-mutex@npm:^0":
|
||||
version: 0.5.0
|
||||
resolution: "async-mutex@npm:0.5.0"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10c0/9096e6ad6b674c894d8ddd5aa4c512b09bb05931b8746ebd634952b05685608b2b0820ed5c406e6569919ff5fe237ab3c491e6f2887d6da6b6ba906db3ee9c32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async@npm:^3.2.3":
|
||||
version: 3.2.6
|
||||
resolution: "async@npm:3.2.6"
|
||||
|
||||
Reference in New Issue
Block a user