🐛 fix(query-editor): 修复 SQL 美化未按数据库方言选择格式化器

- 为 QueryEditor 美化入口按当前连接类型动态选择 sql-formatter language
- 让 postgres、kingbase、highgo、vastbase、opengauss、gaussdb 使用 postgresql 方言
- 让 oracle、dameng 使用 plsql,避免固定 mysql 方言导致语法解析失败
- 补充 PostgreSQL 窗口函数与类型转换 SQL 的美化回归测试

Close #561
This commit is contained in:
Syngnat
2026-06-14 16:07:52 +08:00
parent 5310ec7c44
commit 70b469d349
2 changed files with 66 additions and 2 deletions

View File

@@ -1408,6 +1408,37 @@ describe('QueryEditor external SQL save', () => {
);
});
it('formats postgres window-function SQL with cast syntax through Monaco edits', async () => {
let renderer!: ReactTestRenderer;
storeState.connections[0].config.type = 'postgres';
storeState.connections[0].config.database = 'main';
const pgSql = [
'SELECT',
`FLOOR(DATE_PART('epoch', "CREATE_TIME" - LAG("END_TIME") OVER (ORDER BY "CREATE_TIME" asc, "ID" desc))*1000)::int as time_diff_seconds,`,
'*',
`FROM "FAM_RU_BLOCK" WHERE "RU_JOB_ID" = ''`,
].join('\n');
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ query: pgSql, dbName: 'main' })} />);
});
const formatButton = findButton(renderer, '美化');
await act(async () => {
await formatButton.props.onClick();
});
expect(messageApi.error).not.toHaveBeenCalled();
expect(editorState.editor.executeEdits).toHaveBeenCalledWith(
'gonavi-format-sql',
expect.arrayContaining([
expect.objectContaining({
text: expect.stringContaining(')::int AS time_diff_seconds'),
}),
]),
);
});
it('shows object info via editor ctrl+q action', async () => {
editorState.value = 'select users.id from users';
autoFetchState.visible = true;

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import Editor, { type OnMount } from './MonacoEditor';
import { message, Modal, Input, Form, MenuProps } from 'antd';
import { format } from 'sql-formatter';
import { format, type SqlLanguage } from 'sql-formatter';
import { v4 as uuidv4 } from 'uuid';
import { TabData, ColumnDefinition, IndexDefinition } from '../types';
import { useStore } from '../store';
@@ -468,6 +468,38 @@ const normalizeMetadataDialect = (conn: any): string => {
return String(dialect || '').toLowerCase();
};
const resolveQueryEditorFormatterLanguage = (conn: any): SqlLanguage => {
const dialect = normalizeMetadataDialect(conn);
switch (dialect) {
case 'postgres':
case 'kingbase':
case 'highgo':
case 'vastbase':
case 'opengauss':
case 'gaussdb':
return 'postgresql';
case 'duckdb':
return 'duckdb';
case 'sqlite':
return 'sqlite';
case 'sqlserver':
return 'transactsql';
case 'oracle':
case 'dameng':
return 'plsql';
case 'clickhouse':
return 'clickhouse';
case 'mysql':
case 'goldendb':
case 'sphinx':
return 'mysql';
case 'mariadb':
return 'mariadb';
default:
return 'sql';
}
};
const buildCompletionTableCommentSQL = (dialect: string, dbName: string): string => {
const db = String(dbName || '').trim();
const escapedDb = escapeMetadataSqlLiteral(db);
@@ -3684,7 +3716,8 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
const handleFormat = () => {
try {
const formatted = format(getCurrentQuery(), { language: 'mysql', keywordCase: sqlFormatOptions.keywordCase });
const formatterLanguage = resolveQueryEditorFormatterLanguage(connections.find(c => c.id === currentConnectionId));
const formatted = format(getCurrentQuery(), { language: formatterLanguage, keywordCase: sqlFormatOptions.keywordCase });
const editor = editorRef.current;
const monaco = monacoRef.current;
const model = editor?.getModel?.();