From 5e15c4dd2dd16632c873466e05088976f5e33109 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 24 Jun 2026 00:22:31 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(query-editor):=20=E9=9A=90?= =?UTF-8?q?=E8=97=8F=20SQL=20Server=20=E5=86=97=E4=BD=99=E5=BD=B1=E5=93=8D?= =?UTF-8?q?=E8=A1=8C=E6=95=B0=E7=BB=93=E6=9E=9C=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QueryEditor.results-and-drop.test.tsx | 41 +++++++++++++- frontend/src/components/QueryEditor.tsx | 55 ++++++++++++++++--- 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/QueryEditor.results-and-drop.test.tsx b/frontend/src/components/QueryEditor.results-and-drop.test.tsx index a6daf8f..f47e6ac 100644 --- a/frontend/src/components/QueryEditor.results-and-drop.test.tsx +++ b/frontend/src/components/QueryEditor.results-and-drop.test.tsx @@ -843,6 +843,43 @@ describe('QueryEditor external SQL save', () => { expect(dataGridState.latestProps?.columnNames).toEqual(['name']); }); + it('hides redundant sqlserver affected-row status result after a query result', async () => { + storeState.connections[0].config.type = 'sqlserver'; + storeState.connections[0].config.database = 'hydee'; + backendApp.DBQueryMulti.mockResolvedValueOnce({ + success: true, + data: [ + { + statementIndex: 1, + columns: ['dddwno', 'dddwlist'], + rows: [{ dddwno: '001', dddwlist: 'demo' }], + }, + { statementIndex: 1, columns: ['affectedRows'], rows: [{ affectedRows: 846 }] }, + ], + }); + + let renderer!: ReactTestRenderer; + await act(async () => { + renderer = create(); + }); + + await act(async () => { + await findButton(renderer!, '运行').props.onClick(); + }); + await act(async () => { + await Promise.resolve(); + await Promise.resolve(); + }); + + const rendered = textContent(renderer!.toJSON()); + expect(rendered).toContain('结果 1'); + expect(rendered).not.toContain('结果 2'); + expect(rendered).not.toContain('影响行数:846'); + expect(dataGridState.latestProps?.columnNames).toEqual(['dddwno', 'dddwlist']); + expect(dataGridState.latestProps?.data?.[0]).toMatchObject({ dddwno: '001', dddwlist: 'demo' }); + expect(messageApi.success).toHaveBeenCalledWith('已执行完成,生成 1 个结果集。'); + }); + it('prefers the first displayable sqlserver procedure result when empty result sets are returned', async () => { storeState.connections[0].config.type = 'sqlserver'; storeState.connections[0].config.database = 'hydee'; @@ -953,7 +990,7 @@ describe('QueryEditor external SQL save', () => { await Promise.resolve(); }); - expect(textContent(renderer!.toJSON())).toContain('消息 2'); + expect(textContent(renderer!.toJSON())).toContain('消息 1'); expect(findResultMessageTextarea(renderer!).props.value).toBe([ "insert into c_dyscript(projectid,name) values (1,'demo')", "insert into c_dyscript(projectid,name) values (2,'next')", @@ -1033,7 +1070,7 @@ describe('QueryEditor external SQL save', () => { await Promise.resolve(); }); - expect(textContent(renderer!.toJSON())).toContain('消息 2'); + expect(textContent(renderer!.toJSON())).toContain('消息 1'); expect(findResultMessageTextarea(renderer!).props.value).toBe("insert into c_dyscript(projectid,name) values (1,'demo')"); expect(textContent(renderer!.toJSON())).not.toContain('影响行数:0'); expect(dataGridState.latestProps).toBeNull(); diff --git a/frontend/src/components/QueryEditor.tsx b/frontend/src/components/QueryEditor.tsx index 5ef7a17..84a66e6 100644 --- a/frontend/src/components/QueryEditor.tsx +++ b/frontend/src/components/QueryEditor.tsx @@ -2746,6 +2746,24 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc result.columns[0] === 'affectedRows', ); + const isAffectedRowsResultSetData = (result?: any): boolean => + Boolean( + result && + Array.isArray(result.rows) && + result.rows.length === 1 && + Array.isArray(result.columns) && + result.columns.length === 1 && + result.columns[0] === 'affectedRows', + ); + + const hasConcreteQueryResultSetData = (result: any, messages: string[]): boolean => { + if (!result || isAffectedRowsResultSetData(result)) return false; + if (messages.length > 0) return true; + if (Array.isArray(result.columns) && result.columns.length > 0) return true; + if (Array.isArray(result.rows) && result.rows.length > 0) return true; + return false; + }; + const isMessageLikeResultSet = (result?: ResultSet | null): boolean => Boolean( result && @@ -3461,28 +3479,49 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc const maxRows = Number(queryOptions?.maxRows) || 0; let anyTruncated = false; const statementResultCounts = new Map(); + const sqlServerStatementsWithConcreteResults = new Set(); + if (normalizedDbType === 'sqlserver') { + resultSetDataArray.forEach((rsData, idx) => { + const sourceStatementIndex = Number(rsData?.statementIndex || idx + 1); + const resultMessages = normalizeQueryResultMessages(rsData?.messages); + if (hasConcreteQueryResultSetData(rsData, resultMessages)) { + sqlServerStatementsWithConcreteResults.add(sourceStatementIndex); + } + }); + } + const shouldUseTopLevelSqlServerMessages = normalizedDbType === 'sqlserver' + && topLevelMessages.length > 0 + && sqlServerStatementsWithConcreteResults.size === 0; for (let idx = 0; idx < resultSetDataArray.length; idx++) { const rsData = resultSetDataArray[idx]; const sourceStatementIndex = Number(rsData?.statementIndex || idx + 1); - const statementResultIndex = (statementResultCounts.get(sourceStatementIndex) || 0) + 1; - statementResultCounts.set(sourceStatementIndex, statementResultIndex); const plan = executablePlans[Math.max(0, sourceStatementIndex - 1)]; const originalSql = plan?.originalSql || ''; const executedSql = plan?.executedSql || originalSql; const resultMessages = normalizeQueryResultMessages(rsData?.messages); // 检查是否为 affectedRows 类结果集 - const isAffectedResult = Array.isArray(rsData.rows) && rsData.rows.length === 1 - && rsData.columns && rsData.columns.length === 1 - && rsData.columns[0] === 'affectedRows'; + const isAffectedResult = isAffectedRowsResultSetData(rsData); + const shouldHideSqlServerAffectedResult = normalizedDbType === 'sqlserver' + && isAffectedResult + && ( + sqlServerStatementsWithConcreteResults.has(sourceStatementIndex) + || shouldUseTopLevelSqlServerMessages + ); + if (shouldHideSqlServerAffectedResult) { + continue; + } + + const statementResultIndex = (statementResultCounts.get(sourceStatementIndex) || 0) + 1; + statementResultCounts.set(sourceStatementIndex, statementResultIndex); if (isAffectedResult) { const affected = Number(rsData.rows[0]?.affectedRows); const row = { affectedRows: Number.isFinite(affected) ? affected : 0 }; (row as any)[GONAVI_ROW_KEY] = 0; nextResultSets.push({ - key: `result-${idx + 1}`, + key: `result-${nextResultSets.length + 1}`, sql: executedSql, exportSql: originalSql, sourceStatementIndex, @@ -3495,7 +3534,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc }); } else if ((!Array.isArray(rsData.rows) || rsData.rows.length === 0) && (!Array.isArray(rsData.columns) || rsData.columns.length === 0) && resultMessages.length > 0) { nextResultSets.push({ - key: `result-${idx + 1}`, + key: `result-${nextResultSets.length + 1}`, sql: executedSql, exportSql: originalSql, sourceStatementIndex, @@ -3535,7 +3574,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc fallbackPageSize: maxRows, }); nextResultSets.push({ - key: `result-${idx + 1}`, + key: `result-${nextResultSets.length + 1}`, sql: executedSql, exportSql: originalSql, sourceStatementIndex,