diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx index 31ef1cc6..7fd745b2 100644 --- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx +++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx @@ -4564,6 +4564,32 @@ describe('QueryEditor external SQL save', () => { expect(messageApi.success).toHaveBeenCalledWith('已还原到美化前 SQL'); }); + it('formats OceanBase Oracle SQL with parameter placeholders', async () => { + let renderer!: ReactTestRenderer; + storeState.connections[0].config.type = 'oceanbase'; + (storeState.connections[0].config as any).oceanBaseProtocol = 'oracle'; + const oracleSql = 'select * from users where id = #{id,jdbcType=NUMBER} and tenant = ${tenant} and status = :status and code = ?'; + + 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.stringMatching(/#\{id,jdbcType=NUMBER\}[\s\S]*\$\{tenant\}[\s\S]*:status[\s\S]*\?/), + }), + ]), + ); + }); + it('formats postgres window-function SQL with cast syntax through Monaco edits', async () => { let renderer!: ReactTestRenderer; storeState.connections[0].config.type = 'postgres'; diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index 156a188c..d7806573 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -221,6 +221,13 @@ const QUERY_EDITOR_MAC_FIND_WITH_SELECTION_GUARD_ACTION_ID = 'gonavi.suppressMac const QUERY_EDITOR_AI_INLINE_DEBOUNCE_MS = 220; const QUERY_EDITOR_AI_INLINE_CONTEXT_KEY = 'gonaviAiInlineSuggestionVisible'; const QUERY_EDITOR_IME_FALLBACK_DELAY_MS = 80; +const QUERY_EDITOR_FORMAT_PARAM_TYPES = { + positional: true, + custom: [ + { regex: String.raw`#\{[^{}]+\}` }, + { regex: String.raw`\$\{[^{}]+\}` }, + ], +}; const EMPTY_QUERY_EDITOR_SQL_LOGS: SqlLog[] = []; const isOceanBaseOracleConnection = (config: any): boolean => { @@ -6182,7 +6189,11 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc const formatSelection = !!selection && !!selectedRaw.trim(); const fullQuery = getCurrentQuery(); const sourceSql = formatSelection ? selectedRaw : fullQuery; - const formatted = format(sourceSql, { language: formatterLanguage, keywordCase: sqlFormatOptions.keywordCase }); + const formatted = format(sourceSql, { + language: formatterLanguage, + keywordCase: sqlFormatOptions.keywordCase, + paramTypes: QUERY_EDITOR_FORMAT_PARAM_TYPES, + }); if (sourceSql === formatted) { return; }