4551 generate full ddl for postgresql metaobjects (#4507)

* dbeaver/pro#4551 adds full DDL for GeneratedSql dialog

* dbeaver/pro#4551 introduces core-sql-generator plugin + adds "full DDL" action into data editor footer for DDL properties tab

* dbeaver/pro#4551 adds supportsFullDdl flag to hide full ddl feature if connection doesn't support it

* dbeaver/pro#4551 adds SqlEntityQueryResource to separate sqlGenerator logic

* dbeaver/pro#4551 refactor: remove core-sql-generator package and related files; migrate SQL generation logic to plugin-sql-generator

* dbeaver/pro#4551 implements DDL query state management and refactors DDL viewer context

* dbeaver/pro#4551 removes state & name in favor of onChange for GeneratedSqlDIalog

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
This commit is contained in:
sergeyteleshev
2026-08-09 10:12:16 +02:00
committed by GitHub
co-authored by Daria Marutkina
parent 1c789cf49b
commit 16915940ba
30 changed files with 303 additions and 90 deletions
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 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.
@@ -20,4 +20,5 @@ export enum EObjectFeature {
'dataSourceConnected' = 'dataSourceConnected',
'entityContainer' = 'entityContainer',
'supportsDataFilter' = 'supportsDataFilter',
'supportsFullDdl' = 'supportsFullDdl', // some connections may not have the full DDL feature
}
@@ -0,0 +1,5 @@
mutation asyncSqlGenerateEntityQuery($generatorId: String!, $nodePathList: [String!]!, $generatorOptions: SQLQueryGeneratorOptions) {
taskInfo: asyncSqlGenerateEntityQuery(generatorId: $generatorId, nodePathList: $nodePathList, generatorOptions: $generatorOptions) {
...AsyncTaskInfo
}
}
@@ -1,3 +0,0 @@
query metadataGetNodeDDL( $nodeId: ID! ) {
metadataGetNodeDDL( nodeId: $nodeId )
}
@@ -1,3 +0,0 @@
query sqlGenerateEntityQuery($generatorId: String!, $nodePathList: [String!]!, $generatorOptions: SQLQueryGeneratorOptions) {
sqlGenerateEntityQuery(generatorId: $generatorId, nodePathList: $nodePathList, generatorOptions: $generatorOptions)
}
@@ -179,6 +179,7 @@ export class DataGridContextMenuGenerateSqlService {
query,
nodeId: nodePath ?? connectionId,
nodeName: this.getEntityNameFromNodePath(containerNodePath, model.name),
generatorId,
generatorName: this.localizationService.translate(generatorLabel),
options: getDefaultQueryGeneratorOptions(),
regenerateQuery: options =>
@@ -234,6 +235,7 @@ export class DataGridContextMenuGenerateSqlService {
query,
nodeId: nodePathList,
nodeName: this.getEntityNameFromNodePath(nodePathList, model.name),
generatorId: createGenerator.id,
generatorName: createGenerator.label,
options: getDefaultQueryGeneratorOptions(),
regenerateQuery: genOptions => this.sqlGenerationResource.generateEntityQuery(createGenerator.id, nodePathList, genOptions),
@@ -25,6 +25,8 @@
"@cloudbeaver/core-connections": "workspace:*",
"@cloudbeaver/core-data-context": "workspace:*",
"@cloudbeaver/core-di": "workspace:*",
"@cloudbeaver/core-events": "workspace:*",
"@cloudbeaver/core-localization": "workspace:*",
"@cloudbeaver/core-navigation-tree": "workspace:*",
"@cloudbeaver/core-resource": "workspace:*",
"@cloudbeaver/core-sdk": "workspace:*",
@@ -36,6 +38,7 @@
"@cloudbeaver/plugin-sql-editor": "workspace:*",
"@cloudbeaver/plugin-sql-editor-codemirror": "workspace:*",
"@cloudbeaver/plugin-sql-editor-navigation-tab": "workspace:*",
"@cloudbeaver/plugin-sql-generator": "workspace:*",
"@dbeaver/js-helpers": "workspace:*",
"mobx": "^6",
"mobx-react-lite": "^4",
@@ -0,0 +1,14 @@
/*
* CloudBeaver - Cloud Database Manager
* 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.
*/
import { createAction } from '@cloudbeaver/core-view';
export const ACTION_DDL_VIEWER_FULL_DDL = createAction('ddl-viewer-full-ddl', {
label: 'plugin_ddl_viewer_full_ddl',
tooltip: 'plugin_ddl_viewer_full_ddl',
type: 'checkbox',
});
@@ -1,10 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 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.
*/
import { createDataContext } from '@cloudbeaver/core-data-context';
export const DATA_CONTEXT_DDL_VIEWER_VALUE = createDataContext<string>('ddl-value');
export const DATA_CONTEXT_DDL_VIEWER_QUERY = createDataContext<string>('ddl-query');
@@ -7,36 +7,63 @@
*/
import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { EObjectFeature, NavNodeInfoResource, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { MetadataMap } from '@cloudbeaver/core-utils';
import { download, withTimestamp } from '@dbeaver/js-helpers';
import { ACTION_SAVE, ActionService, MenuService } from '@cloudbeaver/core-view';
import { DDL_GENERATOR_ID, SqlGeneratorsResource } from '@cloudbeaver/plugin-sql-generator';
import { LocalStorageSqlDataSource } from '@cloudbeaver/plugin-sql-editor';
import { ACTION_SQL_EDITOR_OPEN, SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab';
import { ACTION_DDL_VIEWER_FULL_DDL } from './ACTION_DDL_VIEWER_FULL_DDL.js';
import { DATA_CONTEXT_DDL_VIEWER_NODE } from './DATA_CONTEXT_DDL_VIEWER_NODE.js';
import { DATA_CONTEXT_DDL_VIEWER_VALUE } from './DATA_CONTEXT_DDL_VIEWER_VALUE.js';
import { DATA_CONTEXT_DDL_VIEWER_QUERY } from './DATA_CONTEXT_DDL_VIEWER_QUERY.js';
import { MENU_DDL_VIEWER_FOOTER } from './MENU_DDL_VIEWER_FOOTER.js';
@injectable(() => [NavNodeManagerService, ActionService, MenuService, SqlEditorNavigatorService, ConnectionInfoResource])
@injectable(() => [
NavNodeManagerService,
ActionService,
MenuService,
SqlEditorNavigatorService,
ConnectionInfoResource,
NavNodeInfoResource,
SqlGeneratorsResource,
])
export class DDLViewerFooterService {
private readonly fullDdlState = new MetadataMap<string, boolean>(() => false);
constructor(
private readonly navNodeManagerService: NavNodeManagerService,
private readonly actionsService: ActionService,
private readonly menuService: MenuService,
private readonly sqlEditorNavigatorService: SqlEditorNavigatorService,
private readonly connectionInfoResource: ConnectionInfoResource,
private readonly navNodeInfoResource: NavNodeInfoResource,
private readonly sqlGeneratorsResource: SqlGeneratorsResource,
) {}
isFullDdlEnabled(nodeId: string): boolean {
return this.fullDdlState.get(nodeId);
}
setFullDdlEnabled(nodeId: string, value: boolean): void {
this.fullDdlState.set(nodeId, value);
}
toggleFullDdl(nodeId: string): void {
this.setFullDdlEnabled(nodeId, !this.isFullDdlEnabled(nodeId));
}
register(): void {
this.actionsService.addHandler({
id: 'ddl-viewer-footer-base-handler',
menus: [MENU_DDL_VIEWER_FOOTER],
contexts: [DATA_CONTEXT_DDL_VIEWER_NODE, DATA_CONTEXT_DDL_VIEWER_VALUE],
contexts: [DATA_CONTEXT_DDL_VIEWER_NODE, DATA_CONTEXT_DDL_VIEWER_QUERY],
actions: [ACTION_SAVE, ACTION_SQL_EDITOR_OPEN],
handler: async (context, action) => {
switch (action) {
case ACTION_SAVE: {
const ddl = context.get(DATA_CONTEXT_DDL_VIEWER_VALUE)!;
const ddl = context.get(DATA_CONTEXT_DDL_VIEWER_QUERY)!;
const nodeId = context.get(DATA_CONTEXT_DDL_VIEWER_NODE);
const blob = new Blob([ddl], {
@@ -50,7 +77,7 @@ export class DDLViewerFooterService {
break;
}
case ACTION_SQL_EDITOR_OPEN: {
const ddl = context.get(DATA_CONTEXT_DDL_VIEWER_VALUE)!;
const ddl = context.get(DATA_CONTEXT_DDL_VIEWER_QUERY)!;
const nodeId = context.get(DATA_CONTEXT_DDL_VIEWER_NODE)!;
const connection = this.connectionInfoResource.getConnectionForNode(nodeId);
@@ -92,5 +119,34 @@ export class DDLViewerFooterService {
menus: [MENU_DDL_VIEWER_FOOTER],
getItems: (context, items) => [...items, ACTION_SAVE, ACTION_SQL_EDITOR_OPEN],
});
this.menuService.addCreator({
menus: [MENU_DDL_VIEWER_FOOTER],
contexts: [DATA_CONTEXT_DDL_VIEWER_NODE],
isApplicable: context => this.isFullDDLSupported(context.get(DATA_CONTEXT_DDL_VIEWER_NODE)!),
getItems: (context, items) => [...items, ACTION_DDL_VIEWER_FULL_DDL],
});
this.actionsService.addHandler({
id: 'ddl-viewer-footer-full-ddl-handler',
menus: [MENU_DDL_VIEWER_FOOTER],
actions: [ACTION_DDL_VIEWER_FULL_DDL],
contexts: [DATA_CONTEXT_DDL_VIEWER_NODE],
isChecked: context => this.isFullDdlEnabled(context.get(DATA_CONTEXT_DDL_VIEWER_NODE)!),
handler: (context, action) => {
if (action === ACTION_DDL_VIEWER_FULL_DDL) {
this.toggleFullDdl(context.get(DATA_CONTEXT_DDL_VIEWER_NODE)!);
}
},
});
}
private isFullDDLSupported(nodeId: string): boolean {
const supportsFullDdl = this.navNodeInfoResource.get(nodeId)?.objectFeatures.includes(EObjectFeature.supportsFullDdl) ?? false;
const ddlGenerator = this.sqlGeneratorsResource
.get(nodeId)
?.find(generator => generator.id.toLowerCase().includes(DDL_GENERATOR_ID.toLowerCase()));
return supportsFullDdl && !!ddlGenerator;
}
}
@@ -7,7 +7,7 @@
*/
import { observer } from 'mobx-react-lite';
import { s, useResource, useS } from '@cloudbeaver/core-blocks';
import { s, useAutoLoad, useResource, useS } from '@cloudbeaver/core-blocks';
import {
ConnectionDialectResource,
ConnectionInfoActiveProjectKey,
@@ -22,17 +22,15 @@ import type { NavNodeTransformViewComponent } from '@cloudbeaver/plugin-navigati
import { SQLCodeEditor, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-codemirror';
import { DATA_CONTEXT_DDL_VIEWER_NODE } from './DATA_CONTEXT_DDL_VIEWER_NODE.js';
import { DATA_CONTEXT_DDL_VIEWER_VALUE } from './DATA_CONTEXT_DDL_VIEWER_VALUE.js';
import { DdlResource } from './DdlResource.js';
import { DATA_CONTEXT_DDL_VIEWER_QUERY } from './DATA_CONTEXT_DDL_VIEWER_QUERY.js';
import style from './DDLViewerTabPanel.module.css';
import { MENU_DDL_VIEWER_FOOTER } from './MENU_DDL_VIEWER_FOOTER.js';
import { useDdlEntityQuery } from './useDdlEntityQuery.js';
export const DDLViewerTabPanel: NavNodeTransformViewComponent = observer(function DDLViewerTabPanel({ nodeId, folderId }) {
const styles = useS(style, MenuBarStyles, MenuBarItemStyles, MenuBarGroupStyles);
const menu = useMenu({ menu: MENU_DDL_VIEWER_FOOTER });
const ddlResource = useResource(DDLViewerTabPanel, DdlResource, nodeId);
const connectionInfoResource = useResource(DDLViewerTabPanel, ConnectionInfoResource, ConnectionInfoActiveProjectKey);
const connection = connectionInfoResource.resource.getConnectionForNode(nodeId);
const connectionParam = connection ? createConnectionParam(connection) : null;
@@ -42,16 +40,21 @@ export const DDLViewerTabPanel: NavNodeTransformViewComponent = observer(functio
if (sqlDialect) {
extensions.set(...sqlDialect);
}
const ddlData = ddlResource.data;
const ddlQueryState = useDdlEntityQuery(nodeId);
useAutoLoad(DDLViewerTabPanel, ddlQueryState);
const query = ddlQueryState.query ?? '';
useDataContextLink(menu.context, (context, id) => {
context.set(DATA_CONTEXT_DDL_VIEWER_NODE, nodeId, id);
context.set(DATA_CONTEXT_DDL_VIEWER_VALUE, ddlData, id);
context.set(DATA_CONTEXT_DDL_VIEWER_QUERY, query, id);
});
return (
<div className={s(styles, { wrapper: true })}>
<SQLCodeEditor className={s(styles, { sqlCodeEditorLoader: true })} value={ddlResource.data ?? ''} extensions={extensions} readonly />
<SQLCodeEditor className={s(styles, { sqlCodeEditorLoader: true })} value={query} extensions={extensions} readonly />
<MenuBar className={s(styles, { menuBar: true, floating: true, withLabel: true })} menu={menu} compact={false} />
</div>
);
@@ -1,47 +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 { injectable } from '@cloudbeaver/core-di';
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
import { CachedMapResource, isResourceAlias, type ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import { GraphQLService } from '@cloudbeaver/core-sdk';
@injectable(() => [GraphQLService, NavNodeInfoResource])
export class DdlResource extends CachedMapResource<string, string> {
constructor(
private readonly graphQLService: GraphQLService,
private readonly navNodeInfoResource: NavNodeInfoResource,
) {
super();
this.navNodeInfoResource.outdateResource(this);
this.navNodeInfoResource.deleteInResource(this);
}
protected async loader(key: ResourceKey<string>): Promise<Map<string, string>> {
if (isResourceAlias(key)) {
throw new Error('Aliases not supported by this resource.');
}
const values: string[] = [];
await ResourceKeyUtils.forEachAsync(key, async nodeId => {
const { metadataGetNodeDDL } = await this.graphQLService.sdk.metadataGetNodeDDL({ nodeId });
if (metadataGetNodeDDL) {
values.push(metadataGetNodeDDL);
}
});
this.set(ResourceKeyUtils.toList(key), values);
return this.data;
}
protected validateKey(key: string): boolean {
return typeof key === 'string';
}
}
@@ -0,0 +1,99 @@
/*
* CloudBeaver - Cloud Database Manager
* 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.
*/
import { observable } from 'mobx';
import { useObservableRef, useResource } from '@cloudbeaver/core-blocks';
import type { ILoadableState } from '@cloudbeaver/core-utils';
import { DDL_GENERATOR_ID, getDefaultQueryGeneratorOptions, SqlGeneratorsResource } from '@cloudbeaver/plugin-sql-generator';
import { useService } from '@cloudbeaver/core-di';
import { DDLViewerFooterService } from './DDLViewerFooterService.js';
interface IState extends ILoadableState {
query: string | null;
exception: Error | null;
promise: Promise<string> | null;
key: string | null;
nodeId: string;
sqlGeneratorsResource: SqlGeneratorsResource;
ddlGeneratorId: string | null;
ddlViewerFooterService: DDLViewerFooterService;
getCurrentKey: () => string | null;
}
export function useDdlEntityQuery(nodeId: string): IState {
const ddlViewerFooterService = useService(DDLViewerFooterService);
const sqlGeneratorsResource = useResource(useDdlEntityQuery, SqlGeneratorsResource, nodeId);
const ddlGenerator = sqlGeneratorsResource.data?.find(generator => generator.id.toLowerCase().includes(DDL_GENERATOR_ID.toLowerCase()));
const ddlGeneratorId = ddlGenerator?.id ?? null;
const state = useObservableRef<IState>(
() => ({
query: null,
exception: null,
promise: null,
key: null,
isLoadable() {
return this.ddlGeneratorId !== null;
},
isLoaded() {
return this.query !== null;
},
isError() {
return this.exception !== null;
},
isLoading() {
return this.promise !== null;
},
isOutdated() {
return this.getCurrentKey() !== this.key;
},
getCurrentKey() {
if (this.ddlGeneratorId === null) {
return null;
}
const isFullDdl = this.ddlViewerFooterService.isFullDdlEnabled(this.nodeId);
return `${this.nodeId}::${this.ddlGeneratorId}::${isFullDdl}`;
},
async load() {
if (this.ddlGeneratorId === null) {
return;
}
const key = this.getCurrentKey();
const isFullDdl = this.ddlViewerFooterService.isFullDdlEnabled(this.nodeId);
try {
this.exception = null;
this.promise = this.sqlGeneratorsResource.generateEntityQuery(this.ddlGeneratorId, this.nodeId, {
...getDefaultQueryGeneratorOptions(),
showFullDdl: isFullDdl,
});
this.query = await this.promise;
this.key = key;
} catch (exception: any) {
this.exception = exception;
} finally {
this.promise = null;
}
},
}),
{
query: observable.ref,
exception: observable.ref,
promise: observable.ref,
key: observable.ref,
nodeId: observable.ref,
ddlGeneratorId: observable.ref,
},
{ nodeId, ddlGeneratorId, sqlGeneratorsResource: sqlGeneratorsResource.resource, ddlViewerFooterService },
);
return state;
}
@@ -22,7 +22,7 @@ import type { NavNodeTransformViewComponent } from '@cloudbeaver/plugin-navigati
import { SQLCodeEditor, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-codemirror';
import { DATA_CONTEXT_DDL_VIEWER_NODE } from '../DdlViewer/DATA_CONTEXT_DDL_VIEWER_NODE.js';
import { DATA_CONTEXT_DDL_VIEWER_VALUE } from '../DdlViewer/DATA_CONTEXT_DDL_VIEWER_VALUE.js';
import { DATA_CONTEXT_DDL_VIEWER_QUERY } from '../DdlViewer/DATA_CONTEXT_DDL_VIEWER_QUERY.js';
import style from '../DdlViewer/DDLViewerTabPanel.module.css';
import { MENU_DDL_VIEWER_FOOTER } from '../DdlViewer/MENU_DDL_VIEWER_FOOTER.js';
import { ExtendedDDLResource } from './ExtendedDDLResource.js';
@@ -46,7 +46,7 @@ export const ExtendedDDLViewerTabPanel: NavNodeTransformViewComponent = observer
useDataContextLink(menu.context, (context, id) => {
context.set(DATA_CONTEXT_DDL_VIEWER_NODE, nodeId, id);
context.set(DATA_CONTEXT_DDL_VIEWER_VALUE, extendedDDlData, id);
context.set(DATA_CONTEXT_DDL_VIEWER_QUERY, extendedDDlData, id);
});
return (
@@ -0,0 +1,29 @@
/*
* CloudBeaver - Cloud Database Manager
* 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.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { LocalizationService } from '@cloudbeaver/core-localization';
@injectable(() => [LocalizationService])
export class LocaleService extends Bootstrap {
constructor(private readonly localizationService: LocalizationService) {
super();
}
override register(): void {
this.localizationService.addProvider(this.provider.bind(this));
}
private async provider(locale: string) {
switch (locale) {
case 'ru':
return (await import('./locales/ru.js')).default;
default:
return (await import('./locales/en.js')).default;
}
}
}
@@ -0,0 +1,4 @@
export default [
['plugin_ddl_viewer_full_ddl', 'Full DDL'],
['plugin_ddl_viewer_full_ddl_error_title', 'Error generating full DDL'],
];
@@ -0,0 +1,4 @@
export default [
['plugin_ddl_viewer_full_ddl', 'Полный DDL'],
['plugin_ddl_viewer_full_ddl_error_title', 'Ошибка генерации полного DDL'],
];
@@ -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.
@@ -9,8 +9,8 @@
import { Bootstrap, Dependency, ModuleRegistry, proxy } from '@cloudbeaver/core-di';
import { ExtendedDDLResource } from './ExtendedDDLViewer/ExtendedDDLResource.js';
import { DdlViewerBootstrap } from './DdlViewerBootstrap.js';
import { DdlResource } from './DdlViewer/DdlResource.js';
import { DDLViewerFooterService } from './DdlViewer/DDLViewerFooterService.js';
import { LocaleService } from './LocaleService.js';
export default ModuleRegistry.add({
name: '@cloudbeaver/plugin-ddl-viewer',
@@ -18,10 +18,9 @@ export default ModuleRegistry.add({
configure: serviceCollection => {
serviceCollection
.addSingleton(Bootstrap, DdlViewerBootstrap)
.addSingleton(Bootstrap, LocaleService)
.addSingleton(Dependency, proxy(ExtendedDDLResource))
.addSingleton(Dependency, proxy(DdlResource))
.addSingleton(DDLViewerFooterService)
.addSingleton(ExtendedDDLResource)
.addSingleton(DdlResource);
.addSingleton(ExtendedDDLResource);
},
});
@@ -25,6 +25,12 @@
{
"path": "../core-di"
},
{
"path": "../core-events"
},
{
"path": "../core-localization"
},
{
"path": "../core-navigation-tree"
},
@@ -57,6 +63,9 @@
},
{
"path": "../plugin-sql-editor-navigation-tab"
},
{
"path": "../plugin-sql-generator"
}
],
"include": [
@@ -30,6 +30,7 @@
"@cloudbeaver/core-localization": "workspace:*",
"@cloudbeaver/core-navigation-tree": "workspace:*",
"@cloudbeaver/core-resource": "workspace:*",
"@cloudbeaver/core-root": "workspace:*",
"@cloudbeaver/core-sdk": "workspace:*",
"@cloudbeaver/core-utils": "workspace:*",
"@cloudbeaver/core-view": "workspace:*",
@@ -23,8 +23,8 @@ import {
import { ConnectionDialectResource, ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import type { DialogComponentProps } from '@cloudbeaver/core-dialogs';
import { EObjectFeature, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { download, withTimestamp } from '@dbeaver/js-helpers';
import { NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6';
import { SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab';
import { SQLCodeEditor, useSqlDialectExtension } from '@cloudbeaver/plugin-sql-editor-codemirror';
@@ -33,9 +33,12 @@ import { action, observable } from 'mobx';
import { NotificationService } from '@cloudbeaver/core-events';
import type { SqlQueryGeneratorOptions } from '@cloudbeaver/core-sdk';
import { DDL_GENERATOR_ID } from './SqlGeneratorsResource.js';
interface Payload {
nodeId: string;
nodeName?: string;
generatorId: string;
generatorName?: string;
query: string;
options?: SqlQueryGeneratorOptions;
@@ -51,6 +54,7 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
() => ({
useFullyQualifiedNames: payload.options?.useFullyQualifiedNames ?? true,
compactSql: payload.options?.compactSql ?? false,
showFullDdl: payload.options?.showFullDdl,
query: payload.query,
loading: false,
handleOptionChange: async function handleOptionChange<T extends keyof typeof state>(key: T, value: (typeof state)[T]) {
@@ -61,6 +65,7 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
this.query = await this.payload.regenerateQuery({
compactSql: this.compactSql,
useFullyQualifiedNames: this.useFullyQualifiedNames,
showFullDdl: this.showFullDdl,
});
} catch (error: any) {
this.notificationService.logException(error, 'app_shared_sql_generators_error_title');
@@ -73,6 +78,7 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
handleOptionChange: action.bound,
useFullyQualifiedNames: observable.ref,
compactSql: observable.ref,
showFullDdl: observable.ref,
query: observable.ref,
loading: observable.ref,
},
@@ -96,6 +102,10 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
}
const visibleLoading = useStateDelay(state.loading, 300);
const isDdlGenerator = payload.generatorId.toLowerCase().includes(DDL_GENERATOR_ID.toLowerCase());
const node = navNodeManagerService.getNode(payload.nodeId);
const supportsFullDdl = node?.objectFeatures.includes(EObjectFeature.supportsFullDdl) ?? false;
const hasFullDdl = isDdlGenerator && supportsFullDdl;
async function handleOpenInEditor() {
try {
@@ -134,21 +144,24 @@ export const GeneratedSqlDialog = observer<DialogComponentProps<Payload>>(functi
<div className="tw:flex tw:flex-row tw:gap-3 tw:w-full">
<Checkbox
id="use-fully-qualified-names"
state={state}
name="useFullyQualifiedNames"
disabled={visibleLoading}
label={translate('app_shared_sql_generators_use_fully_qualified_names')}
onChange={value => state.handleOptionChange('useFullyQualifiedNames', value)}
/>
<Checkbox
id="compact-sql"
state={state}
name="compactSql"
disabled={visibleLoading}
label={translate('app_shared_sql_generators_compact_sql')}
onChange={value => state.handleOptionChange('compactSql', value)}
/>
{hasFullDdl && (
<Checkbox
id="show-full-ddl"
disabled={visibleLoading}
label={translate('app_shared_sql_generators_full_ddl')}
onChange={value => state.handleOptionChange('showFullDdl', value)}
/>
)}
</div>
</div>
<div className="tw:flex tw:items-center tw:justify-between tw:w-full tw:gap-6">
@@ -12,8 +12,8 @@ import { DATA_CONTEXT_NAV_NODE, EObjectFeature } from '@cloudbeaver/core-navigat
import { getCachedMapResourceLoaderState } from '@cloudbeaver/core-resource';
import { MenuBaseItem, MenuService } from '@cloudbeaver/core-view';
import { MENU_SQL_GENERATORS } from './MENU_SQL_GENERATORS.js';
import { getDefaultQueryGeneratorOptions, SqlGeneratorsResource } from './SqlGeneratorsResource.js';
import { MENU_SQL_GENERATORS } from './MENU_SQL_GENERATORS.js';
import { NotificationService } from '@cloudbeaver/core-events';
const GeneratedSqlDialog = importLazyComponent(() => import('./GeneratedSqlDialog.js').then(m => m.GeneratedSqlDialog));
@@ -86,6 +86,7 @@ export class SqlGeneratorsBootstrap extends Bootstrap {
query,
nodeId: node.uri,
nodeName: node.name,
generatorId: action.id,
generatorName: action.label,
options: getDefaultQueryGeneratorOptions(),
regenerateQuery: options => this.sqlGeneratorsResource.generateEntityQuery(action.id, node.uri, options),
@@ -8,6 +8,7 @@
import { injectable } from '@cloudbeaver/core-di';
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
import { CachedMapResource, isResourceAlias, type ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import { AsyncTaskInfoService } from '@cloudbeaver/core-root';
import {
GraphQLService,
type SqlGenerateResultSetQueryQueryVariables,
@@ -19,13 +20,15 @@ export const MAX_GENERATORS_LENGTH = 15;
export const getDefaultQueryGeneratorOptions = (): SqlQueryGeneratorOptions => ({
useFullyQualifiedNames: true,
compactSql: false,
showFullDdl: false,
});
export const DDL_GENERATOR_ID = 'tableDDL';
@injectable(() => [GraphQLService, NavNodeInfoResource])
@injectable(() => [GraphQLService, AsyncTaskInfoService, NavNodeInfoResource])
export class SqlGeneratorsResource extends CachedMapResource<string, SqlQueryGenerator[]> {
constructor(
private readonly graphQLService: GraphQLService,
private readonly asyncTaskInfoService: AsyncTaskInfoService,
private readonly navNodeInfoResource: NavNodeInfoResource,
) {
super();
@@ -35,13 +38,20 @@ export class SqlGeneratorsResource extends CachedMapResource<string, SqlQueryGen
}
async generateEntityQuery(generatorId: string, nodePathList: string | string[], options?: SqlQueryGeneratorOptions): Promise<string> {
const result = await this.graphQLService.sdk.sqlGenerateEntityQuery({
generatorId,
nodePathList,
generatorOptions: options,
const task = this.asyncTaskInfoService.create(async () => {
const { taskInfo } = await this.graphQLService.sdk.asyncSqlGenerateEntityQuery({
generatorId,
nodePathList,
generatorOptions: options,
});
return taskInfo;
});
return result.sqlGenerateEntityQuery;
const info = await this.asyncTaskInfoService.run(task);
await this.asyncTaskInfoService.remove(task.id);
return info.taskResult;
}
async generateResultSetSql(params: SqlGenerateResultSetQueryQueryVariables): Promise<string | null> {
@@ -2,6 +2,7 @@ export default [
['app_shared_sql_generators_error_title', 'SQL Generation Error'],
['app_shared_sql_generators_use_fully_qualified_names', 'Use fully qualified names'],
['app_shared_sql_generators_compact_sql', 'Compact SQL'],
['app_shared_sql_generators_full_ddl', 'Full DDL'],
['app_shared_sql_generators_open_in_editor', 'Open in SQL Editor'],
['app_shared_sql_generators_error_open_editor', 'Error opening SQL Editor'],
];
@@ -2,6 +2,7 @@ export default [
['app_shared_sql_generators_error_title', 'Erreur de génération SQL'],
['app_shared_sql_generators_use_fully_qualified_names', 'Utiliser les noms complets'],
['app_shared_sql_generators_compact_sql', 'SQL compact'],
['app_shared_sql_generators_full_ddl', 'Full DDL'],
['app_shared_sql_generators_open_in_editor', "Ouvrir dans l'éditeur SQL"],
['app_shared_sql_generators_error_open_editor', "Erreur lors de l'ouverture de l'éditeur SQL"],
];
@@ -2,6 +2,7 @@ export default [
['app_shared_sql_generators_error_title', 'Errore di generazione SQL'],
['app_shared_sql_generators_use_fully_qualified_names', 'Usa nomi completi'],
['app_shared_sql_generators_compact_sql', 'SQL compatto'],
['app_shared_sql_generators_full_ddl', 'Full DDL'],
['app_shared_sql_generators_open_in_editor', "Apri nell'editor SQL"],
['app_shared_sql_generators_error_open_editor', "Errore durante l'apertura dell'editor SQL"],
];
@@ -2,6 +2,7 @@ export default [
['app_shared_sql_generators_error_title', 'Ошибка генерации SQL'],
['app_shared_sql_generators_use_fully_qualified_names', 'Использовать полные имена'],
['app_shared_sql_generators_compact_sql', 'Компактный SQL'],
['app_shared_sql_generators_full_ddl', 'Полный DDL'],
['app_shared_sql_generators_open_in_editor', 'Открыть в SQL редакторе'],
['app_shared_sql_generators_error_open_editor', 'Ошибка при открытии SQL редактора'],
];
@@ -2,6 +2,7 @@ export default [
['app_shared_sql_generators_error_title', 'Lỗi tạo SQL'],
['app_shared_sql_generators_use_fully_qualified_names', 'Sử dụng tên đầy đủ'],
['app_shared_sql_generators_compact_sql', 'SQL gọn'],
['app_shared_sql_generators_full_ddl', 'Full DDL'],
['app_shared_sql_generators_open_in_editor', 'Mở trong trình soạn thảo SQL'],
['app_shared_sql_generators_error_open_editor', 'Lỗi khi mở trình soạn thảo SQL'],
];
@@ -2,6 +2,7 @@ export default [
['app_shared_sql_generators_error_title', 'SQL生成错误'],
['app_shared_sql_generators_use_fully_qualified_names', '使用完全限定名'],
['app_shared_sql_generators_compact_sql', '紧凑SQL'],
['app_shared_sql_generators_full_ddl', 'Full DDL'],
['app_shared_sql_generators_open_in_editor', '在SQL编辑器中打开'],
['app_shared_sql_generators_error_open_editor', '打开SQL编辑器时出错'],
];
@@ -40,6 +40,9 @@
{
"path": "../core-resource"
},
{
"path": "../core-root"
},
{
"path": "../core-sdk"
},
+4
View File
@@ -3504,6 +3504,8 @@ __metadata:
"@cloudbeaver/core-connections": "workspace:*"
"@cloudbeaver/core-data-context": "workspace:*"
"@cloudbeaver/core-di": "workspace:*"
"@cloudbeaver/core-events": "workspace:*"
"@cloudbeaver/core-localization": "workspace:*"
"@cloudbeaver/core-navigation-tree": "workspace:*"
"@cloudbeaver/core-resource": "workspace:*"
"@cloudbeaver/core-sdk": "workspace:*"
@@ -3515,6 +3517,7 @@ __metadata:
"@cloudbeaver/plugin-sql-editor": "workspace:*"
"@cloudbeaver/plugin-sql-editor-codemirror": "workspace:*"
"@cloudbeaver/plugin-sql-editor-navigation-tab": "workspace:*"
"@cloudbeaver/plugin-sql-generator": "workspace:*"
"@cloudbeaver/tsconfig": "workspace:*"
"@dbeaver/js-helpers": "workspace:*"
"@types/react": "npm:^19"
@@ -4719,6 +4722,7 @@ __metadata:
"@cloudbeaver/core-localization": "workspace:*"
"@cloudbeaver/core-navigation-tree": "workspace:*"
"@cloudbeaver/core-resource": "workspace:*"
"@cloudbeaver/core-root": "workspace:*"
"@cloudbeaver/core-sdk": "workspace:*"
"@cloudbeaver/core-utils": "workspace:*"
"@cloudbeaver/core-view": "workspace:*"