diff --git a/frontend/src/components/QueryEditor.results-and-drop.test.tsx b/frontend/src/components/QueryEditor.results-and-drop.test.tsx index 40672c3d..0bdd0fdf 100644 --- a/frontend/src/components/QueryEditor.results-and-drop.test.tsx +++ b/frontend/src/components/QueryEditor.results-and-drop.test.tsx @@ -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(); + }); + + 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'; diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index 01c10a7c..5aa12b56 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -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 => ( diff --git a/frontend/src/components/queryEditor/QueryEditorHelpers.ts b/frontend/src/components/queryEditor/QueryEditorHelpers.ts index 19b177ea..4935019f 100644 --- a/frontend/src/components/queryEditor/QueryEditorHelpers.ts +++ b/frontend/src/components/queryEditor/QueryEditorHelpers.ts @@ -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) : '';