🐛 fix(query-editor): 修复 Oracle 长脚本执行卡死

- 线性屏蔽 SQL 注释和字符串,避免 PL/SQL 定义检测发生正则灾难性回溯
- 仅为顶层 SELECT 解析结果表定位,避免匿名块内部查询触发元数据加载
- 新增长注释匿名块与多结果回归,验证 Oracle 过程和 SQLPlus 分隔符兼容性
This commit is contained in:
Syngnat
2026-07-24 12:22:37 +08:00
parent e43fc6e1a1
commit 9275e93421
3 changed files with 65 additions and 1 deletions

View File

@@ -916,6 +916,63 @@ describe('QueryEditor external SQL save', () => {
renderer?.unmount();
});
it('executes a long commented Oracle anonymous block without blocking the UI thread', async () => {
storeState.appearance.uiVersion = 'v2';
storeState.connections[0].config.type = 'oracle';
storeState.connections[0].config.database = 'ORCLPDB1';
const columns = Array.from(
{ length: 42 },
(_, index) => ` column_${index + 1} VARCHAR2(100) DEFAULT 'value_${index + 1}'`,
).join(',\n');
const sql = [
'-- ------------------------------------------------------------',
'-- Long Oracle anonymous setup block',
'-- ------------------------------------------------------------',
'DECLARE',
' v_cnt NUMBER;',
'BEGIN',
' SELECT COUNT(1) INTO v_cnt',
' FROM user_tables',
" WHERE table_name = 'GONAVI_REPRO_TABLE';",
' IF v_cnt = 0 THEN',
" EXECUTE IMMEDIATE '\n CREATE TABLE gonavi_repro_table (\n" + columns + "\n )\n ';",
' END IF;',
'END;',
'/',
].join('\n');
backendApp.DBQueryMulti.mockResolvedValueOnce({
success: true,
data: Array.from({ length: 52 }, (_, index) => ({
statementIndex: index + 1,
columns: ['affectedRows'],
rows: [{ affectedRows: 0 }],
})),
});
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ dbName: 'ORCLPDB1', query: sql })} />);
});
const runButton = findByClassName(renderer, 'gn-v2-query-toolbar-run-action');
await act(async () => {
await runButton.props.onClick();
await Promise.resolve();
await Promise.resolve();
});
const resultTabs = renderer.root.findAll((node) =>
node.type === 'button' && String(node.props?.['data-tab-key'] || '').startsWith('result-'),
);
expect(backendApp.DBQueryMulti).toHaveBeenCalledOnce();
expect(backendApp.DBGetColumns).not.toHaveBeenCalled();
expect(backendApp.DBGetIndexes).not.toHaveBeenCalled();
expect(resultTabs).toHaveLength(52);
await act(async () => {
renderer.unmount();
});
});
it('runs the whole Oracle procedure when the cursor is in the exception tail', async () => {
storeState.connections[0].config.type = 'oracle';
storeState.connections[0].config.database = 'ORCLPDB1';

View File

@@ -167,6 +167,7 @@ import {
queryCompletionMetadataRowsBySpecs,
readSidebarSqlDropText,
matchLeadingSelectTableReference,
maskQueryEditorSqlLiteralsAndComments,
materializeBoundedQueryEditorCompletionBatches,
resolveNewQueryDefaultTemplate,
resolveEventTargetNode,
@@ -6253,7 +6254,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
};
const containsOraclePlsqlDefinition = (statements: string[]): boolean => (
statements.some((statement) => /^\s*(?:(?:--[^\n]*|\/\*[\s\S]*?\*\/)\s*)*CREATE\s+(?:OR\s+REPLACE\s+)?(?:EDITIONABLE\s+|NONEDITIONABLE\s+)?(?:PROCEDURE|FUNCTION|PACKAGE|TRIGGER)\b/i.test(statement))
statements.some((statement) => /^\s*CREATE\s+(?:OR\s+REPLACE\s+)?(?:EDITIONABLE\s+|NONEDITIONABLE\s+)?(?:PROCEDURE|FUNCTION|PACKAGE|TRIGGER)\b/i.test(
maskQueryEditorSqlLiteralsAndComments(statement),
))
);
const normalizeOracleSqlPlusSlashTerminators = (sql: string): string => (

View File

@@ -15,6 +15,7 @@ import {
type EditRowLocator,
} from '../../utils/rowLocator';
import { getQueryTabDraft, hasQueryTabDraft } from '../../utils/sqlFileTabDrafts';
import { resolveSqlEditorOperationKeyword } from '../../utils/sqlEditorTransaction';
import { getColumnDefinitionKey, getColumnDefinitionName } from '../../utils/columnDefinition';
import { resolveUniqueKeyGroupsFromIndexes } from '../dataGridCopyInsert';
import { t as translate } from '../../i18n';
@@ -2709,6 +2710,9 @@ export const resolveQueryLocatorPlan = async ({
executedSql: statement,
pkColumns: [],
};
if (resolveSqlEditorOperationKeyword(statement) !== 'select') {
return plan;
}
const defaultSchema = isOracleLikeDialect(dbType)
? resolveOracleLikeExecutionSchemaName(config, currentDb)
: '';