mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
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:
co-authored by
Daria Marutkina
parent
1c789cf49b
commit
16915940ba
@@ -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)
|
||||
}
|
||||
+2
@@ -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',
|
||||
});
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
+2
-2
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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:*"
|
||||
|
||||
Reference in New Issue
Block a user