mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-4348 fix: parse sql script (#2212)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
Evgenia Bezborodova
parent
6c456c8a63
commit
5779100b9a
@@ -5,6 +5,7 @@
|
||||
margin: 2px !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import type React from 'react';
|
||||
import { Button, ButtonProps } from 'reakit/Button';
|
||||
import { ButtonProps, Button as ReakitButton } from 'reakit/Button';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
@@ -20,6 +20,7 @@ import { useS } from './useS';
|
||||
import { useStyles } from './useStyles';
|
||||
|
||||
interface Props {
|
||||
tag?: 'button' | 'a' | 'div';
|
||||
name: string;
|
||||
img?: boolean;
|
||||
viewBox?: string;
|
||||
@@ -28,9 +29,11 @@ interface Props {
|
||||
|
||||
export type IconButtonProps = Props & ButtonProps;
|
||||
|
||||
export const IconButton: React.FC<IconButtonProps> = observer(function IconButton({ name, img, viewBox, style, className, ...rest }) {
|
||||
export const IconButton: React.FC<IconButtonProps> = observer(function IconButton({ tag, name, img, viewBox, style, className, ...rest }) {
|
||||
const styles = useS(IconButtonStyles);
|
||||
|
||||
const Button = tag ?? ReakitButton;
|
||||
|
||||
return styled(useStyles(style))(
|
||||
<Button {...rest} className={s(styles, { iconButton: true }, className)}>
|
||||
{img && <StaticImage className={s(styles, { staticImage: true })} icon={name} />}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export function useSQLCodeEditorPanel(data: ISQLEditorData, editor: IEditor) {
|
||||
}
|
||||
},
|
||||
onQueryChange(query: string) {
|
||||
this.data.setQuery(query);
|
||||
this.data.setScript(query);
|
||||
},
|
||||
onUpdate(update: ViewUpdate) {
|
||||
const transactions = update.transactions.filter(t => t.selection !== undefined);
|
||||
|
||||
@@ -29,7 +29,6 @@ export interface ISQLScriptLine {
|
||||
|
||||
export class SQLParser {
|
||||
get scripts(): ISQLScriptSegment[] {
|
||||
this.update();
|
||||
return this._scripts;
|
||||
}
|
||||
|
||||
@@ -39,30 +38,25 @@ export class SQLParser {
|
||||
|
||||
private _scripts: ISQLScriptSegment[];
|
||||
private script: string;
|
||||
private parsedScript: string | null;
|
||||
|
||||
constructor() {
|
||||
this._scripts = [];
|
||||
this.script = '';
|
||||
this.parsedScript = null;
|
||||
|
||||
makeObservable<this, '_scripts' | 'script' | 'parsedScript' | 'update'>(this, {
|
||||
makeObservable<this, '_scripts' | 'script'>(this, {
|
||||
actualScript: computed,
|
||||
_scripts: observable.ref,
|
||||
script: observable.ref,
|
||||
parsedScript: observable.ref,
|
||||
getScriptSegment: action,
|
||||
getSegment: action,
|
||||
getQueryAtPos: action,
|
||||
setScript: action,
|
||||
parse: action,
|
||||
setQueries: action,
|
||||
update: action,
|
||||
});
|
||||
}
|
||||
|
||||
getScriptSegment(): ISQLScriptSegment {
|
||||
const script = this.parsedScript || '';
|
||||
const script = this.script || '';
|
||||
|
||||
return {
|
||||
query: script,
|
||||
@@ -80,10 +74,8 @@ export class SQLParser {
|
||||
end = begin;
|
||||
}
|
||||
|
||||
this.update();
|
||||
|
||||
return {
|
||||
query: (this.parsedScript || '').substring(begin, end),
|
||||
query: (this.script || '').substring(begin, end),
|
||||
begin,
|
||||
end,
|
||||
};
|
||||
@@ -107,15 +99,9 @@ export class SQLParser {
|
||||
|
||||
setScript(script: string): void {
|
||||
this.script = script;
|
||||
this.update();
|
||||
}
|
||||
|
||||
parse(script: string): void {
|
||||
this.parsedScript = script;
|
||||
}
|
||||
|
||||
setQueries(queries: IQueryInfo[]): this {
|
||||
this.update();
|
||||
this._scripts = queries.map<ISQLScriptSegment>(query => ({
|
||||
query: this.script.substring(query.start, query.end),
|
||||
begin: query.start,
|
||||
@@ -124,10 +110,4 @@ export class SQLParser {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private update() {
|
||||
if (this.parsedScript !== this.script) {
|
||||
this.parse(this.script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { action, computed, makeObservable, observable, toJS } from 'mobx';
|
||||
|
||||
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import { isContainsException, isObjectsEqual, isValuesEqual, staticImplements } from '@cloudbeaver/core-utils';
|
||||
import { isContainsException, isValuesEqual, staticImplements } from '@cloudbeaver/core-utils';
|
||||
import type { IDatabaseDataModel, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import type { IDataQueryOptions } from '../QueryDataSource';
|
||||
|
||||
@@ -19,13 +19,13 @@ export interface ISegmentExecutionData {
|
||||
}
|
||||
|
||||
export interface ICursor {
|
||||
begin: number;
|
||||
end: number;
|
||||
readonly begin: number;
|
||||
readonly end: number;
|
||||
}
|
||||
|
||||
export interface ISQLEditorData {
|
||||
readonly cursor: ICursor;
|
||||
readonly activeSegmentMode: ISQLEditorMode;
|
||||
activeSegmentMode: ISQLEditorMode;
|
||||
readonly parser: SQLParser;
|
||||
readonly dialect: SqlDialectInfo | undefined;
|
||||
readonly activeSegment: ISQLScriptSegment | undefined;
|
||||
@@ -48,7 +48,7 @@ export interface ISQLEditorData {
|
||||
readonly hintsLimitIsMet: boolean;
|
||||
|
||||
updateParserScriptsThrottle(): Promise<void>;
|
||||
setQuery(query: string): void;
|
||||
setScript(query: string): void;
|
||||
init(): void;
|
||||
destruct(): void;
|
||||
setCursor(begin: number, end?: number): void;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
.container {
|
||||
composes: theme-border-color-background from global;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
border-right: solid 1px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
|
||||
&:empty {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { ActionIconButton, getComputed, preventFocusHandler, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures';
|
||||
import type { ISQLEditorData } from './ISQLEditorData';
|
||||
import style from './SQLEditorActions.m.css';
|
||||
import { SqlEditorActionsMenu } from './SqlEditorActionsMenu';
|
||||
import { SqlEditorTools } from './SqlEditorTools';
|
||||
|
||||
interface Props {
|
||||
data: ISQLEditorData;
|
||||
state: ISqlEditorTabState;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const SQLEditorActions = observer<Props>(function SQLEditorActions({ data, state }) {
|
||||
const styles = useS(style);
|
||||
const translate = useTranslate();
|
||||
const isActiveSegmentMode = getComputed(() => data.activeSegmentMode.activeSegmentMode);
|
||||
const disabled = getComputed(() => data.isLineScriptEmpty || data.isDisabled);
|
||||
const isQuery = data.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = data.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
return (
|
||||
<div className={s(styles, { container: true })}>
|
||||
<div className={s(styles, { actions: true })} onMouseDown={preventFocusHandler}>
|
||||
{isExecutable && (
|
||||
<>
|
||||
{isQuery && (
|
||||
<>
|
||||
<ActionIconButton
|
||||
name="/icons/sql_exec.svg"
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_button_tooltip')}
|
||||
img
|
||||
onClick={data.executeQuery}
|
||||
/>
|
||||
<ActionIconButton
|
||||
name="/icons/sql_exec_new.svg"
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_new_tab_button_tooltip')}
|
||||
img
|
||||
onClick={data.executeQueryNewTab}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<ActionIconButton
|
||||
name="/icons/sql_script_exec.svg"
|
||||
disabled={disabled}
|
||||
hidden={isActiveSegmentMode}
|
||||
title={translate('sql_editor_sql_execution_script_button_tooltip')}
|
||||
img
|
||||
onClick={data.executeScript}
|
||||
/>
|
||||
{isQuery && data.dialect?.supportsExplainExecutionPlan && (
|
||||
<ActionIconButton
|
||||
name="/icons/sql_execution_plan.svg"
|
||||
disabled={disabled}
|
||||
hidden={isActiveSegmentMode}
|
||||
title={translate('sql_editor_execution_plan_button_tooltip')}
|
||||
img
|
||||
onClick={data.showExecutionPlan}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<SqlEditorActionsMenu state={state} />
|
||||
</div>
|
||||
<SqlEditorTools data={data} state={state} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -17,7 +17,9 @@ export interface ISQLEditorMode {
|
||||
|
||||
export const SQLEditorModeContext: ISyncContextLoader<ISQLEditorMode, ISQLEditorData> = function SQLEditorModeContext(context, data) {
|
||||
return {
|
||||
activeSegment: data.parser.getSegment(data.cursor.begin, data.cursor.end),
|
||||
get activeSegment() {
|
||||
return data.parser.getSegment(data.cursor.begin, data.cursor.end);
|
||||
},
|
||||
activeSegmentMode: false,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,25 +9,19 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { getComputed, preventFocusHandler, StaticImage, useSplit, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, useSplit } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { BASE_TAB_STYLES, ITabData, TabList, TabPanelList, TabsState, VERTICAL_ROTATED_TAB_STYLES } from '@cloudbeaver/core-ui';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
import { useCaptureViewContext } from '@cloudbeaver/core-view';
|
||||
|
||||
import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures';
|
||||
import { ISqlEditorModeProps, SqlEditorModeService } from '../SqlEditorModeService';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_DATA } from './DATA_CONTEXT_SQL_EDITOR_DATA';
|
||||
import type { ISqlEditorProps } from './ISqlEditorProps';
|
||||
import { SqlEditorActionsMenu } from './SqlEditorActionsMenu';
|
||||
import { SqlEditorTools } from './SqlEditorTools';
|
||||
import { SQLEditorActions } from './SQLEditorActions';
|
||||
import { useSqlEditor } from './useSqlEditor';
|
||||
|
||||
const styles = css`
|
||||
button,
|
||||
upload {
|
||||
composes: theme-ripple from global;
|
||||
}
|
||||
sql-editor {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
@@ -36,47 +30,6 @@ const styles = css`
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
container {
|
||||
composes: theme-border-color-background from global;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
border-right: solid 1px;
|
||||
}
|
||||
|
||||
actions {
|
||||
width: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
|
||||
&:empty {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
button,
|
||||
upload {
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
StaticImage {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const tabStyles = css`
|
||||
@@ -104,7 +57,6 @@ const tabStyles = css`
|
||||
const tabListStyles = [BASE_TAB_STYLES, VERTICAL_ROTATED_TAB_STYLES, tabStyles];
|
||||
|
||||
export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, className }) {
|
||||
const translate = useTranslate();
|
||||
const split = useSplit();
|
||||
const sqlEditorModeService = useService(SqlEditorModeService);
|
||||
const data = useSqlEditor(state);
|
||||
@@ -122,8 +74,6 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
|
||||
state.currentModeId = tab.tabId;
|
||||
}
|
||||
|
||||
const disabled = getComputed(() => data.isLineScriptEmpty || data.isDisabled);
|
||||
const isActiveSegmentMode = getComputed(() => data.activeSegmentMode.activeSegmentMode);
|
||||
const displayedEditors = getComputed(() => sqlEditorModeService.tabsContainer.getDisplayed({ state, data }).length);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -135,9 +85,6 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
|
||||
}
|
||||
});
|
||||
|
||||
const isQuery = data.dataSource?.hasFeature(ESqlDataSourceFeatures.query);
|
||||
const isExecutable = data.dataSource?.hasFeature(ESqlDataSourceFeatures.executable);
|
||||
|
||||
return styled(
|
||||
styles,
|
||||
BASE_TAB_STYLES,
|
||||
@@ -154,43 +101,7 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
|
||||
onChange={handleModeSelect}
|
||||
>
|
||||
<sql-editor className={className}>
|
||||
<container>
|
||||
<actions onMouseDown={preventFocusHandler}>
|
||||
{isExecutable && (
|
||||
<>
|
||||
{isQuery && (
|
||||
<>
|
||||
<button disabled={disabled} title={translate('sql_editor_sql_execution_button_tooltip')} onClick={data.executeQuery}>
|
||||
<StaticImage icon="/icons/sql_exec.svg" />
|
||||
</button>
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_new_tab_button_tooltip')}
|
||||
onClick={data.executeQueryNewTab}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_exec_new.svg" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
disabled={disabled}
|
||||
title={translate('sql_editor_sql_execution_script_button_tooltip')}
|
||||
hidden={isActiveSegmentMode}
|
||||
onClick={data.executeScript}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_script_exec.svg" />
|
||||
</button>
|
||||
{isQuery && data.dialect?.supportsExplainExecutionPlan && (
|
||||
<button disabled={disabled} title={translate('sql_editor_execution_plan_button_tooltip')} onClick={data.showExecutionPlan}>
|
||||
<StaticImage icon="/icons/sql_execution_plan.svg" />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<SqlEditorActionsMenu state={state} />
|
||||
</actions>
|
||||
<SqlEditorTools data={data} state={state} style={styles} />
|
||||
</container>
|
||||
<SQLEditorActions data={data} state={state} />
|
||||
<TabPanelList />
|
||||
{displayedEditors > 1 ? (
|
||||
<tabs>
|
||||
|
||||
@@ -41,12 +41,12 @@ const registry: StyleRegistry = [
|
||||
];
|
||||
|
||||
export const SqlEditorActionsMenu = observer<Props>(function SqlEditorActionsMenu({ state, context, className }) {
|
||||
const styles = useS(SqlEditorActionsMenuBarStyles, SqlEditorActionsMenuBarItemStyles);
|
||||
const menuBarStyles = useS(SqlEditorActionsMenuBarStyles, SqlEditorActionsMenuBarItemStyles, MenuBarStyles, MenuBarItemStyles);
|
||||
const menu = useMenu({ menu: SQL_EDITOR_ACTIONS_MENU, context });
|
||||
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
<MenuBar menu={menu} className={s(styles, { sqlActions: true }, className)} />
|
||||
<MenuBar menu={menu} className={s(menuBarStyles, { toolsMenu: true, floating: true, sqlActions: true }, className)} />
|
||||
</SContext>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
.sqlActions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -5,5 +12,4 @@
|
||||
|
||||
.sqlActions.menuBar {
|
||||
height: unset;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
.sqlActions .menuBarItem {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.sqlActions .menuBarItemLabel {
|
||||
display: none;
|
||||
.toolsMenu .menuBarItem {
|
||||
& .menuBarItemLabel {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
.tools {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -6,39 +6,25 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { getComputed, preventFocusHandler, StaticImage, UploadArea, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import { ActionIconButton, getComputed, preventFocusHandler, s, UploadArea, useS, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { ESqlDataSourceFeatures } from '../SqlDataSource/ESqlDataSourceFeatures';
|
||||
import type { ISQLEditorData } from './ISQLEditorData';
|
||||
import style from './SqlEditorTools.m.css';
|
||||
import { SqlEditorToolsMenu } from './SqlEditorToolsMenu';
|
||||
import { useTools } from './useTools';
|
||||
|
||||
const styles = css`
|
||||
tools {
|
||||
width: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
data: ISQLEditorData;
|
||||
state: ISqlEditorTabState;
|
||||
style?: ComponentStyle;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const SqlEditorTools = observer<Props>(function SqlEditorTools({ data, state, style, className }) {
|
||||
export const SqlEditorTools = observer<Props>(function SqlEditorTools({ data, state, className }) {
|
||||
const translate = useTranslate();
|
||||
const styles = useS(style);
|
||||
const tools = useTools(state);
|
||||
const scriptEmpty = getComputed(() => data.value.length === 0);
|
||||
const disabled = getComputed(() => data.isDisabled || data.isScriptEmpty);
|
||||
@@ -55,7 +41,7 @@ export const SqlEditorTools = observer<Props>(function SqlEditorTools({ data, st
|
||||
const script = await tools.tryReadScript(file, prevScript);
|
||||
|
||||
if (script) {
|
||||
data.setQuery(script);
|
||||
data.setScript(script);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,27 +51,27 @@ export const SqlEditorTools = observer<Props>(function SqlEditorTools({ data, st
|
||||
|
||||
const isScript = data.dataSource?.hasFeature(ESqlDataSourceFeatures.script);
|
||||
|
||||
return styled(useStyles(style, styles))(
|
||||
<tools className={className} onMouseDown={preventFocusHandler}>
|
||||
<SqlEditorToolsMenu state={state} />
|
||||
return (
|
||||
<div className={s(styles, { tools: true }, className)} onMouseDown={preventFocusHandler}>
|
||||
<SqlEditorToolsMenu state={state} data={data} />
|
||||
{isScript && (
|
||||
<>
|
||||
<button
|
||||
<ActionIconButton
|
||||
name="/icons/sql_format_sm.svg"
|
||||
disabled={disabled || data.readonly}
|
||||
title={translate('sql_editor_sql_format_button_tooltip')}
|
||||
hidden={isActiveSegmentMode}
|
||||
img
|
||||
onClick={data.formatScript}
|
||||
>
|
||||
<StaticImage icon="/icons/sql_format_sm.svg" />
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<ActionIconButton
|
||||
name="/icons/export.svg"
|
||||
disabled={scriptEmpty}
|
||||
title={translate('sql_editor_download_script_tooltip')}
|
||||
hidden={isActiveSegmentMode}
|
||||
img
|
||||
onClick={downloadScriptHandler}
|
||||
>
|
||||
<StaticImage icon="/icons/export.svg" />
|
||||
</button>
|
||||
/>
|
||||
{!isActiveSegmentMode && (
|
||||
<UploadArea
|
||||
accept=".sql"
|
||||
@@ -94,20 +80,11 @@ export const SqlEditorTools = observer<Props>(function SqlEditorTools({ data, st
|
||||
reset
|
||||
onChange={handleScriptUpload}
|
||||
>
|
||||
<upload disabled={data.readonly}>
|
||||
<StaticImage icon="/icons/import.svg" />
|
||||
</upload>
|
||||
<ActionIconButton tag="div" name="/icons/import.svg" disabled={data.readonly} img />
|
||||
</UploadArea>
|
||||
)}
|
||||
{/*<button
|
||||
title={translate('sql_editor_sql_execution_script_lock_tooltip')}
|
||||
hidden={data.dataSource?.isReadonly() ?? true}
|
||||
onClick={data.switchEditing}
|
||||
>
|
||||
<StaticImage icon={data.editing ? '/icons/sql_unlock_sm.svg' : '/icons/sql_lock_sm.svg'} />
|
||||
</button>*/}
|
||||
</>
|
||||
)}
|
||||
</tools>,
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,11 +14,14 @@ import { useMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE } from '../DATA_CONTEXT_SQL_EDITOR_STATE';
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_DATA } from './DATA_CONTEXT_SQL_EDITOR_DATA';
|
||||
import type { ISQLEditorData } from './ISQLEditorData';
|
||||
import { SQL_EDITOR_TOOLS_MENU } from './SQL_EDITOR_TOOLS_MENU';
|
||||
import SqlEditorActionsMenuBarStyles from './SqlEditorActionsMenuBar.m.css';
|
||||
import SqlEditorActionsMenuBarItemStyles from './SqlEditorActionsMenuBarItem.m.css';
|
||||
|
||||
interface Props {
|
||||
data: ISQLEditorData;
|
||||
state: ISqlEditorTabState;
|
||||
context?: IDataContext;
|
||||
className?: string;
|
||||
@@ -40,14 +43,15 @@ const registry: StyleRegistry = [
|
||||
},
|
||||
],
|
||||
];
|
||||
export const SqlEditorToolsMenu = observer<Props>(function SqlEditorToolsMenu({ state, context, className }) {
|
||||
const styles = useS(SqlEditorActionsMenuBarStyles, SqlEditorActionsMenuBarItemStyles);
|
||||
export const SqlEditorToolsMenu = observer<Props>(function SqlEditorToolsMenu({ data, state, context, className }) {
|
||||
const menuBarStyles = useS(SqlEditorActionsMenuBarStyles, SqlEditorActionsMenuBarItemStyles, MenuBarStyles, MenuBarItemStyles);
|
||||
const menu = useMenu({ menu: SQL_EDITOR_TOOLS_MENU, context });
|
||||
menu.context.set(DATA_CONTEXT_SQL_EDITOR_STATE, state);
|
||||
context?.set(DATA_CONTEXT_SQL_EDITOR_DATA, data);
|
||||
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
<MenuBar menu={menu} className={s(styles, { sqlActions: true }, className)} />
|
||||
<MenuBar menu={menu} className={s(menuBarStyles, { sqlActions: true, floating: true }, className)} />
|
||||
</SContext>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ import { OUTPUT_LOGS_TAB_ID } from '../SqlResultTabs/OutputLogs/OUTPUT_LOGS_TAB_
|
||||
import { SqlQueryService } from '../SqlResultTabs/SqlQueryService';
|
||||
import { SqlResultTabsService } from '../SqlResultTabs/SqlResultTabsService';
|
||||
import type { ICursor, ISQLEditorData } from './ISQLEditorData';
|
||||
import { ISQLEditorMode, SQLEditorModeContext } from './SQLEditorModeContext';
|
||||
import { SQLEditorModeContext } from './SQLEditorModeContext';
|
||||
|
||||
interface ISQLEditorDataPrivate extends ISQLEditorData {
|
||||
readonly sqlDialectInfoService: SqlDialectInfoService;
|
||||
@@ -83,11 +83,9 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
return this.sqlDialectInfoService.getDialectInfo(createConnectionParam(executionContext.projectId, executionContext.connectionId));
|
||||
},
|
||||
|
||||
get activeSegmentMode(): ISQLEditorMode {
|
||||
const contexts = this.onMode.execute(this);
|
||||
const mode = contexts.getContext(SQLEditorModeContext);
|
||||
|
||||
return mode;
|
||||
activeSegmentMode: {
|
||||
activeSegment: undefined,
|
||||
activeSegmentMode: false,
|
||||
},
|
||||
|
||||
get activeSegment(): ISQLScriptSegment | undefined {
|
||||
@@ -228,7 +226,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
const formatted = await this.sqlDialectInfoService.formatScript(this.dataSource.executionContext, script.query);
|
||||
|
||||
this.onFormat.execute([script, formatted]);
|
||||
this.setQuery(query.substring(0, script.begin) + formatted + query.substring(script.end));
|
||||
this.setScript(query.substring(0, script.begin) + formatted + query.substring(script.end));
|
||||
} finally {
|
||||
this.readonlyState = false;
|
||||
}
|
||||
@@ -361,9 +359,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
}
|
||||
},
|
||||
|
||||
setQuery(query: string): void {
|
||||
this.sqlEditorService.setQuery(query, this.state);
|
||||
this.updateParserScriptsThrottle().catch(() => {});
|
||||
setScript(query: string): void {
|
||||
this.dataSource?.setScript(query);
|
||||
},
|
||||
|
||||
updateParserScriptsThrottle: throttleAsync(async function updateParserScriptsThrottle() {
|
||||
@@ -378,6 +375,8 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
const script = this.parser.actualScript;
|
||||
|
||||
if (!connectionId || !script) {
|
||||
this.parser.setQueries([]);
|
||||
this.onUpdate.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -390,8 +389,10 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
}
|
||||
});
|
||||
|
||||
// check if script was changed while we were waiting for response
|
||||
if (this.parser.actualScript === script) {
|
||||
this.parser.setQueries(queries);
|
||||
this.onUpdate.execute();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -470,12 +471,12 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
executeScript: action.bound,
|
||||
switchEditing: action.bound,
|
||||
dialect: computed,
|
||||
isLineScriptEmpty: computed,
|
||||
isDisabled: computed,
|
||||
value: computed,
|
||||
readonly: computed,
|
||||
activeSegmentMode: observable.ref,
|
||||
hintsLimitIsMet: observable.ref,
|
||||
cursor: observable,
|
||||
cursor: observable.ref,
|
||||
readonlyState: observable,
|
||||
executingScript: observable,
|
||||
},
|
||||
@@ -499,6 +500,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
handlers: [
|
||||
function setScript({ script }) {
|
||||
data.parser.setScript(script);
|
||||
data.updateParserScriptsThrottle().catch(() => {});
|
||||
data.onUpdate.execute();
|
||||
},
|
||||
],
|
||||
@@ -513,6 +515,20 @@ 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(() => () => data.destruct(), []);
|
||||
|
||||
return data;
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
} from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
|
||||
import { FEATURE_GIT_ID, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { GraphQLService, SqlCompletionProposal, SqlScriptInfoFragment } from '@cloudbeaver/core-sdk';
|
||||
@@ -34,20 +33,12 @@ import { SqlEditorSettingsService } from './SqlEditorSettingsService';
|
||||
|
||||
export type SQLProposal = SqlCompletionProposal;
|
||||
|
||||
export interface IQueryChangeData {
|
||||
prevQuery: string;
|
||||
query: string;
|
||||
state: ISqlEditorTabState;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SqlEditorService {
|
||||
get autoSave() {
|
||||
return this.sqlEditorSettingsService.settings.getValue('autoSave') && !this.serverConfigResource.isFeatureEnabled(FEATURE_GIT_ID, true);
|
||||
}
|
||||
|
||||
readonly onQueryChange: ISyncExecutor<IQueryChangeData>;
|
||||
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly connectionsManagerService: ConnectionsManagerService,
|
||||
@@ -58,9 +49,7 @@ export class SqlEditorService {
|
||||
private readonly sqlDataSourceService: SqlDataSourceService,
|
||||
private readonly sqlEditorSettingsService: SqlEditorSettingsService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
) {
|
||||
this.onQueryChange = new SyncExecutor();
|
||||
}
|
||||
) {}
|
||||
|
||||
getState(editorId: string, datasourceKey: string, order: number, source?: string): ISqlEditorTabState {
|
||||
return observable({
|
||||
@@ -138,15 +127,10 @@ export class SqlEditorService {
|
||||
}
|
||||
}
|
||||
|
||||
setQuery(query: string, state: ISqlEditorTabState) {
|
||||
setScript(script: string, state: ISqlEditorTabState) {
|
||||
const dataSource = this.sqlDataSourceService.get(state.editorId);
|
||||
|
||||
if (dataSource) {
|
||||
const prevQuery = dataSource.script;
|
||||
|
||||
dataSource.setScript(query);
|
||||
this.onQueryChange.execute({ prevQuery, query, state });
|
||||
}
|
||||
dataSource!.setScript(script);
|
||||
}
|
||||
|
||||
async resetExecutionContext(state: ISqlEditorTabState) {
|
||||
|
||||
Reference in New Issue
Block a user