mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-25 09:34:18 +08:00
dbeaver/pro#7854 feat: display query with parameters (#4064)
* dbeaver/pro#7854 feat: display query with parameters * fix: use replaceAll instead of replace * dbeaver/pro#7854 fix: support parameters and variables --------- Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
This commit is contained in:
co-authored by
Daria Marutkina
parent
1deffb984b
commit
90fcbe6b7f
@@ -1,63 +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 { Decoration, type DecorationSet, EditorView, StateEffect, StateField } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
const ACTIVE_QUERY_EFFECT_ADD = StateEffect.define<{ from: number; to: number | undefined }>({
|
||||
map: (val, mapping) => ({
|
||||
from: mapping.mapPos(val.from),
|
||||
to: val.to === undefined ? undefined : mapping.mapPos(val.to),
|
||||
}),
|
||||
});
|
||||
|
||||
const ACTIVE_QUERY_EFFECT_CLEAR = StateEffect.define();
|
||||
|
||||
const ACTIVE_QUERY_DECORATION = Decoration.mark({
|
||||
class: 'active-query',
|
||||
});
|
||||
|
||||
export const ACTIVE_QUERY_EXTENSION: StateField<DecorationSet> = StateField.define({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(value, transaction) {
|
||||
value = value.map(transaction.changes);
|
||||
|
||||
for (const effect of transaction.effects) {
|
||||
if (effect.is(ACTIVE_QUERY_EFFECT_ADD)) {
|
||||
if (effect.value.from === effect.value.to) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = value.update({
|
||||
add: [ACTIVE_QUERY_DECORATION.range(effect.value.from, effect.value.to)],
|
||||
});
|
||||
}
|
||||
|
||||
if (effect.is(ACTIVE_QUERY_EFFECT_CLEAR)) {
|
||||
value = value.update({
|
||||
filter: () => false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
provide: f => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
export function highlightActiveQuery(view: EditorView, from: number, to: number | undefined) {
|
||||
view.dispatch({
|
||||
effects: ACTIVE_QUERY_EFFECT_ADD.of({ from, to }),
|
||||
});
|
||||
}
|
||||
|
||||
export function clearActiveQueryHighlight(view: EditorView) {
|
||||
view.dispatch({
|
||||
effects: ACTIVE_QUERY_EFFECT_CLEAR.of(null),
|
||||
});
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* 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 EditorView, gutter, GutterMarker, RangeSet, StateEffect, StateField } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
type QueryGutterEffectType = 'run' | 'error';
|
||||
|
||||
const QUERY_STATUS_SIZE_MARKER = new (class extends GutterMarker {})();
|
||||
|
||||
const RUN_QUERY_MARKER = new (class extends GutterMarker {
|
||||
override toDOM(): Node {
|
||||
const span = document.createElement('div');
|
||||
span.className = 'running-query-line';
|
||||
return span;
|
||||
}
|
||||
})();
|
||||
|
||||
const ERROR_QUERY_MARKER = new (class extends GutterMarker {
|
||||
override toDOM(): Node {
|
||||
const span = document.createElement('div');
|
||||
span.className = 'running-query-error-line';
|
||||
return span;
|
||||
}
|
||||
})();
|
||||
|
||||
const QUERY_GUTTER_EFFECT = StateEffect.define<{ pos: number; on: boolean; type: QueryGutterEffectType }>({
|
||||
map: (val, mapping) => ({ pos: mapping.mapPos(val.pos), on: val.on, type: val.type }),
|
||||
});
|
||||
|
||||
const statusGutterState = StateField.define<RangeSet<GutterMarker>>({
|
||||
create() {
|
||||
return RangeSet.empty;
|
||||
},
|
||||
update(set, transaction) {
|
||||
set = set.map(transaction.changes);
|
||||
|
||||
for (const effect of transaction.effects) {
|
||||
if (effect.is(QUERY_GUTTER_EFFECT)) {
|
||||
if (effect.value.on) {
|
||||
const markers = [];
|
||||
|
||||
if (effect.value.type === 'run') {
|
||||
markers.push(RUN_QUERY_MARKER.range(effect.value.pos));
|
||||
}
|
||||
|
||||
if (effect.value.type === 'error') {
|
||||
markers.push(ERROR_QUERY_MARKER.range(effect.value.pos));
|
||||
}
|
||||
|
||||
set = set.update({ add: markers });
|
||||
} else {
|
||||
set = set.update({ filter: () => false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
},
|
||||
});
|
||||
|
||||
export function setGutter(view: EditorView, pos: number, type: QueryGutterEffectType, value: boolean) {
|
||||
view.dispatch({
|
||||
effects: QUERY_GUTTER_EFFECT.of({ pos, on: value, type }),
|
||||
});
|
||||
}
|
||||
|
||||
export const QUERY_STATUS_GUTTER_EXTENSION = [
|
||||
statusGutterState,
|
||||
gutter({
|
||||
class: 'query-status',
|
||||
markers: view => view.state.field(statusGutterState),
|
||||
initialSpacer: () => QUERY_STATUS_SIZE_MARKER,
|
||||
}),
|
||||
];
|
||||
@@ -1,17 +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 { observer } from 'mobx-react-lite';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { EditorLoader, type IDefaultExtensions, type IEditorProps, type IEditorRef } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
export const SQLCodeEditor = observer<IEditorProps & IDefaultExtensions, IEditorRef>(
|
||||
forwardRef(function SQLCodeEditor(props, ref) {
|
||||
return <EditorLoader {...props} ref={ref} />;
|
||||
}),
|
||||
);
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
* 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 { importLazyComponent } from '@cloudbeaver/core-blocks';
|
||||
|
||||
export const SQLCodeEditorLoader = importLazyComponent(() => import('./SQLCodeEditor.js').then(m => m.SQLCodeEditor));
|
||||
@@ -1,74 +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 { observable } from 'mobx';
|
||||
|
||||
import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import type { EditorState, EditorView, IEditorRef } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
import { clearActiveQueryHighlight, highlightActiveQuery } from '../ACTIVE_QUERY_EXTENSION.js';
|
||||
import { setGutter } from '../QUERY_STATUS_GUTTER_EXTENSION.js';
|
||||
|
||||
export interface IEditor {
|
||||
readonly view: EditorView | null;
|
||||
readonly state: EditorState | null;
|
||||
highlightActiveQuery: (from: number, to?: number) => void;
|
||||
clearActiveQueryHighlight: () => void;
|
||||
highlightExecutingLine: (line: number, state: boolean) => void;
|
||||
highlightExecutingErrorLine: (line: number, state: boolean) => void;
|
||||
resetLineStateHighlight: () => void;
|
||||
}
|
||||
|
||||
export function useSQLCodeEditor(editorRef: IEditorRef | null) {
|
||||
const state: IEditor = useObservableRef(
|
||||
() => ({
|
||||
get view() {
|
||||
return this.editorRef?.view ?? null;
|
||||
},
|
||||
get state() {
|
||||
return this.editorRef?.view?.state ?? null;
|
||||
},
|
||||
highlightActiveQuery(from: number, to?: number) {
|
||||
if (!this.view) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the 'to' parameter is provided, ensure it doesn't exceed the document length
|
||||
if (to && to > this.view.state.doc.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
highlightActiveQuery(this.view, from, to);
|
||||
},
|
||||
clearActiveQueryHighlight() {
|
||||
if (this.view) {
|
||||
clearActiveQueryHighlight(this.view);
|
||||
}
|
||||
},
|
||||
highlightExecutingLine(line: number, state: boolean) {
|
||||
if (this.view) {
|
||||
setGutter(this.view, line, 'run', state);
|
||||
}
|
||||
},
|
||||
highlightExecutingErrorLine(line: number, state: boolean) {
|
||||
if (this.view) {
|
||||
setGutter(this.view, line, 'error', state);
|
||||
}
|
||||
},
|
||||
resetLineStateHighlight() {
|
||||
if (this.view) {
|
||||
setGutter(this.view, 0, 'run', false);
|
||||
setGutter(this.view, 0, 'error', false);
|
||||
}
|
||||
},
|
||||
}),
|
||||
{ editorRef: observable.ref },
|
||||
{ editorRef },
|
||||
);
|
||||
|
||||
return state;
|
||||
}
|
||||
+10
-8
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,13 +16,15 @@ import { type TabContainerPanelComponent, useDNDBox } from '@cloudbeaver/core-ui
|
||||
import { closeCompletion, type IEditorRef, Prec, ReactCodemirrorPanel, useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6';
|
||||
import { SqlEditorSettingsService, type ISqlEditorModeProps } from '@cloudbeaver/plugin-sql-editor';
|
||||
|
||||
import { ACTIVE_QUERY_EXTENSION } from '../ACTIVE_QUERY_EXTENSION.js';
|
||||
import { QUERY_STATUS_GUTTER_EXTENSION } from '../QUERY_STATUS_GUTTER_EXTENSION.js';
|
||||
import { SQLCodeEditorLoader } from '../SQLCodeEditor/SQLCodeEditorLoader.js';
|
||||
import { useSQLCodeEditor } from '../SQLCodeEditor/useSQLCodeEditor.js';
|
||||
import {
|
||||
ACTIVE_QUERY_EXTENSION,
|
||||
QUERY_STATUS_GUTTER_EXTENSION,
|
||||
SQLCodeEditor,
|
||||
useSQLCodeEditor,
|
||||
useSqlDialectExtension,
|
||||
} from '@cloudbeaver/plugin-sql-editor-codemirror';
|
||||
import { useHighlightExtensions } from '../useHighlightExtensions.js';
|
||||
import { useSqlDialectAutocompletion } from '../useSqlDialectAutocompletion.js';
|
||||
import { useSqlDialectExtension } from '../useSqlDialectExtension.js';
|
||||
import style from './SQLCodeEditorPanel.module.css';
|
||||
import { SqlEditorInfoBar } from './SqlEditorInfoBar.js';
|
||||
import { useSQLCodeEditorPanel } from './useSQLCodeEditorPanel.js';
|
||||
@@ -109,7 +111,7 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
|
||||
|
||||
return (
|
||||
<div ref={dndBox.setRef} className={styles['box']}>
|
||||
<SQLCodeEditorLoader
|
||||
<SQLCodeEditor
|
||||
ref={setEditorRef}
|
||||
getValue={() => data.value}
|
||||
cursor={{
|
||||
@@ -143,7 +145,7 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent<ISqlEditorModeProps>
|
||||
<SqlEditorInfoBar state={editor.state} />
|
||||
</ReactCodemirrorPanel>
|
||||
)}
|
||||
</SQLCodeEditorLoader>
|
||||
</SQLCodeEditor>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -10,13 +10,14 @@ import { useCallback } from 'react';
|
||||
|
||||
import { useExecutor, useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { throttle } from '@cloudbeaver/core-utils';
|
||||
import type { ISqlEditorCursor, ISQLEditorData } from '@cloudbeaver/plugin-sql-editor';
|
||||
import type { ISQLEditorData } from '@cloudbeaver/plugin-sql-editor';
|
||||
|
||||
import type { IEditor } from '../SQLCodeEditor/useSQLCodeEditor.js';
|
||||
import type { IEditor } from '@cloudbeaver/plugin-sql-editor-codemirror';
|
||||
import type { SelectionRange } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
interface State {
|
||||
highlightActiveQuery: () => void;
|
||||
onQueryChange: (query: string, selection: ISqlEditorCursor) => void;
|
||||
onQueryChange: (query: string, selection: SelectionRange) => void;
|
||||
onCursorChange: (anchor: number, head?: number) => void;
|
||||
}
|
||||
|
||||
@@ -34,9 +35,8 @@ export function useSQLCodeEditorPanel(data: ISQLEditorData, editor: IEditor) {
|
||||
this.editor.highlightActiveQuery(segment.begin, segment.end);
|
||||
}
|
||||
},
|
||||
onQueryChange(query: string, selection: ISqlEditorCursor) {
|
||||
this.data.setScript(query, ON_QUERY_CHANGE_SOURCE, selection);
|
||||
this.onCursorChange(selection.anchor, selection.head);
|
||||
onQueryChange(query: string, selection: SelectionRange) {
|
||||
this.data.setScript(query, ON_QUERY_CHANGE_SOURCE, { anchor: selection.anchor, head: selection.head }); // do not pass selection directly because anchor and head are getters
|
||||
},
|
||||
onCursorChange(anchor: number, head?: number) {
|
||||
this.data.setCursor(anchor, head);
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
|
||||
import { createLazyLoader, useLazyImport } from '@cloudbeaver/core-blocks';
|
||||
import type { SqlDialectInfo } from '@cloudbeaver/core-sdk';
|
||||
import type { Compartment, Extension } from '@cloudbeaver/plugin-codemirror6';
|
||||
|
||||
const codemirrorPluginLoader = createLazyLoader(() => import('@cloudbeaver/plugin-codemirror6'));
|
||||
|
||||
export function useSqlDialectExtension(dialectInfo: SqlDialectInfo | undefined): [Compartment, Extension] | null {
|
||||
const codemirror = useLazyImport(codemirrorPluginLoader);
|
||||
const loader = getDialectLoader(codemirror, dialectInfo?.name);
|
||||
const dialect = useLazyImport(loader);
|
||||
const SQL_EDITOR_COMPARTMENT = useMemo(() => {
|
||||
if (!codemirror) {
|
||||
return null;
|
||||
}
|
||||
return new codemirror.Compartment();
|
||||
}, [codemirror]);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!dialect || !codemirror || !SQL_EDITOR_COMPARTMENT) {
|
||||
return null;
|
||||
}
|
||||
let dialectInner = dialect;
|
||||
|
||||
if (dialectInfo) {
|
||||
dialectInner = codemirror.SQLDialect.define({
|
||||
keywords: dialectInfo.reservedWords.join(' ').toLowerCase(),
|
||||
builtin: dialectInfo.functions.join(' ').toUpperCase(),
|
||||
types: dialectInfo.dataTypes.join(' ').toUpperCase(),
|
||||
|
||||
hashComments: dialectInfo.singleLineComments.includes('#'),
|
||||
slashComments: dialectInfo.singleLineComments.includes('//'),
|
||||
doubleDollarQuotedStrings: dialectInfo.quoteStrings.some(v => v.includes('$$')),
|
||||
doubleQuotedStrings: dialectInfo.quoteStrings.some(v => v.includes('"')),
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
SQL_EDITOR_COMPARTMENT,
|
||||
codemirror.SQL_EDITOR({
|
||||
dialect: dialectInner,
|
||||
}),
|
||||
];
|
||||
}, [SQL_EDITOR_COMPARTMENT, codemirror, dialect, dialectInfo]);
|
||||
}
|
||||
|
||||
const emptyDialectLoader = createLazyLoader(() => Promise.resolve(null));
|
||||
|
||||
function getDialectLoader(plugin: typeof import('@cloudbeaver/plugin-codemirror6') | null, name?: string) {
|
||||
if (!plugin) {
|
||||
return emptyDialectLoader;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case 'PostgreSQL':
|
||||
return plugin.PostgreSQLLoader;
|
||||
case 'MySQL':
|
||||
return plugin.MySQLLoader;
|
||||
case 'MariaSQL':
|
||||
return plugin.MariaSQLLoader;
|
||||
case 'SQLServer':
|
||||
return plugin.MSSQLLoader;
|
||||
case 'SQLite':
|
||||
return plugin.SQLiteLoader;
|
||||
case 'CQL':
|
||||
return plugin.CassandraLoader;
|
||||
case 'PLSQL':
|
||||
return plugin.PLSQLLoader;
|
||||
default:
|
||||
return plugin.StandardSQLLoader;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2026 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -8,6 +8,4 @@
|
||||
|
||||
import './module.js';
|
||||
|
||||
export * from './SQLEditor/SQLCodeEditor/SQLCodeEditorLoader.js';
|
||||
export * from './SQLEditor/useSqlDialectAutocompletion.js';
|
||||
export * from './SQLEditor/useSqlDialectExtension.js';
|
||||
|
||||
Reference in New Issue
Block a user