diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx index fc5b2b1..8684f30 100644 --- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx +++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx @@ -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(); + }); + + 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; diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index 7bbd090..833e721 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -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?.();