mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-14 18:39:54 +08:00
✨ feat(ddl): 为 DDL 视图增加按方言格式化展示能力
- 新增通用 DDL 格式化工具 - DataGrid 查看 DDL 时按数据源方言输出可读 SQL - 覆盖 DuckDB DDL 展示与工具层测试
This commit is contained in:
@@ -1246,10 +1246,48 @@ describe('DataGrid DDL interactions', () => {
|
||||
expect(editors).toHaveLength(1);
|
||||
expect(editors[0].props['data-language']).toBe('sql');
|
||||
expect(editors[0].props['data-read-only']).toBe('true');
|
||||
expect(textContent(editors[0])).toContain('CREATE TABLE users');
|
||||
expect(textContent(editors[0])).toContain('CREATE TABLE');
|
||||
expect(textContent(editors[0])).toContain('users');
|
||||
expect(renderer!.root.findAll((node) => node.type === 'pre' && textContent(node).includes('CREATE TABLE users'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('formats DuckDB DDL into readable multiline SQL in the v2 view', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
storeState.connections[0].config.type = 'duckdb';
|
||||
backendApp.DBShowCreateTable.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: 'CREATE TABLE customers(customer_id BIGINT, customer_code VARCHAR, city VARCHAR, tier VARCHAR, signup_date DATE, lifetime_value DECIMAL(12,2), PRIMARY KEY(customer_id));',
|
||||
});
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<DataGrid
|
||||
data={[{ __gonavi_row_key__: 'row-1', customer_id: 1 }]}
|
||||
columnNames={['customer_id']}
|
||||
loading={false}
|
||||
tableName="example.main.customers"
|
||||
dbName="main"
|
||||
connectionId="conn-1"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
await waitForEffects();
|
||||
|
||||
await act(async () => {
|
||||
findButton(renderer!, '查看 DDL').props.onClick();
|
||||
});
|
||||
await waitForEffects();
|
||||
|
||||
const editors = renderer!.root.findAll((node) => node.props['data-monaco-editor'] === 'true');
|
||||
expect(editors).toHaveLength(1);
|
||||
const ddlText = textContent(editors[0]);
|
||||
expect(ddlText).toContain('CREATE TABLE customers (');
|
||||
expect(ddlText).toContain('customer_id BIGINT,');
|
||||
expect(ddlText).toContain('PRIMARY KEY (customer_id)');
|
||||
expect(ddlText).toContain('\n');
|
||||
});
|
||||
|
||||
it('opens the v2 DDL view as a right sidebar while keeping the table visible', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
backendApp.DBShowCreateTable.mockResolvedValueOnce({
|
||||
|
||||
@@ -3214,6 +3214,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
canViewDdl,
|
||||
currentConnConfig,
|
||||
dbName,
|
||||
dbType,
|
||||
tableName,
|
||||
isV2Ui,
|
||||
cellEditMode,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { DBShowCreateTable } from '../../wailsjs/go/app/App';
|
||||
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
|
||||
import { formatDdlForDisplay } from '../utils/ddlFormat';
|
||||
|
||||
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
|
||||
type DdlViewLayoutMode = 'bottom' | 'side';
|
||||
@@ -20,6 +21,7 @@ interface UseDataGridDdlViewParams {
|
||||
messageApi: {
|
||||
error: (content: string) => void;
|
||||
};
|
||||
dbType?: string;
|
||||
}
|
||||
|
||||
export interface UseDataGridDdlViewResult {
|
||||
@@ -54,6 +56,7 @@ export const useDataGridDdlView = ({
|
||||
closeCellEditModeRef,
|
||||
setTextRecordIndex,
|
||||
messageApi,
|
||||
dbType,
|
||||
}: UseDataGridDdlViewParams): UseDataGridDdlViewResult => {
|
||||
const [viewMode, setViewMode] = React.useState<GridViewMode>('table');
|
||||
const [ddlModalOpen, setDdlModalOpen] = React.useState(false);
|
||||
@@ -92,7 +95,7 @@ export const useDataGridDdlView = ({
|
||||
const res = await DBShowCreateTable(buildRpcConnectionConfig(currentConnConfig as any) as any, dbName || '', tableName);
|
||||
if (requestSeq !== ddlRequestSeqRef.current) return;
|
||||
if (res.success) {
|
||||
setDdlText(String(res.data ?? ''));
|
||||
setDdlText(formatDdlForDisplay(res.data, dbType || String((currentConnConfig as any)?.type || '')));
|
||||
return;
|
||||
}
|
||||
messageApi.error(res.message || '获取 DDL 失败');
|
||||
@@ -104,7 +107,7 @@ export const useDataGridDdlView = ({
|
||||
setDdlLoading(false);
|
||||
}
|
||||
}
|
||||
}, [canViewDdl, currentConnConfig, dbName, isV2Ui, messageApi, tableName]);
|
||||
}, [canViewDdl, currentConnConfig, dbName, dbType, isV2Ui, messageApi, tableName]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isV2Ui || (viewMode !== 'fields' && viewMode !== 'ddl' && viewMode !== 'er')) return;
|
||||
|
||||
22
frontend/src/utils/ddlFormat.test.ts
Normal file
22
frontend/src/utils/ddlFormat.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { formatDdlForDisplay } from './ddlFormat';
|
||||
|
||||
describe('formatDdlForDisplay', () => {
|
||||
it('formats DuckDB create table SQL into multiline output', () => {
|
||||
const raw = 'CREATE TABLE customers(customer_id BIGINT, customer_code VARCHAR, city VARCHAR, tier VARCHAR, signup_date DATE, lifetime_value DECIMAL(12,2), PRIMARY KEY(customer_id));';
|
||||
|
||||
const formatted = formatDdlForDisplay(raw, 'duckdb');
|
||||
|
||||
expect(formatted).toContain('CREATE TABLE customers (');
|
||||
expect(formatted).toContain('customer_id BIGINT,');
|
||||
expect(formatted).toContain('PRIMARY KEY (customer_id)');
|
||||
expect(formatted).toContain('\n');
|
||||
});
|
||||
|
||||
it('returns original text when formatter cannot parse the statement', () => {
|
||||
const raw = 'not valid ddl(';
|
||||
|
||||
expect(formatDdlForDisplay(raw, 'duckdb')).toBe(raw);
|
||||
});
|
||||
});
|
||||
53
frontend/src/utils/ddlFormat.ts
Normal file
53
frontend/src/utils/ddlFormat.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { format } from 'sql-formatter';
|
||||
|
||||
const resolveDdlFormatterLanguage = (dbType: string): string | null => {
|
||||
const normalized = String(dbType || '').trim().toLowerCase();
|
||||
switch (normalized) {
|
||||
case 'duckdb':
|
||||
return 'duckdb';
|
||||
case 'sqlite':
|
||||
return 'sqlite';
|
||||
case 'postgres':
|
||||
case 'postgresql':
|
||||
case 'kingbase':
|
||||
case 'highgo':
|
||||
case 'opengauss':
|
||||
case 'vastbase':
|
||||
return 'postgresql';
|
||||
case 'mariadb':
|
||||
return 'mariadb';
|
||||
case 'mysql':
|
||||
case 'sphinx':
|
||||
return 'mysql';
|
||||
case 'sqlserver':
|
||||
return 'transactsql';
|
||||
case 'oracle':
|
||||
case 'dameng':
|
||||
case 'oceanbase':
|
||||
return 'plsql';
|
||||
case 'clickhouse':
|
||||
return 'clickhouse';
|
||||
default:
|
||||
return 'sql';
|
||||
}
|
||||
};
|
||||
|
||||
export const formatDdlForDisplay = (ddlText: unknown, dbType: string): string => {
|
||||
const raw = String(ddlText ?? '').trim();
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
const language = resolveDdlFormatterLanguage(dbType);
|
||||
if (!language) {
|
||||
return raw;
|
||||
}
|
||||
try {
|
||||
return format(raw, {
|
||||
language,
|
||||
keywordCase: 'upper',
|
||||
linesBetweenQueries: 1,
|
||||
});
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user